diff options
Diffstat (limited to 'src/client')
183 files changed, 7322 insertions, 14821 deletions
diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss new file mode 100644 index 000000000..3f9102f15 --- /dev/null +++ b/src/client/ClientRecommender.scss @@ -0,0 +1,12 @@ +// @import "/views/globalCssVariables.scss"; + +.space{ + border: 1px dashed blue; + border-spacing: 25px; + border-collapse: separate; + align-content: center; +} + +.wrapper{ + text-align: -webkit-center; +}
\ No newline at end of file diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx new file mode 100644 index 000000000..537e331ab --- /dev/null +++ b/src/client/ClientRecommender.tsx @@ -0,0 +1,425 @@ +import { Doc, FieldResult } from "../new_fields/Doc"; +import { StrCast, Cast } from "../new_fields/Types"; +import { List } from "../new_fields/List"; +import { CognitiveServices, Confidence, Tag, Service } from "./cognitive_services/CognitiveServices"; +import React = require("react"); +import { observer } from "mobx-react"; +import { observable, action, computed, reaction } from "mobx"; +// var assert = require('assert'); +// var sw = require('stopword'); +// var FeedParser = require('feedparser'); +// var https = require('https'); +import "./ClientRecommender.scss"; +import { JSXElement } from "babel-types"; +import { RichTextField } from "../new_fields/RichTextField"; +import { ToPlainText } from "../new_fields/FieldSymbols"; +import { listSpec } from "../new_fields/Schema"; +import { ComputedField } from "../new_fields/ScriptField"; +import { ImageField } from "../new_fields/URLField"; +import { KeyphraseQueryView } from "./views/KeyphraseQueryView"; +import { Networking } from "./Network"; + +export interface RecommenderProps { + title: string; +} + +/** + * actualDoc: datadoc + * vectorDoc: mean vector of text + * score: similarity score to main doc + */ + +export interface RecommenderDocument { + actualDoc: Doc; + vectorDoc: number[]; + score: number; +} + +const fieldkey = "data"; + +@observer +export class ClientRecommender extends React.Component<RecommenderProps> { + + + + static Instance: ClientRecommender; + private mainDoc?: RecommenderDocument; + private docVectors: Set<RecommenderDocument> = new Set(); + public _queries: string[] = []; + + @observable private corr_matrix = [[0, 0], [0, 0]]; // for testing + + constructor(props: RecommenderProps) { + //console.log("creating client recommender..."); + super(props); + if (!ClientRecommender.Instance) ClientRecommender.Instance = this; + ClientRecommender.Instance.docVectors = new Set(); + //ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; + } + + @action + public reset_docs() { + ClientRecommender.Instance.docVectors = new Set(); + ClientRecommender.Instance.mainDoc = undefined; + ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; + } + + /*** + * Computes the cosine similarity between two vectors in Euclidean space. + */ + + private distance(vector1: number[], vector2: number[], metric: string = "cosine") { + // assert(vector1.length === vector2.length, "Vectors are not the same length"); + let similarity: number; + switch (metric) { + case "cosine": + var dotproduct = 0; + var mA = 0; + var mB = 0; + for (let i = 0; i < vector1.length; i++) { // here you missed the i++ + dotproduct += (vector1[i] * vector2[i]); + mA += (vector1[i] * vector1[i]); + mB += (vector2[i] * vector2[i]); + } + mA = Math.sqrt(mA); + mB = Math.sqrt(mB); + similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets + return similarity; + case "euclidian": + var sum = 0; + for (let i = 0; i < vector1.length; i++) { + sum += Math.pow(vector1[i] - vector2[i], 2); + } + similarity = Math.sqrt(sum); + return similarity; + default: + return 0; + } + } + + /** + * Returns list of {doc, similarity (to main doc)} in increasing score + */ + + public computeSimilarities(distance_metric: string) { + const parameters: any = {}; + Networking.PostToServer("/IBMAnalysis", parameters).then(response => { + console.log("ANALYSIS RESULTS! ", response); + }); + ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { + if (ClientRecommender.Instance.mainDoc) { + const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, distance_metric); + doc.score = distance; + } + } + ); + const doclist = Array.from(ClientRecommender.Instance.docVectors); + if (distance_metric === "euclidian") { + doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score); + } + else { + doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => b.score - a.score); + } + return doclist; + } + + /*** + * Computes the mean of a set of vectors + */ + + public mean(paragraph: Set<number[]>) { + const n = 512; + const num_words = paragraph.size; + let meanVector = new Array<number>(n).fill(0); // mean vector + if (num_words > 0) { // check to see if paragraph actually was vectorized + paragraph.forEach((wordvec: number[]) => { + for (let i = 0; i < n; i++) { + meanVector[i] += wordvec[i]; + } + }); + meanVector = meanVector.map(x => x / num_words); + } + return meanVector; + } + + /*** + * Processes sentence vector as Recommender Document, adds to Doc Set. + */ + + public processVector(vector: number[], dataDoc: Doc, isMainDoc: boolean) { + if (vector.length > 0) { + const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: vector, score: 0 }; + ClientRecommender.Instance.addToDocSet(internalDoc, isMainDoc); + } + } + + /*** + * Adds to Doc set. Updates mainDoc (one clicked) if necessary. + */ + + private addToDocSet(internalDoc: RecommenderDocument, isMainDoc: boolean) { + if (ClientRecommender.Instance.docVectors) { + if (isMainDoc) ClientRecommender.Instance.mainDoc = internalDoc; + ClientRecommender.Instance.docVectors.add(internalDoc); + } + } + + /*** + * Generates tags for an image using Cognitive Services + */ + + generateMetadata = async (dataDoc: Doc, extDoc: Doc, threshold: Confidence = Confidence.Excellent) => { + const converter = (results: any) => { + const tagDoc = new Doc; + const tagsList = new List(); + results.tags.map((tag: Tag) => { + tagsList.push(tag.name); + const sanitized = tag.name.replace(" ", "_"); + tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); + }); + extDoc.generatedTags = tagsList; + tagDoc.title = "Generated Tags Doc"; + tagDoc.confidence = threshold; + return tagDoc; + }; + const url = this.url(dataDoc); + if (url) { + return CognitiveServices.Image.Appliers.ProcessImage(extDoc, ["generatedTagsDoc"], url, Service.ComputerVision, converter); + } + } + + /*** + * Gets URL of image + */ + + private url(dataDoc: Doc) { + const data = Cast(Doc.GetProto(dataDoc)[fieldkey], ImageField); + return data ? data.url.href : undefined; + } + + /*** + * Uses Cognitive Services to extract keywords from a document + */ + + public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, api: string = "bing", isMainDoc: boolean = false, image: boolean = false) { + // STEP 1. Consolidate data of document. Depends on type of document. + let data: string = ""; + let taglist: FieldResult<List<string>> = undefined; + if (image) { + if (!extDoc.generatedTags) await this.generateMetadata(dataDoc, extDoc); // TODO: Automatically generate tags. Need to ask Sam about this. + if (extDoc.generatedTags) { + taglist = Cast(extDoc.generatedTags, listSpec("string")); + taglist!.forEach(tag => { + data += tag + ", "; + }); + } + } + else { + const fielddata = Cast(dataDoc.data, RichTextField, null); + data = fielddata?.Text || ""; + } + + // STEP 2. Upon receiving response from Text Cognitive Services, do additional processing on keywords. + // Currently we are still using Cognitive Services for internal recommendations, but in the future this might not be necessary. + + const converter = async (results: any, data: string, isImage: boolean = false) => { + let keyterms = new List<string>(); // raw keywords + let kp_string: string = ""; // keywords*frequency concatenated into a string. input into TF + let highKP: string[] = [""]; // most frequent keyphrase + let high = 0; + + if (isImage) { // no keyphrase processing necessary + kp_string = data; + if (taglist) { + keyterms = taglist; + highKP = [taglist[0]]; + } + } + else { // text processing + results.documents.forEach((doc: any) => { + const keyPhrases = doc.keyPhrases; // returned by Cognitive Services + keyPhrases.map((kp: string) => { + keyterms.push(kp); + const frequency = this.countFrequencies(kp, data); // frequency of keyphrase in paragraph + kp_string += kp + ", "; // ensures that if frequency is 0 for some reason kp is still added + for (let i = 0; i < frequency - 1; i++) { + kp_string += kp + ", "; // weights repeated keywords higher + } + // replaces highKP with new one + if (frequency > high) { + high = frequency; + highKP = [kp]; + } + // appends to current highKP phrase + else if (frequency === high) { + highKP.push(kp); + } + }); + }); + } + if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2); // strips extra comma and space if there are a lot of keywords + console.log("kp_string: ", kp_string); + + let ext_recs = ""; + // Pushing keyword extraction to IBM for external recommendations. Should shift to internal eventually. + if (!internal) { + const parameters: any = { + 'language': 'en', + 'text': data, + 'features': { + 'keywords': { + 'sentiment': true, + 'emotion': true, + 'limit': 3 + } + } + }; + await Networking.PostToServer("/IBMAnalysis", parameters).then(response => { + const sorted_keywords = response.result.keywords; + if (sorted_keywords.length > 0) { + console.log("IBM keyphrase", sorted_keywords[0]); + highKP = []; + for (let i = 0; i < 5; i++) { + if (sorted_keywords[i]) { + highKP.push(sorted_keywords[i].text); + } + } + keyterms = new List<string>(highKP); + } + }); + //let kpqv = new KeyphraseQueryView({ keyphrases: ["hello"] }); + ext_recs = await this.sendRequest([highKP[0]], api); + } + + // keyterms: list for extDoc, kp_string: input to TF, ext_recs: {titles, urls} of retrieved results from highKP query + return { keyterms: keyterms, external_recommendations: ext_recs, kp_string: [kp_string] }; + }; + + // STEP 3: Start recommendation pipeline. Branches off into internal and external in Cognitive Services + if (data !== "") { + return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, isMainDoc, internal); + } + return; + } + + /** + * + * Counts frequencies of keyphrase in paragraph. + */ + + private countFrequencies(keyphrase: string, paragraph: string) { + const data = paragraph.split(/ |\n/); // splits by new lines and spaces + const kp_array = keyphrase.split(" "); + const num_keywords = kp_array.length; + const par_length = data.length; + let frequency = 0; + // slides keyphrase windows across paragraph and checks if it matches with corresponding paragraph slice + for (let i = 0; i <= par_length - num_keywords; i++) { + const window = data.slice(i, i + num_keywords); + if (JSON.stringify(window).toLowerCase() === JSON.stringify(kp_array).toLowerCase() || kp_array.every(val => window.includes(val))) { + frequency++; + } + } + return frequency; + } + + /** + * + * API for sending arXiv request. + */ + + private async sendRequest(keywords: string[], api: string) { + let query = ""; + keywords.forEach((kp: string) => query += " " + kp); + if (api === "arxiv") { + return new Promise<any>(resolve => { + this.arxivrequest(query).then(resolve); + }); + } + else if (api === "bing") { + return new Promise<any>(resolve => { + this.bingWebSearch(query).then(resolve); + }); + } + else { + console.log("no api specified :("); + } + + } + + /** + * Request to Bing API. Most of code is in Cognitive Services. + */ + + bingWebSearch = async (query: string) => { + const converter = async (results: any) => { + const title_vals: string[] = []; + const url_vals: string[] = []; + results.webPages.value.forEach((doc: any) => { + title_vals.push(doc.name); + url_vals.push(doc.url); + }); + return { title_vals, url_vals }; + }; + return CognitiveServices.BingSearch.Appliers.analyzer(query, converter); + } + + /** + * Actual request to the arXiv server for ML articles. + */ + + arxivrequest = async (query: string) => { + const xhttp = new XMLHttpRequest(); + const serveraddress = "http://export.arxiv.org/api"; + const maxresults = 5; + const endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=" + maxresults.toString(); + const promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + const result = xhttp.response; + const xml = xhttp.responseXML; + console.log("arXiv Result: ", xml); + switch (this.status) { + case 200: + const title_vals: string[] = []; + const url_vals: string[] = []; + //console.log(result); + if (xml) { + const titles = xml.getElementsByTagName("title"); + let counter = 1; + if (titles && titles.length > 1) { + while (counter <= maxresults) { + const title = titles[counter].childNodes[0].nodeValue!; + title_vals.push(title); + counter++; + } + } + const ids = xml.getElementsByTagName("id"); + counter = 1; + if (ids && ids.length > 1) { + while (counter <= maxresults) { + const url = ids[counter].childNodes[0].nodeValue!; + url_vals.push(url); + counter++; + } + } + } + return resolve({ title_vals, url_vals }); + case 400: + default: + return reject(result); + } + } + }; + xhttp.open("GET", endpoint, true); + xhttp.send(); + }; + return new Promise<any>(promisified); + } + + render() { + return (<div className="wrapper"> + </div>); + } + +}
\ No newline at end of file diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 7fbf30af8..0c9d5f75c 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,10 +1,12 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes } from "./../server/Message"; +import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; +import GestureOverlay from './views/GestureOverlay'; +import MobileInkOverlay from '../mobile/MobileInkOverlay'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -64,6 +66,26 @@ export namespace DocServer { } } + export namespace Mobile { + + export function dispatchGesturePoints(content: GestureContent) { + Utils.Emit(_socket, MessageStore.GesturePoints, content); + } + + export function dispatchOverlayTrigger(content: MobileInkOverlayContent) { + // _socket.emit("dispatchBoxTrigger"); + Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content); + } + + export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPositionContent) { + Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content); + } + + export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) { + Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content); + } + } + const instructions = "This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds."; function alertUser(connectionTerminationReason: string) { switch (connectionTerminationReason) { @@ -101,6 +123,21 @@ export namespace DocServer { Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete); Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete); Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser); + + // mobile ink overlay socket events to communicate between mobile view and desktop view + _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { + MobileInkOverlay.Instance.drawStroke(content); + }); + _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => { + GestureOverlay.Instance.enableMobileInkOverlay(content); + MobileInkOverlay.Instance.initMobileInkOverlay(content); + }); + _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => { + MobileInkOverlay.Instance.updatePosition(content); + }); + _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => { + MobileInkOverlay.Instance.uploadDocument(content); + }); } function errorFunc(): never { diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts new file mode 100644 index 000000000..4e1c541c8 --- /dev/null +++ b/src/client/apis/IBM_Recommender.ts @@ -0,0 +1,40 @@ +// import { Opt } from "../../new_fields/Doc"; + +// const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'); +// const { IamAuthenticator } = require('ibm-watson/auth'); + +// export namespace IBM_Recommender { + +// // pass to IBM account is Browngfx1 + +// const naturalLanguageUnderstanding = new NaturalLanguageUnderstandingV1({ +// version: '2019-07-12', +// authenticator: new IamAuthenticator({ +// apikey: 'tLiYwbRim3CnBcCO4phubpf-zEiGcub1uh0V-sD9OKhw', +// }), +// url: 'https://gateway-wdc.watsonplatform.net/natural-language-understanding/api' +// }); + +// const analyzeParams = { +// 'text': 'this is a test of the keyword extraction feature I am integrating into the program', +// 'features': { +// 'keywords': { +// 'sentiment': true, +// 'emotion': true, +// 'limit': 3 +// }, +// } +// }; + +// export const analyze = async (_parameters: any): Promise<Opt<string>> => { +// try { +// const response = await naturalLanguageUnderstanding.analyze(_parameters); +// console.log(response); +// return (JSON.stringify(response, null, 2)); +// } catch (err) { +// console.log('error: ', err); +// return undefined; +// } +// }; + +// }
\ No newline at end of file diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 0d44ee8e0..fa67ddbef 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -97,7 +97,7 @@ export namespace GoogleApiClientUtils { const paragraphs = extractParagraphs(document); let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join(""); text = text.substring(0, text.length - 1); - removeNewlines && text.ReplaceAll("\n", ""); + removeNewlines && text.replace(/\n/g, ""); return { text, paragraphs }; }; diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index f8723f02d..7c4137f59 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -340,7 +340,7 @@ export namespace GooglePhotos { const url = data.url.href; const target = Doc.MakeAlias(source); const description = parseDescription(target, descriptionKey); - await DocumentView.makeCustomViewClicked(target, undefined, Docs.Create.FreeformDocument); + await DocumentView.makeCustomViewClicked(target, Docs.Create.FreeformDocument); media.push({ url, description }); } if (media.length) { diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index c940bba43..4b4145fcc 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -171,9 +171,9 @@ export class YoutubeBox extends React.Component<FieldViewProps> { * in the title of the videos. */ filterYoutubeTitleResult = (resultTitle: string) => { - let processedTitle: string = resultTitle.ReplaceAll("&", "&"); - processedTitle = processedTitle.ReplaceAll("'", "'"); - processedTitle = processedTitle.ReplaceAll(""", "\""); + let processedTitle: string = resultTitle.replace(/&/g, "&");//.ReplaceAll("&", "&"); + processedTitle = processedTitle.replace(/"'/g, "'"); + processedTitle = processedTitle.replace(/"/g, "\""); return processedTitle; } diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 9e2ceac62..3f3726621 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -5,12 +5,18 @@ import { Docs } from "../documents/Documents"; import { Utils } from "../../Utils"; import { InkData } from "../../new_fields/InkField"; import { UndoManager } from "../util/UndoManager"; +import requestPromise = require("request-promise"); +import { List } from "../../new_fields/List"; +import { ClientRecommender } from "../ClientRecommender"; +import { ImageBox } from "../views/nodes/ImageBox"; type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>; type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter<D> = (data: D) => string; type Converter = (results: any) => Field; +type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, external_recommendations: any, kp_string: string[] }>; +type BingConverter = (results: any) => Promise<{ title_vals: string[], url_vals: string[] }>; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -18,7 +24,9 @@ export type Rectangle = { top: number, left: number, width: number, height: numb export enum Service { ComputerVision = "vision", Face = "face", - Handwriting = "handwriting" + Handwriting = "handwriting", + Text = "text", + Bing = "bing" } export enum Confidence { @@ -47,7 +55,8 @@ export namespace CognitiveServices { let results: any; try { results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); - } catch { + } catch (e) { + throw e; results = undefined; } return results; @@ -193,6 +202,13 @@ export namespace CognitiveServices { batch.end(); }; + export const InterpretStrokes = async (strokes: InkData[]) => { + let results = await ExecuteQuery(Service.Handwriting, Manager, strokes); + if (results) { + results.recognitionUnits && (results = results.recognitionUnits); + } + return results; + }; } export interface AzureStrokeData { @@ -210,4 +226,185 @@ export namespace CognitiveServices { } + export namespace BingSearch { + export const Manager: APIManager<string> = { + converter: (data: string) => { + return data; + }, + requester: async (apiKey: string, query: string) => { + const xhttp = new XMLHttpRequest(); + const serverAddress = "https://api.cognitive.microsoft.com"; + const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); + const promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + const result = xhttp.responseText; + switch (this.status) { + case 200: + return resolve(result); + case 400: + default: + return reject(result); + } + } + }; + + if (apiKey) { + xhttp.open("GET", endpoint, true); + xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(); + } + else { + console.log("API key for BING unavailable"); + } + }; + return new Promise<any>(promisified); + } + + }; + + export namespace Appliers { + export const analyzer = async (query: string, converter: BingConverter) => { + const results = await ExecuteQuery(Service.Bing, Manager, query); + console.log("Bing results: ", results); + const { title_vals, url_vals } = await converter(results); + return { title_vals, url_vals }; + }; + } + + } + + export namespace HathiTrust { + export const Manager: APIManager<string> = { + converter: (data: string) => { + return data; + }, + requester: async (apiKey: string, query: string) => { + const xhttp = new XMLHttpRequest(); + const serverAddress = "https://babel.hathitrust.org/cgi/htd/​"; + const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); + const promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + const result = xhttp.responseText; + switch (this.status) { + case 200: + return resolve(result); + case 400: + default: + return reject(result); + } + } + }; + + if (apiKey) { + xhttp.open("GET", endpoint, true); + xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(); + } + else { + console.log("API key for BING unavailable"); + } + }; + return new Promise<any>(promisified); + } + + }; + + export namespace Appliers { + export const analyzer = async (query: string, converter: BingConverter) => { + const results = await ExecuteQuery(Service.Bing, Manager, query); + console.log("Bing results: ", results); + const { title_vals, url_vals } = await converter(results); + return { title_vals, url_vals }; + }; + } + + } + + + export namespace Text { + export const Manager: APIManager<string> = { + converter: (data: string) => { + return JSON.stringify({ + documents: [{ + id: 1, + language: "en", + text: data + }] + }); + }, + requester: async (apiKey: string, body: string, service: Service) => { + const serverAddress = "https://eastus.api.cognitive.microsoft.com"; + const endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases"; + const sampleBody = { + "documents": [ + { + "language": "en", + "id": 1, + "text": "Hello world. This is some input text that I love." + } + ] + }; + const actualBody = body; + const options = { + uri: endpoint, + body: actualBody, + headers: { + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': apiKey + } + + }; + return request.post(options); + } + }; + + export namespace Appliers { + + export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) { + console.log("vectorizing..."); + //keyterms = ["father", "king"]; + + const args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; + await requestPromise.post(args).then(async (wordvecs) => { + if (wordvecs) { + const indices = Object.keys(wordvecs); + console.log("successful vectorization!"); + const vectorValues = new List<number>(); + indices.forEach((ind: any) => { + //console.log(wordvec.word); + vectorValues.push(wordvecs[ind]); + }); + ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc); + } // adds document to internal doc set + else { + console.log("unsuccessful :( word(s) not in vocabulary"); + } + //console.log(vectorValues.size); + } + ); + } + + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => { + const results = await ExecuteQuery(Service.Text, Manager, data); + console.log("Cognitive Services keyphrases: ", results); + const { keyterms, external_recommendations, kp_string } = await converter(results, data); + target[keys[0]] = keyterms; + if (isInternal) { + //await vectorize([data], dataDoc, isMainDoc); + await vectorize(kp_string, dataDoc, isMainDoc); + } else { + return { recs: external_recommendations, keyterms: keyterms }; + } + }; + + // export async function countFrequencies() + } + + } + + }
\ No newline at end of file diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 170d54e96..ab32d7301 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -1,30 +1,36 @@ export enum DocumentType { NONE = "none", - TEXT = "text", - HIST = "histogram", - IMG = "image", - WEB = "web", - COL = "collection", - KVP = "kvp", - VID = "video", - AUDIO = "audio", - PDF = "pdf", - IMPORT = "import", - LINK = "link", - LINKDOC = "linkdoc", - BUTTON = "button", - SLIDER = "slider", - EXTENSION = "extension", - YOUTUBE = "youtube", - WEBCAM = "webcam", - FONTICON = "fonticonbox", - PRES = "presentation", - PRESELEMENT = "preselement", - QUERY = "search", - COLOR = "color", - DOCULINK = "doculink", - PDFANNO = "pdfanno", - INK = "ink", - DOCUMENT = "document", - SCRIPT = "script" + + // core data types + RTF = "rtf", // rich text + IMG = "image", // image box + WEB = "web", // web page or html clipping + COL = "collection", // collection + KVP = "kvp", // key value pane + VID = "video", // video + AUDIO = "audio", // audio + PDF = "pdf", // pdf + INK = "ink", // ink stroke + SCREENSHOT = "screenshot", // view of a desktop application + FONTICON = "fonticonbox", // font icon + QUERY = "query", // search query + LABEL = "label", // simple text label + WEBCAM = "webcam", // webcam + PDFANNO = "pdfanno", // pdf text selection (could be just a collection?) + DATE = "date", // calendar view of a date + SCRIPTING = "script", // script editor + + // special purpose wrappers that either take no data or are compositions of lower level types + LINK = "link", // link (view of a document that acts as a link) + LINKANCHOR = "linkanchor", // blue dot link anchor (view of a link document's anchor) + IMPORT = "import", // directory import box (file system directory) + SLIDER = "slider", // number slider (view of a number) + PRES = "presentation", // presentation (view of a collection) --- shouldn't this be a view type? technically requires a special view in which documents must have their aliasOf fields filled in + PRESELEMENT = "preselement",// presentation item (view of a document in a collection) + COLOR = "color", // color picker (view of a color picker for a color string) + YOUTUBE = "youtube", // youtube directory (view of you tube search results) + DOCHOLDER = "docholder", // nested document (view of a document) + + LINKDB = "linkdb", // database of links ??? why do we have this + RECOMMENDATION = "recommendation", // view of a recommendation }
\ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1d5f5fd95..08aa31db2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,6 +1,3 @@ -import { HistogramField } from "../northstar/dash-fields/HistogramField"; -import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; -import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { CollectionView } from "../views/collections/CollectionView"; import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; @@ -8,21 +5,16 @@ import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { PDFBox } from "../views/nodes/PDFBox"; +import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; -import { Gateway } from "../northstar/manager/Gateway"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; -import { action } from "mobx"; -import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel"; -import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel"; -import { AggregateFunction } from "../northstar/model/idea/idea"; import { OmitKeys, JSONUtils, Utils } from "../../Utils"; import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../new_fields/Doc"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; import { Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; @@ -32,7 +24,7 @@ import { LinkManager } from "../util/LinkManager"; import { DocumentManager } from "../util/DocumentManager"; import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox"; import { Scripting } from "../util/Scripting"; -import { ButtonBox } from "../views/nodes/ButtonBox"; +import { LabelBox } from "../views/nodes/LabelBox"; import { SliderBox } from "../views/nodes/SliderBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; @@ -40,12 +32,13 @@ import { PresBox } from "../views/nodes/PresBox"; import { ComputedField, ScriptField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; +import { RecommendationsBox } from "../views/RecommendationsBox"; import { PresElementBox } from "../views/presentationview/PresElementBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; -import { DocuLinkBox } from "../views/nodes/DocuLinkBox"; -import { DocumentBox } from "../views/nodes/DocumentBox"; +import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox"; +import { DocHolderBox } from "../views/nodes/DocumentBox"; import { InkingStroke } from "../views/InkingStroke"; import { InkField } from "../../new_fields/InkField"; import { InkingControl } from "../views/InkingControl"; @@ -55,7 +48,7 @@ import { MessageStore } from "../../server/Message"; import { ContextMenuProps } from "../views/ContextMenuItem"; import { ContextMenu } from "../views/ContextMenu"; import { LinkBox } from "../views/nodes/LinkBox"; -import { ScriptBox } from "../views/ScriptBox"; +import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; const requestImageSize = require('../util/request-image-size'); const path = require('path'); @@ -73,8 +66,9 @@ export interface DocumentOptions { _showTitleHover?: string; // _showTitle?: string; // which field to display in the title area. leave empty to have no title _showCaption?: string; // which field to display in the caption area. leave empty to have no caption + _scrollTop?: number; // scroll location for pdfs _chromeStatus?: string; - _viewType?: number; + _viewType?: string; // sub type of a collection _gridGap?: number; // gap between items in masonry view _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts @@ -91,11 +85,13 @@ export interface DocumentOptions { layoutKey?: string; type?: string; title?: string; + style?: string; page?: number; scale?: number; isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents forceActive?: boolean; layout?: string | Doc; + hideFilterView?: boolean; // whether to hide the filter popout on collections hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; @@ -104,13 +100,14 @@ export interface DocumentOptions { _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) color?: string; // foreground color data doc _color?: string; // foreground color for each template layout doc (overrides color) + caption?: RichTextField; ignoreClick?: boolean; lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; isBackground?: boolean; - isButton?: boolean; + isLinkButton?: boolean; columnWidth?: number; fontSize?: number; curPage?: number; @@ -118,7 +115,8 @@ export interface DocumentOptions { displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) borderRounding?: string; boxShadow?: string; - sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views + dontRegisterChildren?: boolean; + _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views schemaColumns?: List<SchemaHeaderField>; dockingConfig?: string; annotationOn?: Doc; @@ -146,11 +144,18 @@ export interface DocumentOptions { treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents + editScriptOnClick?: string; // script field key to edit when document is clicked (e.g., "onClick", "onChecked") // [key: string]: Opt<Field>; pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown + textTransform?: string; // is linear view expanded + letterSpacing?: string; // is linear view expanded + flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; + selectedIndex?: number; + syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text + searchText?: string; //for searchbox + searchQuery?: string; // for queryBox + filterQuery?: string; linearViewIsExpanded?: boolean; // is linear view expanded - textTransform?: string; - letterSpacing?: string; } class EmptyBox { @@ -176,14 +181,10 @@ export namespace Docs { const data = "data"; const TemplateMap: TemplateMap = new Map([ - [DocumentType.TEXT, { - layout: { view: FormattedTextBox, dataField: data }, + [DocumentType.RTF, { + layout: { view: FormattedTextBox, dataField: "text" }, options: { _height: 150, _xMargin: 10, _yMargin: 10 } }], - [DocumentType.HIST, { - layout: { view: HistogramBox, dataField: data }, - options: { _height: 300, backgroundColor: "black" } - }], [DocumentType.QUERY, { layout: { view: QueryBox, dataField: data }, options: { _width: 400 } @@ -208,8 +209,8 @@ export namespace Docs { layout: { view: KeyValueBox, dataField: data }, options: { _height: 150 } }], - [DocumentType.DOCUMENT, { - layout: { view: DocumentBox, dataField: data }, + [DocumentType.DOCHOLDER, { + layout: { view: DocHolderBox, dataField: data }, options: { _height: 250 } }], [DocumentType.VID, { @@ -232,16 +233,19 @@ export namespace Docs { layout: { view: LinkBox, dataField: data }, options: { _height: 150 } }], - [DocumentType.LINKDOC, { + [DocumentType.LINKDB, { data: new List<Doc>(), layout: { view: EmptyBox, dataField: data }, options: { childDropAction: "alias", title: "LINK DB" } }], + [DocumentType.SCRIPTING, { + layout: { view: ScriptingBox, dataField: data } + }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: data } }], - [DocumentType.BUTTON, { - layout: { view: ButtonBox, dataField: data }, + [DocumentType.LABEL, { + layout: { view: LabelBox, dataField: data }, }], [DocumentType.SLIDER, { layout: { view: SliderBox, dataField: data }, @@ -254,6 +258,10 @@ export namespace Docs { layout: { view: FontIconBox, dataField: data }, options: { _width: 40, _height: 40, borderRounding: "100%" }, }], + [DocumentType.RECOMMENDATION, { + layout: { view: RecommendationsBox, dataField: data }, + options: { width: 200, height: 200 }, + }], [DocumentType.WEBCAM, { layout: { view: DashWebRTCVideo, dataField: data } }], @@ -264,9 +272,9 @@ export namespace Docs { layout: { view: InkingStroke, dataField: data }, options: { backgroundColor: "transparent" } }], - [DocumentType.SCRIPT, { - layout: { view: ScriptBox, dataField: data }, - options: { backgroundColor: "transparent" } + [DocumentType.SCREENSHOT, { + layout: { view: ScreenshotBox, dataField: data }, + options: {} }] ]); @@ -320,7 +328,7 @@ export namespace Docs { * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. */ export function MainLinkDocument() { - return Prototypes.get(DocumentType.LINKDOC); + return Prototypes.get(DocumentType.LINKDB); } /** @@ -350,6 +358,7 @@ export namespace Docs { const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; options.layout = layout.view.LayoutString(layout.dataField); const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options }); + doc.layout_keyValue = KeyValueBox.LayoutString(""); return doc; } @@ -414,9 +423,9 @@ export namespace Docs { Scripting.addGlobal(Buxton); const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn", - "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor", - "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", - "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"]; + "_chromeStatus", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor", + "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop", + "_color", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"]; /** * This function receives the relevant document prototype and uses @@ -436,7 +445,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string) { + export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); if (!("author" in protoProps)) { @@ -449,10 +458,10 @@ export namespace Docs { protoProps.isPrototype = true; - const dataDoc = MakeDataDelegate(proto, protoProps, data); + const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey); const viewDoc = Doc.MakeDelegate(dataDoc, delegId); - AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title)); + viewDoc.type !== DocumentType.LINK && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "audio timeline")); return Doc.assign(viewDoc, delegateProps, true); } @@ -467,10 +476,10 @@ export namespace Docs { * @param options initial values to apply to this new delegate * @param value the data to store in this new delegate */ - function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D) { + function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data") { const deleg = Doc.MakeDelegate(proto); if (value !== undefined) { - deleg.data = value; + deleg[fieldKey] = value; } return Doc.assign(deleg, options); } @@ -483,7 +492,7 @@ export namespace Docs { const extension = path.extname(target); target = `${target.substring(0, target.length - extension.length)}_o${extension}`; } - requestImageSize(target) + requestImageSize(Utils.CorsProxy(target)) .then((size: any) => { const aspect = size.height / size.width; if (!inst._nativeWidth) { @@ -500,6 +509,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options); } + export function ScriptingDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), "", options); + } + export function VideoDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(new URL(url)), options); } @@ -512,12 +525,14 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options); + export function ScreenshotDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); } - export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.HIST), new HistogramField(histoOp), options); + export function AudioDocument(url: string, options: DocumentOptions = {}) { + const instance = InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options); + Doc.GetProto(instance).backgroundColor = ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'"); + return instance; } export function QueryDocument(options: DocumentOptions = {}) { @@ -529,7 +544,7 @@ export namespace Docs { } export function TextDocument(text: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options); + return InstanceFromProto(Prototypes.get(DocumentType.RTF), text, options, undefined, "text"); } export function ScriptDocument(options: DocumentOptions = {}) { @@ -537,20 +552,16 @@ export namespace Docs { } export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { - const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isButton: true, treeViewHideTitle: true, treeViewOpen: false, removeDropProperties: new List(["isBackground", "isButton"]), ...options }); + const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isLinkButton: true, treeViewHideTitle: true, treeViewOpen: false, removeDropProperties: new List(["isBackground", "isLinkButton"]), ...options }); const linkDocProto = Doc.GetProto(doc); linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; - linkDocProto.anchor1Context = source.ctx; - linkDocProto.anchor2Context = target.ctx; - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor2Timecode = target.doc.currentTimecode; if (linkDocProto.layout_key1 === undefined) { - Cast(linkDocProto.proto, Doc, null)!.layout_key1 = DocuLinkBox.LayoutString("anchor1"); - Cast(linkDocProto.proto, Doc, null)!.layout_key2 = DocuLinkBox.LayoutString("anchor2"); - Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]); - Cast(linkDocProto.proto, Doc, null)!.layoutKey = undefined; + Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1"); + Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2"); + Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]); + Cast(linkDocProto.proto, Doc, null).layoutKey = undefined; } LinkManager.Instance.addLink(doc); @@ -561,48 +572,31 @@ export namespace Docs { } export function InkDocument(color: string, tool: number, strokeWidth: number, points: { X: number, Y: number }[], options: DocumentOptions = {}) { - const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options); - doc.color = color; - doc.strokeWidth = strokeWidth; - doc.tool = tool; - return doc; + const I = new Doc(); + I.type = DocumentType.INK; + I.layout = InkingStroke.LayoutString("data"); + I.color = color; + I.strokeWidth = strokeWidth; + I.tool = tool; + I.title = "ink"; + I.x = options.x; + I.y = options.y; + I._width = options._width; + I._height = options._height; + I.data = new InkField(points); + return I; + // return I; + // const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options); + // doc.color = color; + // doc.strokeWidth = strokeWidth; + // doc.tool = tool; + // return doc; } export function PdfDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options); } - export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) { - const schemaName = options.title ? options.title : "-no schema-"; - const ctlog = await Gateway.Instance.GetSchema(url, schemaName); - if (ctlog && ctlog.schemas) { - const schema = ctlog.schemas[0]; - const schemaDoc = Docs.Create.TreeDocument([], { ...options, _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: schema.displayName! }); - const schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []); - if (!schemaDocuments) { - return; - } - CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); - const docs = schemaDocuments; - CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => { - if (field instanceof Doc) { - docs.push(field); - } else { - const atmod = new ColumnAttributeModel(attr); - const histoOp = new HistogramOperation(schema.displayName!, - new AttributeTransformationModel(atmod, AggregateFunction.None), - new AttributeTransformationModel(atmod, AggregateFunction.Count), - new AttributeTransformationModel(atmod, AggregateFunction.Count)); - docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, _width: 200, _height: 200, title: attr.displayName! })); - } - })); - }); - return schemaDoc; - } - return Docs.Create.TreeDocument([], { _width: 50, _height: 100, title: schemaName }); - } - export function WebDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(new URL(url)), options); } @@ -616,7 +610,7 @@ export namespace Docs { } export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.DOCUMENT), document, { title: document ? document.title + "" : "container", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", ...options }); } export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -655,8 +649,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry }); } + export function LabelDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}) }); + } + export function ButtonDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) }); + return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}), editScriptOnClick: "onClick" }); } export function SliderDocument(options?: DocumentOptions) { @@ -682,6 +680,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List<Doc>(), options); } + export function RecommendationsDocument(data: Doc[], options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.RECOMMENDATION), new List<Doc>(data), options); + } + export type DocConfig = { doc: Doc, initialWidth?: number, @@ -805,9 +807,6 @@ export namespace Docs { } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; - } else if (field instanceof HistogramField) { - created = Docs.Create.HistogramDocument((field).HistoOp, resolved); - layout = HistogramBox.LayoutString; } else if (field instanceof InkField) { const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance; created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field).inkData, resolved); @@ -819,9 +818,11 @@ export namespace Docs { created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); layout = FormattedTextBox.LayoutString; } - created.layout = layout ?.(fieldKey); - created.title = fieldKey; - proto && (created.proto = Doc.GetProto(proto)); + if (created) { + created.layout = layout?.(fieldKey); + created.title = fieldKey; + proto && created.proto && (created.proto = Doc.GetProto(proto)); + } return created; } @@ -844,10 +845,6 @@ export namespace Docs { if (!options._width) options._width = 400; if (!options._height) options._height = options._width * 1200 / 927; } - if (type.indexOf("excel") !== -1) { - ctor = Docs.Create.DBDocument; - options.dropAction = "copy"; - } if (type.indexOf("html") !== -1) { if (path.includes(window.location.hostname)) { const s = path.split('/'); @@ -905,13 +902,13 @@ export namespace DocUtils { }); } - export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", linkRelationship: string = "", id?: string) { + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === CurrentUserUtils.UserDocument) return undefined; - const linkDoc = Docs.Create.LinkDocument(source, target, { title, linkRelationship }, id); - Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" " + (this.linkRelationship||"to") +" " + this.anchor2.title'); + const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship }, id); + Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" (" + (this.linkRelationship||"to") +") " + this.anchor2.title'); Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); @@ -923,7 +920,15 @@ export namespace DocUtils { description: "Add Note ...", subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({ description: ":" + StrCast(note.title), - event: (args: { x: number, y: number }) => docTextAdder(Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: note._autoHeight !== false, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })), + event: (args: { x: number, y: number }) => { + const textDoc = Docs.Create.TextDocument("", { + _width: 200, x, y, _autoHeight: note._autoHeight !== false, + title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) + }); + textDoc.layoutKey = "layout_" + note.title; + textDoc[textDoc.layoutKey] = note; + docTextAdder(textDoc); + }, icon: "eye" })) as ContextMenuProps[], icon: "eye" diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts deleted file mode 100644 index ed3818071..000000000 --- a/src/client/northstar/core/BaseObject.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IEquatable } from '../utils/IEquatable'; -import { IDisposable } from '../utils/IDisposable'; - -export class BaseObject implements IEquatable, IDisposable { - - public Equals(other: Object): boolean { - return this === other; - } - - public Dispose(): void { - } -}
\ No newline at end of file diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts deleted file mode 100644 index c89b1617c..000000000 --- a/src/client/northstar/core/attribute/AttributeModel.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea'; -import { BaseObject } from '../BaseObject'; -import { observable } from "mobx"; - -export abstract class AttributeModel extends BaseObject { - public abstract get DisplayName(): string; - public abstract get CodeName(): string; - public abstract get DataType(): DataType; - public abstract get VisualizationHints(): VisualizationHint[]; -} - -export class ColumnAttributeModel extends AttributeModel { - public Attribute: Attribute; - - constructor(attribute: Attribute) { - super(); - this.Attribute = attribute; - } - - public get DataType(): DataType { - return this.Attribute.dataType ? this.Attribute.dataType : DataType.Undefined; - } - - public get DisplayName(): string { - return this.Attribute.displayName ? this.Attribute.displayName.ReplaceAll("_", " ") : ""; - } - - public get CodeName(): string { - return this.Attribute.rawName ? this.Attribute.rawName : ""; - } - - public get VisualizationHints(): VisualizationHint[] { - return this.Attribute.visualizationHints ? this.Attribute.visualizationHints : []; - } - - public Equals(other: ColumnAttributeModel): boolean { - return this.Attribute.rawName === other.Attribute.rawName; - } -} - -export class CodeAttributeModel extends AttributeModel { - private _visualizationHints: VisualizationHint[]; - - public CodeName: string; - - @observable - public Code: string; - - constructor(code: string, codeName: string, displayName: string, visualizationHints: VisualizationHint[]) { - super(); - this.Code = code; - this.CodeName = codeName; - this.DisplayName = displayName; - this._visualizationHints = visualizationHints; - } - - public get DataType(): DataType { - return DataType.Undefined; - } - - @observable - public DisplayName: string; - - public get VisualizationHints(): VisualizationHint[] { - return this._visualizationHints; - } - - public Equals(other: CodeAttributeModel): boolean { - return this.CodeName === other.CodeName; - } - -} - -export class BackendAttributeModel extends AttributeModel { - private _dataType: DataType; - private _displayName: string; - private _codeName: string; - private _visualizationHints: VisualizationHint[]; - - public Id: string; - - constructor(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]) { - super(); - this.Id = id; - this._dataType = dataType; - this._displayName = displayName; - this._codeName = codeName; - this._visualizationHints = visualizationHints; - } - - public get DataType(): DataType { - return this._dataType; - } - - public get DisplayName(): string { - return this._displayName.ReplaceAll("_", " "); - } - - public get CodeName(): string { - return this._codeName; - } - - public get VisualizationHints(): VisualizationHint[] { - return this._visualizationHints; - } - - public Equals(other: BackendAttributeModel): boolean { - return this.Id === other.Id; - } - -}
\ No newline at end of file diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts deleted file mode 100644 index 66485183b..000000000 --- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts +++ /dev/null @@ -1,52 +0,0 @@ - -import { computed, observable } from "mobx"; -import { AggregateFunction } from "../../model/idea/idea"; -import { AttributeModel } from "./AttributeModel"; -import { IEquatable } from "../../utils/IEquatable"; - -export class AttributeTransformationModel implements IEquatable { - - @observable public AggregateFunction: AggregateFunction; - @observable public AttributeModel: AttributeModel; - - constructor(attributeModel: AttributeModel, aggregateFunction: AggregateFunction = AggregateFunction.None) { - this.AttributeModel = attributeModel; - this.AggregateFunction = aggregateFunction; - } - - @computed - public get PresentedName(): string { - var displayName = this.AttributeModel.DisplayName; - if (this.AggregateFunction === AggregateFunction.Count) { - return "count"; - } - if (this.AggregateFunction === AggregateFunction.Avg) { - displayName = "avg(" + displayName + ")"; - } - else if (this.AggregateFunction === AggregateFunction.Max) { - displayName = "max(" + displayName + ")"; - } - else if (this.AggregateFunction === AggregateFunction.Min) { - displayName = "min(" + displayName + ")"; - } - else if (this.AggregateFunction === AggregateFunction.Sum) { - displayName = "sum(" + displayName + ")"; - } - else if (this.AggregateFunction === AggregateFunction.SumE) { - displayName = "sumE(" + displayName + ")"; - } - - return displayName; - } - - public clone(): AttributeTransformationModel { - var clone = new AttributeTransformationModel(this.AttributeModel); - clone.AggregateFunction = this.AggregateFunction; - return clone; - } - - public Equals(other: AttributeTransformationModel): boolean { - return this.AggregateFunction === other.AggregateFunction && - this.AttributeModel.Equals(other.AttributeModel); - } -}
\ No newline at end of file diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts deleted file mode 100644 index a197c1305..000000000 --- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel"; -import { DataType, VisualizationHint } from '../../model/idea/idea'; - -export class CalculatedAttributeManager { - public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>(); - - public static Clear() { - this.AllCalculatedAttributes = new Array<AttributeModel>(); - } - - public static CreateBackendAttributeModel(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]): BackendAttributeModel { - var filtered = this.AllCalculatedAttributes.filter(am => { - if (am instanceof BackendAttributeModel && - am.Id === id) { - return true; - } - return false; - }); - if (filtered.length > 0) { - return filtered[0] as BackendAttributeModel; - } - var newAttr = new BackendAttributeModel(id, dataType, displayName, codeName, visualizationHints); - this.AllCalculatedAttributes.push(newAttr); - return newAttr; - } - - public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel { - var filtered = this.AllCalculatedAttributes.filter(am => { - if (am instanceof CodeAttributeModel && - am.CodeName === codeName) { - return true; - } - return false; - }); - if (filtered.length > 0) { - return filtered[0] as CodeAttributeModel; - } - var newAttr = new CodeAttributeModel(code, codeName, codeName.ReplaceAll("_", " "), visualizationHints); - this.AllCalculatedAttributes.push(newAttr); - return newAttr; - } -}
\ No newline at end of file diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts deleted file mode 100644 index 87f4ba413..000000000 --- a/src/client/northstar/core/brusher/IBaseBrushable.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PIXIPoint } from '../../utils/MathUtil'; -import { IEquatable } from '../../utils/IEquatable'; -import { Doc } from '../../../../new_fields/Doc'; - -export interface IBaseBrushable<T> extends IEquatable { - BrusherModels: Array<Doc>; - BrushColors: Array<number>; - Position: PIXIPoint; - Size: PIXIPoint; -} -export function instanceOfIBaseBrushable<T>(object: any): object is IBaseBrushable<T> { - return 'BrusherModels' in object; -}
\ No newline at end of file diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts deleted file mode 100644 index d2de6ed62..000000000 --- a/src/client/northstar/core/brusher/IBaseBrusher.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PIXIPoint } from '../../utils/MathUtil'; -import { IEquatable } from '../../utils/IEquatable'; - - -export interface IBaseBrusher<T> extends IEquatable { - Position: PIXIPoint; - Size: PIXIPoint; -} -export function instanceOfIBaseBrusher<T>(object: any): object is IBaseBrusher<T> { - return 'BrushableModels' in object; -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts deleted file mode 100644 index 6ab96b33d..000000000 --- a/src/client/northstar/core/filter/FilterModel.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ValueComparison } from "./ValueComparision"; -import { Utils } from "../../utils/Utils"; -import { IBaseFilterProvider } from "./IBaseFilterProvider"; -import { FilterOperand } from "./FilterOperand"; -import { HistogramField } from "../../dash-fields/HistogramField"; -import { Cast, FieldValue } from "../../../../new_fields/Types"; -import { Doc } from "../../../../new_fields/Doc"; - -export class FilterModel { - public ValueComparisons: ValueComparison[]; - constructor() { - this.ValueComparisons = new Array<ValueComparison>(); - } - - public Equals(other: FilterModel): boolean { - if (!Utils.EqualityHelper(this, other)) return false; - if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false; - return true; - } - - private isSame(a: ValueComparison[], b: ValueComparison[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - let valueComp = a[i]; - if (!valueComp.Equals(b[i])) { - return false; - } - } - return true; - } - - public ToPythonString(): string { - return "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")"; - } - - public static And(filters: string[]): string { - let ret = filters.filter(f => f !== "").join(" && "); - return ret; - } - public static GetFilterModelsRecursive(baseOperation: IBaseFilterProvider, visitedFilterProviders: Set<IBaseFilterProvider>, filterModels: FilterModel[], isFirst: boolean): string { - let ret = ""; - visitedFilterProviders.add(baseOperation); - let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0); - if (!isFirst && filtered.length > 0) { - filterModels.push(...filtered); - ret = "(" + baseOperation.FilterModels.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")"; - } - if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) { - let children = new Array<string>(); - let linkedGraphNodes = baseOperation.Links; - linkedGraphNodes.map(linkVm => { - let filterDoc = FieldValue(Cast(linkVm.linkedFrom, Doc)); - if (filterDoc) { - let filterHistogram = Cast(filterDoc.data, HistogramField); - if (filterHistogram) { - if (!visitedFilterProviders.has(filterHistogram.HistoOp)) { - let child = FilterModel.GetFilterModelsRecursive(filterHistogram.HistoOp, visitedFilterProviders, filterModels, false); - if (child !== "") { - // if (linkVm.IsInverted) { - // child = "! " + child; - // } - children.push(child); - } - } - } - } - }); - - let childrenJoined = children.join(baseOperation.FilterOperand === FilterOperand.AND ? " && " : " || "); - if (children.length > 0) { - if (ret !== "") { - ret = "(" + ret + " && (" + childrenJoined + "))"; - } - else { - ret = "(" + childrenJoined + ")"; - } - } - } - return ret; - } -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/FilterOperand.ts b/src/client/northstar/core/filter/FilterOperand.ts deleted file mode 100644 index 2e8e8d6a0..000000000 --- a/src/client/northstar/core/filter/FilterOperand.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum FilterOperand -{ - AND, - OR -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/FilterType.ts b/src/client/northstar/core/filter/FilterType.ts deleted file mode 100644 index 9adbc087f..000000000 --- a/src/client/northstar/core/filter/FilterType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum FilterType -{ - Filter, - Brush, - Slice -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts deleted file mode 100644 index e7549d113..000000000 --- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { FilterOperand } from '../filter/FilterOperand'; -import { IEquatable } from '../../utils/IEquatable'; -import { Doc } from '../../../../new_fields/Doc'; - -export interface IBaseFilterConsumer extends IEquatable { - FilterOperand: FilterOperand; - Links: Doc[]; -} - -export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer { - return 'FilterOperand' in object; -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts deleted file mode 100644 index fc3301b11..000000000 --- a/src/client/northstar/core/filter/IBaseFilterProvider.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FilterModel } from '../filter/FilterModel'; - -export interface IBaseFilterProvider { - FilterModels: Array<FilterModel>; -} -export function instanceOfIBaseFilterProvider(object: any): object is IBaseFilterProvider { - return 'FilterModels' in object; -}
\ No newline at end of file diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts deleted file mode 100644 index 65687a82b..000000000 --- a/src/client/northstar/core/filter/ValueComparision.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Predicate } from '../../model/idea/idea'; -import { Utils } from '../../utils/Utils'; -import { AttributeModel } from '../attribute/AttributeModel'; - -export class ValueComparison { - - public attributeModel: AttributeModel; - public Value: any; - public Predicate: Predicate; - - public constructor(attributeModel: AttributeModel, predicate: Predicate, value: any) { - this.attributeModel = attributeModel; - this.Value = value; - this.Predicate = predicate; - } - - public Equals(other: Object): boolean { - if (!Utils.EqualityHelper(this, other)) { - return false; - } - if (this.Predicate !== (other as ValueComparison).Predicate) { - return false; - } - let isComplex = (typeof this.Value === "object"); - if (!isComplex && this.Value !== (other as ValueComparison).Value) { - return false; - } - if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) { - return false; - } - return true; - } - - public ToPythonString(): string { - var op = ""; - switch (this.Predicate) { - case Predicate.EQUALS: - op = "=="; - break; - case Predicate.GREATER_THAN: - op = ">"; - break; - case Predicate.GREATER_THAN_EQUAL: - op = ">="; - break; - case Predicate.LESS_THAN: - op = "<"; - break; - case Predicate.LESS_THAN_EQUAL: - op = "<="; - break; - default: - op = "=="; - break; - } - - var val = this.Value.toString(); - if (typeof this.Value === 'string' || this.Value instanceof String) { - val = "\"" + val + "\""; - } - var ret = " "; - var rawName = this.attributeModel.CodeName; - switch (this.Predicate) { - case Predicate.STARTS_WITH: - ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") "; - return ret; - case Predicate.ENDS_WITH: - ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") "; - return ret; - case Predicate.CONTAINS: - ret += rawName + " != null && " + rawName + ".Contains(" + val + ") "; - return ret; - default: - ret += rawName + " " + op + " " + val + " "; - return ret; - } - } -}
\ No newline at end of file diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts deleted file mode 100644 index 076516977..000000000 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { observable } from "mobx"; -import { custom, serializable } from "serializr"; -import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel"; -import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel"; -import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { OmitKeys } from "../../../Utils"; -import { Deserializable } from "../../util/SerializationHelper"; -import { Copy, ToScriptString, ToString } from "../../../new_fields/FieldSymbols"; - -function serialize(field: HistogramField) { - const obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit; - return obj; -} - -function deserialize(jp: any) { - let X: AttributeTransformationModel | undefined; - let Y: AttributeTransformationModel | undefined; - let V: AttributeTransformationModel | undefined; - - const schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName); - if (schema) { - CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) { - X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction); - } - if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) { - Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction); - } - if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) { - V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction); - } - }); - if (X && Y && V) { - return new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization); - } - } - return HistogramOperation.Empty; -} - -@Deserializable("histogramField") -export class HistogramField extends ObjectField { - @serializable(custom(serialize, deserialize)) @observable public readonly HistoOp: HistogramOperation; - constructor(data?: HistogramOperation) { - super(); - this.HistoOp = data ? data : HistogramOperation.Empty; - } - - toString(): string { - return JSON.stringify(OmitKeys(this.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit); - } - - [Copy]() { - // const y = this.HistoOp; - // const z = this.HistoOp.Copy; - return new HistogramField(HistogramOperation.Duplicate(this.HistoOp)); - } - - [ToScriptString]() { - return this.toString(); - } - [ToString]() { - return this.toString(); - } -}
\ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts deleted file mode 100644 index 6b36ffc9e..000000000 --- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts +++ /dev/null @@ -1,240 +0,0 @@ -import React = require("react"); -import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; -import { ChartType } from '../../northstar/model/binRanges/VisualBinRange'; -import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea"; -import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { LABColor } from '../../northstar/utils/LABColor'; -import { PIXIRectangle } from "../../northstar/utils/MathUtil"; -import { StyleConstants } from "../../northstar/utils/StyleContants"; -import { HistogramBox } from "./HistogramBox"; -import "./HistogramBoxPrimitives.scss"; - -export class HistogramBinPrimitive { - constructor(init?: Partial<HistogramBinPrimitive>) { - Object.assign(this, init); - } - public DataValue: number = 0; - public Rect: PIXIRectangle = PIXIRectangle.EMPTY; - public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY; - public MarginPercentage: number = 0; - public Color: number = StyleConstants.WARNING_COLOR; - public Opacity: number = 1; - public BrushIndex: number = 0; - public BarAxis: number = -1; -} - -export class HistogramBinPrimitiveCollection { - private static TOLERANCE: number = 0.0001; - - private _histoBox: HistogramBox; - private get histoOp() { return this._histoBox.HistoOp; } - private get histoResult() { return this.histoOp.Result as HistogramResult; } - private get sizeConverter() { return this._histoBox.SizeConverter; } - public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>(); - public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY; - - constructor(bin: Bin, histoBox: HistogramBox) { - this._histoBox = histoBox; - let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2 - - brushing.orderedBrushes.reduce((brushFactorSum, brush) => { - switch (histoBox.ChartType) { - case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); - case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); - case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush); - case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum); - } - }, 0); - - // adjust brush rects (stacking or not) - var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult); - var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex !== allBrushIndex && b.DataValue !== 0.0); - filteredBinPrims.reduce((sum, fbp) => { - if (histoBox.ChartType === ChartType.VerticalBar) { - if (this.histoOp.Y.AggregateFunction === AggregateFunction.Count) { - fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.height; - } - if (this.histoOp.Y.AggregateFunction === AggregateFunction.Avg) { - var w = fbp.Rect.width / 2.0; - fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.width; - } - } - else if (histoBox.ChartType === ChartType.HorizontalBar) { - if (this.histoOp.X.AggregateFunction === AggregateFunction.Count) { - fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.width; - } - if (this.histoOp.X.AggregateFunction === AggregateFunction.Avg) { - var h = fbp.Rect.height / 2.0; - fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.height; - } - } - return 0; - }, 0); - this.BinPrimitives = this.BinPrimitives.reverse(); - var f = this.BinPrimitives.filter(b => b.BrushIndex === allBrushIndex); - this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY; - } - - private setupBrushing(bin: Bin, normalization: number) { - var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult); - var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]]; - this.histoResult.brushes!.map(brush => brush.brushIndex !== 0 && brush.brushIndex !== overlapBrushIndex && orderedBrushes.push(brush)); - return { - orderedBrushes, - maxAxis: orderedBrushes.reduce((prev, Brush) => { - let aggResult = this.getBinValue(normalization, bin, Brush.brushIndex!); - return aggResult !== undefined && aggResult > prev ? aggResult : prev; - }, Number.MIN_VALUE) - }; - } - - private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number { - - let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!); - if (unNormalizedValue === undefined) { - return brushFactorSum; - } - - var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ? - unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]); - - let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult)); - - // bcz: are these calls needed? - let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin); - let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin); - - var returnBrushFactorSum = brushFactorSum; - if (allUnNormalizedValue !== undefined) { - var brushFactor = (unNormalizedValue / allUnNormalizedValue); - returnBrushFactorSum += brushFactor; - returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0); - - var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo); - var ratio = (tempRect.width / tempRect.height); - var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum)); - var newWidth = newHeight * ratio; - - xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0); - yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0); - xTo = (xFrom + newWidth); - yFrom = (yTo + newHeight); - } - var alpha = 0.0; - var color = this.baseColorFromBrush(brush); - var lerpColor = LABColor.Lerp( - LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR), - LABColor.FromColor(color), - (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha))); - var dataColor = LABColor.ToColor(lerpColor); - - this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue); - return returnBrushFactorSum; - } - - private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number { - let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!); - if (unNormalizedValue !== undefined) { - let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!)); - let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!)); - - if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined) { - this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue); - } - } - return 0; - } - - private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { - let dataValue = this.getBinValue(1, bin, brush.brushIndex!); - if (dataValue !== undefined) { - let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis); - let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin); - - var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y); - var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1, - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2, - this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute)); - - this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, - this.baseColorFromBrush(brush), normalization !== 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue); - } - return 0; - } - - private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { - let dataValue = this.getBinValue(0, bin, brush.brushIndex!); - if (dataValue !== undefined) { - let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis); - let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin); - - var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X); - var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), - yTo + (yFrom - yTo) / 2.0 - 1, - this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), - 2.0); - - this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, - this.baseColorFromBrush(brush), normalization !== 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue); - } - return 0; - } - - public getBinValue(axis: number, bin: Bin, brushIndex: number) { - var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis === 0 ? this.histoOp.X : axis === 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex); - let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult; - return dataValue !== null && dataValue.hasResult ? dataValue.result : undefined; - } - - private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) { - var marginParams = new MarginAggregateParameters(); - marginParams.aggregateFunction = axis.AggregateFunction; - var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams); - let aggResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey); - return aggResult instanceof MarginAggregateResult && aggResult.absolutMargin ? aggResult.absolutMargin : 0; - } - - private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle, - marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) { - var binPrimitive = new HistogramBinPrimitive( - { - Rect: new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo), - MarginRect: marginRect, - MarginPercentage: marginPercentage, - BrushIndex: brush.brushIndex, - Color: color, - Opacity: opacity, - DataValue: dataValue, - BarAxis: barAxis - }); - this.BinPrimitives.push(binPrimitive); - } - - private baseColorFromBrush(brush: Brush): number { - let bc = StyleConstants.BRUSH_COLORS; - if (brush.brushIndex === ModelHelpers.RestBrushIndex(this.histoResult)) { - return StyleConstants.HIGHLIGHT_COLOR; - } - else if (brush.brushIndex === ModelHelpers.OverlapBrushIndex(this.histoResult)) { - return StyleConstants.OVERLAP_COLOR; - } - else if (brush.brushIndex === ModelHelpers.AllBrushIndex(this.histoResult)) { - return 0x00ff00; - } - else if (bc.length > 0) { - return bc[brush.brushIndex! % bc.length]; - } - // else if (this.histoOp.BrushColors.length > 0) { - // return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length]; - // } - return StyleConstants.HIGHLIGHT_COLOR; - } -} diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss deleted file mode 100644 index 06d781263..000000000 --- a/src/client/northstar/dash-nodes/HistogramBox.scss +++ /dev/null @@ -1,40 +0,0 @@ -.histogrambox-container { - padding: 0vw; - position: absolute; - top: -50%; - left:-50%; - text-align: center; - width: 100%; - height: 100%; - background: black; - } - .histogrambox-xaxislabel { - position:absolute; - left:0; - width:100%; - text-align: center; - bottom:0; - background: lightgray; - font-size: 14; - font-weight: bold; - } - .histogrambox-yaxislabel { - position:absolute; - height:100%; - width: 25px; - left:0; - bottom:0; - background: lightgray; - } - .histogrambox-yaxislabel-text { - position:absolute; - left:0; - width: 1000px; - transform-origin: 10px 10px; - transform: rotate(-90deg); - text-align: left; - font-size: 14; - font-weight: bold; - bottom: calc(50% - 25px); - } -
\ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx deleted file mode 100644 index 8fee53fb9..000000000 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React = require("react"); -import { action, computed, observable, reaction, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange'; -import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper"; -import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea"; -import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; -import { SizeConverter } from "../../northstar/utils/SizeConverter"; -import { DragManager } from "../../util/DragManager"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; -import { HistogramField } from "../dash-fields/HistogramField"; -import "../utils/Extensions"; -import "./HistogramBox.scss"; -import { HistogramBoxPrimitives } from './HistogramBoxPrimitives'; -import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives"; -import { StyleConstants } from "../utils/StyleContants"; -import { Cast } from "../../../new_fields/Types"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; - - -@observer -export class HistogramBox extends React.Component<FieldViewProps> { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); } - private _dropXRef = React.createRef<HTMLDivElement>(); - private _dropYRef = React.createRef<HTMLDivElement>(); - private _dropXDisposer?: DragManager.DragDropDisposer; - private _dropYDisposer?: DragManager.DragDropDisposer; - - @observable public HistoOp: HistogramOperation = HistogramOperation.Empty; - @observable public VisualBinRanges: VisualBinRange[] = []; - @observable public ValueRange: number[] = []; - @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; } - @observable public SizeConverter: SizeConverter = new SizeConverter(); - - @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); } - @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; } - @computed get ChartType() { - return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ? - (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) : - this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap; - } - - @action - dropX = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField); - if (h) { - this.HistoOp.X = h.HistoOp.X; - } - e.stopPropagation(); - e.preventDefault(); - } - } - @action - dropY = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField); - if (h) { - this.HistoOp.Y = h.HistoOp.X; - } - e.stopPropagation(); - e.preventDefault(); - } - } - - @action - xLabelPointerDown = (e: React.PointerEvent) => { - this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None); - } - @action - yLabelPointerDown = (e: React.PointerEvent) => { - this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None); - } - - componentDidMount() { - if (this._dropXRef.current) { - this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, this.dropX.bind(this)); - } - if (this._dropYRef.current) { - 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)); - reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], (size: number[]) => this.SizeConverter.SetIsSmall(size[0] < 40 && size[1] < 40)); - reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined, - (binRanges: BinRange[] | undefined) => { - if (binRanges) { - this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) => - VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType))); - - let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult, ModelHelpers.AllBrushIndex(this.HistogramResult)); - this.ValueRange = Object.values(this.HistogramResult.bins!).reduce((prev, cur) => { - let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult; - return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev; - }, [Number.MAX_VALUE, Number.MIN_VALUE]); - } - }); - } - - componentWillUnmount() { - if (this._dropXDisposer) { - this._dropXDisposer(); - } - if (this._dropYDisposer) { - this._dropYDisposer(); - } - } - - async activateHistogramOperation(catalog?: Catalog) { - if (catalog) { - let histoOp = await Cast(this.props.Document[this.props.fieldKey], HistogramField); - runInAction(() => { - this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty; - if (this.HistoOp !== HistogramOperation.Empty) { - reaction(() => DocListCast(this.props.Document.linkedFromDocs), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); - reaction(() => DocListCast(this.props.Document.brushingDocs).length, - async () => { - let brushingDocs = await DocListCastAsync(this.props.Document.brushingDocs); - const proto = this.props.Document.proto; - if (proto && brushingDocs) { - let mapped = brushingDocs.map((brush, i) => { - brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]; - let brushed = DocListCast(brush.brushingDocs); - if (!brushed.length) return null; - return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] }; - }); - runInAction(() => this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped.filter(m => m) as { l: Doc, b: Doc }[])); - } - }, { fireImmediately: true }); - reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true }); - } - }); - } - } - - @action - private onScrollWheel = (e: React.WheelEvent) => { - this.HistoOp.DrillDown(e.deltaY > 0); - e.stopPropagation(); - } - - render() { - let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>"; - let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>"; - let loff = this.SizeConverter.LeftOffset; - let toff = this.SizeConverter.TopOffset; - let roff = this.SizeConverter.RightOffset; - let boff = this.SizeConverter.BottomOffset; - return ( - <div className="histogrambox-container" onWheel={this.onScrollWheel}> - <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} > - <span className="histogrambox-yaxislabel-text"> - {labelY} - </span> - </div> - <div className="histogrambox-primitives" style={{ - transform: `translate(${loff + 25}px, ${toff}px)`, - width: `calc(100% - ${loff + roff + 25}px)`, - height: `calc(100% - ${toff + boff}px)`, - }}> - <HistogramLabelPrimitives HistoBox={this} /> - <HistogramBoxPrimitives HistoBox={this} /> - </div> - <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} > - {labelX} - </div> - </div> - ); - } -} - diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss deleted file mode 100644 index 26203612a..000000000 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss +++ /dev/null @@ -1,42 +0,0 @@ -.histogramboxprimitives-container { - width: 100%; - height: 100%; -} -.histogramboxprimitives-border { - border: 3px; - pointer-events: none; - position: absolute; - fill:"transparent"; - stroke: white; - stroke-width: 1px; -} -.histogramboxprimitives-bar { - position: absolute; - border: 1px; - border-style: solid; - border-color: #282828; - pointer-events: all; -} - -.histogramboxprimitives-placer { - position: absolute; - pointer-events: none; - width: 100%; - height: 100%; -} -.histogramboxprimitives-svgContainer { - position: absolute; - top:0; - left:0; - width:100%; - height: 100%; -} -.histogramboxprimitives-line { - position: absolute; - background: darkGray; - stroke: darkGray; - stroke-width: 1px; - width:100%; - height:100%; - opacity: 0.4; -}
\ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx deleted file mode 100644 index 66d91cc1d..000000000 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React = require("react"); -import { computed, observable, reaction, runInAction, trace, action } from "mobx"; -import { observer } from "mobx-react"; -import { Utils as DashUtils, emptyFunction } from '../../../Utils'; -import { FilterModel } from "../../northstar/core/filter/FilterModel"; -import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { ArrayUtil } from "../../northstar/utils/ArrayUtil"; -import { LABColor } from '../../northstar/utils/LABColor'; -import { PIXIRectangle } from "../../northstar/utils/MathUtil"; -import { StyleConstants } from "../../northstar/utils/StyleContants"; -import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection"; -import { HistogramBox } from "./HistogramBox"; -import "./HistogramBoxPrimitives.scss"; - -export interface HistogramPrimitivesProps { - HistoBox: HistogramBox; -} -@observer -export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> { - private get histoOp() { return this.props.HistoBox.HistoOp; } - private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; } - @observable _selectedPrims: HistogramBinPrimitive[] = []; - @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } - @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } - @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); } - @computed get barPrimitives() { - let histoResult = this.props.HistoBox.HistogramResult; - if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) { - return (null); - } - let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult); - return Object.keys(histoResult.bins).reduce((prims: JSX.Element[], key: string) => { - let drawPrims = new HistogramBinPrimitiveCollection(histoResult.bins![key], this.props.HistoBox); - let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, - ModelHelpers.GetBinFilterModel(histoResult.bins![key], allBrushIndex, histoResult, this.histoOp.X, this.histoOp.Y)); - drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp => - prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle)))); - return prims; - }, [] as JSX.Element[]); - } - - componentDidMount() { - reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0); - } - - private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) { - let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex); - if (!rawAllBrushPrim) { - return emptyFunction; - } - let allBrushPrim = rawAllBrushPrim; - return () => runInAction(() => { - if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) { - this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim), 1); - this.histoOp.RemoveFilterModels([filterModel]); - } - else { - this._selectedPrims.push(allBrushPrim); - this.histoOp.AddFilterModels([filterModel]); - } - }); - } - - private renderGridLinesAndLabels(axis: number) { - if (!this.props.HistoBox.SizeConverter.Initialized) { - return (null); - } - let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels(); - return <svg className="histogramboxprimitives-svgContainer"> - {labels.reduce((prims, binLabel, i) => { - let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); - prims.push(this.drawLine(r.xFrom, r.yFrom, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0)); - if (i === labels.length - 1) { - prims.push(this.drawLine(axis === 0 ? r.xTo : r.xFrom, axis === 0 ? r.yFrom : r.yTo, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0)); - } - return prims; - }, [] as JSX.Element[])} - </svg>; - } - - drawLine(xFrom: number, yFrom: number, width: number, height: number) { - if (height < 0) { - yFrom += height; - height = -height; - } - if (width < 0) { - xFrom += width; - width = -width; - } - let trans2Xpercent = `${(xFrom + width) / this.renderDimension * 100}%`; - let trans2Ypercent = `${(yFrom + height) / this.renderDimension * 100}%`; - let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`; - let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`; - return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />; - } - drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = emptyFunction) { - if (r.height < 0) { - r.y += r.height; - r.height = -r.height; - } - if (r.width < 0) { - r.x += r.width; - r.width = -r.width; - } - let transXpercent = `${r.x / this.renderDimension * 100}%`; - let transYpercent = `${r.y / this.renderDimension * 100}%`; - let widthXpercent = `${r.width / this.renderDimension * 100}%`; - let heightYpercent = `${r.height / this.renderDimension * 100}%`; - return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler(); }} - x={transXpercent} width={`${widthXpercent}`} y={transYpercent} height={`${heightYpercent}`} fill={color ? `${LABColor.RGBtoHexString(color)}` : "transparent"} />); - } - render() { - return <div className="histogramboxprimitives-container"> - {this.xaxislines} - {this.yaxislines} - <svg className="histogramboxprimitives-svgContainer"> - {this.barPrimitives} - {this.selectedPrimitives} - </svg> - </div>; - } -} diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss deleted file mode 100644 index 304d33771..000000000 --- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss +++ /dev/null @@ -1,13 +0,0 @@ - - .histogramLabelPrimitives-gridlabel { - position:absolute; - transform-origin: left top; - font-size: 11; - color:white; - } - .histogramLabelPrimitives-placer { - position:absolute; - width:100%; - height:100%; - pointer-events: none; - }
\ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx deleted file mode 100644 index 62aebd3c6..000000000 --- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React = require("react"); -import { action, computed, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Utils as DashUtils } from '../../../Utils'; -import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange"; -import "../utils/Extensions"; -import { StyleConstants } from "../utils/StyleContants"; -import { HistogramBox } from "./HistogramBox"; -import "./HistogramLabelPrimitives.scss"; -import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives"; - -@observer -export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> { - componentDidMount() { - reaction(() => [this.props.HistoBox.props.PanelWidth(), this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length], - (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true }); - } - - @action - static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) { - const textWidth = 30; - if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) { - let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length; - histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2))); - } else if (histoBox.SizeConverter.LabelAngle) { - histoBox.SizeConverter.SetLabelAngle(0); - } - } - @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } - @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } - - private renderGridLinesAndLabels(axis: number) { - let sc = this.props.HistoBox.SizeConverter; - let vb = this.props.HistoBox.VisualBinRanges; - if (!vb.length || !sc.Initialized) { - return (null); - } - let dim = (axis === 0 ? this.props.HistoBox.props.PanelWidth() : this.props.HistoBox.props.PanelHeight()) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ? - (12 + 5) : // (<number>FontStyles.AxisLabel.fontSize + 5))); - sc.MaxLabelSizes[axis].coords[axis] + 5); - - let labels = vb[axis].GetLabels(); - return labels.reduce((prims, binLabel, i) => { - let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); - if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) { - const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "..."); - const textHeight = 14; const textWidth = 30; - let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth); - let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom); - - if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) { - let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.props.PanelWidth(); - xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2; - } - - let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`; - let yPercent = axis === 0 ? `${this.props.HistoBox.props.PanelHeight() - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`; - - prims.push( - <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}> - <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis === 0 ? sc.LabelAngle : 0}rad)` }}> - {label} - </div> - </div> - ); - } - return prims; - }, [] as JSX.Element[]); - } - - render() { - let xaxislines = this.xaxislines; - let yaxislines = this.yaxislines; - return <div className="histogramLabelPrimitives-container"> - {xaxislines} - {yaxislines} - </div>; - } - -}
\ No newline at end of file diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts deleted file mode 100644 index c541cce6a..000000000 --- a/src/client/northstar/manager/Gateway.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea"; -import { computed, observable, action } from "mobx"; - -export class Gateway { - - private static _instance: Gateway; - - private constructor() { - } - - public static get Instance() { - return this._instance || (this._instance = new this()); - } - - public async GetCatalog(): Promise<Catalog> { - try { - const json = await this.MakeGetRequest("catalog"); - const cat = Catalog.fromJS(json); - return cat; - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async PostSchema(csvdata: string, schemaname: string): Promise<string> { - try { - const json = await this.MakePostJsonRequest("postSchema", { csv: csvdata, schema: schemaname }); - // const cat = Catalog.fromJS(json); - // return cat; - return json; - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> { - try { - const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname }); - const cat = Catalog.fromJS(json); - return cat; - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async ClearCatalog(): Promise<void> { - try { - await this.MakePostJsonRequest("Datamart/ClearAllAugmentations", {}); - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async TerminateServer(): Promise<void> { - try { - const url = Gateway.ConstructUrl("terminateServer"); - const response = await fetch(url, - { - redirect: "follow", - method: "POST", - credentials: "include" - }); - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async Compile(data: any): Promise<CompileResults | undefined> { - const json = await this.MakePostJsonRequest("compile", data); - if (json !== null) { - const cr = CompileResults.fromJS(json); - return cr; - } - } - - public async SubmitResult(data: any): Promise<void> { - try { - console.log(data); - const url = Gateway.ConstructUrl("submitProblem"); - const response = await fetch(url, - { - redirect: "follow", - method: "POST", - credentials: "include", - body: JSON.stringify(data) - }); - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async SpecifyProblem(data: any): Promise<void> { - try { - console.log(data); - const url = Gateway.ConstructUrl("specifyProblem"); - const response = await fetch(url, - { - redirect: "follow", - method: "POST", - credentials: "include", - body: JSON.stringify(data) - }); - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - public async ExportToScript(solutionId: string): Promise<string> { - try { - const url = Gateway.ConstructUrl("exportsolution/script/" + solutionId); - const response = await fetch(url, - { - redirect: "follow", - method: "GET", - credentials: "include" - }); - return await response.text(); - } - catch (error) { - throw new Error("can not reach northstar's backend"); - } - } - - - public async StartOperation(data: any): Promise<OperationReference | undefined> { - const json = await this.MakePostJsonRequest("operation", data); - if (json !== null) { - const or = OperationReference.fromJS(json); - return or; - } - } - - public async GetResult(data: any): Promise<Result | undefined> { - const json = await this.MakePostJsonRequest("result", data); - if (json !== null) { - const res = Result.fromJS(json); - return res; - } - } - - public async PauseOperation(data: any): Promise<void> { - const url = Gateway.ConstructUrl("pause"); - await fetch(url, - { - redirect: "follow", - method: "POST", - credentials: "include", - body: JSON.stringify(data) - }); - } - - public async MakeGetRequest(endpoint: string, signal?: AbortSignal, params?: any): Promise<any> { - let url = !params ? Gateway.ConstructUrl(endpoint) : - (() => { - let newUrl = new URL(Gateway.ConstructUrl(endpoint)); - Object.getOwnPropertyNames(params).map(prop => - newUrl.searchParams.append(prop, params[prop])); - return Gateway.ConstructUrl(endpoint) + newUrl.search; - })(); - - const response = await fetch(url, - { - redirect: "follow", - method: "GET", - credentials: "include", - signal - }); - const json = await response.json(); - return json; - } - - public async MakePostJsonRequest(endpoint: string, data: any, signal?: AbortSignal): Promise<any> { - const url = Gateway.ConstructUrl(endpoint); - const response = await fetch(url, - { - redirect: "follow", - method: "POST", - credentials: "include", - body: JSON.stringify(data), - signal - }); - const json = await response.json(); - return json; - } - - - public static ConstructUrl(appendix: string): string { - let base = NorthstarSettings.Instance.ServerUrl; - if (base.slice(-1) === "/") { - base = base.slice(0, -1); - } - let url = base + "/" + NorthstarSettings.Instance.ServerApiPath + "/" + appendix; - return url; - } -} - -declare var ENV: any; - -export class NorthstarSettings { - private _environment: any; - - @observable - public ServerUrl: string = document.URL; - - @observable - public ServerApiPath?: string; - - @observable - public SampleSize?: number; - - @observable - public XBins?: number; - - @observable - public YBins?: number; - - @observable - public SplashTimeInMS?: number; - - @observable - public ShowFpsCounter?: boolean; - - @observable - public IsMenuFixed?: boolean; - - @observable - public ShowShutdownButton?: boolean; - - @observable - public IsDarpa?: boolean; - - @observable - public IsIGT?: boolean; - - @observable - public DegreeOfParallelism?: number; - - @observable - public ShowWarnings?: boolean; - - @computed - public get IsDev(): boolean { - return ENV.IsDev; - } - - @computed - public get TestDataFolderPath(): string { - return this.Origin + "testdata/"; - } - - @computed - public get Origin(): string { - return window.location.origin + "/"; - } - - private static _instance: NorthstarSettings; - - @action - public UpdateEnvironment(environment: any): void { - /*let serverParam = new URL(document.URL).searchParams.get("serverUrl"); - if (serverParam) { - if (serverParam === "debug") { - this.ServerUrl = `http://${window.location.hostname}:1234`; - } - else { - this.ServerUrl = serverParam; - } - } - else { - this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL; - }*/ - this.ServerUrl = environment.SERVER_URL ? environment.SERVER_URL : document.URL; - this.ServerApiPath = environment.SERVER_API_PATH; - this.SampleSize = environment.SAMPLE_SIZE; - this.XBins = environment.X_BINS; - this.YBins = environment.Y_BINS; - this.SplashTimeInMS = environment.SPLASH_TIME_IN_MS; - this.ShowFpsCounter = environment.SHOW_FPS_COUNTER; - this.ShowShutdownButton = environment.SHOW_SHUTDOWN_BUTTON; - this.IsMenuFixed = environment.IS_MENU_FIXED; - this.IsDarpa = environment.IS_DARPA; - this.IsIGT = environment.IS_IGT; - this.DegreeOfParallelism = environment.DEGREE_OF_PARALLISM; - } - - public static get Instance(): NorthstarSettings { - if (!this._instance) { - this._instance = new NorthstarSettings(); - } - return this._instance; - } -} diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts deleted file mode 100644 index 29f80d2d1..000000000 --- a/src/client/northstar/model/ModelExtensions.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea'; -import { Utils } from '../utils/Utils'; - -import { FilterModel } from '../core/filter/FilterModel'; - -(SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) { - if (!Utils.EqualityHelper(this, other)) return false; - if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!, - (other as SingleDimensionAggregateParameters).attributeParameters!)) return false; - if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false; - return true; -}; - -{ - (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) { - return (this).constructor.name === (<any>other).constructor.name && - this.rawName === other.rawName; - }; -} - -{ - (Solution as any).prototype.Equals = function (other: Object) { - if (!Utils.EqualityHelper(this, other)) return false; - if ((this as Solution).solutionId !== (other as Solution).solutionId) return false; - return true; - }; -} - -{ - (MarginAggregateParameters as any).prototype.Equals = function (other: Object) { - if (!Utils.EqualityHelper(this, other)) return false; - if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!, - (other as SingleDimensionAggregateParameters).attributeParameters!)) return false; - if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters!)) return false; - - if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false; - return true; - }; -} - -{ - (Brush as any).prototype.Equals = function (other: Object) { - if (!Utils.EqualityHelper(this, other)) return false; - if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false; - if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false; - return true; - }; -}
\ No newline at end of file diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts deleted file mode 100644 index 88e6e72b8..000000000 --- a/src/client/northstar/model/ModelHelpers.ts +++ /dev/null @@ -1,220 +0,0 @@ - -import { action } from "mobx"; -import { AggregateFunction, AggregateKey, AggregateParameters, AttributeColumnParameters, AttributeParameters, AverageAggregateParameters, Bin, BinningParameters, Brush, BrushEnum, CountAggregateParameters, DataType, EquiWidthBinningParameters, HistogramResult, MarginAggregateParameters, SingleBinBinningParameters, SingleDimensionAggregateParameters, SumAggregateParameters, AggregateBinRange, NominalBinRange, AlphabeticBinRange, Predicate, Schema, Attribute, AttributeGroup, Exception, AttributeBackendParameters, AttributeCodeParameters } from '../model/idea/idea'; -import { ValueComparison } from "../core/filter/ValueComparision"; -import { ArrayUtil } from "../utils/ArrayUtil"; -import { AttributeModel, ColumnAttributeModel, BackendAttributeModel, CodeAttributeModel } from "../core/attribute/AttributeModel"; -import { FilterModel } from "../core/filter/FilterModel"; -import { AlphabeticVisualBinRange } from "./binRanges/AlphabeticVisualBinRange"; -import { NominalVisualBinRange } from "./binRanges/NominalVisualBinRange"; -import { VisualBinRangeHelper } from "./binRanges/VisualBinRangeHelper"; -import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; - -export class ModelHelpers { - - public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult, - brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey { - { - if (aggParameters === undefined) { - aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm); - } - else { - aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - } - return new AggregateKey( - { - aggregateParameterIndex: ModelHelpers.GetAggregateParametersIndex(histogramResult, aggParameters), - brushIndex: brushIndex - }); - } - } - - public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number { - return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => { - if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters) return true; - if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters) return true; - if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters) return true; - return false; - }); - } - - public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined { - var aggParam: AggregateParameters | undefined; - if (atm.AggregateFunction === AggregateFunction.Avg) { - var avg = new AverageAggregateParameters(); - avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - avg.distinctAttributeParameters = distinctAttributeParameters; - aggParam = avg; - } - else if (atm.AggregateFunction === AggregateFunction.Count) { - var cnt = new CountAggregateParameters(); - cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - cnt.distinctAttributeParameters = distinctAttributeParameters; - aggParam = cnt; - } - else if (atm.AggregateFunction === AggregateFunction.Sum) { - var sum = new SumAggregateParameters(); - sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - sum.distinctAttributeParameters = distinctAttributeParameters; - aggParam = sum; - } - return aggParam; - } - - public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array<AttributeTransformationModel>): Array<AggregateParameters> { - var aggregateParameters = new Array<AggregateParameters>(); - atms.forEach(agg => { - var aggParams = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, agg); - if (aggParams) { - aggregateParameters.push(aggParams); - - var margin = new MarginAggregateParameters(); - margin.aggregateFunction = agg.AggregateFunction; - margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel); - margin.distinctAttributeParameters = distinctAttributeParameters; - aggregateParameters.push(margin); - } - }); - - return aggregateParameters; - } - - public static GetBinningParameters(attr: AttributeTransformationModel, nrOfBins: number, minvalue?: number, maxvalue?: number): BinningParameters { - if (attr.AggregateFunction === AggregateFunction.None) { - return new EquiWidthBinningParameters( - { - attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel), - requestedNrOfBins: nrOfBins, - minValue: minvalue, - maxValue: maxvalue - }); - } - else { - return new SingleBinBinningParameters( - { - attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel) - }); - } - } - - public static GetAttributeParametersFromAttributeModel(am: AttributeModel): AttributeParameters { - if (am instanceof ColumnAttributeModel) { - return new AttributeColumnParameters( - { - rawName: am.CodeName, - visualizationHints: am.VisualizationHints - }); - } - else if (am instanceof BackendAttributeModel) { - return new AttributeBackendParameters( - { - rawName: am.CodeName, - visualizationHints: am.VisualizationHints, - id: (am).Id - }); - } - else if (am instanceof CodeAttributeModel) { - return new AttributeCodeParameters( - { - rawName: am.CodeName, - visualizationHints: am.VisualizationHints, - code: (am).Code - }); - } - else { - throw new Exception(); - } - } - - public static GetAttributeParameters(am: AttributeModel): AttributeParameters { - return this.GetAttributeParametersFromAttributeModel(am); - } - - public static OverlapBrushIndex(histogramResult: HistogramResult): number { - var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.Overlap); - return ModelHelpers.GetBrushIndex(histogramResult, brush); - } - - public static AllBrushIndex(histogramResult: HistogramResult): number { - var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.All); - return ModelHelpers.GetBrushIndex(histogramResult, brush); - } - - public static RestBrushIndex(histogramResult: HistogramResult): number { - var brush = ArrayUtil.First(histogramResult.brushes!, (b: Brush) => b.brushEnum === BrushEnum.Rest); - return ModelHelpers.GetBrushIndex(histogramResult, brush); - } - - public static GetBrushIndex(histogramResult: HistogramResult, brush: Brush): number { - return ArrayUtil.IndexOfWithEqual(histogramResult.brushes!, brush); - } - - public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) { - if (aggregateKey.aggregateParameterIndex === -1 || aggregateKey.brushIndex === -1) { - return null; - } - return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!]; - } - - @action - public static PossibleAggegationFunctions(atm: AttributeTransformationModel): Array<AggregateFunction> { - var ret = new Array<AggregateFunction>(); - ret.push(AggregateFunction.None); - ret.push(AggregateFunction.Count); - if (atm.AttributeModel.DataType === DataType.Float || - atm.AttributeModel.DataType === DataType.Double || - atm.AttributeModel.DataType === DataType.Int) { - ret.push(AggregateFunction.Avg); - ret.push(AggregateFunction.Sum); - } - return ret; - } - - public static GetBinFilterModel( - bin: Bin, brushIndex: number, histogramResult: HistogramResult, - xAom: AttributeTransformationModel, yAom: AttributeTransformationModel): FilterModel { - var dimensions: Array<AttributeTransformationModel> = [xAom, yAom]; - var filterModel = new FilterModel(); - - for (var i = 0; i < histogramResult.binRanges!.length; i++) { - if (!(histogramResult.binRanges![i] instanceof AggregateBinRange)) { - var binRange = VisualBinRangeHelper.GetNonAggregateVisualBinRange(histogramResult.binRanges![i]); - var dataFrom = binRange.GetValueFromIndex(bin.binIndex!.indices![i]); - var dataTo = binRange.AddStep(dataFrom); - - if (binRange instanceof NominalVisualBinRange) { - var tt = binRange.GetLabel(dataFrom); - filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.EQUALS, tt)); - } - else if (binRange instanceof AlphabeticVisualBinRange) { - filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.STARTS_WITH, - binRange.GetLabel(dataFrom))); - } - else { - filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.GREATER_THAN_EQUAL, dataFrom)); - filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.LESS_THAN, dataTo)); - } - } - } - - return filterModel; - } - - public GetAllAttributes(schema: Schema) { - if (!schema || !schema.rootAttributeGroup) { - return []; - } - const recurs = (attrs: Attribute[], g: AttributeGroup) => { - if (g.attributes) { - attrs.push.apply(attrs, g.attributes); - if (g.attributeGroups) { - g.attributeGroups.forEach(ng => recurs(attrs, ng)); - } - } - }; - const allAttributes: Attribute[] = new Array<Attribute>(); - recurs(allAttributes, schema.rootAttributeGroup); - return allAttributes; - } -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts deleted file mode 100644 index 120b034f2..000000000 --- a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea'; -import { VisualBinRange } from './VisualBinRange'; - -export class AlphabeticVisualBinRange extends VisualBinRange { - public DataBinRange: AlphabeticBinRange; - - constructor(dataBinRange: AlphabeticBinRange) { - super(); - this.DataBinRange = dataBinRange; - } - - public AddStep(value: number): number { - return value + 1; - } - - public GetValueFromIndex(index: number): number { - return index; - } - - public GetBins(): number[] { - var bins = new Array<number>(); - var idx = 0; - for (var key in this.DataBinRange.labelsValue) { - if (this.DataBinRange.labelsValue.hasOwnProperty(key)) { - bins.push(idx); - idx++; - } - } - return bins; - } - - public GetLabel(value: number): string { - return this.DataBinRange.prefix + this.DataBinRange.valuesLabel![value]; - } - - public GetLabels(): Array<BinLabel> { - var labels = new Array<BinLabel>(); - var count = 0; - for (var key in this.DataBinRange.valuesLabel) { - if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) { - var value = this.DataBinRange.valuesLabel[key]; - labels.push(new BinLabel({ - value: parseFloat(key), - minValue: count++, - maxValue: count, - label: this.DataBinRange.prefix + value - })); - } - } - return labels; - } -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts deleted file mode 100644 index 776e643cd..000000000 --- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea'; -import { VisualBinRange } from './VisualBinRange'; - -export class DateTimeVisualBinRange extends VisualBinRange { - public DataBinRange: DateTimeBinRange; - - constructor(dataBinRange: DateTimeBinRange) { - super(); - this.DataBinRange = dataBinRange; - } - - public AddStep(value: number): number { - return DateTimeVisualBinRange.AddToDateTimeTicks(value, this.DataBinRange.step!); - } - - public GetValueFromIndex(index: number): number { - var v = this.DataBinRange.minValue!; - for (var i = 0; i < index; i++) { - v = this.AddStep(v); - } - return v; - } - - public GetBins(): number[] { - var bins = new Array<number>(); - for (var v: number = this.DataBinRange.minValue!; - v < this.DataBinRange.maxValue!; - v = DateTimeVisualBinRange.AddToDateTimeTicks(v, this.DataBinRange.step!)) { - bins.push(v); - } - return bins; - } - - private pad(n: number, size: number) { - var sign = n < 0 ? '-' : ''; - return sign + new Array(size).concat([Math.abs(n)]).join('0').slice(-size); - } - - - public GetLabel(value: number): string { - var dt = DateTimeVisualBinRange.TicksToDate(value); - if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Second || - this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Minute) { - return ("" + this.pad(dt.getMinutes(), 2) + ":" + this.pad(dt.getSeconds(), 2)); - //return dt.ToString("mm:ss"); - } - else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Hour) { - return (this.pad(dt.getHours(), 2) + ":" + this.pad(dt.getMinutes(), 2)); - //return dt.ToString("HH:mm"); - } - else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Day) { - return ((dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear()); - //return dt.ToString("MM/dd/yyyy"); - } - else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Month) { - //return dt.ToString("MM/yyyy"); - return ((dt.getMonth() + 1) + "/" + dt.getFullYear()); - } - else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Year) { - return "" + dt.getFullYear(); - } - return "n/a"; - } - - public static TicksToDate(ticks: number): Date { - var dd = new Date((ticks - 621355968000000000) / 10000); - dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset()); - return dd; - } - - - public static DateToTicks(date: Date): number { - var copiedDate = new Date(date.getTime()); - copiedDate.setMinutes(copiedDate.getMinutes() - copiedDate.getTimezoneOffset()); - var t = copiedDate.getTime() * 10000 + 621355968000000000; - /*var dd = new Date((ticks - 621355968000000000) / 10000); - dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset()); - return dd;*/ - return t; - } - - public static AddToDateTimeTicks(ticks: number, dateTimeStep: DateTimeStep): number { - var copiedDate = DateTimeVisualBinRange.TicksToDate(ticks); - var returnDate: Date = new Date(Date.now()); - if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Second) { - returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!)); - } - else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Minute) { - returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!)); - } - else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Hour) { - returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!)); - } - else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Day) { - returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!)); - } - else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Month) { - returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!)); - } - else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Year) { - returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!)); - } - return DateTimeVisualBinRange.DateToTicks(returnDate); - } -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts deleted file mode 100644 index 42509d797..000000000 --- a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NominalBinRange, BinLabel } from '../../model/idea/idea'; -import { VisualBinRange } from './VisualBinRange'; - -export class NominalVisualBinRange extends VisualBinRange { - public DataBinRange: NominalBinRange; - - constructor(dataBinRange: NominalBinRange) { - super(); - this.DataBinRange = dataBinRange; - } - - public AddStep(value: number): number { - return value + 1; - } - - public GetValueFromIndex(index: number): number { - return index; - } - - public GetBins(): number[] { - var bins = new Array<number>(); - var idx = 0; - for (var key in this.DataBinRange.labelsValue) { - if (this.DataBinRange.labelsValue.hasOwnProperty(key)) { - bins.push(idx); - idx++; - } - } - return bins; - } - - public GetLabel(value: number): string { - return this.DataBinRange.valuesLabel![value]; - } - - public GetLabels(): Array<BinLabel> { - var labels = new Array<BinLabel>(); - var count = 0; - for (var key in this.DataBinRange.valuesLabel) { - if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) { - var value = this.DataBinRange.valuesLabel[key]; - labels.push(new BinLabel({ - value: parseFloat(key), - minValue: count++, - maxValue: count, - label: value - })); - } - } - return labels; - } -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts deleted file mode 100644 index 7bc097e1d..000000000 --- a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { QuantitativeBinRange } from '../idea/idea'; -import { VisualBinRange } from './VisualBinRange'; -import { format } from "d3-format"; - -export class QuantitativeVisualBinRange extends VisualBinRange { - - public DataBinRange: QuantitativeBinRange; - - constructor(dataBinRange: QuantitativeBinRange) { - super(); - this.DataBinRange = dataBinRange; - } - - public AddStep(value: number): number { - return value + this.DataBinRange.step!; - } - - public GetValueFromIndex(index: number): number { - return this.DataBinRange.minValue! + (index * this.DataBinRange.step!); - } - - public GetLabel(value: number): string { - return QuantitativeVisualBinRange.NumberFormatter(value); - } - - public static NumberFormatter(val: number): string { - if (val === 0) { - return "0"; - } - if (val < 1) { - /*if (val < Math.abs(0.001)) { - return val.toExponential(2); - }*/ - return format(".3")(val); - } - return format("~s")(val); - } - - public GetBins(): number[] { - const bins = new Array<number>(); - - for (let v: number = this.DataBinRange.minValue!; v < this.DataBinRange.maxValue!; v += this.DataBinRange.step!) { - bins.push(v); - } - return bins; - } - - public static Initialize(dataMinValue: number, dataMaxValue: number, targetBinNumber: number, isIntegerRange: boolean): QuantitativeVisualBinRange { - const extent = QuantitativeVisualBinRange.getExtent(dataMinValue, dataMaxValue, targetBinNumber, isIntegerRange); - const dataBinRange = new QuantitativeBinRange(); - dataBinRange.minValue = extent[0]; - dataBinRange.maxValue = extent[1]; - dataBinRange.step = extent[2]; - - return new QuantitativeVisualBinRange(dataBinRange); - } - - private static getExtent(dataMin: number, dataMax: number, m: number, isIntegerRange: boolean): number[] { - if (dataMin === dataMax) { - // dataMin -= 0.1; - dataMax += 0.1; - } - const span = dataMax - dataMin; - - let step = Math.pow(10, Math.floor(Math.log10(span / m))); - const err = m / span * step; - - if (err <= .15) { - step *= 10; - } - else if (err <= .35) { - step *= 5; - } - else if (err <= .75) { - step *= 2; - } - - if (isIntegerRange) { - step = Math.ceil(step); - } - const ret: number[] = new Array<number>(3); - const minDivStep = Math.floor(dataMin / step); - const maxDivStep = Math.floor(dataMax / step); - ret[0] = minDivStep * step; // Math.floor(Math.Round(dataMin, 8)/step)*step; - ret[1] = maxDivStep * step + step; // Math.floor(Math.Round(dataMax, 8)/step)*step + step; - ret[2] = step; - - return ret; - } -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts deleted file mode 100644 index 449a22e91..000000000 --- a/src/client/northstar/model/binRanges/VisualBinRange.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BinLabel } from '../../model/idea/idea'; - -export abstract class VisualBinRange { - - public abstract AddStep(value: number): number; - - public abstract GetValueFromIndex(index: number): number; - - public abstract GetBins(): Array<number>; - - public GetLabel(value: number): string { - return value.toString(); - } - - public GetLabels(): Array<BinLabel> { - var labels = new Array<BinLabel>(); - var bins = this.GetBins(); - bins.forEach(b => { - labels.push(new BinLabel({ - value: b, - minValue: b, - maxValue: this.AddStep(b), - label: this.GetLabel(b) - })); - }); - return labels; - } -} - -export enum ChartType { - HorizontalBar = 0, VerticalBar = 1, HeatMap = 2, SinglePoint = 3 -}
\ No newline at end of file diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts deleted file mode 100644 index a92412686..000000000 --- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult, AttributeParameters } from "../idea/idea"; -import { VisualBinRange, ChartType } from "./VisualBinRange"; -import { NominalVisualBinRange } from "./NominalVisualBinRange"; -import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange"; -import { AlphabeticVisualBinRange } from "./AlphabeticVisualBinRange"; -import { DateTimeVisualBinRange } from "./DateTimeVisualBinRange"; -import { NorthstarSettings } from "../../manager/Gateway"; -import { ModelHelpers } from "../ModelHelpers"; -import { AttributeTransformationModel } from "../../core/attribute/AttributeTransformationModel"; - -export const SETTINGS_X_BINS = 15; -export const SETTINGS_Y_BINS = 15; -export const SETTINGS_SAMPLE_SIZE = 100000; - -export class VisualBinRangeHelper { - - public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange { - if (dataBinRange instanceof NominalBinRange) { - return new NominalVisualBinRange(dataBinRange); - } - else if (dataBinRange instanceof QuantitativeBinRange) { - return new QuantitativeVisualBinRange(dataBinRange); - } - else if (dataBinRange instanceof AlphabeticBinRange) { - return new AlphabeticVisualBinRange(dataBinRange); - } - else if (dataBinRange instanceof DateTimeBinRange) { - return new DateTimeVisualBinRange(dataBinRange); - } - throw new Exception(); - } - - public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange { - - if (!(dataBinRange instanceof AggregateBinRange)) { - return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange); - } - else { - var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult)); - var minValue = Number.MAX_VALUE; - var maxValue = Number.MIN_VALUE; - for (const brush of histoResult.brushes!) { - aggregateKey.brushIndex = brush.brushIndex; - for (var key in histoResult.bins) { - if (histoResult.bins.hasOwnProperty(key)) { - var bin = histoResult.bins[key]; - var res = <DoubleValueAggregateResult>ModelHelpers.GetAggregateResult(bin, aggregateKey); - if (res && res.hasResult && res.result) { - minValue = Math.min(minValue, res.result); - maxValue = Math.max(maxValue, res.result); - } - } - } - } - - let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false); - - if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) { - visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), - Math.max(0, (visualBinRange).DataBinRange.maxValue!), - SETTINGS_X_BINS, false); - } - else if (chartType === ChartType.SinglePoint) { - visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue), - SETTINGS_X_BINS, false); - } - return visualBinRange; - } - } -} diff --git a/src/client/northstar/model/idea/MetricTypeMapping.ts b/src/client/northstar/model/idea/MetricTypeMapping.ts deleted file mode 100644 index e9759cf16..000000000 --- a/src/client/northstar/model/idea/MetricTypeMapping.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { MetricType } from "./Idea"; -import { Dictionary } from 'typescript-collections'; - - -export class MetricTypeMapping { - - public static GetMetricInterpretation(metricType: MetricType): MetricInterpretation { - if (metricType === MetricType.Accuracy || - metricType === MetricType.F1 || - metricType === MetricType.F1Macro || - metricType === MetricType.F1Micro || - metricType === MetricType.JaccardSimilarityScore || - metricType === MetricType.ObjectDetectionAveragePrecision || - metricType === MetricType.Precision || - metricType === MetricType.PrecisionAtTopK || - metricType === MetricType.NormalizedMutualInformation || - metricType === MetricType.Recall || - metricType === MetricType.RocAucMacro || - metricType === MetricType.RocAuc || - metricType === MetricType.RocAucMicro || - metricType === MetricType.RSquared) { - return MetricInterpretation.HigherIsBetter; - } - return MetricInterpretation.LowerIsBetter; - } -} - -export enum MetricInterpretation { - HigherIsBetter, LowerIsBetter -}
\ No newline at end of file diff --git a/src/client/northstar/model/idea/idea.ts b/src/client/northstar/model/idea/idea.ts deleted file mode 100644 index c73a822c7..000000000 --- a/src/client/northstar/model/idea/idea.ts +++ /dev/null @@ -1,8557 +0,0 @@ -/* tslint:disable */ -//---------------------- -// <auto-generated> -// Generated using the NSwag toolchain v11.19.2.0 (NJsonSchema v9.10.73.0 (Newtonsoft.Json v9.0.0.0)) (http://NSwag.org) -// </auto-generated> -//---------------------- -// ReSharper disable InconsistentNaming - - - -export enum AggregateFunction { - None = "None", - Sum = "Sum", - SumE = "SumE", - Count = "Count", - Min = "Min", - Max = "Max", - Avg = "Avg", -} - -export abstract class AggregateParameters implements IAggregateParameters { - - protected _discriminator: string; - - public Equals(other: Object): boolean { - return this == other; - } - constructor(data?: IAggregateParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "AggregateParameters"; - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): AggregateParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "AverageAggregateParameters") { - let result = new AverageAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SingleDimensionAggregateParameters") { - throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated."); - } - if (data["discriminator"] === "CountAggregateParameters") { - let result = new CountAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "KDEAggregateParameters") { - let result = new KDEAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MarginAggregateParameters") { - let result = new MarginAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MaxAggregateParameters") { - let result = new MaxAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MinAggregateParameters") { - let result = new MinAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SumAggregateParameters") { - let result = new SumAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SumEstimationAggregateParameters") { - let result = new SumEstimationAggregateParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'AggregateParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - return data; - } -} - -export interface IAggregateParameters { -} - -export abstract class SingleDimensionAggregateParameters extends AggregateParameters implements ISingleDimensionAggregateParameters { - attributeParameters?: AttributeParameters | undefined; - distinctAttributeParameters?: AttributeParameters | undefined; - - constructor(data?: ISingleDimensionAggregateParameters) { - super(data); - this._discriminator = "SingleDimensionAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined; - this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined; - } - } - - static fromJS(data: any): SingleDimensionAggregateParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "AverageAggregateParameters") { - let result = new AverageAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "CountAggregateParameters") { - let result = new CountAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "KDEAggregateParameters") { - let result = new KDEAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MarginAggregateParameters") { - let result = new MarginAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MaxAggregateParameters") { - let result = new MaxAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "MinAggregateParameters") { - let result = new MinAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SumAggregateParameters") { - let result = new SumAggregateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SumEstimationAggregateParameters") { - let result = new SumEstimationAggregateParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined; - data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface ISingleDimensionAggregateParameters extends IAggregateParameters { - attributeParameters?: AttributeParameters | undefined; - distinctAttributeParameters?: AttributeParameters | undefined; -} - -export class AverageAggregateParameters extends SingleDimensionAggregateParameters implements IAverageAggregateParameters { - - constructor(data?: IAverageAggregateParameters) { - super(data); - this._discriminator = "AverageAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): AverageAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new AverageAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IAverageAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export abstract class AttributeParameters implements IAttributeParameters { - visualizationHints?: VisualizationHint[] | undefined; - rawName?: string | undefined; - public Equals(other: Object): boolean { - return this == other; - } - - protected _discriminator: string; - - constructor(data?: IAttributeParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "AttributeParameters"; - } - - init(data?: any) { - if (data) { - if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) { - this.visualizationHints = []; - for (let item of data["VisualizationHints"]) - this.visualizationHints.push(item); - } - this.rawName = data["RawName"]; - } - } - - static fromJS(data: any): AttributeParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "AttributeBackendParameters") { - let result = new AttributeBackendParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "AttributeCaclculatedParameters") { - throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated."); - } - if (data["discriminator"] === "AttributeCodeParameters") { - let result = new AttributeCodeParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "AttributeColumnParameters") { - let result = new AttributeColumnParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'AttributeParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - if (this.visualizationHints && this.visualizationHints.constructor === Array) { - data["VisualizationHints"] = []; - for (let item of this.visualizationHints) - data["VisualizationHints"].push(item); - } - data["RawName"] = this.rawName; - return data; - } -} - -export interface IAttributeParameters { - visualizationHints?: VisualizationHint[] | undefined; - rawName?: string | undefined; -} - -export enum VisualizationHint { - TreatAsEnumeration = "TreatAsEnumeration", - DefaultFlipAxis = "DefaultFlipAxis", - Image = "Image", -} - -export abstract class AttributeCaclculatedParameters extends AttributeParameters implements IAttributeCaclculatedParameters { - - constructor(data?: IAttributeCaclculatedParameters) { - super(data); - this._discriminator = "AttributeCaclculatedParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): AttributeCaclculatedParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "AttributeBackendParameters") { - let result = new AttributeBackendParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "AttributeCodeParameters") { - let result = new AttributeCodeParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IAttributeCaclculatedParameters extends IAttributeParameters { -} - -export class AttributeBackendParameters extends AttributeCaclculatedParameters implements IAttributeBackendParameters { - id?: string | undefined; - - constructor(data?: IAttributeBackendParameters) { - super(data); - this._discriminator = "AttributeBackendParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.id = data["Id"]; - } - } - - static fromJS(data: any): AttributeBackendParameters { - data = typeof data === 'object' ? data : {}; - let result = new AttributeBackendParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - super.toJSON(data); - return data; - } -} - -export interface IAttributeBackendParameters extends IAttributeCaclculatedParameters { - id?: string | undefined; -} - -export class AttributeCodeParameters extends AttributeCaclculatedParameters implements IAttributeCodeParameters { - code?: string | undefined; - - constructor(data?: IAttributeCodeParameters) { - super(data); - this._discriminator = "AttributeCodeParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.code = data["Code"]; - } - } - - static fromJS(data: any): AttributeCodeParameters { - data = typeof data === 'object' ? data : {}; - let result = new AttributeCodeParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Code"] = this.code; - super.toJSON(data); - return data; - } -} - -export interface IAttributeCodeParameters extends IAttributeCaclculatedParameters { - code?: string | undefined; -} - -export class AttributeColumnParameters extends AttributeParameters implements IAttributeColumnParameters { - - constructor(data?: IAttributeColumnParameters) { - super(data); - this._discriminator = "AttributeColumnParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): AttributeColumnParameters { - data = typeof data === 'object' ? data : {}; - let result = new AttributeColumnParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IAttributeColumnParameters extends IAttributeParameters { -} - -export class CountAggregateParameters extends SingleDimensionAggregateParameters implements ICountAggregateParameters { - - constructor(data?: ICountAggregateParameters) { - super(data); - this._discriminator = "CountAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): CountAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new CountAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ICountAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export class KDEAggregateParameters extends SingleDimensionAggregateParameters implements IKDEAggregateParameters { - nrOfSamples?: number | undefined; - - constructor(data?: IKDEAggregateParameters) { - super(data); - this._discriminator = "KDEAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.nrOfSamples = data["NrOfSamples"]; - } - } - - static fromJS(data: any): KDEAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new KDEAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["NrOfSamples"] = this.nrOfSamples; - super.toJSON(data); - return data; - } -} - -export interface IKDEAggregateParameters extends ISingleDimensionAggregateParameters { - nrOfSamples?: number | undefined; -} - -export class MarginAggregateParameters extends SingleDimensionAggregateParameters implements IMarginAggregateParameters { - aggregateFunction?: AggregateFunction | undefined; - - constructor(data?: IMarginAggregateParameters) { - super(data); - this._discriminator = "MarginAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.aggregateFunction = data["AggregateFunction"]; - } - } - - static fromJS(data: any): MarginAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new MarginAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AggregateFunction"] = this.aggregateFunction; - super.toJSON(data); - return data; - } -} - -export interface IMarginAggregateParameters extends ISingleDimensionAggregateParameters { - aggregateFunction?: AggregateFunction | undefined; -} - -export class MaxAggregateParameters extends SingleDimensionAggregateParameters implements IMaxAggregateParameters { - - constructor(data?: IMaxAggregateParameters) { - super(data); - this._discriminator = "MaxAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): MaxAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new MaxAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IMaxAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export class MinAggregateParameters extends SingleDimensionAggregateParameters implements IMinAggregateParameters { - - constructor(data?: IMinAggregateParameters) { - super(data); - this._discriminator = "MinAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): MinAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new MinAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IMinAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export class SumAggregateParameters extends SingleDimensionAggregateParameters implements ISumAggregateParameters { - - constructor(data?: ISumAggregateParameters) { - super(data); - this._discriminator = "SumAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): SumAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new SumAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ISumAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export class SumEstimationAggregateParameters extends SingleDimensionAggregateParameters implements ISumEstimationAggregateParameters { - - constructor(data?: ISumEstimationAggregateParameters) { - super(data); - this._discriminator = "SumEstimationAggregateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): SumEstimationAggregateParameters { - data = typeof data === 'object' ? data : {}; - let result = new SumEstimationAggregateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ISumEstimationAggregateParameters extends ISingleDimensionAggregateParameters { -} - -export enum OrderingFunction { - None = 0, - SortUp = 1, - SortDown = 2, -} - -export abstract class BinningParameters implements IBinningParameters { - attributeParameters?: AttributeParameters | undefined; - - protected _discriminator: string; - - constructor(data?: IBinningParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "BinningParameters"; - } - - init(data?: any) { - if (data) { - this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined; - } - } - - static fromJS(data: any): BinningParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "EquiWidthBinningParameters") { - let result = new EquiWidthBinningParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SingleBinBinningParameters") { - let result = new SingleBinBinningParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'BinningParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined; - return data; - } -} - -export interface IBinningParameters { - attributeParameters?: AttributeParameters | undefined; -} - -export class EquiWidthBinningParameters extends BinningParameters implements IEquiWidthBinningParameters { - minValue?: number | undefined; - maxValue?: number | undefined; - requestedNrOfBins?: number | undefined; - referenceValue?: number | undefined; - step?: number | undefined; - - constructor(data?: IEquiWidthBinningParameters) { - super(data); - this._discriminator = "EquiWidthBinningParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.minValue = data["MinValue"]; - this.maxValue = data["MaxValue"]; - this.requestedNrOfBins = data["RequestedNrOfBins"]; - this.referenceValue = data["ReferenceValue"]; - this.step = data["Step"]; - } - } - - static fromJS(data: any): EquiWidthBinningParameters { - data = typeof data === 'object' ? data : {}; - let result = new EquiWidthBinningParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["MinValue"] = this.minValue; - data["MaxValue"] = this.maxValue; - data["RequestedNrOfBins"] = this.requestedNrOfBins; - data["ReferenceValue"] = this.referenceValue; - data["Step"] = this.step; - super.toJSON(data); - return data; - } -} - -export interface IEquiWidthBinningParameters extends IBinningParameters { - minValue?: number | undefined; - maxValue?: number | undefined; - requestedNrOfBins?: number | undefined; - referenceValue?: number | undefined; - step?: number | undefined; -} - -export class SingleBinBinningParameters extends BinningParameters implements ISingleBinBinningParameters { - - constructor(data?: ISingleBinBinningParameters) { - super(data); - this._discriminator = "SingleBinBinningParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): SingleBinBinningParameters { - data = typeof data === 'object' ? data : {}; - let result = new SingleBinBinningParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ISingleBinBinningParameters extends IBinningParameters { -} - -export class Attribute implements IAttribute { - displayName?: string | undefined; - rawName?: string | undefined; - description?: string | undefined; - dataType?: DataType | undefined; - visualizationHints?: VisualizationHint[] | undefined; - isTarget?: boolean | undefined; - - constructor(data?: IAttribute) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.displayName = data["DisplayName"]; - this.rawName = data["RawName"]; - this.description = data["Description"]; - this.dataType = data["DataType"]; - if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) { - this.visualizationHints = []; - for (let item of data["VisualizationHints"]) - this.visualizationHints.push(item); - } - this.isTarget = data["IsTarget"]; - } - } - - static fromJS(data: any): Attribute { - data = typeof data === 'object' ? data : {}; - let result = new Attribute(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["DisplayName"] = this.displayName; - data["RawName"] = this.rawName; - data["Description"] = this.description; - data["DataType"] = this.dataType; - if (this.visualizationHints && this.visualizationHints.constructor === Array) { - data["VisualizationHints"] = []; - for (let item of this.visualizationHints) - data["VisualizationHints"].push(item); - } - data["IsTarget"] = this.isTarget; - return data; - } -} - -export interface IAttribute { - displayName?: string | undefined; - rawName?: string | undefined; - description?: string | undefined; - dataType?: DataType | undefined; - visualizationHints?: VisualizationHint[] | undefined; - isTarget?: boolean | undefined; -} - -export enum DataType { - Int = "Int", - String = "String", - Float = "Float", - Double = "Double", - DateTime = "DateTime", - Object = "Object", - Undefined = "Undefined", -} - -export class AttributeGroup implements IAttributeGroup { - name?: string | undefined; - attributeGroups?: AttributeGroup[] | undefined; - attributes?: Attribute[] | undefined; - - constructor(data?: IAttributeGroup) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.name = data["Name"]; - if (data["AttributeGroups"] && data["AttributeGroups"].constructor === Array) { - this.attributeGroups = []; - for (let item of data["AttributeGroups"]) - this.attributeGroups.push(AttributeGroup.fromJS(item)); - } - if (data["Attributes"] && data["Attributes"].constructor === Array) { - this.attributes = []; - for (let item of data["Attributes"]) - this.attributes.push(Attribute.fromJS(item)); - } - } - } - - static fromJS(data: any): AttributeGroup { - data = typeof data === 'object' ? data : {}; - let result = new AttributeGroup(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Name"] = this.name; - if (this.attributeGroups && this.attributeGroups.constructor === Array) { - data["AttributeGroups"] = []; - for (let item of this.attributeGroups) - data["AttributeGroups"].push(item.toJSON()); - } - if (this.attributes && this.attributes.constructor === Array) { - data["Attributes"] = []; - for (let item of this.attributes) - data["Attributes"].push(item.toJSON()); - } - return data; - } -} - -export interface IAttributeGroup { - name?: string | undefined; - attributeGroups?: AttributeGroup[] | undefined; - attributes?: Attribute[] | undefined; -} - -export class Catalog implements ICatalog { - supportedOperations?: string[] | undefined; - schemas?: Schema[] | undefined; - - constructor(data?: ICatalog) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["SupportedOperations"] && data["SupportedOperations"].constructor === Array) { - this.supportedOperations = []; - for (let item of data["SupportedOperations"]) - this.supportedOperations.push(item); - } - if (data["Schemas"] && data["Schemas"].constructor === Array) { - this.schemas = []; - for (let item of data["Schemas"]) - this.schemas.push(Schema.fromJS(item)); - } - } - } - - static fromJS(data: any): Catalog { - data = typeof data === 'object' ? data : {}; - let result = new Catalog(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.supportedOperations && this.supportedOperations.constructor === Array) { - data["SupportedOperations"] = []; - for (let item of this.supportedOperations) - data["SupportedOperations"].push(item); - } - if (this.schemas && this.schemas.constructor === Array) { - data["Schemas"] = []; - for (let item of this.schemas) - data["Schemas"].push(item.toJSON()); - } - return data; - } -} - -export interface ICatalog { - supportedOperations?: string[] | undefined; - schemas?: Schema[] | undefined; -} - -export class Schema implements ISchema { - rootAttributeGroup?: AttributeGroup | undefined; - displayName?: string | undefined; - augmentedFrom?: string | undefined; - rawName?: string | undefined; - problemDescription?: string | undefined; - darpaProblemDoc?: DarpaProblemDoc | undefined; - distinctAttributeParameters?: AttributeParameters | undefined; - darpaDatasetDoc?: DarpaDatasetDoc | undefined; - darpaDatasetLocation?: string | undefined; - isMultiResourceData?: boolean | undefined; - problemFinderRows?: ProblemFinderRows[] | undefined; - correlationRows?: ProblemFinderRows[] | undefined; - - constructor(data?: ISchema) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.rootAttributeGroup = data["RootAttributeGroup"] ? AttributeGroup.fromJS(data["RootAttributeGroup"]) : <any>undefined; - this.displayName = data["DisplayName"]; - this.augmentedFrom = data["AugmentedFrom"]; - this.rawName = data["RawName"]; - this.problemDescription = data["ProblemDescription"]; - this.darpaProblemDoc = data["DarpaProblemDoc"] ? DarpaProblemDoc.fromJS(data["DarpaProblemDoc"]) : <any>undefined; - this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined; - this.darpaDatasetDoc = data["DarpaDatasetDoc"] ? DarpaDatasetDoc.fromJS(data["DarpaDatasetDoc"]) : <any>undefined; - this.darpaDatasetLocation = data["DarpaDatasetLocation"]; - this.isMultiResourceData = data["IsMultiResourceData"]; - if (data["ProblemFinderRows"] && data["ProblemFinderRows"].constructor === Array) { - this.problemFinderRows = []; - for (let item of data["ProblemFinderRows"]) - this.problemFinderRows.push(ProblemFinderRows.fromJS(item)); - } - if (data["CorrelationRows"] && data["CorrelationRows"].constructor === Array) { - this.correlationRows = []; - for (let item of data["CorrelationRows"]) - this.correlationRows.push(ProblemFinderRows.fromJS(item)); - } - } - } - - static fromJS(data: any): Schema { - data = typeof data === 'object' ? data : {}; - let result = new Schema(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["RootAttributeGroup"] = this.rootAttributeGroup ? this.rootAttributeGroup.toJSON() : <any>undefined; - data["DisplayName"] = this.displayName; - data["AugmentedFrom"] = this.augmentedFrom; - data["RawName"] = this.rawName; - data["ProblemDescription"] = this.problemDescription; - data["DarpaProblemDoc"] = this.darpaProblemDoc ? this.darpaProblemDoc.toJSON() : <any>undefined; - data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined; - data["DarpaDatasetDoc"] = this.darpaDatasetDoc ? this.darpaDatasetDoc.toJSON() : <any>undefined; - data["DarpaDatasetLocation"] = this.darpaDatasetLocation; - data["IsMultiResourceData"] = this.isMultiResourceData; - if (this.problemFinderRows && this.problemFinderRows.constructor === Array) { - data["ProblemFinderRows"] = []; - for (let item of this.problemFinderRows) - data["ProblemFinderRows"].push(item.toJSON()); - } - if (this.correlationRows && this.correlationRows.constructor === Array) { - data["CorrelationRows"] = []; - for (let item of this.correlationRows) - data["CorrelationRows"].push(item.toJSON()); - } - return data; - } -} - -export interface ISchema { - rootAttributeGroup?: AttributeGroup | undefined; - displayName?: string | undefined; - augmentedFrom?: string | undefined; - rawName?: string | undefined; - problemDescription?: string | undefined; - darpaProblemDoc?: DarpaProblemDoc | undefined; - distinctAttributeParameters?: AttributeParameters | undefined; - darpaDatasetDoc?: DarpaDatasetDoc | undefined; - darpaDatasetLocation?: string | undefined; - isMultiResourceData?: boolean | undefined; - problemFinderRows?: ProblemFinderRows[] | undefined; - correlationRows?: ProblemFinderRows[] | undefined; -} - -export class DarpaProblemDoc implements IDarpaProblemDoc { - about?: ProblemAbout | undefined; - inputs?: ProblemInputs | undefined; - - constructor(data?: IDarpaProblemDoc) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.about = data["about"] ? ProblemAbout.fromJS(data["about"]) : <any>undefined; - this.inputs = data["inputs"] ? ProblemInputs.fromJS(data["inputs"]) : <any>undefined; - } - } - - static fromJS(data: any): DarpaProblemDoc { - data = typeof data === 'object' ? data : {}; - let result = new DarpaProblemDoc(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["about"] = this.about ? this.about.toJSON() : <any>undefined; - data["inputs"] = this.inputs ? this.inputs.toJSON() : <any>undefined; - return data; - } -} - -export interface IDarpaProblemDoc { - about?: ProblemAbout | undefined; - inputs?: ProblemInputs | undefined; -} - -export class ProblemAbout implements IProblemAbout { - problemID?: string | undefined; - problemName?: string | undefined; - problemDescription?: string | undefined; - taskType?: string | undefined; - taskSubType?: string | undefined; - problemSchemaVersion?: string | undefined; - problemVersion?: string | undefined; - - constructor(data?: IProblemAbout) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.problemID = data["problemID"]; - this.problemName = data["problemName"]; - this.problemDescription = data["problemDescription"]; - this.taskType = data["taskType"]; - this.taskSubType = data["taskSubType"]; - this.problemSchemaVersion = data["problemSchemaVersion"]; - this.problemVersion = data["problemVersion"]; - } - } - - static fromJS(data: any): ProblemAbout { - data = typeof data === 'object' ? data : {}; - let result = new ProblemAbout(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["problemID"] = this.problemID; - data["problemName"] = this.problemName; - data["problemDescription"] = this.problemDescription; - data["taskType"] = this.taskType; - data["taskSubType"] = this.taskSubType; - data["problemSchemaVersion"] = this.problemSchemaVersion; - data["problemVersion"] = this.problemVersion; - return data; - } -} - -export interface IProblemAbout { - problemID?: string | undefined; - problemName?: string | undefined; - problemDescription?: string | undefined; - taskType?: string | undefined; - taskSubType?: string | undefined; - problemSchemaVersion?: string | undefined; - problemVersion?: string | undefined; -} - -export class ProblemInputs implements IProblemInputs { - data?: ProblemData[] | undefined; - performanceMetrics?: ProblemPerformanceMetric[] | undefined; - - constructor(data?: IProblemInputs) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["data"] && data["data"].constructor === Array) { - this.data = []; - for (let item of data["data"]) - this.data.push(ProblemData.fromJS(item)); - } - if (data["performanceMetrics"] && data["performanceMetrics"].constructor === Array) { - this.performanceMetrics = []; - for (let item of data["performanceMetrics"]) - this.performanceMetrics.push(ProblemPerformanceMetric.fromJS(item)); - } - } - } - - static fromJS(data: any): ProblemInputs { - data = typeof data === 'object' ? data : {}; - let result = new ProblemInputs(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.data && this.data.constructor === Array) { - data["data"] = []; - for (let item of this.data) - data["data"].push(item.toJSON()); - } - if (this.performanceMetrics && this.performanceMetrics.constructor === Array) { - data["performanceMetrics"] = []; - for (let item of this.performanceMetrics) - data["performanceMetrics"].push(item.toJSON()); - } - return data; - } -} - -export interface IProblemInputs { - data?: ProblemData[] | undefined; - performanceMetrics?: ProblemPerformanceMetric[] | undefined; -} - -export class ProblemData implements IProblemData { - datasetID?: string | undefined; - targets?: ProblemTarget[] | undefined; - - constructor(data?: IProblemData) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.datasetID = data["datasetID"]; - if (data["targets"] && data["targets"].constructor === Array) { - this.targets = []; - for (let item of data["targets"]) - this.targets.push(ProblemTarget.fromJS(item)); - } - } - } - - static fromJS(data: any): ProblemData { - data = typeof data === 'object' ? data : {}; - let result = new ProblemData(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["datasetID"] = this.datasetID; - if (this.targets && this.targets.constructor === Array) { - data["targets"] = []; - for (let item of this.targets) - data["targets"].push(item.toJSON()); - } - return data; - } -} - -export interface IProblemData { - datasetID?: string | undefined; - targets?: ProblemTarget[] | undefined; -} - -export class ProblemTarget implements IProblemTarget { - targetIndex?: number | undefined; - resID?: string | undefined; - colIndex?: number | undefined; - colName?: string | undefined; - - constructor(data?: IProblemTarget) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.targetIndex = data["targetIndex"]; - this.resID = data["resID"]; - this.colIndex = data["colIndex"]; - this.colName = data["colName"]; - } - } - - static fromJS(data: any): ProblemTarget { - data = typeof data === 'object' ? data : {}; - let result = new ProblemTarget(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["targetIndex"] = this.targetIndex; - data["resID"] = this.resID; - data["colIndex"] = this.colIndex; - data["colName"] = this.colName; - return data; - } -} - -export interface IProblemTarget { - targetIndex?: number | undefined; - resID?: string | undefined; - colIndex?: number | undefined; - colName?: string | undefined; -} - -export class ProblemPerformanceMetric implements IProblemPerformanceMetric { - metric?: string | undefined; - - constructor(data?: IProblemPerformanceMetric) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.metric = data["metric"]; - } - } - - static fromJS(data: any): ProblemPerformanceMetric { - data = typeof data === 'object' ? data : {}; - let result = new ProblemPerformanceMetric(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["metric"] = this.metric; - return data; - } -} - -export interface IProblemPerformanceMetric { - metric?: string | undefined; -} - -export class DarpaDatasetDoc implements IDarpaDatasetDoc { - about?: DatasetAbout | undefined; - dataResources?: Resource[] | undefined; - - constructor(data?: IDarpaDatasetDoc) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.about = data["about"] ? DatasetAbout.fromJS(data["about"]) : <any>undefined; - if (data["dataResources"] && data["dataResources"].constructor === Array) { - this.dataResources = []; - for (let item of data["dataResources"]) - this.dataResources.push(Resource.fromJS(item)); - } - } - } - - static fromJS(data: any): DarpaDatasetDoc { - data = typeof data === 'object' ? data : {}; - let result = new DarpaDatasetDoc(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["about"] = this.about ? this.about.toJSON() : <any>undefined; - if (this.dataResources && this.dataResources.constructor === Array) { - data["dataResources"] = []; - for (let item of this.dataResources) - data["dataResources"].push(item.toJSON()); - } - return data; - } -} - -export interface IDarpaDatasetDoc { - about?: DatasetAbout | undefined; - dataResources?: Resource[] | undefined; -} - -export class DatasetAbout implements IDatasetAbout { - datasetID?: string | undefined; - datasetName?: string | undefined; - description?: string | undefined; - citation?: string | undefined; - license?: string | undefined; - source?: string | undefined; - sourceURI?: string | undefined; - approximateSize?: string | undefined; - datasetSchemaVersion?: string | undefined; - redacted?: boolean | undefined; - datasetVersion?: string | undefined; - - constructor(data?: IDatasetAbout) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.datasetID = data["datasetID"]; - this.datasetName = data["datasetName"]; - this.description = data["description"]; - this.citation = data["citation"]; - this.license = data["license"]; - this.source = data["source"]; - this.sourceURI = data["sourceURI"]; - this.approximateSize = data["approximateSize"]; - this.datasetSchemaVersion = data["datasetSchemaVersion"]; - this.redacted = data["redacted"]; - this.datasetVersion = data["datasetVersion"]; - } - } - - static fromJS(data: any): DatasetAbout { - data = typeof data === 'object' ? data : {}; - let result = new DatasetAbout(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["datasetID"] = this.datasetID; - data["datasetName"] = this.datasetName; - data["description"] = this.description; - data["citation"] = this.citation; - data["license"] = this.license; - data["source"] = this.source; - data["sourceURI"] = this.sourceURI; - data["approximateSize"] = this.approximateSize; - data["datasetSchemaVersion"] = this.datasetSchemaVersion; - data["redacted"] = this.redacted; - data["datasetVersion"] = this.datasetVersion; - return data; - } -} - -export interface IDatasetAbout { - datasetID?: string | undefined; - datasetName?: string | undefined; - description?: string | undefined; - citation?: string | undefined; - license?: string | undefined; - source?: string | undefined; - sourceURI?: string | undefined; - approximateSize?: string | undefined; - datasetSchemaVersion?: string | undefined; - redacted?: boolean | undefined; - datasetVersion?: string | undefined; -} - -export class Resource implements IResource { - resID?: string | undefined; - resPath?: string | undefined; - resType?: string | undefined; - resFormat?: string[] | undefined; - columns?: Column[] | undefined; - isCollection?: boolean | undefined; - - constructor(data?: IResource) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.resID = data["resID"]; - this.resPath = data["resPath"]; - this.resType = data["resType"]; - if (data["resFormat"] && data["resFormat"].constructor === Array) { - this.resFormat = []; - for (let item of data["resFormat"]) - this.resFormat.push(item); - } - if (data["columns"] && data["columns"].constructor === Array) { - this.columns = []; - for (let item of data["columns"]) - this.columns.push(Column.fromJS(item)); - } - this.isCollection = data["isCollection"]; - } - } - - static fromJS(data: any): Resource { - data = typeof data === 'object' ? data : {}; - let result = new Resource(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["resID"] = this.resID; - data["resPath"] = this.resPath; - data["resType"] = this.resType; - if (this.resFormat && this.resFormat.constructor === Array) { - data["resFormat"] = []; - for (let item of this.resFormat) - data["resFormat"].push(item); - } - if (this.columns && this.columns.constructor === Array) { - data["columns"] = []; - for (let item of this.columns) - data["columns"].push(item.toJSON()); - } - data["isCollection"] = this.isCollection; - return data; - } -} - -export interface IResource { - resID?: string | undefined; - resPath?: string | undefined; - resType?: string | undefined; - resFormat?: string[] | undefined; - columns?: Column[] | undefined; - isCollection?: boolean | undefined; -} - -export class Column implements IColumn { - colIndex?: number | undefined; - colDescription?: string | undefined; - colName?: string | undefined; - colType?: string | undefined; - role?: string[] | undefined; - refersTo?: Reference | undefined; - - constructor(data?: IColumn) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.colIndex = data["colIndex"]; - this.colDescription = data["colDescription"]; - this.colName = data["colName"]; - this.colType = data["colType"]; - if (data["role"] && data["role"].constructor === Array) { - this.role = []; - for (let item of data["role"]) - this.role.push(item); - } - this.refersTo = data["refersTo"] ? Reference.fromJS(data["refersTo"]) : <any>undefined; - } - } - - static fromJS(data: any): Column { - data = typeof data === 'object' ? data : {}; - let result = new Column(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["colIndex"] = this.colIndex; - data["colDescription"] = this.colDescription; - data["colName"] = this.colName; - data["colType"] = this.colType; - if (this.role && this.role.constructor === Array) { - data["role"] = []; - for (let item of this.role) - data["role"].push(item); - } - data["refersTo"] = this.refersTo ? this.refersTo.toJSON() : <any>undefined; - return data; - } -} - -export interface IColumn { - colIndex?: number | undefined; - colDescription?: string | undefined; - colName?: string | undefined; - colType?: string | undefined; - role?: string[] | undefined; - refersTo?: Reference | undefined; -} - -export class Reference implements IReference { - resID?: string | undefined; - - constructor(data?: IReference) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.resID = data["resID"]; - } - } - - static fromJS(data: any): Reference { - data = typeof data === 'object' ? data : {}; - let result = new Reference(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["resID"] = this.resID; - return data; - } -} - -export interface IReference { - resID?: string | undefined; -} - -export class ProblemFinderRows implements IProblemFinderRows { - label?: string | undefined; - type?: string | undefined; - features?: Feature[] | undefined; - - constructor(data?: IProblemFinderRows) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.label = data["label"]; - this.type = data["type"]; - if (data["features"] && data["features"].constructor === Array) { - this.features = []; - for (let item of data["features"]) - this.features.push(Feature.fromJS(item)); - } - } - } - - static fromJS(data: any): ProblemFinderRows { - data = typeof data === 'object' ? data : {}; - let result = new ProblemFinderRows(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["label"] = this.label; - data["type"] = this.type; - if (this.features && this.features.constructor === Array) { - data["features"] = []; - for (let item of this.features) - data["features"].push(item.toJSON()); - } - return data; - } -} - -export interface IProblemFinderRows { - label?: string | undefined; - type?: string | undefined; - features?: Feature[] | undefined; -} - -export class Feature implements IFeature { - name?: string | undefined; - selected?: boolean | undefined; - value?: number | undefined; - - constructor(data?: IFeature) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.name = data["name"]; - this.selected = data["selected"]; - this.value = data["value"]; - } - } - - static fromJS(data: any): Feature { - data = typeof data === 'object' ? data : {}; - let result = new Feature(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["name"] = this.name; - data["selected"] = this.selected; - data["value"] = this.value; - return data; - } -} - -export interface IFeature { - name?: string | undefined; - selected?: boolean | undefined; - value?: number | undefined; -} - -export enum DataType2 { - Int = 0, - String = 1, - Float = 2, - Double = 3, - DateTime = 4, - Object = 5, - Undefined = 6, -} - -export abstract class DataTypeExtensions implements IDataTypeExtensions { - - constructor(data?: IDataTypeExtensions) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): DataTypeExtensions { - data = typeof data === 'object' ? data : {}; - throw new Error("The abstract class 'DataTypeExtensions' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - return data; - } -} - -export interface IDataTypeExtensions { -} - -export class ResObject implements IResObject { - columnName?: string | undefined; - - constructor(data?: IResObject) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.columnName = data["columnName"]; - } - } - - static fromJS(data: any): ResObject { - data = typeof data === 'object' ? data : {}; - let result = new ResObject(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["columnName"] = this.columnName; - return data; - } -} - -export interface IResObject { - columnName?: string | undefined; -} - -export class Exception implements IException { - message?: string | undefined; - innerException?: Exception | undefined; - stackTrace?: string | undefined; - source?: string | undefined; - - constructor(data?: IException) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.message = data["Message"]; - this.innerException = data["InnerException"] ? Exception.fromJS(data["InnerException"]) : <any>undefined; - this.stackTrace = data["StackTrace"]; - this.source = data["Source"]; - } - } - - static fromJS(data: any): Exception { - data = typeof data === 'object' ? data : {}; - let result = new Exception(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Message"] = this.message; - data["InnerException"] = this.innerException ? this.innerException.toJSON() : <any>undefined; - data["StackTrace"] = this.stackTrace; - data["Source"] = this.source; - return data; - } -} - -export interface IException { - message?: string | undefined; - innerException?: Exception | undefined; - stackTrace?: string | undefined; - source?: string | undefined; -} - -export class IDEAException extends Exception implements IIDEAException { - - constructor(data?: IIDEAException) { - super(data); - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): IDEAException { - data = typeof data === 'object' ? data : {}; - let result = new IDEAException(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IIDEAException extends IException { -} - -export class CodeParameters implements ICodeParameters { - attributeCodeParameters?: AttributeCodeParameters[] | undefined; - adapterName?: string | undefined; - - constructor(data?: ICodeParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) { - this.attributeCodeParameters = []; - for (let item of data["AttributeCodeParameters"]) - this.attributeCodeParameters.push(AttributeCodeParameters.fromJS(item)); - } - this.adapterName = data["AdapterName"]; - } - } - - static fromJS(data: any): CodeParameters { - data = typeof data === 'object' ? data : {}; - let result = new CodeParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) { - data["AttributeCodeParameters"] = []; - for (let item of this.attributeCodeParameters) - data["AttributeCodeParameters"].push(item.toJSON()); - } - data["AdapterName"] = this.adapterName; - return data; - } -} - -export interface ICodeParameters { - attributeCodeParameters?: AttributeCodeParameters[] | undefined; - adapterName?: string | undefined; -} - -export class CompileResult implements ICompileResult { - compileSuccess?: boolean | undefined; - compileMessage?: string | undefined; - dataType?: DataType | undefined; - replaceAttributeParameters?: AttributeParameters[] | undefined; - - constructor(data?: ICompileResult) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.compileSuccess = data["CompileSuccess"]; - this.compileMessage = data["CompileMessage"]; - this.dataType = data["DataType"]; - if (data["ReplaceAttributeParameters"] && data["ReplaceAttributeParameters"].constructor === Array) { - this.replaceAttributeParameters = []; - for (let item of data["ReplaceAttributeParameters"]) - this.replaceAttributeParameters.push(AttributeParameters.fromJS(item)); - } - } - } - - static fromJS(data: any): CompileResult { - data = typeof data === 'object' ? data : {}; - let result = new CompileResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["CompileSuccess"] = this.compileSuccess; - data["CompileMessage"] = this.compileMessage; - data["DataType"] = this.dataType; - if (this.replaceAttributeParameters && this.replaceAttributeParameters.constructor === Array) { - data["ReplaceAttributeParameters"] = []; - for (let item of this.replaceAttributeParameters) - data["ReplaceAttributeParameters"].push(item.toJSON()); - } - return data; - } -} - -export interface ICompileResult { - compileSuccess?: boolean | undefined; - compileMessage?: string | undefined; - dataType?: DataType | undefined; - replaceAttributeParameters?: AttributeParameters[] | undefined; -} - -export class CompileResults implements ICompileResults { - rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined; - - constructor(data?: ICompileResults) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["RawNameToCompileResult"]) { - this.rawNameToCompileResult = {}; - for (let key in data["RawNameToCompileResult"]) { - if (data["RawNameToCompileResult"].hasOwnProperty(key)) - this.rawNameToCompileResult[key] = data["RawNameToCompileResult"][key] ? CompileResult.fromJS(data["RawNameToCompileResult"][key]) : new CompileResult(); - } - } - } - } - - static fromJS(data: any): CompileResults { - data = typeof data === 'object' ? data : {}; - let result = new CompileResults(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.rawNameToCompileResult) { - data["RawNameToCompileResult"] = {}; - for (let key in this.rawNameToCompileResult) { - if (this.rawNameToCompileResult.hasOwnProperty(key)) - data["RawNameToCompileResult"][key] = this.rawNameToCompileResult[key]; - } - } - return data; - } -} - -export interface ICompileResults { - rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined; -} - -export abstract class UniqueJson implements IUniqueJson { - - constructor(data?: IUniqueJson) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): UniqueJson { - data = typeof data === 'object' ? data : {}; - throw new Error("The abstract class 'UniqueJson' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - return data; - } -} - -export interface IUniqueJson { -} - -export abstract class OperationParameters extends UniqueJson implements IOperationParameters { - isCachable?: boolean | undefined; - - protected _discriminator: string; - - constructor(data?: IOperationParameters) { - super(data); - this._discriminator = "OperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.isCachable = data["IsCachable"]; - } - } - - static fromJS(data: any): OperationParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "DataOperationParameters") { - throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "ExampleOperationParameters") { - let result = new ExampleOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "HistogramOperationParameters") { - let result = new HistogramOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "DistOperationParameters") { - throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "OptimizerOperationParameters") { - let result = new OptimizerOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RawDataOperationParameters") { - let result = new RawDataOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RecommenderOperationParameters") { - let result = new RecommenderOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "CDFOperationParameters") { - let result = new CDFOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "TestDistOperationParameters") { - throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "ChiSquaredTestOperationParameters") { - let result = new ChiSquaredTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "HypothesisTestParameters") { - throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated."); - } - if (data["discriminator"] === "CorrelationTestOperationParameters") { - let result = new CorrelationTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "EmpiricalDistOperationParameters") { - let result = new EmpiricalDistOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "KSTestOperationParameters") { - let result = new KSTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "NewModelOperationParameters") { - let result = new NewModelOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "ModelOperationParameters") { - throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "RootMeanSquareTestOperationParameters") { - let result = new RootMeanSquareTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "TTestOperationParameters") { - let result = new TTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FeatureImportanceOperationParameters") { - let result = new FeatureImportanceOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SampleOperationParameters") { - let result = new SampleOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "AddComparisonParameters") { - let result = new AddComparisonParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "GetModelStateParameters") { - let result = new GetModelStateParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FrequentItemsetOperationParameters") { - let result = new FrequentItemsetOperationParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'OperationParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["IsCachable"] = this.isCachable; - super.toJSON(data); - return data; - } -} - -export interface IOperationParameters extends IUniqueJson { - isCachable?: boolean | undefined; -} - -export abstract class DataOperationParameters extends OperationParameters implements IDataOperationParameters { - sampleStreamBlockSize?: number | undefined; - adapterName?: string | undefined; - isCachable?: boolean | undefined; - - constructor(data?: IDataOperationParameters) { - super(data); - this._discriminator = "DataOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.sampleStreamBlockSize = data["SampleStreamBlockSize"]; - this.adapterName = data["AdapterName"]; - this.isCachable = data["IsCachable"]; - } - } - - static fromJS(data: any): DataOperationParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ExampleOperationParameters") { - let result = new ExampleOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "HistogramOperationParameters") { - let result = new HistogramOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "DistOperationParameters") { - throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "OptimizerOperationParameters") { - let result = new OptimizerOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RawDataOperationParameters") { - let result = new RawDataOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RecommenderOperationParameters") { - let result = new RecommenderOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "CDFOperationParameters") { - let result = new CDFOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "TestDistOperationParameters") { - throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "EmpiricalDistOperationParameters") { - let result = new EmpiricalDistOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FeatureImportanceOperationParameters") { - let result = new FeatureImportanceOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SampleOperationParameters") { - let result = new SampleOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FrequentItemsetOperationParameters") { - let result = new FrequentItemsetOperationParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SampleStreamBlockSize"] = this.sampleStreamBlockSize; - data["AdapterName"] = this.adapterName; - data["IsCachable"] = this.isCachable; - super.toJSON(data); - return data; - } -} - -export interface IDataOperationParameters extends IOperationParameters { - sampleStreamBlockSize?: number | undefined; - adapterName?: string | undefined; - isCachable?: boolean | undefined; -} - -export class ExampleOperationParameters extends DataOperationParameters implements IExampleOperationParameters { - filter?: string | undefined; - attributeParameters?: AttributeParameters[] | undefined; - attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined; - dummyValue?: number | undefined; - exampleType?: string | undefined; - - constructor(data?: IExampleOperationParameters) { - super(data); - this._discriminator = "ExampleOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.filter = data["Filter"]; - if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) { - this.attributeParameters = []; - for (let item of data["AttributeParameters"]) - this.attributeParameters.push(AttributeParameters.fromJS(item)); - } - if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) { - this.attributeCodeParameters = []; - for (let item of data["AttributeCodeParameters"]) - this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item)); - } - this.dummyValue = data["DummyValue"]; - this.exampleType = data["ExampleType"]; - } - } - - static fromJS(data: any): ExampleOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new ExampleOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Filter"] = this.filter; - if (this.attributeParameters && this.attributeParameters.constructor === Array) { - data["AttributeParameters"] = []; - for (let item of this.attributeParameters) - data["AttributeParameters"].push(item.toJSON()); - } - if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) { - data["AttributeCodeParameters"] = []; - for (let item of this.attributeCodeParameters) - data["AttributeCodeParameters"].push(item.toJSON()); - } - data["DummyValue"] = this.dummyValue; - data["ExampleType"] = this.exampleType; - super.toJSON(data); - return data; - } -} - -export interface IExampleOperationParameters extends IDataOperationParameters { - filter?: string | undefined; - attributeParameters?: AttributeParameters[] | undefined; - attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined; - dummyValue?: number | undefined; - exampleType?: string | undefined; -} - -export abstract class DistOperationParameters extends DataOperationParameters implements IDistOperationParameters { - filter?: string | undefined; - attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined; - - constructor(data?: IDistOperationParameters) { - super(data); - this._discriminator = "DistOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.filter = data["Filter"]; - if (data["AttributeCalculatedParameters"] && data["AttributeCalculatedParameters"].constructor === Array) { - this.attributeCalculatedParameters = []; - for (let item of data["AttributeCalculatedParameters"]) - this.attributeCalculatedParameters.push(AttributeCaclculatedParameters.fromJS(item)); - } - } - } - - static fromJS(data: any): DistOperationParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "HistogramOperationParameters") { - let result = new HistogramOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "OptimizerOperationParameters") { - let result = new OptimizerOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RawDataOperationParameters") { - let result = new RawDataOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "CDFOperationParameters") { - let result = new CDFOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "TestDistOperationParameters") { - throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated."); - } - if (data["discriminator"] === "EmpiricalDistOperationParameters") { - let result = new EmpiricalDistOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FeatureImportanceOperationParameters") { - let result = new FeatureImportanceOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "SampleOperationParameters") { - let result = new SampleOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "FrequentItemsetOperationParameters") { - let result = new FrequentItemsetOperationParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Filter"] = this.filter; - if (this.attributeCalculatedParameters && this.attributeCalculatedParameters.constructor === Array) { - data["AttributeCalculatedParameters"] = []; - for (let item of this.attributeCalculatedParameters) - data["AttributeCalculatedParameters"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IDistOperationParameters extends IDataOperationParameters { - filter?: string | undefined; - attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined; -} - -export class HistogramOperationParameters extends DistOperationParameters implements IHistogramOperationParameters { - sortPerBinAggregateParameter?: AggregateParameters | undefined; - binningParameters?: BinningParameters[] | undefined; - perBinAggregateParameters?: AggregateParameters[] | undefined; - globalAggregateParameters?: AggregateParameters[] | undefined; - brushes?: string[] | undefined; - enableBrushComputation?: boolean | undefined; - degreeOfParallism?: number | undefined; - - constructor(data?: IHistogramOperationParameters) { - super(data); - this._discriminator = "HistogramOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.sortPerBinAggregateParameter = data["SortPerBinAggregateParameter"] ? AggregateParameters.fromJS(data["SortPerBinAggregateParameter"]) : <any>undefined; - if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) { - this.binningParameters = []; - for (let item of data["BinningParameters"]) - this.binningParameters.push(BinningParameters.fromJS(item)); - } - if (data["PerBinAggregateParameters"] && data["PerBinAggregateParameters"].constructor === Array) { - this.perBinAggregateParameters = []; - for (let item of data["PerBinAggregateParameters"]) - this.perBinAggregateParameters.push(AggregateParameters.fromJS(item)); - } - if (data["GlobalAggregateParameters"] && data["GlobalAggregateParameters"].constructor === Array) { - this.globalAggregateParameters = []; - for (let item of data["GlobalAggregateParameters"]) - this.globalAggregateParameters.push(AggregateParameters.fromJS(item)); - } - if (data["Brushes"] && data["Brushes"].constructor === Array) { - this.brushes = []; - for (let item of data["Brushes"]) - this.brushes.push(item); - } - this.enableBrushComputation = data["EnableBrushComputation"]; - this.degreeOfParallism = data["DegreeOfParallism"]; - } - } - - static fromJS(data: any): HistogramOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new HistogramOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SortPerBinAggregateParameter"] = this.sortPerBinAggregateParameter ? this.sortPerBinAggregateParameter.toJSON() : <any>undefined; - if (this.binningParameters && this.binningParameters.constructor === Array) { - data["BinningParameters"] = []; - for (let item of this.binningParameters) - data["BinningParameters"].push(item.toJSON()); - } - if (this.perBinAggregateParameters && this.perBinAggregateParameters.constructor === Array) { - data["PerBinAggregateParameters"] = []; - for (let item of this.perBinAggregateParameters) - data["PerBinAggregateParameters"].push(item.toJSON()); - } - if (this.globalAggregateParameters && this.globalAggregateParameters.constructor === Array) { - data["GlobalAggregateParameters"] = []; - for (let item of this.globalAggregateParameters) - data["GlobalAggregateParameters"].push(item.toJSON()); - } - if (this.brushes && this.brushes.constructor === Array) { - data["Brushes"] = []; - for (let item of this.brushes) - data["Brushes"].push(item); - } - data["EnableBrushComputation"] = this.enableBrushComputation; - data["DegreeOfParallism"] = this.degreeOfParallism; - super.toJSON(data); - return data; - } -} - -export interface IHistogramOperationParameters extends IDistOperationParameters { - sortPerBinAggregateParameter?: AggregateParameters | undefined; - binningParameters?: BinningParameters[] | undefined; - perBinAggregateParameters?: AggregateParameters[] | undefined; - globalAggregateParameters?: AggregateParameters[] | undefined; - brushes?: string[] | undefined; - enableBrushComputation?: boolean | undefined; - degreeOfParallism?: number | undefined; -} - -export class OptimizerOperationParameters extends DistOperationParameters implements IOptimizerOperationParameters { - taskType?: TaskType | undefined; - taskSubType?: TaskSubType | undefined; - metricType?: MetricType | undefined; - labelAttribute?: AttributeParameters | undefined; - featureAttributes?: AttributeParameters[] | undefined; - requiredPrimitives?: string[] | undefined; - pythonFilter?: string | undefined; - trainFilter?: string | undefined; - pythonTrainFilter?: string | undefined; - testFilter?: string | undefined; - pythonTestFilter?: string | undefined; - - constructor(data?: IOptimizerOperationParameters) { - super(data); - this._discriminator = "OptimizerOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.taskType = data["TaskType"]; - this.taskSubType = data["TaskSubType"]; - this.metricType = data["MetricType"]; - this.labelAttribute = data["LabelAttribute"] ? AttributeParameters.fromJS(data["LabelAttribute"]) : <any>undefined; - if (data["FeatureAttributes"] && data["FeatureAttributes"].constructor === Array) { - this.featureAttributes = []; - for (let item of data["FeatureAttributes"]) - this.featureAttributes.push(AttributeParameters.fromJS(item)); - } - if (data["RequiredPrimitives"] && data["RequiredPrimitives"].constructor === Array) { - this.requiredPrimitives = []; - for (let item of data["RequiredPrimitives"]) - this.requiredPrimitives.push(item); - } - this.pythonFilter = data["PythonFilter"]; - this.trainFilter = data["TrainFilter"]; - this.pythonTrainFilter = data["PythonTrainFilter"]; - this.testFilter = data["TestFilter"]; - this.pythonTestFilter = data["PythonTestFilter"]; - } - } - - static fromJS(data: any): OptimizerOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new OptimizerOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["TaskType"] = this.taskType; - data["TaskSubType"] = this.taskSubType; - data["MetricType"] = this.metricType; - data["LabelAttribute"] = this.labelAttribute ? this.labelAttribute.toJSON() : <any>undefined; - if (this.featureAttributes && this.featureAttributes.constructor === Array) { - data["FeatureAttributes"] = []; - for (let item of this.featureAttributes) - data["FeatureAttributes"].push(item.toJSON()); - } - if (this.requiredPrimitives && this.requiredPrimitives.constructor === Array) { - data["RequiredPrimitives"] = []; - for (let item of this.requiredPrimitives) - data["RequiredPrimitives"].push(item); - } - data["PythonFilter"] = this.pythonFilter; - data["TrainFilter"] = this.trainFilter; - data["PythonTrainFilter"] = this.pythonTrainFilter; - data["TestFilter"] = this.testFilter; - data["PythonTestFilter"] = this.pythonTestFilter; - super.toJSON(data); - return data; - } -} - -export interface IOptimizerOperationParameters extends IDistOperationParameters { - taskType?: TaskType | undefined; - taskSubType?: TaskSubType | undefined; - metricType?: MetricType | undefined; - labelAttribute?: AttributeParameters | undefined; - featureAttributes?: AttributeParameters[] | undefined; - requiredPrimitives?: string[] | undefined; - pythonFilter?: string | undefined; - trainFilter?: string | undefined; - pythonTrainFilter?: string | undefined; - testFilter?: string | undefined; - pythonTestFilter?: string | undefined; -} - -export enum TaskType { - Undefined = 0, - Classification = 1, - Regression = 2, - Clustering = 3, - LinkPrediction = 4, - VertexNomination = 5, - CommunityDetection = 6, - GraphClustering = 7, - GraphMatching = 8, - TimeSeriesForecasting = 9, - CollaborativeFiltering = 10, -} - -export enum TaskSubType { - Undefined = 0, - None = 1, - Binary = 2, - Multiclass = 3, - Multilabel = 4, - Univariate = 5, - Multivariate = 6, - Overlapping = 7, - Nonoverlapping = 8, -} - -export enum MetricType { - MetricUndefined = 0, - Accuracy = 1, - Precision = 2, - Recall = 3, - F1 = 4, - F1Micro = 5, - F1Macro = 6, - RocAuc = 7, - RocAucMicro = 8, - RocAucMacro = 9, - MeanSquaredError = 10, - RootMeanSquaredError = 11, - RootMeanSquaredErrorAvg = 12, - MeanAbsoluteError = 13, - RSquared = 14, - NormalizedMutualInformation = 15, - JaccardSimilarityScore = 16, - PrecisionAtTopK = 17, - ObjectDetectionAveragePrecision = 18, - Loss = 100, -} - -export class RawDataOperationParameters extends DistOperationParameters implements IRawDataOperationParameters { - sortUpRawName?: string | undefined; - sortDownRawName?: string | undefined; - numRecords?: number | undefined; - binningParameters?: BinningParameters[] | undefined; - brushes?: string[] | undefined; - enableBrushComputation?: boolean | undefined; - - constructor(data?: IRawDataOperationParameters) { - super(data); - this._discriminator = "RawDataOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.sortUpRawName = data["SortUpRawName"]; - this.sortDownRawName = data["SortDownRawName"]; - this.numRecords = data["NumRecords"]; - if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) { - this.binningParameters = []; - for (let item of data["BinningParameters"]) - this.binningParameters.push(BinningParameters.fromJS(item)); - } - if (data["Brushes"] && data["Brushes"].constructor === Array) { - this.brushes = []; - for (let item of data["Brushes"]) - this.brushes.push(item); - } - this.enableBrushComputation = data["EnableBrushComputation"]; - } - } - - static fromJS(data: any): RawDataOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new RawDataOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SortUpRawName"] = this.sortUpRawName; - data["SortDownRawName"] = this.sortDownRawName; - data["NumRecords"] = this.numRecords; - if (this.binningParameters && this.binningParameters.constructor === Array) { - data["BinningParameters"] = []; - for (let item of this.binningParameters) - data["BinningParameters"].push(item.toJSON()); - } - if (this.brushes && this.brushes.constructor === Array) { - data["Brushes"] = []; - for (let item of this.brushes) - data["Brushes"].push(item); - } - data["EnableBrushComputation"] = this.enableBrushComputation; - super.toJSON(data); - return data; - } -} - -export interface IRawDataOperationParameters extends IDistOperationParameters { - sortUpRawName?: string | undefined; - sortDownRawName?: string | undefined; - numRecords?: number | undefined; - binningParameters?: BinningParameters[] | undefined; - brushes?: string[] | undefined; - enableBrushComputation?: boolean | undefined; -} - -export class RecommenderOperationParameters extends DataOperationParameters implements IRecommenderOperationParameters { - target?: HistogramOperationParameters | undefined; - budget?: number | undefined; - modelId?: ModelId | undefined; - includeAttributeParameters?: AttributeParameters[] | undefined; - excludeAttributeParameters?: AttributeParameters[] | undefined; - riskControlType?: RiskControlType | undefined; - - constructor(data?: IRecommenderOperationParameters) { - super(data); - this._discriminator = "RecommenderOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.target = data["Target"] ? HistogramOperationParameters.fromJS(data["Target"]) : <any>undefined; - this.budget = data["Budget"]; - this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined; - if (data["IncludeAttributeParameters"] && data["IncludeAttributeParameters"].constructor === Array) { - this.includeAttributeParameters = []; - for (let item of data["IncludeAttributeParameters"]) - this.includeAttributeParameters.push(AttributeParameters.fromJS(item)); - } - if (data["ExcludeAttributeParameters"] && data["ExcludeAttributeParameters"].constructor === Array) { - this.excludeAttributeParameters = []; - for (let item of data["ExcludeAttributeParameters"]) - this.excludeAttributeParameters.push(AttributeParameters.fromJS(item)); - } - this.riskControlType = data["RiskControlType"]; - } - } - - static fromJS(data: any): RecommenderOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new RecommenderOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Target"] = this.target ? this.target.toJSON() : <any>undefined; - data["Budget"] = this.budget; - data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined; - if (this.includeAttributeParameters && this.includeAttributeParameters.constructor === Array) { - data["IncludeAttributeParameters"] = []; - for (let item of this.includeAttributeParameters) - data["IncludeAttributeParameters"].push(item.toJSON()); - } - if (this.excludeAttributeParameters && this.excludeAttributeParameters.constructor === Array) { - data["ExcludeAttributeParameters"] = []; - for (let item of this.excludeAttributeParameters) - data["ExcludeAttributeParameters"].push(item.toJSON()); - } - data["RiskControlType"] = this.riskControlType; - super.toJSON(data); - return data; - } -} - -export interface IRecommenderOperationParameters extends IDataOperationParameters { - target?: HistogramOperationParameters | undefined; - budget?: number | undefined; - modelId?: ModelId | undefined; - includeAttributeParameters?: AttributeParameters[] | undefined; - excludeAttributeParameters?: AttributeParameters[] | undefined; - riskControlType?: RiskControlType | undefined; -} - -export class ModelId implements IModelId { - value?: string | undefined; - - constructor(data?: IModelId) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): ModelId { - data = typeof data === 'object' ? data : {}; - let result = new ModelId(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - return data; - } -} - -export interface IModelId { - value?: string | undefined; -} - -export enum RiskControlType { - PCER = 0, - Bonferroni = 1, - AdaBonferroni = 2, - HolmBonferroni = 3, - BHFDR = 4, - SeqFDR = 5, - AlphaFDR = 6, - BestFootForward = 7, - BetaFarsighted = 8, - BetaFarsightedWithSupport = 9, - GammaFixed = 10, - DeltaHopeful = 11, - EpsilonHybrid = 12, - EpsilonHybridWithoutSupport = 13, - PsiSupport = 14, - ZetaDynamic = 15, - Unknown = 16, -} - -export abstract class TestDistOperationParameters extends DistOperationParameters implements ITestDistOperationParameters { - attributeParameters?: AttributeParameters[] | undefined; - - constructor(data?: ITestDistOperationParameters) { - super(data); - this._discriminator = "TestDistOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) { - this.attributeParameters = []; - for (let item of data["AttributeParameters"]) - this.attributeParameters.push(AttributeParameters.fromJS(item)); - } - } - } - - static fromJS(data: any): TestDistOperationParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "CDFOperationParameters") { - let result = new CDFOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "EmpiricalDistOperationParameters") { - let result = new EmpiricalDistOperationParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.attributeParameters && this.attributeParameters.constructor === Array) { - data["AttributeParameters"] = []; - for (let item of this.attributeParameters) - data["AttributeParameters"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface ITestDistOperationParameters extends IDistOperationParameters { - attributeParameters?: AttributeParameters[] | undefined; -} - -export class CDFOperationParameters extends TestDistOperationParameters implements ICDFOperationParameters { - - constructor(data?: ICDFOperationParameters) { - super(data); - this._discriminator = "CDFOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): CDFOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new CDFOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ICDFOperationParameters extends ITestDistOperationParameters { -} - -export abstract class HypothesisTestParameters extends OperationParameters implements IHypothesisTestParameters { - childOperationParameters?: OperationParameters[] | undefined; - isCachable?: boolean | undefined; - - constructor(data?: IHypothesisTestParameters) { - super(data); - this._discriminator = "HypothesisTestParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) { - this.childOperationParameters = []; - for (let item of data["ChildOperationParameters"]) - this.childOperationParameters.push(OperationParameters.fromJS(item)); - } - this.isCachable = data["IsCachable"]; - } - } - - static fromJS(data: any): HypothesisTestParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ChiSquaredTestOperationParameters") { - let result = new ChiSquaredTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "CorrelationTestOperationParameters") { - let result = new CorrelationTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "KSTestOperationParameters") { - let result = new KSTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "RootMeanSquareTestOperationParameters") { - let result = new RootMeanSquareTestOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "TTestOperationParameters") { - let result = new TTestOperationParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.childOperationParameters && this.childOperationParameters.constructor === Array) { - data["ChildOperationParameters"] = []; - for (let item of this.childOperationParameters) - data["ChildOperationParameters"].push(item.toJSON()); - } - data["IsCachable"] = this.isCachable; - super.toJSON(data); - return data; - } -} - -export interface IHypothesisTestParameters extends IOperationParameters { - childOperationParameters?: OperationParameters[] | undefined; - isCachable?: boolean | undefined; -} - -export class ChiSquaredTestOperationParameters extends HypothesisTestParameters implements IChiSquaredTestOperationParameters { - - constructor(data?: IChiSquaredTestOperationParameters) { - super(data); - this._discriminator = "ChiSquaredTestOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): ChiSquaredTestOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new ChiSquaredTestOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IChiSquaredTestOperationParameters extends IHypothesisTestParameters { -} - -export class CorrelationTestOperationParameters extends HypothesisTestParameters implements ICorrelationTestOperationParameters { - - constructor(data?: ICorrelationTestOperationParameters) { - super(data); - this._discriminator = "CorrelationTestOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): CorrelationTestOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new CorrelationTestOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ICorrelationTestOperationParameters extends IHypothesisTestParameters { -} - -export class SubmitProblemParameters implements ISubmitProblemParameters { - id?: string | undefined; - - constructor(data?: ISubmitProblemParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - } - } - - static fromJS(data: any): SubmitProblemParameters { - data = typeof data === 'object' ? data : {}; - let result = new SubmitProblemParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - return data; - } -} - -export interface ISubmitProblemParameters { - id?: string | undefined; -} - -export class SpecifyProblemParameters implements ISpecifyProblemParameters { - id?: string | undefined; - userComment?: string | undefined; - optimizerOperationParameters?: OptimizerOperationParameters | undefined; - - constructor(data?: ISpecifyProblemParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - this.userComment = data["UserComment"]; - this.optimizerOperationParameters = data["OptimizerOperationParameters"] ? OptimizerOperationParameters.fromJS(data["OptimizerOperationParameters"]) : <any>undefined; - } - } - - static fromJS(data: any): SpecifyProblemParameters { - data = typeof data === 'object' ? data : {}; - let result = new SpecifyProblemParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - data["UserComment"] = this.userComment; - data["OptimizerOperationParameters"] = this.optimizerOperationParameters ? this.optimizerOperationParameters.toJSON() : <any>undefined; - return data; - } -} - -export interface ISpecifyProblemParameters { - id?: string | undefined; - userComment?: string | undefined; - optimizerOperationParameters?: OptimizerOperationParameters | undefined; -} - -export class EmpiricalDistOperationParameters extends TestDistOperationParameters implements IEmpiricalDistOperationParameters { - keepSamples?: boolean | undefined; - - constructor(data?: IEmpiricalDistOperationParameters) { - super(data); - this._discriminator = "EmpiricalDistOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.keepSamples = data["KeepSamples"]; - } - } - - static fromJS(data: any): EmpiricalDistOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new EmpiricalDistOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["KeepSamples"] = this.keepSamples; - super.toJSON(data); - return data; - } -} - -export interface IEmpiricalDistOperationParameters extends ITestDistOperationParameters { - keepSamples?: boolean | undefined; -} - -export class KSTestOperationParameters extends HypothesisTestParameters implements IKSTestOperationParameters { - - constructor(data?: IKSTestOperationParameters) { - super(data); - this._discriminator = "KSTestOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): KSTestOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new KSTestOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IKSTestOperationParameters extends IHypothesisTestParameters { -} - -export abstract class ModelOperationParameters extends OperationParameters implements IModelOperationParameters { - - constructor(data?: IModelOperationParameters) { - super(data); - this._discriminator = "ModelOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): ModelOperationParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "NewModelOperationParameters") { - let result = new NewModelOperationParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "AddComparisonParameters") { - let result = new AddComparisonParameters(); - result.init(data); - return result; - } - if (data["discriminator"] === "GetModelStateParameters") { - let result = new GetModelStateParameters(); - result.init(data); - return result; - } - throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IModelOperationParameters extends IOperationParameters { -} - -export class NewModelOperationParameters extends ModelOperationParameters implements INewModelOperationParameters { - riskControlTypes?: RiskControlType[] | undefined; - alpha?: number | undefined; - alphaInvestParameter?: AlphaInvestParameter | undefined; - - constructor(data?: INewModelOperationParameters) { - super(data); - this._discriminator = "NewModelOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["RiskControlTypes"] && data["RiskControlTypes"].constructor === Array) { - this.riskControlTypes = []; - for (let item of data["RiskControlTypes"]) - this.riskControlTypes.push(item); - } - this.alpha = data["Alpha"]; - this.alphaInvestParameter = data["AlphaInvestParameter"] ? AlphaInvestParameter.fromJS(data["AlphaInvestParameter"]) : <any>undefined; - } - } - - static fromJS(data: any): NewModelOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new NewModelOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.riskControlTypes && this.riskControlTypes.constructor === Array) { - data["RiskControlTypes"] = []; - for (let item of this.riskControlTypes) - data["RiskControlTypes"].push(item); - } - data["Alpha"] = this.alpha; - data["AlphaInvestParameter"] = this.alphaInvestParameter ? this.alphaInvestParameter.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface INewModelOperationParameters extends IModelOperationParameters { - riskControlTypes?: RiskControlType[] | undefined; - alpha?: number | undefined; - alphaInvestParameter?: AlphaInvestParameter | undefined; -} - -export class AlphaInvestParameter extends UniqueJson implements IAlphaInvestParameter { - beta?: number | undefined; - gamma?: number | undefined; - delta?: number | undefined; - epsilon?: number | undefined; - windowSize?: number | undefined; - psi?: number | undefined; - - constructor(data?: IAlphaInvestParameter) { - super(data); - } - - init(data?: any) { - super.init(data); - if (data) { - this.beta = data["Beta"]; - this.gamma = data["Gamma"]; - this.delta = data["Delta"]; - this.epsilon = data["Epsilon"]; - this.windowSize = data["WindowSize"]; - this.psi = data["Psi"]; - } - } - - static fromJS(data: any): AlphaInvestParameter { - data = typeof data === 'object' ? data : {}; - let result = new AlphaInvestParameter(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Beta"] = this.beta; - data["Gamma"] = this.gamma; - data["Delta"] = this.delta; - data["Epsilon"] = this.epsilon; - data["WindowSize"] = this.windowSize; - data["Psi"] = this.psi; - super.toJSON(data); - return data; - } -} - -export interface IAlphaInvestParameter extends IUniqueJson { - beta?: number | undefined; - gamma?: number | undefined; - delta?: number | undefined; - epsilon?: number | undefined; - windowSize?: number | undefined; - psi?: number | undefined; -} - -export class RootMeanSquareTestOperationParameters extends HypothesisTestParameters implements IRootMeanSquareTestOperationParameters { - seeded?: boolean | undefined; - pValueConvergenceThreshold?: number | undefined; - maxSimulationBatchCount?: number | undefined; - perBatchSimulationCount?: number | undefined; - - constructor(data?: IRootMeanSquareTestOperationParameters) { - super(data); - this._discriminator = "RootMeanSquareTestOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.seeded = data["Seeded"]; - this.pValueConvergenceThreshold = data["PValueConvergenceThreshold"]; - this.maxSimulationBatchCount = data["MaxSimulationBatchCount"]; - this.perBatchSimulationCount = data["PerBatchSimulationCount"]; - } - } - - static fromJS(data: any): RootMeanSquareTestOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new RootMeanSquareTestOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Seeded"] = this.seeded; - data["PValueConvergenceThreshold"] = this.pValueConvergenceThreshold; - data["MaxSimulationBatchCount"] = this.maxSimulationBatchCount; - data["PerBatchSimulationCount"] = this.perBatchSimulationCount; - super.toJSON(data); - return data; - } -} - -export interface IRootMeanSquareTestOperationParameters extends IHypothesisTestParameters { - seeded?: boolean | undefined; - pValueConvergenceThreshold?: number | undefined; - maxSimulationBatchCount?: number | undefined; - perBatchSimulationCount?: number | undefined; -} - -export class TTestOperationParameters extends HypothesisTestParameters implements ITTestOperationParameters { - - constructor(data?: ITTestOperationParameters) { - super(data); - this._discriminator = "TTestOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): TTestOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new TTestOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface ITTestOperationParameters extends IHypothesisTestParameters { -} - -export enum EffectSize { - Small = 1, - Meduim = 2, - Large = 4, -} - -export abstract class Result extends UniqueJson implements IResult { - progress?: number | undefined; - - protected _discriminator: string; - - constructor(data?: IResult) { - super(data); - this._discriminator = "Result"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.progress = data["Progress"]; - } - } - - static fromJS(data: any): Result { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ErrorResult") { - let result = new ErrorResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "HistogramResult") { - let result = new HistogramResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "DistResult") { - throw new Error("The abstract class 'DistResult' cannot be instantiated."); - } - if (data["discriminator"] === "ModelWealthResult") { - let result = new ModelWealthResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "HypothesisTestResult") { - throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated."); - } - if (data["discriminator"] === "ModelOperationResult") { - throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated."); - } - if (data["discriminator"] === "RecommenderResult") { - let result = new RecommenderResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "Decision") { - let result = new Decision(); - result.init(data); - return result; - } - if (data["discriminator"] === "OptimizerResult") { - let result = new OptimizerResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "ExampleResult") { - let result = new ExampleResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "NewModelOperationResult") { - let result = new NewModelOperationResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "AddComparisonResult") { - let result = new AddComparisonResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "GetModelStateResult") { - let result = new GetModelStateResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "FeatureImportanceResult") { - let result = new FeatureImportanceResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "RawDataResult") { - let result = new RawDataResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "SampleResult") { - let result = new SampleResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "CDFResult") { - let result = new CDFResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "ChiSquaredTestResult") { - let result = new ChiSquaredTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "CorrelationTestResult") { - let result = new CorrelationTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "EmpiricalDistResult") { - let result = new EmpiricalDistResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "KSTestResult") { - let result = new KSTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "RootMeanSquareTestResult") { - let result = new RootMeanSquareTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "TTestResult") { - let result = new TTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "FrequentItemsetResult") { - let result = new FrequentItemsetResult(); - result.init(data); - return result; - } - throw new Error("The abstract class 'Result' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["Progress"] = this.progress; - super.toJSON(data); - return data; - } -} - -export interface IResult extends IUniqueJson { - progress?: number | undefined; -} - -export class ErrorResult extends Result implements IErrorResult { - message?: string | undefined; - - constructor(data?: IErrorResult) { - super(data); - this._discriminator = "ErrorResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.message = data["Message"]; - } - } - - static fromJS(data: any): ErrorResult { - data = typeof data === 'object' ? data : {}; - let result = new ErrorResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Message"] = this.message; - super.toJSON(data); - return data; - } -} - -export interface IErrorResult extends IResult { - message?: string | undefined; -} - -export abstract class DistResult extends Result implements IDistResult { - sampleSize?: number | undefined; - populationSize?: number | undefined; - - constructor(data?: IDistResult) { - super(data); - this._discriminator = "DistResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.sampleSize = data["SampleSize"]; - this.populationSize = data["PopulationSize"]; - } - } - - static fromJS(data: any): DistResult { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "HistogramResult") { - let result = new HistogramResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "RawDataResult") { - let result = new RawDataResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "SampleResult") { - let result = new SampleResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "CDFResult") { - let result = new CDFResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "EmpiricalDistResult") { - let result = new EmpiricalDistResult(); - result.init(data); - return result; - } - throw new Error("The abstract class 'DistResult' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SampleSize"] = this.sampleSize; - data["PopulationSize"] = this.populationSize; - super.toJSON(data); - return data; - } -} - -export interface IDistResult extends IResult { - sampleSize?: number | undefined; - populationSize?: number | undefined; -} - -export class HistogramResult extends DistResult implements IHistogramResult { - aggregateResults?: AggregateResult[][] | undefined; - isEmpty?: boolean | undefined; - brushes?: Brush[] | undefined; - binRanges?: BinRange[] | undefined; - aggregateParameters?: AggregateParameters[] | undefined; - nullValueCount?: number | undefined; - bins?: { [key: string]: Bin; } | undefined; - - constructor(data?: IHistogramResult) { - super(data); - this._discriminator = "HistogramResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) { - this.aggregateResults = []; - for (let item of data["AggregateResults"]) - this.aggregateResults.push(item); - } - this.isEmpty = data["IsEmpty"]; - if (data["Brushes"] && data["Brushes"].constructor === Array) { - this.brushes = []; - for (let item of data["Brushes"]) - this.brushes.push(Brush.fromJS(item)); - } - if (data["BinRanges"] && data["BinRanges"].constructor === Array) { - this.binRanges = []; - for (let item of data["BinRanges"]) - this.binRanges.push(BinRange.fromJS(item)); - } - if (data["AggregateParameters"] && data["AggregateParameters"].constructor === Array) { - this.aggregateParameters = []; - for (let item of data["AggregateParameters"]) - this.aggregateParameters.push(AggregateParameters.fromJS(item)); - } - this.nullValueCount = data["NullValueCount"]; - if (data["Bins"]) { - this.bins = {}; - for (let key in data["Bins"]) { - if (data["Bins"].hasOwnProperty(key)) - this.bins[key] = data["Bins"][key] ? Bin.fromJS(data["Bins"][key]) : new Bin(); - } - } - } - } - - static fromJS(data: any): HistogramResult { - data = typeof data === 'object' ? data : {}; - let result = new HistogramResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.aggregateResults && this.aggregateResults.constructor === Array) { - data["AggregateResults"] = []; - for (let item of this.aggregateResults) - data["AggregateResults"].push(item); - } - data["IsEmpty"] = this.isEmpty; - if (this.brushes && this.brushes.constructor === Array) { - data["Brushes"] = []; - for (let item of this.brushes) - data["Brushes"].push(item.toJSON()); - } - if (this.binRanges && this.binRanges.constructor === Array) { - data["BinRanges"] = []; - for (let item of this.binRanges) - data["BinRanges"].push(item.toJSON()); - } - if (this.aggregateParameters && this.aggregateParameters.constructor === Array) { - data["AggregateParameters"] = []; - for (let item of this.aggregateParameters) - data["AggregateParameters"].push(item.toJSON()); - } - data["NullValueCount"] = this.nullValueCount; - if (this.bins) { - data["Bins"] = {}; - for (let key in this.bins) { - if (this.bins.hasOwnProperty(key)) - data["Bins"][key] = this.bins[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IHistogramResult extends IDistResult { - aggregateResults?: AggregateResult[][] | undefined; - isEmpty?: boolean | undefined; - brushes?: Brush[] | undefined; - binRanges?: BinRange[] | undefined; - aggregateParameters?: AggregateParameters[] | undefined; - nullValueCount?: number | undefined; - bins?: { [key: string]: Bin; } | undefined; -} - -export abstract class AggregateResult implements IAggregateResult { - hasResult?: boolean | undefined; - n?: number | undefined; - - protected _discriminator: string; - - constructor(data?: IAggregateResult) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "AggregateResult"; - } - - init(data?: any) { - if (data) { - this.hasResult = data["HasResult"]; - this.n = data["N"]; - } - } - - static fromJS(data: any): AggregateResult | undefined { - if (data === null || data === undefined) { - return undefined; - } - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "MarginAggregateResult") { - let result = new MarginAggregateResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "DoubleValueAggregateResult") { - let result = new DoubleValueAggregateResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "PointsAggregateResult") { - let result = new PointsAggregateResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "SumEstimationAggregateResult") { - let result = new SumEstimationAggregateResult(); - result.init(data); - return result; - } - throw new Error("The abstract class 'AggregateResult' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["HasResult"] = this.hasResult; - data["N"] = this.n; - return data; - } -} - -export interface IAggregateResult { - hasResult?: boolean | undefined; - n?: number | undefined; -} - -export class MarginAggregateResult extends AggregateResult implements IMarginAggregateResult { - margin?: number | undefined; - absolutMargin?: number | undefined; - sumOfSquare?: number | undefined; - sampleStandardDeviation?: number | undefined; - mean?: number | undefined; - ex?: number | undefined; - ex2?: number | undefined; - variance?: number | undefined; - - constructor(data?: IMarginAggregateResult) { - super(data); - this._discriminator = "MarginAggregateResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.margin = data["Margin"]; - this.absolutMargin = data["AbsolutMargin"]; - this.sumOfSquare = data["SumOfSquare"]; - this.sampleStandardDeviation = data["SampleStandardDeviation"]; - this.mean = data["Mean"]; - this.ex = data["Ex"]; - this.ex2 = data["Ex2"]; - this.variance = data["Variance"]; - } - } - - static fromJS(data: any): MarginAggregateResult { - data = typeof data === 'object' ? data : {}; - let result = new MarginAggregateResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Margin"] = this.margin; - data["AbsolutMargin"] = this.absolutMargin; - data["SumOfSquare"] = this.sumOfSquare; - data["SampleStandardDeviation"] = this.sampleStandardDeviation; - data["Mean"] = this.mean; - data["Ex"] = this.ex; - data["Ex2"] = this.ex2; - data["Variance"] = this.variance; - super.toJSON(data); - return data; - } -} - -export interface IMarginAggregateResult extends IAggregateResult { - margin?: number | undefined; - absolutMargin?: number | undefined; - sumOfSquare?: number | undefined; - sampleStandardDeviation?: number | undefined; - mean?: number | undefined; - ex?: number | undefined; - ex2?: number | undefined; - variance?: number | undefined; -} - -export class DoubleValueAggregateResult extends AggregateResult implements IDoubleValueAggregateResult { - result?: number | undefined; - temporaryResult?: number | undefined; - - constructor(data?: IDoubleValueAggregateResult) { - super(data); - this._discriminator = "DoubleValueAggregateResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.result = data["Result"]; - this.temporaryResult = data["TemporaryResult"]; - } - } - - static fromJS(data: any): DoubleValueAggregateResult { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "SumEstimationAggregateResult") { - let result = new SumEstimationAggregateResult(); - result.init(data); - return result; - } - let result = new DoubleValueAggregateResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Result"] = this.result; - data["TemporaryResult"] = this.temporaryResult; - super.toJSON(data); - return data; - } -} - -export interface IDoubleValueAggregateResult extends IAggregateResult { - result?: number | undefined; - temporaryResult?: number | undefined; -} - -export class PointsAggregateResult extends AggregateResult implements IPointsAggregateResult { - points?: Point[] | undefined; - - constructor(data?: IPointsAggregateResult) { - super(data); - this._discriminator = "PointsAggregateResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Points"] && data["Points"].constructor === Array) { - this.points = []; - for (let item of data["Points"]) - this.points.push(Point.fromJS(item)); - } - } - } - - static fromJS(data: any): PointsAggregateResult { - data = typeof data === 'object' ? data : {}; - let result = new PointsAggregateResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.points && this.points.constructor === Array) { - data["Points"] = []; - for (let item of this.points) - data["Points"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IPointsAggregateResult extends IAggregateResult { - points?: Point[] | undefined; -} - -export class Point implements IPoint { - x?: number | undefined; - y?: number | undefined; - - constructor(data?: IPoint) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.x = data["X"]; - this.y = data["Y"]; - } - } - - static fromJS(data: any): Point { - data = typeof data === 'object' ? data : {}; - let result = new Point(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["X"] = this.x; - data["Y"] = this.y; - return data; - } -} - -export interface IPoint { - x?: number | undefined; - y?: number | undefined; -} - -export class SumEstimationAggregateResult extends DoubleValueAggregateResult implements ISumEstimationAggregateResult { - sum?: number | undefined; - sumEstimation?: number | undefined; - - constructor(data?: ISumEstimationAggregateResult) { - super(data); - this._discriminator = "SumEstimationAggregateResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.sum = data["Sum"]; - this.sumEstimation = data["SumEstimation"]; - } - } - - static fromJS(data: any): SumEstimationAggregateResult { - data = typeof data === 'object' ? data : {}; - let result = new SumEstimationAggregateResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Sum"] = this.sum; - data["SumEstimation"] = this.sumEstimation; - super.toJSON(data); - return data; - } -} - -export interface ISumEstimationAggregateResult extends IDoubleValueAggregateResult { - sum?: number | undefined; - sumEstimation?: number | undefined; -} - -export class Brush implements IBrush { - brushIndex?: number | undefined; - brushEnum?: BrushEnum | undefined; - - constructor(data?: IBrush) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.brushIndex = data["BrushIndex"]; - this.brushEnum = data["BrushEnum"]; - } - } - - static fromJS(data: any): Brush { - data = typeof data === 'object' ? data : {}; - let result = new Brush(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["BrushIndex"] = this.brushIndex; - data["BrushEnum"] = this.brushEnum; - return data; - } -} - -export interface IBrush { - brushIndex?: number | undefined; - brushEnum?: BrushEnum | undefined; -} - -export enum BrushEnum { - Overlap = 0, - Rest = 1, - All = 2, - UserSpecified = 3, -} - -export abstract class BinRange implements IBinRange { - minValue?: number | undefined; - maxValue?: number | undefined; - targetBinNumber?: number | undefined; - - protected _discriminator: string; - - constructor(data?: IBinRange) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "BinRange"; - } - - init(data?: any) { - if (data) { - this.minValue = data["MinValue"]; - this.maxValue = data["MaxValue"]; - this.targetBinNumber = data["TargetBinNumber"]; - } - } - - static fromJS(data: any): BinRange { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "NominalBinRange") { - let result = new NominalBinRange(); - result.init(data); - return result; - } - if (data["discriminator"] === "QuantitativeBinRange") { - let result = new QuantitativeBinRange(); - result.init(data); - return result; - } - if (data["discriminator"] === "AggregateBinRange") { - let result = new AggregateBinRange(); - result.init(data); - return result; - } - if (data["discriminator"] === "AlphabeticBinRange") { - let result = new AlphabeticBinRange(); - result.init(data); - return result; - } - if (data["discriminator"] === "DateTimeBinRange") { - let result = new DateTimeBinRange(); - result.init(data); - return result; - } - throw new Error("The abstract class 'BinRange' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["MinValue"] = this.minValue; - data["MaxValue"] = this.maxValue; - data["TargetBinNumber"] = this.targetBinNumber; - return data; - } -} - -export interface IBinRange { - minValue?: number | undefined; - maxValue?: number | undefined; - targetBinNumber?: number | undefined; -} - -export class NominalBinRange extends BinRange implements INominalBinRange { - labelsValue?: { [key: string]: number; } | undefined; - valuesLabel?: { [key: string]: string; } | undefined; - - constructor(data?: INominalBinRange) { - super(data); - this._discriminator = "NominalBinRange"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["LabelsValue"]) { - this.labelsValue = {}; - for (let key in data["LabelsValue"]) { - if (data["LabelsValue"].hasOwnProperty(key)) - this.labelsValue[key] = data["LabelsValue"][key]; - } - } - if (data["ValuesLabel"]) { - this.valuesLabel = {}; - for (let key in data["ValuesLabel"]) { - if (data["ValuesLabel"].hasOwnProperty(key)) - this.valuesLabel[key] = data["ValuesLabel"][key]; - } - } - } - } - - static fromJS(data: any): NominalBinRange { - data = typeof data === 'object' ? data : {}; - let result = new NominalBinRange(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.labelsValue) { - data["LabelsValue"] = {}; - for (let key in this.labelsValue) { - if (this.labelsValue.hasOwnProperty(key)) - data["LabelsValue"][key] = this.labelsValue[key]; - } - } - if (this.valuesLabel) { - data["ValuesLabel"] = {}; - for (let key in this.valuesLabel) { - if (this.valuesLabel.hasOwnProperty(key)) - data["ValuesLabel"][key] = this.valuesLabel[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface INominalBinRange extends IBinRange { - labelsValue?: { [key: string]: number; } | undefined; - valuesLabel?: { [key: string]: string; } | undefined; -} - -export class QuantitativeBinRange extends BinRange implements IQuantitativeBinRange { - isIntegerRange?: boolean | undefined; - step?: number | undefined; - - constructor(data?: IQuantitativeBinRange) { - super(data); - this._discriminator = "QuantitativeBinRange"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.isIntegerRange = data["IsIntegerRange"]; - this.step = data["Step"]; - } - } - - static fromJS(data: any): QuantitativeBinRange { - data = typeof data === 'object' ? data : {}; - let result = new QuantitativeBinRange(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["IsIntegerRange"] = this.isIntegerRange; - data["Step"] = this.step; - super.toJSON(data); - return data; - } -} - -export interface IQuantitativeBinRange extends IBinRange { - isIntegerRange?: boolean | undefined; - step?: number | undefined; -} - -export class AggregateBinRange extends BinRange implements IAggregateBinRange { - - constructor(data?: IAggregateBinRange) { - super(data); - this._discriminator = "AggregateBinRange"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): AggregateBinRange { - data = typeof data === 'object' ? data : {}; - let result = new AggregateBinRange(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IAggregateBinRange extends IBinRange { -} - -export class AlphabeticBinRange extends BinRange implements IAlphabeticBinRange { - prefix?: string | undefined; - labelsValue?: { [key: string]: number; } | undefined; - valuesLabel?: { [key: string]: string; } | undefined; - - constructor(data?: IAlphabeticBinRange) { - super(data); - this._discriminator = "AlphabeticBinRange"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.prefix = data["Prefix"]; - if (data["LabelsValue"]) { - this.labelsValue = {}; - for (let key in data["LabelsValue"]) { - if (data["LabelsValue"].hasOwnProperty(key)) - this.labelsValue[key] = data["LabelsValue"][key]; - } - } - if (data["ValuesLabel"]) { - this.valuesLabel = {}; - for (let key in data["ValuesLabel"]) { - if (data["ValuesLabel"].hasOwnProperty(key)) - this.valuesLabel[key] = data["ValuesLabel"][key]; - } - } - } - } - - static fromJS(data: any): AlphabeticBinRange { - data = typeof data === 'object' ? data : {}; - let result = new AlphabeticBinRange(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Prefix"] = this.prefix; - if (this.labelsValue) { - data["LabelsValue"] = {}; - for (let key in this.labelsValue) { - if (this.labelsValue.hasOwnProperty(key)) - data["LabelsValue"][key] = this.labelsValue[key]; - } - } - if (this.valuesLabel) { - data["ValuesLabel"] = {}; - for (let key in this.valuesLabel) { - if (this.valuesLabel.hasOwnProperty(key)) - data["ValuesLabel"][key] = this.valuesLabel[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IAlphabeticBinRange extends IBinRange { - prefix?: string | undefined; - labelsValue?: { [key: string]: number; } | undefined; - valuesLabel?: { [key: string]: string; } | undefined; -} - -export class DateTimeBinRange extends BinRange implements IDateTimeBinRange { - step?: DateTimeStep | undefined; - - constructor(data?: IDateTimeBinRange) { - super(data); - this._discriminator = "DateTimeBinRange"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.step = data["Step"] ? DateTimeStep.fromJS(data["Step"]) : <any>undefined; - } - } - - static fromJS(data: any): DateTimeBinRange { - data = typeof data === 'object' ? data : {}; - let result = new DateTimeBinRange(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Step"] = this.step ? this.step.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface IDateTimeBinRange extends IBinRange { - step?: DateTimeStep | undefined; -} - -export class DateTimeStep implements IDateTimeStep { - dateTimeStepGranularity?: DateTimeStepGranularity | undefined; - dateTimeStepValue?: number | undefined; - dateTimeStepMaxValue?: number | undefined; - - constructor(data?: IDateTimeStep) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.dateTimeStepGranularity = data["DateTimeStepGranularity"]; - this.dateTimeStepValue = data["DateTimeStepValue"]; - this.dateTimeStepMaxValue = data["DateTimeStepMaxValue"]; - } - } - - static fromJS(data: any): DateTimeStep { - data = typeof data === 'object' ? data : {}; - let result = new DateTimeStep(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["DateTimeStepGranularity"] = this.dateTimeStepGranularity; - data["DateTimeStepValue"] = this.dateTimeStepValue; - data["DateTimeStepMaxValue"] = this.dateTimeStepMaxValue; - return data; - } -} - -export interface IDateTimeStep { - dateTimeStepGranularity?: DateTimeStepGranularity | undefined; - dateTimeStepValue?: number | undefined; - dateTimeStepMaxValue?: number | undefined; -} - -export enum DateTimeStepGranularity { - Second = 0, - Minute = 1, - Hour = 2, - Day = 3, - Month = 4, - Year = 5, -} - -export class Bin implements IBin { - aggregateResults?: AggregateResult[] | undefined; - count?: number | undefined; - binIndex?: BinIndex | undefined; - spans?: Span[] | undefined; - xSize?: number | undefined; - ySize?: number | undefined; - - constructor(data?: IBin) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) { - this.aggregateResults = []; - for (let item of data["AggregateResults"]) { - let fromJs = AggregateResult.fromJS(item); - if (fromJs) - this.aggregateResults.push(fromJs); - } - } - this.count = data["Count"]; - this.binIndex = data["BinIndex"] ? BinIndex.fromJS(data["BinIndex"]) : <any>undefined; - if (data["Spans"] && data["Spans"].constructor === Array) { - this.spans = []; - for (let item of data["Spans"]) - this.spans.push(Span.fromJS(item)); - } - this.xSize = data["XSize"]; - this.ySize = data["YSize"]; - } - } - - static fromJS(data: any): Bin { - data = typeof data === 'object' ? data : {}; - let result = new Bin(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.aggregateResults && this.aggregateResults.constructor === Array) { - data["AggregateResults"] = []; - for (let item of this.aggregateResults) - data["AggregateResults"].push(item.toJSON()); - } - data["Count"] = this.count; - data["BinIndex"] = this.binIndex ? this.binIndex.toJSON() : <any>undefined; - if (this.spans && this.spans.constructor === Array) { - data["Spans"] = []; - for (let item of this.spans) - data["Spans"].push(item.toJSON()); - } - data["XSize"] = this.xSize; - data["YSize"] = this.ySize; - return data; - } -} - -export interface IBin { - aggregateResults?: AggregateResult[] | undefined; - count?: number | undefined; - binIndex?: BinIndex | undefined; - spans?: Span[] | undefined; - xSize?: number | undefined; - ySize?: number | undefined; -} - -export class BinIndex implements IBinIndex { - indices?: number[] | undefined; - flatIndex?: number | undefined; - - constructor(data?: IBinIndex) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["Indices"] && data["Indices"].constructor === Array) { - this.indices = []; - for (let item of data["Indices"]) - this.indices.push(item); - } - this.flatIndex = data["FlatIndex"]; - } - } - - static fromJS(data: any): BinIndex { - data = typeof data === 'object' ? data : {}; - let result = new BinIndex(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.indices && this.indices.constructor === Array) { - data["Indices"] = []; - for (let item of this.indices) - data["Indices"].push(item); - } - data["FlatIndex"] = this.flatIndex; - return data; - } -} - -export interface IBinIndex { - indices?: number[] | undefined; - flatIndex?: number | undefined; -} - -export class Span implements ISpan { - min?: number | undefined; - max?: number | undefined; - index?: number | undefined; - - constructor(data?: ISpan) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.min = data["Min"]; - this.max = data["Max"]; - this.index = data["Index"]; - } - } - - static fromJS(data: any): Span { - data = typeof data === 'object' ? data : {}; - let result = new Span(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Min"] = this.min; - data["Max"] = this.max; - data["Index"] = this.index; - return data; - } -} - -export interface ISpan { - min?: number | undefined; - max?: number | undefined; - index?: number | undefined; -} - -export class ModelWealthResult extends Result implements IModelWealthResult { - wealth?: number | undefined; - startWealth?: number | undefined; - - constructor(data?: IModelWealthResult) { - super(data); - this._discriminator = "ModelWealthResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.wealth = data["Wealth"]; - this.startWealth = data["StartWealth"]; - } - } - - static fromJS(data: any): ModelWealthResult { - data = typeof data === 'object' ? data : {}; - let result = new ModelWealthResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Wealth"] = this.wealth; - data["StartWealth"] = this.startWealth; - super.toJSON(data); - return data; - } -} - -export interface IModelWealthResult extends IResult { - wealth?: number | undefined; - startWealth?: number | undefined; -} - -export abstract class HypothesisTestResult extends Result implements IHypothesisTestResult { - pValue?: number | undefined; - statistic?: number | undefined; - support?: number | undefined; - sampleSizes?: number[] | undefined; - errorMessage?: string | undefined; - - constructor(data?: IHypothesisTestResult) { - super(data); - this._discriminator = "HypothesisTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.pValue = data["PValue"]; - this.statistic = data["Statistic"]; - this.support = data["Support"]; - if (data["SampleSizes"] && data["SampleSizes"].constructor === Array) { - this.sampleSizes = []; - for (let item of data["SampleSizes"]) - this.sampleSizes.push(item); - } - this.errorMessage = data["ErrorMessage"]; - } - } - - static fromJS(data: any): HypothesisTestResult { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ChiSquaredTestResult") { - let result = new ChiSquaredTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "CorrelationTestResult") { - let result = new CorrelationTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "KSTestResult") { - let result = new KSTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "RootMeanSquareTestResult") { - let result = new RootMeanSquareTestResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "TTestResult") { - let result = new TTestResult(); - result.init(data); - return result; - } - throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["PValue"] = this.pValue; - data["Statistic"] = this.statistic; - data["Support"] = this.support; - if (this.sampleSizes && this.sampleSizes.constructor === Array) { - data["SampleSizes"] = []; - for (let item of this.sampleSizes) - data["SampleSizes"].push(item); - } - data["ErrorMessage"] = this.errorMessage; - super.toJSON(data); - return data; - } -} - -export interface IHypothesisTestResult extends IResult { - pValue?: number | undefined; - statistic?: number | undefined; - support?: number | undefined; - sampleSizes?: number[] | undefined; - errorMessage?: string | undefined; -} - -export abstract class ModelOperationResult extends Result implements IModelOperationResult { - modelId?: ModelId | undefined; - - constructor(data?: IModelOperationResult) { - super(data); - this._discriminator = "ModelOperationResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined; - } - } - - static fromJS(data: any): ModelOperationResult { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "NewModelOperationResult") { - let result = new NewModelOperationResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "AddComparisonResult") { - let result = new AddComparisonResult(); - result.init(data); - return result; - } - if (data["discriminator"] === "GetModelStateResult") { - let result = new GetModelStateResult(); - result.init(data); - return result; - } - throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface IModelOperationResult extends IResult { - modelId?: ModelId | undefined; -} - -export class RecommenderResult extends Result implements IRecommenderResult { - recommendedHistograms?: RecommendedHistogram[] | undefined; - totalCount?: number | undefined; - - constructor(data?: IRecommenderResult) { - super(data); - this._discriminator = "RecommenderResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["RecommendedHistograms"] && data["RecommendedHistograms"].constructor === Array) { - this.recommendedHistograms = []; - for (let item of data["RecommendedHistograms"]) - this.recommendedHistograms.push(RecommendedHistogram.fromJS(item)); - } - this.totalCount = data["TotalCount"]; - } - } - - static fromJS(data: any): RecommenderResult { - data = typeof data === 'object' ? data : {}; - let result = new RecommenderResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.recommendedHistograms && this.recommendedHistograms.constructor === Array) { - data["RecommendedHistograms"] = []; - for (let item of this.recommendedHistograms) - data["RecommendedHistograms"].push(item.toJSON()); - } - data["TotalCount"] = this.totalCount; - super.toJSON(data); - return data; - } -} - -export interface IRecommenderResult extends IResult { - recommendedHistograms?: RecommendedHistogram[] | undefined; - totalCount?: number | undefined; -} - -export class RecommendedHistogram implements IRecommendedHistogram { - histogramResult?: HistogramResult | undefined; - selectedBinIndices?: BinIndex[] | undefined; - selections?: Selection[] | undefined; - pValue?: number | undefined; - significance?: boolean | undefined; - decision?: Decision | undefined; - effectSize?: number | undefined; - hypothesisTestResult?: HypothesisTestResult | undefined; - id?: string | undefined; - xAttribute?: Attribute | undefined; - yAttribute?: Attribute | undefined; - - constructor(data?: IRecommendedHistogram) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.histogramResult = data["HistogramResult"] ? HistogramResult.fromJS(data["HistogramResult"]) : <any>undefined; - if (data["SelectedBinIndices"] && data["SelectedBinIndices"].constructor === Array) { - this.selectedBinIndices = []; - for (let item of data["SelectedBinIndices"]) - this.selectedBinIndices.push(BinIndex.fromJS(item)); - } - if (data["Selections"] && data["Selections"].constructor === Array) { - this.selections = []; - for (let item of data["Selections"]) - this.selections.push(Selection.fromJS(item)); - } - this.pValue = data["PValue"]; - this.significance = data["Significance"]; - this.decision = data["Decision"] ? Decision.fromJS(data["Decision"]) : <any>undefined; - this.effectSize = data["EffectSize"]; - this.hypothesisTestResult = data["HypothesisTestResult"] ? HypothesisTestResult.fromJS(data["HypothesisTestResult"]) : <any>undefined; - this.id = data["Id"]; - this.xAttribute = data["XAttribute"] ? Attribute.fromJS(data["XAttribute"]) : <any>undefined; - this.yAttribute = data["YAttribute"] ? Attribute.fromJS(data["YAttribute"]) : <any>undefined; - } - } - - static fromJS(data: any): RecommendedHistogram { - data = typeof data === 'object' ? data : {}; - let result = new RecommendedHistogram(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["HistogramResult"] = this.histogramResult ? this.histogramResult.toJSON() : <any>undefined; - if (this.selectedBinIndices && this.selectedBinIndices.constructor === Array) { - data["SelectedBinIndices"] = []; - for (let item of this.selectedBinIndices) - data["SelectedBinIndices"].push(item.toJSON()); - } - if (this.selections && this.selections.constructor === Array) { - data["Selections"] = []; - for (let item of this.selections) - data["Selections"].push(item.toJSON()); - } - data["PValue"] = this.pValue; - data["Significance"] = this.significance; - data["Decision"] = this.decision ? this.decision.toJSON() : <any>undefined; - data["EffectSize"] = this.effectSize; - data["HypothesisTestResult"] = this.hypothesisTestResult ? this.hypothesisTestResult.toJSON() : <any>undefined; - data["Id"] = this.id; - data["XAttribute"] = this.xAttribute ? this.xAttribute.toJSON() : <any>undefined; - data["YAttribute"] = this.yAttribute ? this.yAttribute.toJSON() : <any>undefined; - return data; - } -} - -export interface IRecommendedHistogram { - histogramResult?: HistogramResult | undefined; - selectedBinIndices?: BinIndex[] | undefined; - selections?: Selection[] | undefined; - pValue?: number | undefined; - significance?: boolean | undefined; - decision?: Decision | undefined; - effectSize?: number | undefined; - hypothesisTestResult?: HypothesisTestResult | undefined; - id?: string | undefined; - xAttribute?: Attribute | undefined; - yAttribute?: Attribute | undefined; -} - -export class Selection implements ISelection { - statements?: Statement[] | undefined; - filterHistogramOperationReference?: IOperationReference | undefined; - - constructor(data?: ISelection) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - if (data["Statements"] && data["Statements"].constructor === Array) { - this.statements = []; - for (let item of data["Statements"]) - this.statements.push(Statement.fromJS(item)); - } - this.filterHistogramOperationReference = data["FilterHistogramOperationReference"] ? IOperationReference.fromJS(data["FilterHistogramOperationReference"]) : <any>undefined; - } - } - - static fromJS(data: any): Selection { - data = typeof data === 'object' ? data : {}; - let result = new Selection(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.statements && this.statements.constructor === Array) { - data["Statements"] = []; - for (let item of this.statements) - data["Statements"].push(item.toJSON()); - } - data["FilterHistogramOperationReference"] = this.filterHistogramOperationReference ? this.filterHistogramOperationReference.toJSON() : <any>undefined; - return data; - } -} - -export interface ISelection { - statements?: Statement[] | undefined; - filterHistogramOperationReference?: IOperationReference | undefined; -} - -export class Statement implements IStatement { - attribute?: Attribute | undefined; - predicate?: Predicate | undefined; - value?: any | undefined; - - constructor(data?: IStatement) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.attribute = data["Attribute"] ? Attribute.fromJS(data["Attribute"]) : <any>undefined; - this.predicate = data["Predicate"]; - this.value = data["Value"]; - } - } - - static fromJS(data: any): Statement { - data = typeof data === 'object' ? data : {}; - let result = new Statement(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Attribute"] = this.attribute ? this.attribute.toJSON() : <any>undefined; - data["Predicate"] = this.predicate; - data["Value"] = this.value; - return data; - } -} - -export interface IStatement { - attribute?: Attribute | undefined; - predicate?: Predicate | undefined; - value?: any | undefined; -} - -export enum Predicate { - EQUALS = 0, - LIKE = 1, - GREATER_THAN = 2, - LESS_THAN = 3, - GREATER_THAN_EQUAL = 4, - LESS_THAN_EQUAL = 5, - STARTS_WITH = 6, - ENDS_WITH = 7, - CONTAINS = 8, -} - -export abstract class IOperationReference implements IIOperationReference { - id?: string | undefined; - - protected _discriminator: string; - - constructor(data?: IIOperationReference) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "IOperationReference"; - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - } - } - - static fromJS(data: any): IOperationReference { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "OperationReference") { - let result = new OperationReference(); - result.init(data); - return result; - } - throw new Error("The abstract class 'IOperationReference' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["Id"] = this.id; - return data; - } -} - -export interface IIOperationReference { - id?: string | undefined; -} - -export class OperationReference extends IOperationReference implements IOperationReference { - id?: string | undefined; - - constructor(data?: IOperationReference) { - super(data); - this._discriminator = "OperationReference"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.id = data["Id"]; - } - } - - static fromJS(data: any): OperationReference { - data = typeof data === 'object' ? data : {}; - let result = new OperationReference(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - super.toJSON(data); - return data; - } -} - -export interface IOperationReference extends IIOperationReference { - id?: string | undefined; -} - -export class Decision extends Result implements IDecision { - comparisonId?: ComparisonId | undefined; - riskControlType?: RiskControlType | undefined; - significance?: boolean | undefined; - pValue?: number | undefined; - lhs?: number | undefined; - significanceLevel?: number | undefined; - sampleSizeEstimate?: number | undefined; - - constructor(data?: IDecision) { - super(data); - this._discriminator = "Decision"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined; - this.riskControlType = data["RiskControlType"]; - this.significance = data["Significance"]; - this.pValue = data["PValue"]; - this.lhs = data["Lhs"]; - this.significanceLevel = data["SignificanceLevel"]; - this.sampleSizeEstimate = data["SampleSizeEstimate"]; - } - } - - static fromJS(data: any): Decision { - data = typeof data === 'object' ? data : {}; - let result = new Decision(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined; - data["RiskControlType"] = this.riskControlType; - data["Significance"] = this.significance; - data["PValue"] = this.pValue; - data["Lhs"] = this.lhs; - data["SignificanceLevel"] = this.significanceLevel; - data["SampleSizeEstimate"] = this.sampleSizeEstimate; - super.toJSON(data); - return data; - } -} - -export interface IDecision extends IResult { - comparisonId?: ComparisonId | undefined; - riskControlType?: RiskControlType | undefined; - significance?: boolean | undefined; - pValue?: number | undefined; - lhs?: number | undefined; - significanceLevel?: number | undefined; - sampleSizeEstimate?: number | undefined; -} - -export class ComparisonId implements IComparisonId { - value?: string | undefined; - - constructor(data?: IComparisonId) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): ComparisonId { - data = typeof data === 'object' ? data : {}; - let result = new ComparisonId(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - return data; - } -} - -export interface IComparisonId { - value?: string | undefined; -} - -export class OptimizerResult extends Result implements IOptimizerResult { - topKSolutions?: Solution[] | undefined; - - constructor(data?: IOptimizerResult) { - super(data); - this._discriminator = "OptimizerResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["TopKSolutions"] && data["TopKSolutions"].constructor === Array) { - this.topKSolutions = []; - for (let item of data["TopKSolutions"]) - this.topKSolutions.push(Solution.fromJS(item)); - } - } - } - - static fromJS(data: any): OptimizerResult { - data = typeof data === 'object' ? data : {}; - let result = new OptimizerResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.topKSolutions && this.topKSolutions.constructor === Array) { - data["TopKSolutions"] = []; - for (let item of this.topKSolutions) - data["TopKSolutions"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IOptimizerResult extends IResult { - topKSolutions?: Solution[] | undefined; -} - -export class Solution implements ISolution { - solutionId?: string | undefined; - stepDescriptions?: StepDescription[] | undefined; - pipelineDescription?: PipelineDescription | undefined; - score?: Score | undefined; - naiveScore?: Score | undefined; - - constructor(data?: ISolution) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.solutionId = data["SolutionId"]; - if (data["StepDescriptions"] && data["StepDescriptions"].constructor === Array) { - this.stepDescriptions = []; - for (let item of data["StepDescriptions"]) - this.stepDescriptions.push(StepDescription.fromJS(item)); - } - this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined; - this.score = data["Score"] ? Score.fromJS(data["Score"]) : <any>undefined; - this.naiveScore = data["NaiveScore"] ? Score.fromJS(data["NaiveScore"]) : <any>undefined; - } - } - - static fromJS(data: any): Solution { - data = typeof data === 'object' ? data : {}; - let result = new Solution(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SolutionId"] = this.solutionId; - if (this.stepDescriptions && this.stepDescriptions.constructor === Array) { - data["StepDescriptions"] = []; - for (let item of this.stepDescriptions) - data["StepDescriptions"].push(item.toJSON()); - } - data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined; - data["Score"] = this.score ? this.score.toJSON() : <any>undefined; - data["NaiveScore"] = this.naiveScore ? this.naiveScore.toJSON() : <any>undefined; - return data; - } -} - -export interface ISolution { - solutionId?: string | undefined; - stepDescriptions?: StepDescription[] | undefined; - pipelineDescription?: PipelineDescription | undefined; - score?: Score | undefined; - naiveScore?: Score | undefined; -} - -export class StepDescription implements IStepDescription { - - protected _discriminator: string; - - constructor(data?: IStepDescription) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "StepDescription"; - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): StepDescription { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "SubpipelineStepDescription") { - let result = new SubpipelineStepDescription(); - result.init(data); - return result; - } - if (data["discriminator"] === "PrimitiveStepDescription") { - let result = new PrimitiveStepDescription(); - result.init(data); - return result; - } - let result = new StepDescription(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - return data; - } -} - -export interface IStepDescription { -} - -export class SubpipelineStepDescription extends StepDescription implements ISubpipelineStepDescription { - steps?: StepDescription[] | undefined; - - constructor(data?: ISubpipelineStepDescription) { - super(data); - this._discriminator = "SubpipelineStepDescription"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Steps"] && data["Steps"].constructor === Array) { - this.steps = []; - for (let item of data["Steps"]) - this.steps.push(StepDescription.fromJS(item)); - } - } - } - - static fromJS(data: any): SubpipelineStepDescription { - data = typeof data === 'object' ? data : {}; - let result = new SubpipelineStepDescription(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.steps && this.steps.constructor === Array) { - data["Steps"] = []; - for (let item of this.steps) - data["Steps"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface ISubpipelineStepDescription extends IStepDescription { - steps?: StepDescription[] | undefined; -} - -export class PipelineDescription implements IPipelineDescription { - id?: string | undefined; - name?: string | undefined; - description?: string | undefined; - inputs?: PipelineDescriptionInput[] | undefined; - outputs?: PipelineDescriptionOutput[] | undefined; - steps?: PipelineDescriptionStep[] | undefined; - - constructor(data?: IPipelineDescription) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - this.name = data["Name"]; - this.description = data["Description"]; - if (data["Inputs"] && data["Inputs"].constructor === Array) { - this.inputs = []; - for (let item of data["Inputs"]) - this.inputs.push(PipelineDescriptionInput.fromJS(item)); - } - if (data["Outputs"] && data["Outputs"].constructor === Array) { - this.outputs = []; - for (let item of data["Outputs"]) - this.outputs.push(PipelineDescriptionOutput.fromJS(item)); - } - if (data["Steps"] && data["Steps"].constructor === Array) { - this.steps = []; - for (let item of data["Steps"]) - this.steps.push(PipelineDescriptionStep.fromJS(item)); - } - } - } - - static fromJS(data: any): PipelineDescription { - data = typeof data === 'object' ? data : {}; - let result = new PipelineDescription(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - data["Name"] = this.name; - data["Description"] = this.description; - if (this.inputs && this.inputs.constructor === Array) { - data["Inputs"] = []; - for (let item of this.inputs) - data["Inputs"].push(item.toJSON()); - } - if (this.outputs && this.outputs.constructor === Array) { - data["Outputs"] = []; - for (let item of this.outputs) - data["Outputs"].push(item.toJSON()); - } - if (this.steps && this.steps.constructor === Array) { - data["Steps"] = []; - for (let item of this.steps) - data["Steps"].push(item.toJSON()); - } - return data; - } -} - -export interface IPipelineDescription { - id?: string | undefined; - name?: string | undefined; - description?: string | undefined; - inputs?: PipelineDescriptionInput[] | undefined; - outputs?: PipelineDescriptionOutput[] | undefined; - steps?: PipelineDescriptionStep[] | undefined; -} - -export class PipelineDescriptionInput implements IPipelineDescriptionInput { - name?: string | undefined; - - constructor(data?: IPipelineDescriptionInput) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.name = data["Name"]; - } - } - - static fromJS(data: any): PipelineDescriptionInput { - data = typeof data === 'object' ? data : {}; - let result = new PipelineDescriptionInput(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Name"] = this.name; - return data; - } -} - -export interface IPipelineDescriptionInput { - name?: string | undefined; -} - -export class PipelineDescriptionOutput implements IPipelineDescriptionOutput { - name?: string | undefined; - data?: string | undefined; - - constructor(data?: IPipelineDescriptionOutput) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.name = data["Name"]; - this.data = data["Data"]; - } - } - - static fromJS(data: any): PipelineDescriptionOutput { - data = typeof data === 'object' ? data : {}; - let result = new PipelineDescriptionOutput(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Name"] = this.name; - data["Data"] = this.data; - return data; - } -} - -export interface IPipelineDescriptionOutput { - name?: string | undefined; - data?: string | undefined; -} - -export class PipelineDescriptionStep implements IPipelineDescriptionStep { - outputs?: StepOutput[] | undefined; - - protected _discriminator: string; - - constructor(data?: IPipelineDescriptionStep) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "PipelineDescriptionStep"; - } - - init(data?: any) { - if (data) { - if (data["Outputs"] && data["Outputs"].constructor === Array) { - this.outputs = []; - for (let item of data["Outputs"]) - this.outputs.push(StepOutput.fromJS(item)); - } - } - } - - static fromJS(data: any): PipelineDescriptionStep { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "PlaceholderPipelineDescriptionStep") { - let result = new PlaceholderPipelineDescriptionStep(); - result.init(data); - return result; - } - if (data["discriminator"] === "SubpipelinePipelineDescriptionStep") { - let result = new SubpipelinePipelineDescriptionStep(); - result.init(data); - return result; - } - if (data["discriminator"] === "PrimitivePipelineDescriptionStep") { - let result = new PrimitivePipelineDescriptionStep(); - result.init(data); - return result; - } - let result = new PipelineDescriptionStep(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - if (this.outputs && this.outputs.constructor === Array) { - data["Outputs"] = []; - for (let item of this.outputs) - data["Outputs"].push(item.toJSON()); - } - return data; - } -} - -export interface IPipelineDescriptionStep { - outputs?: StepOutput[] | undefined; -} - -export class StepOutput implements IStepOutput { - id?: string | undefined; - - constructor(data?: IStepOutput) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - } - } - - static fromJS(data: any): StepOutput { - data = typeof data === 'object' ? data : {}; - let result = new StepOutput(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - return data; - } -} - -export interface IStepOutput { - id?: string | undefined; -} - -export class PlaceholderPipelineDescriptionStep extends PipelineDescriptionStep implements IPlaceholderPipelineDescriptionStep { - inputs?: StepInput[] | undefined; - - constructor(data?: IPlaceholderPipelineDescriptionStep) { - super(data); - this._discriminator = "PlaceholderPipelineDescriptionStep"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Inputs"] && data["Inputs"].constructor === Array) { - this.inputs = []; - for (let item of data["Inputs"]) - this.inputs.push(StepInput.fromJS(item)); - } - } - } - - static fromJS(data: any): PlaceholderPipelineDescriptionStep { - data = typeof data === 'object' ? data : {}; - let result = new PlaceholderPipelineDescriptionStep(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.inputs && this.inputs.constructor === Array) { - data["Inputs"] = []; - for (let item of this.inputs) - data["Inputs"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IPlaceholderPipelineDescriptionStep extends IPipelineDescriptionStep { - inputs?: StepInput[] | undefined; -} - -export class StepInput implements IStepInput { - data?: string | undefined; - - constructor(data?: IStepInput) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.data = data["Data"]; - } - } - - static fromJS(data: any): StepInput { - data = typeof data === 'object' ? data : {}; - let result = new StepInput(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Data"] = this.data; - return data; - } -} - -export interface IStepInput { - data?: string | undefined; -} - -export class SubpipelinePipelineDescriptionStep extends PipelineDescriptionStep implements ISubpipelinePipelineDescriptionStep { - pipelineDescription?: PipelineDescription | undefined; - inputs?: StepInput[] | undefined; - - constructor(data?: ISubpipelinePipelineDescriptionStep) { - super(data); - this._discriminator = "SubpipelinePipelineDescriptionStep"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined; - if (data["Inputs"] && data["Inputs"].constructor === Array) { - this.inputs = []; - for (let item of data["Inputs"]) - this.inputs.push(StepInput.fromJS(item)); - } - } - } - - static fromJS(data: any): SubpipelinePipelineDescriptionStep { - data = typeof data === 'object' ? data : {}; - let result = new SubpipelinePipelineDescriptionStep(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined; - if (this.inputs && this.inputs.constructor === Array) { - data["Inputs"] = []; - for (let item of this.inputs) - data["Inputs"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface ISubpipelinePipelineDescriptionStep extends IPipelineDescriptionStep { - pipelineDescription?: PipelineDescription | undefined; - inputs?: StepInput[] | undefined; -} - -export class PrimitivePipelineDescriptionStep extends PipelineDescriptionStep implements IPrimitivePipelineDescriptionStep { - primitive?: Primitive | undefined; - arguments?: { [key: string]: PrimitiveStepArgument; } | undefined; - hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined; - - constructor(data?: IPrimitivePipelineDescriptionStep) { - super(data); - this._discriminator = "PrimitivePipelineDescriptionStep"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.primitive = data["Primitive"] ? Primitive.fromJS(data["Primitive"]) : <any>undefined; - if (data["Arguments"]) { - this.arguments = {}; - for (let key in data["Arguments"]) { - if (data["Arguments"].hasOwnProperty(key)) - this.arguments[key] = data["Arguments"][key] ? PrimitiveStepArgument.fromJS(data["Arguments"][key]) : new PrimitiveStepArgument(); - } - } - if (data["Hyperparams"]) { - this.hyperparams = {}; - for (let key in data["Hyperparams"]) { - if (data["Hyperparams"].hasOwnProperty(key)) - this.hyperparams[key] = data["Hyperparams"][key] ? PrimitiveStepHyperparameter.fromJS(data["Hyperparams"][key]) : new PrimitiveStepHyperparameter(); - } - } - } - } - - static fromJS(data: any): PrimitivePipelineDescriptionStep { - data = typeof data === 'object' ? data : {}; - let result = new PrimitivePipelineDescriptionStep(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Primitive"] = this.primitive ? this.primitive.toJSON() : <any>undefined; - if (this.arguments) { - data["Arguments"] = {}; - for (let key in this.arguments) { - if (this.arguments.hasOwnProperty(key)) - data["Arguments"][key] = this.arguments[key]; - } - } - if (this.hyperparams) { - data["Hyperparams"] = {}; - for (let key in this.hyperparams) { - if (this.hyperparams.hasOwnProperty(key)) - data["Hyperparams"][key] = this.hyperparams[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IPrimitivePipelineDescriptionStep extends IPipelineDescriptionStep { - primitive?: Primitive | undefined; - arguments?: { [key: string]: PrimitiveStepArgument; } | undefined; - hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined; -} - -export class Primitive implements IPrimitive { - id?: string | undefined; - version?: string | undefined; - pythonPath?: string | undefined; - name?: string | undefined; - digest?: string | undefined; - - constructor(data?: IPrimitive) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.id = data["Id"]; - this.version = data["Version"]; - this.pythonPath = data["PythonPath"]; - this.name = data["Name"]; - this.digest = data["Digest"]; - } - } - - static fromJS(data: any): Primitive { - data = typeof data === 'object' ? data : {}; - let result = new Primitive(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Id"] = this.id; - data["Version"] = this.version; - data["PythonPath"] = this.pythonPath; - data["Name"] = this.name; - data["Digest"] = this.digest; - return data; - } -} - -export interface IPrimitive { - id?: string | undefined; - version?: string | undefined; - pythonPath?: string | undefined; - name?: string | undefined; - digest?: string | undefined; -} - -export class PrimitiveStepHyperparameter implements IPrimitiveStepHyperparameter { - - protected _discriminator: string; - - constructor(data?: IPrimitiveStepHyperparameter) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "PrimitiveStepHyperparameter"; - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): PrimitiveStepHyperparameter { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "PrimitiveStepArgument") { - let result = new PrimitiveStepArgument(); - result.init(data); - return result; - } - if (data["discriminator"] === "DataArguments") { - let result = new DataArguments(); - result.init(data); - return result; - } - if (data["discriminator"] === "PrimitiveArgument") { - let result = new PrimitiveArgument(); - result.init(data); - return result; - } - if (data["discriminator"] === "PrimitiveArguments") { - let result = new PrimitiveArguments(); - result.init(data); - return result; - } - if (data["discriminator"] === "ValueArgument") { - let result = new ValueArgument(); - result.init(data); - return result; - } - if (data["discriminator"] === "ContainerArgument") { - let result = new ContainerArgument(); - result.init(data); - return result; - } - if (data["discriminator"] === "DataArgument") { - let result = new DataArgument(); - result.init(data); - return result; - } - let result = new PrimitiveStepHyperparameter(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - return data; - } -} - -export interface IPrimitiveStepHyperparameter { -} - -export class PrimitiveStepArgument extends PrimitiveStepHyperparameter implements IPrimitiveStepArgument { - - protected _discriminator: string; - - constructor(data?: IPrimitiveStepArgument) { - super(data); - this._discriminator = "PrimitiveStepArgument"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): PrimitiveStepArgument { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ContainerArgument") { - let result = new ContainerArgument(); - result.init(data); - return result; - } - if (data["discriminator"] === "DataArgument") { - let result = new DataArgument(); - result.init(data); - return result; - } - let result = new PrimitiveStepArgument(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - super.toJSON(data); - return data; - } -} - -export interface IPrimitiveStepArgument extends IPrimitiveStepHyperparameter { -} - -export class DataArguments extends PrimitiveStepHyperparameter implements IDataArguments { - data?: string[] | undefined; - - constructor(data?: IDataArguments) { - super(data); - this._discriminator = "DataArguments"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Data"] && data["Data"].constructor === Array) { - this.data = []; - for (let item of data["Data"]) - this.data.push(item); - } - } - } - - static fromJS(data: any): DataArguments { - data = typeof data === 'object' ? data : {}; - let result = new DataArguments(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.data && this.data.constructor === Array) { - data["Data"] = []; - for (let item of this.data) - data["Data"].push(item); - } - super.toJSON(data); - return data; - } -} - -export interface IDataArguments extends IPrimitiveStepHyperparameter { - data?: string[] | undefined; -} - -export class PrimitiveArgument extends PrimitiveStepHyperparameter implements IPrimitiveArgument { - data?: number | undefined; - - constructor(data?: IPrimitiveArgument) { - super(data); - this._discriminator = "PrimitiveArgument"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.data = data["Data"]; - } - } - - static fromJS(data: any): PrimitiveArgument { - data = typeof data === 'object' ? data : {}; - let result = new PrimitiveArgument(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Data"] = this.data; - super.toJSON(data); - return data; - } -} - -export interface IPrimitiveArgument extends IPrimitiveStepHyperparameter { - data?: number | undefined; -} - -export class PrimitiveArguments extends PrimitiveStepHyperparameter implements IPrimitiveArguments { - data?: number[] | undefined; - - constructor(data?: IPrimitiveArguments) { - super(data); - this._discriminator = "PrimitiveArguments"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Data"] && data["Data"].constructor === Array) { - this.data = []; - for (let item of data["Data"]) - this.data.push(item); - } - } - } - - static fromJS(data: any): PrimitiveArguments { - data = typeof data === 'object' ? data : {}; - let result = new PrimitiveArguments(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.data && this.data.constructor === Array) { - data["Data"] = []; - for (let item of this.data) - data["Data"].push(item); - } - super.toJSON(data); - return data; - } -} - -export interface IPrimitiveArguments extends IPrimitiveStepHyperparameter { - data?: number[] | undefined; -} - -export class ValueArgument extends PrimitiveStepHyperparameter implements IValueArgument { - data?: Value | undefined; - - constructor(data?: IValueArgument) { - super(data); - this._discriminator = "ValueArgument"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.data = data["Data"] ? Value.fromJS(data["Data"]) : <any>undefined; - } - } - - static fromJS(data: any): ValueArgument { - data = typeof data === 'object' ? data : {}; - let result = new ValueArgument(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Data"] = this.data ? this.data.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface IValueArgument extends IPrimitiveStepHyperparameter { - data?: Value | undefined; -} - -export abstract class Value implements IValue { - - protected _discriminator: string; - - constructor(data?: IValue) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - this._discriminator = "Value"; - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): Value { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "ErrorValue") { - let result = new ErrorValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "DoubleValue") { - let result = new DoubleValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "LongValue") { - let result = new LongValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "BoolValue") { - let result = new BoolValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "StringValue") { - let result = new StringValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "DatasetUriValue") { - let result = new DatasetUriValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "CsvUriValue") { - let result = new CsvUriValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "PickleUriValue") { - let result = new PickleUriValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "PickleBlobValue") { - let result = new PickleBlobValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "PlasmaIdValue") { - let result = new PlasmaIdValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "BytesValue") { - let result = new BytesValue(); - result.init(data); - return result; - } - if (data["discriminator"] === "ListValue") { - let result = new ListValue(); - result.init(data); - return result; - } - throw new Error("The abstract class 'Value' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - return data; - } -} - -export interface IValue { -} - -export class ErrorValue extends Value implements IErrorValue { - message?: string | undefined; - - constructor(data?: IErrorValue) { - super(data); - this._discriminator = "ErrorValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.message = data["Message"]; - } - } - - static fromJS(data: any): ErrorValue { - data = typeof data === 'object' ? data : {}; - let result = new ErrorValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Message"] = this.message; - super.toJSON(data); - return data; - } -} - -export interface IErrorValue extends IValue { - message?: string | undefined; -} - -export class DoubleValue extends Value implements IDoubleValue { - value?: number | undefined; - - constructor(data?: IDoubleValue) { - super(data); - this._discriminator = "DoubleValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): DoubleValue { - data = typeof data === 'object' ? data : {}; - let result = new DoubleValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IDoubleValue extends IValue { - value?: number | undefined; -} - -export class LongValue extends Value implements ILongValue { - value?: number | undefined; - - constructor(data?: ILongValue) { - super(data); - this._discriminator = "LongValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): LongValue { - data = typeof data === 'object' ? data : {}; - let result = new LongValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface ILongValue extends IValue { - value?: number | undefined; -} - -export class BoolValue extends Value implements IBoolValue { - value?: boolean | undefined; - - constructor(data?: IBoolValue) { - super(data); - this._discriminator = "BoolValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): BoolValue { - data = typeof data === 'object' ? data : {}; - let result = new BoolValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IBoolValue extends IValue { - value?: boolean | undefined; -} - -export class StringValue extends Value implements IStringValue { - value?: string | undefined; - - constructor(data?: IStringValue) { - super(data); - this._discriminator = "StringValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): StringValue { - data = typeof data === 'object' ? data : {}; - let result = new StringValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IStringValue extends IValue { - value?: string | undefined; -} - -export class DatasetUriValue extends Value implements IDatasetUriValue { - value?: string | undefined; - - constructor(data?: IDatasetUriValue) { - super(data); - this._discriminator = "DatasetUriValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): DatasetUriValue { - data = typeof data === 'object' ? data : {}; - let result = new DatasetUriValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IDatasetUriValue extends IValue { - value?: string | undefined; -} - -export class CsvUriValue extends Value implements ICsvUriValue { - value?: string | undefined; - - constructor(data?: ICsvUriValue) { - super(data); - this._discriminator = "CsvUriValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): CsvUriValue { - data = typeof data === 'object' ? data : {}; - let result = new CsvUriValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface ICsvUriValue extends IValue { - value?: string | undefined; -} - -export class PickleUriValue extends Value implements IPickleUriValue { - value?: string | undefined; - - constructor(data?: IPickleUriValue) { - super(data); - this._discriminator = "PickleUriValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): PickleUriValue { - data = typeof data === 'object' ? data : {}; - let result = new PickleUriValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IPickleUriValue extends IValue { - value?: string | undefined; -} - -export class PickleBlobValue extends Value implements IPickleBlobValue { - value?: string | undefined; - - constructor(data?: IPickleBlobValue) { - super(data); - this._discriminator = "PickleBlobValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): PickleBlobValue { - data = typeof data === 'object' ? data : {}; - let result = new PickleBlobValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IPickleBlobValue extends IValue { - value?: string | undefined; -} - -export class PlasmaIdValue extends Value implements IPlasmaIdValue { - value?: string | undefined; - - constructor(data?: IPlasmaIdValue) { - super(data); - this._discriminator = "PlasmaIdValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): PlasmaIdValue { - data = typeof data === 'object' ? data : {}; - let result = new PlasmaIdValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IPlasmaIdValue extends IValue { - value?: string | undefined; -} - -export class BytesValue extends Value implements IBytesValue { - value?: string | undefined; - - constructor(data?: IBytesValue) { - super(data); - this._discriminator = "BytesValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.value = data["Value"]; - } - } - - static fromJS(data: any): BytesValue { - data = typeof data === 'object' ? data : {}; - let result = new BytesValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - super.toJSON(data); - return data; - } -} - -export interface IBytesValue extends IValue { - value?: string | undefined; -} - -export class ContainerArgument extends PrimitiveStepArgument implements IContainerArgument { - data?: string | undefined; - - constructor(data?: IContainerArgument) { - super(data); - this._discriminator = "ContainerArgument"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.data = data["Data"]; - } - } - - static fromJS(data: any): ContainerArgument { - data = typeof data === 'object' ? data : {}; - let result = new ContainerArgument(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Data"] = this.data; - super.toJSON(data); - return data; - } -} - -export interface IContainerArgument extends IPrimitiveStepArgument { - data?: string | undefined; -} - -export class Score implements IScore { - metricType?: MetricType | undefined; - value?: number | undefined; - - constructor(data?: IScore) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.metricType = data["MetricType"]; - this.value = data["Value"]; - } - } - - static fromJS(data: any): Score { - data = typeof data === 'object' ? data : {}; - let result = new Score(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["MetricType"] = this.metricType; - data["Value"] = this.value; - return data; - } -} - -export interface IScore { - metricType?: MetricType | undefined; - value?: number | undefined; -} - -export class ExampleResult extends Result implements IExampleResult { - resultValues?: { [key: string]: string; } | undefined; - message?: string | undefined; - - constructor(data?: IExampleResult) { - super(data); - this._discriminator = "ExampleResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["ResultValues"]) { - this.resultValues = {}; - for (let key in data["ResultValues"]) { - if (data["ResultValues"].hasOwnProperty(key)) - this.resultValues[key] = data["ResultValues"][key]; - } - } - this.message = data["Message"]; - } - } - - static fromJS(data: any): ExampleResult { - data = typeof data === 'object' ? data : {}; - let result = new ExampleResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.resultValues) { - data["ResultValues"] = {}; - for (let key in this.resultValues) { - if (this.resultValues.hasOwnProperty(key)) - data["ResultValues"][key] = this.resultValues[key]; - } - } - data["Message"] = this.message; - super.toJSON(data); - return data; - } -} - -export interface IExampleResult extends IResult { - resultValues?: { [key: string]: string; } | undefined; - message?: string | undefined; -} - -export class NewModelOperationResult extends ModelOperationResult implements INewModelOperationResult { - - constructor(data?: INewModelOperationResult) { - super(data); - this._discriminator = "NewModelOperationResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): NewModelOperationResult { - data = typeof data === 'object' ? data : {}; - let result = new NewModelOperationResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface INewModelOperationResult extends IModelOperationResult { -} - -export class AddComparisonResult extends ModelOperationResult implements IAddComparisonResult { - comparisonId?: ComparisonId | undefined; - - constructor(data?: IAddComparisonResult) { - super(data); - this._discriminator = "AddComparisonResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined; - } - } - - static fromJS(data: any): AddComparisonResult { - data = typeof data === 'object' ? data : {}; - let result = new AddComparisonResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface IAddComparisonResult extends IModelOperationResult { - comparisonId?: ComparisonId | undefined; -} - -export class GetModelStateResult extends ModelOperationResult implements IGetModelStateResult { - decisions?: Decision[] | undefined; - startingWealth?: number | undefined; - currentWealth?: number | undefined; - - constructor(data?: IGetModelStateResult) { - super(data); - this._discriminator = "GetModelStateResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Decisions"] && data["Decisions"].constructor === Array) { - this.decisions = []; - for (let item of data["Decisions"]) - this.decisions.push(Decision.fromJS(item)); - } - this.startingWealth = data["StartingWealth"]; - this.currentWealth = data["CurrentWealth"]; - } - } - - static fromJS(data: any): GetModelStateResult { - data = typeof data === 'object' ? data : {}; - let result = new GetModelStateResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.decisions && this.decisions.constructor === Array) { - data["Decisions"] = []; - for (let item of this.decisions) - data["Decisions"].push(item.toJSON()); - } - data["StartingWealth"] = this.startingWealth; - data["CurrentWealth"] = this.currentWealth; - super.toJSON(data); - return data; - } -} - -export interface IGetModelStateResult extends IModelOperationResult { - decisions?: Decision[] | undefined; - startingWealth?: number | undefined; - currentWealth?: number | undefined; -} - -export class AggregateKey implements IAggregateKey { - aggregateParameterIndex?: number | undefined; - brushIndex?: number | undefined; - - constructor(data?: IAggregateKey) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.aggregateParameterIndex = data["AggregateParameterIndex"]; - this.brushIndex = data["BrushIndex"]; - } - } - - static fromJS(data: any): AggregateKey { - data = typeof data === 'object' ? data : {}; - let result = new AggregateKey(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AggregateParameterIndex"] = this.aggregateParameterIndex; - data["BrushIndex"] = this.brushIndex; - return data; - } -} - -export interface IAggregateKey { - aggregateParameterIndex?: number | undefined; - brushIndex?: number | undefined; -} - -export abstract class IResult implements IIResult { - - constructor(data?: IIResult) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): IResult { - data = typeof data === 'object' ? data : {}; - throw new Error("The abstract class 'IResult' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - return data; - } -} - -export interface IIResult { -} - -export class DataArgument extends PrimitiveStepArgument implements IDataArgument { - data?: string | undefined; - - constructor(data?: IDataArgument) { - super(data); - this._discriminator = "DataArgument"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.data = data["Data"]; - } - } - - static fromJS(data: any): DataArgument { - data = typeof data === 'object' ? data : {}; - let result = new DataArgument(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Data"] = this.data; - super.toJSON(data); - return data; - } -} - -export interface IDataArgument extends IPrimitiveStepArgument { - data?: string | undefined; -} - -export class ListValue extends Value implements IListValue { - items?: Value[] | undefined; - - constructor(data?: IListValue) { - super(data); - this._discriminator = "ListValue"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Items"] && data["Items"].constructor === Array) { - this.items = []; - for (let item of data["Items"]) - this.items.push(Value.fromJS(item)); - } - } - } - - static fromJS(data: any): ListValue { - data = typeof data === 'object' ? data : {}; - let result = new ListValue(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.items && this.items.constructor === Array) { - data["Items"] = []; - for (let item of this.items) - data["Items"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IListValue extends IValue { - items?: Value[] | undefined; -} - -export class Metrics implements IMetrics { - averageAccuracy?: number | undefined; - averageRSquared?: number | undefined; - f1Macro?: number | undefined; - - constructor(data?: IMetrics) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.averageAccuracy = data["AverageAccuracy"]; - this.averageRSquared = data["AverageRSquared"]; - this.f1Macro = data["F1Macro"]; - } - } - - static fromJS(data: any): Metrics { - data = typeof data === 'object' ? data : {}; - let result = new Metrics(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AverageAccuracy"] = this.averageAccuracy; - data["AverageRSquared"] = this.averageRSquared; - data["F1Macro"] = this.f1Macro; - return data; - } -} - -export interface IMetrics { - averageAccuracy?: number | undefined; - averageRSquared?: number | undefined; - f1Macro?: number | undefined; -} - -export class FeatureImportanceOperationParameters extends DistOperationParameters implements IFeatureImportanceOperationParameters { - solutionId?: string | undefined; - - constructor(data?: IFeatureImportanceOperationParameters) { - super(data); - this._discriminator = "FeatureImportanceOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.solutionId = data["SolutionId"]; - } - } - - static fromJS(data: any): FeatureImportanceOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new FeatureImportanceOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SolutionId"] = this.solutionId; - super.toJSON(data); - return data; - } -} - -export interface IFeatureImportanceOperationParameters extends IDistOperationParameters { - solutionId?: string | undefined; -} - -export class FeatureImportanceResult extends Result implements IFeatureImportanceResult { - featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined; - - constructor(data?: IFeatureImportanceResult) { - super(data); - this._discriminator = "FeatureImportanceResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["FeatureImportances"]) { - this.featureImportances = {}; - for (let key in data["FeatureImportances"]) { - if (data["FeatureImportances"].hasOwnProperty(key)) - this.featureImportances[key] = data["FeatureImportances"][key] ? TupleOfDoubleAndDouble.fromJS(data["FeatureImportances"][key]) : new TupleOfDoubleAndDouble(); - } - } - } - } - - static fromJS(data: any): FeatureImportanceResult { - data = typeof data === 'object' ? data : {}; - let result = new FeatureImportanceResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.featureImportances) { - data["FeatureImportances"] = {}; - for (let key in this.featureImportances) { - if (this.featureImportances.hasOwnProperty(key)) - data["FeatureImportances"][key] = this.featureImportances[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IFeatureImportanceResult extends IResult { - featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined; -} - -export class TupleOfDoubleAndDouble implements ITupleOfDoubleAndDouble { - item1?: number | undefined; - item2?: number | undefined; - - constructor(data?: ITupleOfDoubleAndDouble) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.item1 = data["Item1"]; - this.item2 = data["Item2"]; - } - } - - static fromJS(data: any): TupleOfDoubleAndDouble { - data = typeof data === 'object' ? data : {}; - let result = new TupleOfDoubleAndDouble(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Item1"] = this.item1; - data["Item2"] = this.item2; - return data; - } -} - -export interface ITupleOfDoubleAndDouble { - item1?: number | undefined; - item2?: number | undefined; -} - -export class PrimitiveStepDescription extends StepDescription implements IPrimitiveStepDescription { - hyperparams?: { [key: string]: Value; } | undefined; - - constructor(data?: IPrimitiveStepDescription) { - super(data); - this._discriminator = "PrimitiveStepDescription"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Hyperparams"]) { - this.hyperparams = {}; - for (let key in data["Hyperparams"]) { - if (data["Hyperparams"].hasOwnProperty(key)) - this.hyperparams[key] = data["Hyperparams"][key] ? Value.fromJS(data["Hyperparams"][key]) : <any>undefined; - } - } - } - } - - static fromJS(data: any): PrimitiveStepDescription { - data = typeof data === 'object' ? data : {}; - let result = new PrimitiveStepDescription(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.hyperparams) { - data["Hyperparams"] = {}; - for (let key in this.hyperparams) { - if (this.hyperparams.hasOwnProperty(key)) - data["Hyperparams"][key] = this.hyperparams[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IPrimitiveStepDescription extends IStepDescription { - hyperparams?: { [key: string]: Value; } | undefined; -} - -export enum ValueType { - VALUE_TYPE_UNDEFINED = 0, - RAW = 1, - DATASET_URI = 2, - CSV_URI = 3, - PICKLE_URI = 4, - PICKLE_BLOB = 5, - PLASMA_ID = 6, -} - -export class DatamartSearchParameters implements IDatamartSearchParameters { - adapterName?: string | undefined; - queryJson?: string | undefined; - - constructor(data?: IDatamartSearchParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.adapterName = data["AdapterName"]; - this.queryJson = data["QueryJson"]; - } - } - - static fromJS(data: any): DatamartSearchParameters { - data = typeof data === 'object' ? data : {}; - let result = new DatamartSearchParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AdapterName"] = this.adapterName; - data["QueryJson"] = this.queryJson; - return data; - } -} - -export interface IDatamartSearchParameters { - adapterName?: string | undefined; - queryJson?: string | undefined; -} - -export class DatamartAugmentParameters implements IDatamartAugmentParameters { - adapterName?: string | undefined; - augmentationJson?: string | undefined; - numberOfSamples?: number | undefined; - augmentedAdapterName?: string | undefined; - - constructor(data?: IDatamartAugmentParameters) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.adapterName = data["AdapterName"]; - this.augmentationJson = data["AugmentationJson"]; - this.numberOfSamples = data["NumberOfSamples"]; - this.augmentedAdapterName = data["AugmentedAdapterName"]; - } - } - - static fromJS(data: any): DatamartAugmentParameters { - data = typeof data === 'object' ? data : {}; - let result = new DatamartAugmentParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["AdapterName"] = this.adapterName; - data["AugmentationJson"] = this.augmentationJson; - data["NumberOfSamples"] = this.numberOfSamples; - data["AugmentedAdapterName"] = this.augmentedAdapterName; - return data; - } -} - -export interface IDatamartAugmentParameters { - adapterName?: string | undefined; - augmentationJson?: string | undefined; - numberOfSamples?: number | undefined; - augmentedAdapterName?: string | undefined; -} - -export class RawDataResult extends DistResult implements IRawDataResult { - samples?: { [key: string]: any[]; } | undefined; - weightedWords?: { [key: string]: Word[]; } | undefined; - - constructor(data?: IRawDataResult) { - super(data); - this._discriminator = "RawDataResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Samples"]) { - this.samples = {}; - for (let key in data["Samples"]) { - if (data["Samples"].hasOwnProperty(key)) - this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : []; - } - } - if (data["WeightedWords"]) { - this.weightedWords = {}; - for (let key in data["WeightedWords"]) { - if (data["WeightedWords"].hasOwnProperty(key)) - this.weightedWords[key] = data["WeightedWords"][key] ? data["WeightedWords"][key].map((i: any) => Word.fromJS(i)) : []; - } - } - } - } - - static fromJS(data: any): RawDataResult { - data = typeof data === 'object' ? data : {}; - let result = new RawDataResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.samples) { - data["Samples"] = {}; - for (let key in this.samples) { - if (this.samples.hasOwnProperty(key)) - data["Samples"][key] = this.samples[key]; - } - } - if (this.weightedWords) { - data["WeightedWords"] = {}; - for (let key in this.weightedWords) { - if (this.weightedWords.hasOwnProperty(key)) - data["WeightedWords"][key] = this.weightedWords[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IRawDataResult extends IDistResult { - samples?: { [key: string]: any[]; } | undefined; - weightedWords?: { [key: string]: Word[]; } | undefined; -} - -export class Word implements IWord { - text?: string | undefined; - occurrences?: number | undefined; - stem?: string | undefined; - isWordGroup?: boolean | undefined; - - constructor(data?: IWord) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.text = data["Text"]; - this.occurrences = data["Occurrences"]; - this.stem = data["Stem"]; - this.isWordGroup = data["IsWordGroup"]; - } - } - - static fromJS(data: any): Word { - data = typeof data === 'object' ? data : {}; - let result = new Word(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Text"] = this.text; - data["Occurrences"] = this.occurrences; - data["Stem"] = this.stem; - data["IsWordGroup"] = this.isWordGroup; - return data; - } -} - -export interface IWord { - text?: string | undefined; - occurrences?: number | undefined; - stem?: string | undefined; - isWordGroup?: boolean | undefined; -} - -export class SampleOperationParameters extends DistOperationParameters implements ISampleOperationParameters { - numSamples?: number | undefined; - attributeParameters?: AttributeParameters[] | undefined; - brushes?: string[] | undefined; - - constructor(data?: ISampleOperationParameters) { - super(data); - this._discriminator = "SampleOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.numSamples = data["NumSamples"]; - if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) { - this.attributeParameters = []; - for (let item of data["AttributeParameters"]) - this.attributeParameters.push(AttributeParameters.fromJS(item)); - } - if (data["Brushes"] && data["Brushes"].constructor === Array) { - this.brushes = []; - for (let item of data["Brushes"]) - this.brushes.push(item); - } - } - } - - static fromJS(data: any): SampleOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new SampleOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["NumSamples"] = this.numSamples; - if (this.attributeParameters && this.attributeParameters.constructor === Array) { - data["AttributeParameters"] = []; - for (let item of this.attributeParameters) - data["AttributeParameters"].push(item.toJSON()); - } - if (this.brushes && this.brushes.constructor === Array) { - data["Brushes"] = []; - for (let item of this.brushes) - data["Brushes"].push(item); - } - super.toJSON(data); - return data; - } -} - -export interface ISampleOperationParameters extends IDistOperationParameters { - numSamples?: number | undefined; - attributeParameters?: AttributeParameters[] | undefined; - brushes?: string[] | undefined; -} - -export class SampleResult extends DistResult implements ISampleResult { - samples?: { [key: string]: { [key: string]: number; }; } | undefined; - isTruncated?: boolean | undefined; - - constructor(data?: ISampleResult) { - super(data); - this._discriminator = "SampleResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Samples"]) { - this.samples = {}; - for (let key in data["Samples"]) { - if (data["Samples"].hasOwnProperty(key)) - this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : {}; - } - } - this.isTruncated = data["IsTruncated"]; - } - } - - static fromJS(data: any): SampleResult { - data = typeof data === 'object' ? data : {}; - let result = new SampleResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.samples) { - data["Samples"] = {}; - for (let key in this.samples) { - if (this.samples.hasOwnProperty(key)) - data["Samples"][key] = this.samples[key]; - } - } - data["IsTruncated"] = this.isTruncated; - super.toJSON(data); - return data; - } -} - -export interface ISampleResult extends IDistResult { - samples?: { [key: string]: { [key: string]: number; }; } | undefined; - isTruncated?: boolean | undefined; -} - -export class ResultParameters extends UniqueJson implements IResultParameters { - operationReference?: IOperationReference | undefined; - stopOperation?: boolean | undefined; - - protected _discriminator: string; - - constructor(data?: IResultParameters) { - super(data); - this._discriminator = "ResultParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.operationReference = data["OperationReference"] ? IOperationReference.fromJS(data["OperationReference"]) : <any>undefined; - this.stopOperation = data["StopOperation"]; - } - } - - static fromJS(data: any): ResultParameters { - data = typeof data === 'object' ? data : {}; - if (data["discriminator"] === "RecommenderResultParameters") { - let result = new RecommenderResultParameters(); - result.init(data); - return result; - } - let result = new ResultParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["discriminator"] = this._discriminator; - data["OperationReference"] = this.operationReference ? this.operationReference.toJSON() : <any>undefined; - data["StopOperation"] = this.stopOperation; - super.toJSON(data); - return data; - } -} - -export interface IResultParameters extends IUniqueJson { - operationReference?: IOperationReference | undefined; - stopOperation?: boolean | undefined; -} - -export class RecommenderResultParameters extends ResultParameters implements IRecommenderResultParameters { - from?: number | undefined; - to?: number | undefined; - pValueSorting?: Sorting | undefined; - effectSizeFilter?: EffectSize | undefined; - - constructor(data?: IRecommenderResultParameters) { - super(data); - this._discriminator = "RecommenderResultParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.from = data["From"]; - this.to = data["To"]; - this.pValueSorting = data["PValueSorting"]; - this.effectSizeFilter = data["EffectSizeFilter"]; - } - } - - static fromJS(data: any): RecommenderResultParameters { - data = typeof data === 'object' ? data : {}; - let result = new RecommenderResultParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["From"] = this.from; - data["To"] = this.to; - data["PValueSorting"] = this.pValueSorting; - data["EffectSizeFilter"] = this.effectSizeFilter; - super.toJSON(data); - return data; - } -} - -export interface IRecommenderResultParameters extends IResultParameters { - from?: number | undefined; - to?: number | undefined; - pValueSorting?: Sorting | undefined; - effectSizeFilter?: EffectSize | undefined; -} - -export enum Sorting { - Ascending = "Ascending", - Descending = "Descending", -} - -export class AddComparisonParameters extends ModelOperationParameters implements IAddComparisonParameters { - modelId?: ModelId | undefined; - comparisonOrder?: number | undefined; - childOperationParameters?: OperationParameters[] | undefined; - isCachable?: boolean | undefined; - - constructor(data?: IAddComparisonParameters) { - super(data); - this._discriminator = "AddComparisonParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined; - this.comparisonOrder = data["ComparisonOrder"]; - if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) { - this.childOperationParameters = []; - for (let item of data["ChildOperationParameters"]) - this.childOperationParameters.push(OperationParameters.fromJS(item)); - } - this.isCachable = data["IsCachable"]; - } - } - - static fromJS(data: any): AddComparisonParameters { - data = typeof data === 'object' ? data : {}; - let result = new AddComparisonParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined; - data["ComparisonOrder"] = this.comparisonOrder; - if (this.childOperationParameters && this.childOperationParameters.constructor === Array) { - data["ChildOperationParameters"] = []; - for (let item of this.childOperationParameters) - data["ChildOperationParameters"].push(item.toJSON()); - } - data["IsCachable"] = this.isCachable; - super.toJSON(data); - return data; - } -} - -export interface IAddComparisonParameters extends IModelOperationParameters { - modelId?: ModelId | undefined; - comparisonOrder?: number | undefined; - childOperationParameters?: OperationParameters[] | undefined; - isCachable?: boolean | undefined; -} - -export class CDFResult extends DistResult implements ICDFResult { - cDF?: { [key: string]: number; } | undefined; - - constructor(data?: ICDFResult) { - super(data); - this._discriminator = "CDFResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["CDF"]) { - this.cDF = {}; - for (let key in data["CDF"]) { - if (data["CDF"].hasOwnProperty(key)) - this.cDF[key] = data["CDF"][key]; - } - } - } - } - - static fromJS(data: any): CDFResult { - data = typeof data === 'object' ? data : {}; - let result = new CDFResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.cDF) { - data["CDF"] = {}; - for (let key in this.cDF) { - if (this.cDF.hasOwnProperty(key)) - data["CDF"][key] = this.cDF[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface ICDFResult extends IDistResult { - cDF?: { [key: string]: number; } | undefined; -} - -export class ChiSquaredTestResult extends HypothesisTestResult implements IChiSquaredTestResult { - hs_aligned?: TupleOfDoubleAndDouble[] | undefined; - - constructor(data?: IChiSquaredTestResult) { - super(data); - this._discriminator = "ChiSquaredTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["hs_aligned"] && data["hs_aligned"].constructor === Array) { - this.hs_aligned = []; - for (let item of data["hs_aligned"]) - this.hs_aligned.push(TupleOfDoubleAndDouble.fromJS(item)); - } - } - } - - static fromJS(data: any): ChiSquaredTestResult { - data = typeof data === 'object' ? data : {}; - let result = new ChiSquaredTestResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.hs_aligned && this.hs_aligned.constructor === Array) { - data["hs_aligned"] = []; - for (let item of this.hs_aligned) - data["hs_aligned"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IChiSquaredTestResult extends IHypothesisTestResult { - hs_aligned?: TupleOfDoubleAndDouble[] | undefined; -} - -export class CorrelationTestResult extends HypothesisTestResult implements ICorrelationTestResult { - degreeOfFreedom?: number | undefined; - sampleCorrelationCoefficient?: number | undefined; - distResult?: EmpiricalDistResult | undefined; - - constructor(data?: ICorrelationTestResult) { - super(data); - this._discriminator = "CorrelationTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.degreeOfFreedom = data["DegreeOfFreedom"]; - this.sampleCorrelationCoefficient = data["SampleCorrelationCoefficient"]; - this.distResult = data["DistResult"] ? EmpiricalDistResult.fromJS(data["DistResult"]) : <any>undefined; - } - } - - static fromJS(data: any): CorrelationTestResult { - data = typeof data === 'object' ? data : {}; - let result = new CorrelationTestResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["DegreeOfFreedom"] = this.degreeOfFreedom; - data["SampleCorrelationCoefficient"] = this.sampleCorrelationCoefficient; - data["DistResult"] = this.distResult ? this.distResult.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface ICorrelationTestResult extends IHypothesisTestResult { - degreeOfFreedom?: number | undefined; - sampleCorrelationCoefficient?: number | undefined; - distResult?: EmpiricalDistResult | undefined; -} - -export class EmpiricalDistResult extends DistResult implements IEmpiricalDistResult { - marginals?: AttributeParameters[] | undefined; - marginalDistParameters?: { [key: string]: DistParameter; } | undefined; - jointDistParameter?: JointDistParameter | undefined; - - constructor(data?: IEmpiricalDistResult) { - super(data); - this._discriminator = "EmpiricalDistResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["Marginals"] && data["Marginals"].constructor === Array) { - this.marginals = []; - for (let item of data["Marginals"]) - this.marginals.push(AttributeParameters.fromJS(item)); - } - if (data["MarginalDistParameters"]) { - this.marginalDistParameters = {}; - for (let key in data["MarginalDistParameters"]) { - if (data["MarginalDistParameters"].hasOwnProperty(key)) - this.marginalDistParameters[key] = data["MarginalDistParameters"][key] ? DistParameter.fromJS(data["MarginalDistParameters"][key]) : new DistParameter(); - } - } - this.jointDistParameter = data["JointDistParameter"] ? JointDistParameter.fromJS(data["JointDistParameter"]) : <any>undefined; - } - } - - static fromJS(data: any): EmpiricalDistResult { - data = typeof data === 'object' ? data : {}; - let result = new EmpiricalDistResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.marginals && this.marginals.constructor === Array) { - data["Marginals"] = []; - for (let item of this.marginals) - data["Marginals"].push(item.toJSON()); - } - if (this.marginalDistParameters) { - data["MarginalDistParameters"] = {}; - for (let key in this.marginalDistParameters) { - if (this.marginalDistParameters.hasOwnProperty(key)) - data["MarginalDistParameters"][key] = this.marginalDistParameters[key]; - } - } - data["JointDistParameter"] = this.jointDistParameter ? this.jointDistParameter.toJSON() : <any>undefined; - super.toJSON(data); - return data; - } -} - -export interface IEmpiricalDistResult extends IDistResult { - marginals?: AttributeParameters[] | undefined; - marginalDistParameters?: { [key: string]: DistParameter; } | undefined; - jointDistParameter?: JointDistParameter | undefined; -} - -export class DistParameter implements IDistParameter { - mean?: number | undefined; - moment2?: number | undefined; - variance?: number | undefined; - varianceEstimate?: number | undefined; - min?: number | undefined; - max?: number | undefined; - - constructor(data?: IDistParameter) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.mean = data["Mean"]; - this.moment2 = data["Moment2"]; - this.variance = data["Variance"]; - this.varianceEstimate = data["VarianceEstimate"]; - this.min = data["Min"]; - this.max = data["Max"]; - } - } - - static fromJS(data: any): DistParameter { - data = typeof data === 'object' ? data : {}; - let result = new DistParameter(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Mean"] = this.mean; - data["Moment2"] = this.moment2; - data["Variance"] = this.variance; - data["VarianceEstimate"] = this.varianceEstimate; - data["Min"] = this.min; - data["Max"] = this.max; - return data; - } -} - -export interface IDistParameter { - mean?: number | undefined; - moment2?: number | undefined; - variance?: number | undefined; - varianceEstimate?: number | undefined; - min?: number | undefined; - max?: number | undefined; -} - -export class JointDistParameter implements IJointDistParameter { - jointDist?: DistParameter | undefined; - covariance?: number | undefined; - - constructor(data?: IJointDistParameter) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.jointDist = data["JointDist"] ? DistParameter.fromJS(data["JointDist"]) : <any>undefined; - this.covariance = data["Covariance"]; - } - } - - static fromJS(data: any): JointDistParameter { - data = typeof data === 'object' ? data : {}; - let result = new JointDistParameter(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["JointDist"] = this.jointDist ? this.jointDist.toJSON() : <any>undefined; - data["Covariance"] = this.covariance; - return data; - } -} - -export interface IJointDistParameter { - jointDist?: DistParameter | undefined; - covariance?: number | undefined; -} - -export enum DistributionType { - Continuous = 0, - Discrete = 1, -} - -export abstract class DistributionTypeExtension implements IDistributionTypeExtension { - - constructor(data?: IDistributionTypeExtension) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): DistributionTypeExtension { - data = typeof data === 'object' ? data : {}; - throw new Error("The abstract class 'DistributionTypeExtension' cannot be instantiated."); - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - return data; - } -} - -export interface IDistributionTypeExtension { -} - -export class GetModelStateParameters extends ModelOperationParameters implements IGetModelStateParameters { - modelId?: ModelId | undefined; - comparisonIds?: ComparisonId[] | undefined; - riskControlType?: RiskControlType | undefined; - - constructor(data?: IGetModelStateParameters) { - super(data); - this._discriminator = "GetModelStateParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined; - if (data["ComparisonIds"] && data["ComparisonIds"].constructor === Array) { - this.comparisonIds = []; - for (let item of data["ComparisonIds"]) - this.comparisonIds.push(ComparisonId.fromJS(item)); - } - this.riskControlType = data["RiskControlType"]; - } - } - - static fromJS(data: any): GetModelStateParameters { - data = typeof data === 'object' ? data : {}; - let result = new GetModelStateParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined; - if (this.comparisonIds && this.comparisonIds.constructor === Array) { - data["ComparisonIds"] = []; - for (let item of this.comparisonIds) - data["ComparisonIds"].push(item.toJSON()); - } - data["RiskControlType"] = this.riskControlType; - super.toJSON(data); - return data; - } -} - -export interface IGetModelStateParameters extends IModelOperationParameters { - modelId?: ModelId | undefined; - comparisonIds?: ComparisonId[] | undefined; - riskControlType?: RiskControlType | undefined; -} - -export class KSTestResult extends HypothesisTestResult implements IKSTestResult { - - constructor(data?: IKSTestResult) { - super(data); - this._discriminator = "KSTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - } - } - - static fromJS(data: any): KSTestResult { - data = typeof data === 'object' ? data : {}; - let result = new KSTestResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - super.toJSON(data); - return data; - } -} - -export interface IKSTestResult extends IHypothesisTestResult { -} - -export class ModelWealthParameters extends UniqueJson implements IModelWealthParameters { - modelId?: ModelId | undefined; - riskControlType?: RiskControlType | undefined; - - constructor(data?: IModelWealthParameters) { - super(data); - } - - init(data?: any) { - super.init(data); - if (data) { - this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined; - this.riskControlType = data["RiskControlType"]; - } - } - - static fromJS(data: any): ModelWealthParameters { - data = typeof data === 'object' ? data : {}; - let result = new ModelWealthParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined; - data["RiskControlType"] = this.riskControlType; - super.toJSON(data); - return data; - } -} - -export interface IModelWealthParameters extends IUniqueJson { - modelId?: ModelId | undefined; - riskControlType?: RiskControlType | undefined; -} - -export class RootMeanSquareTestResult extends HypothesisTestResult implements IRootMeanSquareTestResult { - simulationCount?: number | undefined; - extremeSimulationCount?: number | undefined; - - constructor(data?: IRootMeanSquareTestResult) { - super(data); - this._discriminator = "RootMeanSquareTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.simulationCount = data["SimulationCount"]; - this.extremeSimulationCount = data["ExtremeSimulationCount"]; - } - } - - static fromJS(data: any): RootMeanSquareTestResult { - data = typeof data === 'object' ? data : {}; - let result = new RootMeanSquareTestResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["SimulationCount"] = this.simulationCount; - data["ExtremeSimulationCount"] = this.extremeSimulationCount; - super.toJSON(data); - return data; - } -} - -export interface IRootMeanSquareTestResult extends IHypothesisTestResult { - simulationCount?: number | undefined; - extremeSimulationCount?: number | undefined; -} - -export class TTestResult extends HypothesisTestResult implements ITTestResult { - degreeOfFreedom?: number | undefined; - distResults?: EmpiricalDistResult[] | undefined; - - constructor(data?: ITTestResult) { - super(data); - this._discriminator = "TTestResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.degreeOfFreedom = data["DegreeOfFreedom"]; - if (data["DistResults"] && data["DistResults"].constructor === Array) { - this.distResults = []; - for (let item of data["DistResults"]) - this.distResults.push(EmpiricalDistResult.fromJS(item)); - } - } - } - - static fromJS(data: any): TTestResult { - data = typeof data === 'object' ? data : {}; - let result = new TTestResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["DegreeOfFreedom"] = this.degreeOfFreedom; - if (this.distResults && this.distResults.constructor === Array) { - data["DistResults"] = []; - for (let item of this.distResults) - data["DistResults"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface ITTestResult extends IHypothesisTestResult { - degreeOfFreedom?: number | undefined; - distResults?: EmpiricalDistResult[] | undefined; -} - -export enum Sorting2 { - Ascending = 0, - Descending = 1, -} - -export class BinLabel implements IBinLabel { - value?: number | undefined; - minValue?: number | undefined; - maxValue?: number | undefined; - label?: string | undefined; - - constructor(data?: IBinLabel) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.value = data["Value"]; - this.minValue = data["MinValue"]; - this.maxValue = data["MaxValue"]; - this.label = data["Label"]; - } - } - - static fromJS(data: any): BinLabel { - data = typeof data === 'object' ? data : {}; - let result = new BinLabel(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - data["MinValue"] = this.minValue; - data["MaxValue"] = this.maxValue; - data["Label"] = this.label; - return data; - } -} - -export interface IBinLabel { - value?: number | undefined; - minValue?: number | undefined; - maxValue?: number | undefined; - label?: string | undefined; -} - -export class PreProcessedString implements IPreProcessedString { - value?: string | undefined; - id?: number | undefined; - stringLookup?: { [key: string]: number; } | undefined; - indexLookup?: { [key: string]: string; } | undefined; - - constructor(data?: IPreProcessedString) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.value = data["Value"]; - this.id = data["Id"]; - if (data["StringLookup"]) { - this.stringLookup = {}; - for (let key in data["StringLookup"]) { - if (data["StringLookup"].hasOwnProperty(key)) - this.stringLookup[key] = data["StringLookup"][key]; - } - } - if (data["IndexLookup"]) { - this.indexLookup = {}; - for (let key in data["IndexLookup"]) { - if (data["IndexLookup"].hasOwnProperty(key)) - this.indexLookup[key] = data["IndexLookup"][key]; - } - } - } - } - - static fromJS(data: any): PreProcessedString { - data = typeof data === 'object' ? data : {}; - let result = new PreProcessedString(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Value"] = this.value; - data["Id"] = this.id; - if (this.stringLookup) { - data["StringLookup"] = {}; - for (let key in this.stringLookup) { - if (this.stringLookup.hasOwnProperty(key)) - data["StringLookup"][key] = this.stringLookup[key]; - } - } - if (this.indexLookup) { - data["IndexLookup"] = {}; - for (let key in this.indexLookup) { - if (this.indexLookup.hasOwnProperty(key)) - data["IndexLookup"][key] = this.indexLookup[key]; - } - } - return data; - } -} - -export interface IPreProcessedString { - value?: string | undefined; - id?: number | undefined; - stringLookup?: { [key: string]: number; } | undefined; - indexLookup?: { [key: string]: string; } | undefined; -} - -export class BitSet implements IBitSet { - length?: number | undefined; - size?: number | undefined; - - constructor(data?: IBitSet) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - this.length = data["Length"]; - this.size = data["Size"]; - } - } - - static fromJS(data: any): BitSet { - data = typeof data === 'object' ? data : {}; - let result = new BitSet(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Length"] = this.length; - data["Size"] = this.size; - return data; - } -} - -export interface IBitSet { - length?: number | undefined; - size?: number | undefined; -} - -export class DateTimeUtil implements IDateTimeUtil { - - constructor(data?: IDateTimeUtil) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (<any>this)[property] = (<any>data)[property]; - } - } - } - - init(data?: any) { - if (data) { - } - } - - static fromJS(data: any): DateTimeUtil { - data = typeof data === 'object' ? data : {}; - let result = new DateTimeUtil(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - return data; - } -} - -export interface IDateTimeUtil { -} - -export class FrequentItemsetOperationParameters extends DistOperationParameters implements IFrequentItemsetOperationParameters { - filter?: string | undefined; - attributeParameters?: AttributeParameters[] | undefined; - attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined; - - constructor(data?: IFrequentItemsetOperationParameters) { - super(data); - this._discriminator = "FrequentItemsetOperationParameters"; - } - - init(data?: any) { - super.init(data); - if (data) { - this.filter = data["Filter"]; - if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) { - this.attributeParameters = []; - for (let item of data["AttributeParameters"]) - this.attributeParameters.push(AttributeParameters.fromJS(item)); - } - if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) { - this.attributeCodeParameters = []; - for (let item of data["AttributeCodeParameters"]) - this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item)); - } - } - } - - static fromJS(data: any): FrequentItemsetOperationParameters { - data = typeof data === 'object' ? data : {}; - let result = new FrequentItemsetOperationParameters(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["Filter"] = this.filter; - if (this.attributeParameters && this.attributeParameters.constructor === Array) { - data["AttributeParameters"] = []; - for (let item of this.attributeParameters) - data["AttributeParameters"].push(item.toJSON()); - } - if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) { - data["AttributeCodeParameters"] = []; - for (let item of this.attributeCodeParameters) - data["AttributeCodeParameters"].push(item.toJSON()); - } - super.toJSON(data); - return data; - } -} - -export interface IFrequentItemsetOperationParameters extends IDistOperationParameters { - filter?: string | undefined; - attributeParameters?: AttributeParameters[] | undefined; - attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined; -} - -export class FrequentItemsetResult extends Result implements IFrequentItemsetResult { - frequentItems?: { [key: string]: number; } | undefined; - - constructor(data?: IFrequentItemsetResult) { - super(data); - this._discriminator = "FrequentItemsetResult"; - } - - init(data?: any) { - super.init(data); - if (data) { - if (data["FrequentItems"]) { - this.frequentItems = {}; - for (let key in data["FrequentItems"]) { - if (data["FrequentItems"].hasOwnProperty(key)) - this.frequentItems[key] = data["FrequentItems"][key]; - } - } - } - } - - static fromJS(data: any): FrequentItemsetResult { - data = typeof data === 'object' ? data : {}; - let result = new FrequentItemsetResult(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (this.frequentItems) { - data["FrequentItems"] = {}; - for (let key in this.frequentItems) { - if (this.frequentItems.hasOwnProperty(key)) - data["FrequentItems"][key] = this.frequentItems[key]; - } - } - super.toJSON(data); - return data; - } -} - -export interface IFrequentItemsetResult extends IResult { - frequentItems?: { [key: string]: number; } | undefined; -} - diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts deleted file mode 100644 index 013f2244e..000000000 --- a/src/client/northstar/operations/BaseOperation.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { FilterModel } from '../core/filter/FilterModel'; -import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea'; -import { action, computed, observable } from "mobx"; -import { Gateway } from '../manager/Gateway'; - -export abstract class BaseOperation { - private _interactionTimeoutId: number = 0; - private static _currentOperations: Map<number, PollPromise> = new Map<number, PollPromise>(); - //public InteractionTimeout: EventDelegate<InteractionTimeoutEventArgs> = new EventDelegate<InteractionTimeoutEventArgs>(); - - @observable public Error: string = ""; - @observable public OverridingFilters: FilterModel[] = []; - //@observable - @observable public Result?: Result = undefined; - @observable public ComputationStarted: boolean = false; - public OperationReference?: OperationReference = undefined; - - private static _nextId = 0; - public RequestSalt: string = ""; - public Id: number; - - constructor() { - this.Id = BaseOperation._nextId++; - } - - @computed - public get FilterString(): string { - return ""; - } - - - @action - public SetResult(result: Result): void { - this.Result = result; - } - - public async Update(): Promise<void> { - - try { - if (BaseOperation._currentOperations.has(this.Id)) { - BaseOperation._currentOperations.get(this.Id)!.Cancel(); - if (this.OperationReference) { - Gateway.Instance.PauseOperation(this.OperationReference.toJSON()); - } - } - - const operationParameters = this.CreateOperationParameters(); - if (this.Result) { - this.Result.progress = 0; - } // bcz: used to set Result to undefined, but that causes the display to blink - this.Error = ""; - const salt = Math.random().toString(); - this.RequestSalt = salt; - - if (!operationParameters) { - this.ComputationStarted = false; - return; - } - - this.ComputationStarted = true; - //let start = performance.now(); - const promise = Gateway.Instance.StartOperation(operationParameters.toJSON()); - promise.catch(err => { - action(() => { - this.Error = err; - console.error(err); - }); - }); - const operationReference = await promise; - - - if (operationReference) { - this.OperationReference = operationReference; - - const resultParameters = new ResultParameters(); - resultParameters.operationReference = operationReference; - - const pollPromise = new PollPromise(salt, operationReference); - BaseOperation._currentOperations.set(this.Id, pollPromise); - - pollPromise.Start(async () => { - const result = await Gateway.Instance.GetResult(resultParameters.toJSON()); - if (result instanceof ErrorResult) { - throw new Error((result).message); - } - if (this.RequestSalt === pollPromise.RequestSalt) { - if (result && (!this.Result || this.Result.progress !== result.progress)) { - /*if (operationViewModel.Result !== null && operationViewModel.Result !== undefined) { - let t1 = performance.now(); - console.log((t1 - start) + " milliseconds."); - start = performance.now(); - }*/ - this.SetResult(result); - } - - if (!result || result.progress! < 1) { - return true; - } - } - return false; - }, 100).catch((err: Error) => action(() => { - this.Error = err.message; - console.error(err.message); - })() - ); - } - } - catch (err) { - console.error(err as Exception); - // ErrorDialog.Instance.HandleError(err, operationViewModel); - } - } - - public CreateOperationParameters(): OperationParameters | undefined { return undefined; } - - private interactionTimeout() { - // clearTimeout(this._interactionTimeoutId); - // this.InteractionTimeout.Fire(new InteractionTimeoutEventArgs(this.TypedViewModel, InteractionTimeoutType.Timeout)); - } -} - -export class PollPromise { - public RequestSalt: string; - public OperationReference: OperationReference; - - private _notCanceled: boolean = true; - private _poll: undefined | (() => Promise<boolean>); - private _delay: number = 0; - - public constructor(requestKey: string, operationReference: OperationReference) { - this.RequestSalt = requestKey; - this.OperationReference = operationReference; - } - - public Cancel(): void { - this._notCanceled = false; - } - - public Start(poll: () => Promise<boolean>, delay: number): Promise<void> { - this._poll = poll; - this._delay = delay; - return this.pollRecursive(); - } - - private pollRecursive = (): Promise<void> => { - return Promise.resolve().then(this._poll).then((flag) => { - this._notCanceled && flag && new Promise((res) => (setTimeout(res, this._delay))) - .then(this.pollRecursive); - }); - } -} - - -export class InteractionTimeoutEventArgs { - constructor(public Sender: object, public Type: InteractionTimeoutType) { - } -} - -export enum InteractionTimeoutType { - Reset = 0, - Timeout = 1 -} diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts deleted file mode 100644 index 74e23ea48..000000000 --- a/src/client/northstar/operations/HistogramOperation.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { action, computed, observable, trace } from "mobx"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { ColumnAttributeModel } from "../core/attribute/AttributeModel"; -import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; -import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel"; -import { FilterModel } from "../core/filter/FilterModel"; -import { FilterOperand } from "../core/filter/FilterOperand"; -import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer"; -import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider"; -import { HistogramField } from "../dash-fields/HistogramField"; -import { SETTINGS_SAMPLE_SIZE, SETTINGS_X_BINS, SETTINGS_Y_BINS } from "../model/binRanges/VisualBinRangeHelper"; -import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, Bin, DataType, DoubleValueAggregateResult, HistogramOperationParameters, HistogramResult, QuantitativeBinRange } from "../model/idea/idea"; -import { ModelHelpers } from "../model/ModelHelpers"; -import { ArrayUtil } from "../utils/ArrayUtil"; -import { BaseOperation } from "./BaseOperation"; -import { Doc } from "../../../new_fields/Doc"; -import { Cast, NumCast } from "../../../new_fields/Types"; - -export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider { - public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute()))); - @observable public FilterOperand: FilterOperand = FilterOperand.AND; - @observable public Links: Doc[] = []; - @observable public BrushLinks: { l: Doc, b: Doc }[] = []; - @observable public BrushColors: number[] = []; - @observable public BarFilterModels: FilterModel[] = []; - - @observable public Normalization: number = -1; - @observable public X: AttributeTransformationModel; - @observable public Y: AttributeTransformationModel; - @observable public V: AttributeTransformationModel; - @observable public SchemaName: string; - @observable public QRange: QuantitativeBinRange | undefined; - public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); } - - constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) { - super(); - this.X = x; - this.Y = y; - this.V = v; - this.Normalization = normalized ? normalized : -1; - this.SchemaName = schemaName; - } - - public static Duplicate(op: HistogramOperation) { - - return new HistogramOperation(op.SchemaName, op.X, op.Y, op.V, op.Normalization); - } - public Copy(): HistogramOperation { - return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization); - } - - Equals(other: Object): boolean { - throw new Error("Method not implemented."); - } - - - public get FilterModels() { - return this.BarFilterModels; - } - @action - public AddFilterModels(filterModels: FilterModel[]): void { - filterModels.filter(f => f !== null).forEach(fm => this.BarFilterModels.push(fm)); - } - @action - public RemoveFilterModels(filterModels: FilterModel[]): void { - ArrayUtil.RemoveMany(this.BarFilterModels, filterModels); - } - - @computed - public get FilterString(): string { - if (this.OverridingFilters.length > 0) { - return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")"; - } - let filterModels: FilterModel[] = []; - return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true); - } - - public get BrushString(): string[] { - let brushes: string[] = []; - this.BrushLinks.map(brushLink => { - let brushHistogram = Cast(brushLink.b.data, HistogramField); - if (brushHistogram) { - let filterModels: FilterModel[] = []; - brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.HistoOp, new Set<IBaseFilterProvider>(), filterModels, false)); - } - }); - return brushes; - } - - _stackedFilters: (FilterModel[])[] = []; - @action - public DrillDown(up: boolean) { - if (!up) { - if (!this.BarFilterModels.length) { - return; - } - this._stackedFilters.push(this.BarFilterModels.map(f => f)); - this.OverridingFilters.length = 0; - this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]); - this.BarFilterModels.map(fm => fm).map(fm => this.RemoveFilterModels([fm])); - //this.updateHistogram(); - } else { - this.OverridingFilters.length = 0; - if (this._stackedFilters.length) { - this.OverridingFilters.push(...this._stackedFilters.pop()!); - } - // else - // this.updateHistogram(); - } - } - - private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) { - let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue); - allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None)); - - let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float]; - let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(this.Schema!.distinctAttributeParameters, allAttributes); - let globalAggregateParameters: AggregateParameters[] = []; - [histoX, histoY] - .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType)) - .forEach(a => { - let avg = new AverageAggregateParameters(); - avg.attributeParameters = ModelHelpers.GetAttributeParameters(a.AttributeModel); - globalAggregateParameters.push(avg); - }); - return [perBinAggregateParameters, globalAggregateParameters]; - } - - public CreateOperationParameters(): HistogramOperationParameters | undefined { - if (this.X && this.Y && this.V) { - let [perBinAggregateParameters, globalAggregateParameters] = this.getAggregateParameters(this.X, this.Y, this.V); - return new HistogramOperationParameters({ - enableBrushComputation: true, - adapterName: this.SchemaName, - filter: this.FilterString, - brushes: this.BrushString, - binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined), - ModelHelpers.GetBinningParameters(this.Y, SETTINGS_Y_BINS)], - sampleStreamBlockSize: SETTINGS_SAMPLE_SIZE, - perBinAggregateParameters: perBinAggregateParameters, - globalAggregateParameters: globalAggregateParameters, - sortPerBinAggregateParameter: undefined, - attributeCalculatedParameters: CalculatedAttributeManager - .AllCalculatedAttributes.map(a => ModelHelpers.GetAttributeParametersFromAttributeModel(a)), - degreeOfParallism: 1, // Settings.Instance.DegreeOfParallelism, - isCachable: false - }); - } - } - - @action - public async Update(): Promise<void> { - this.BrushColors = this.BrushLinks.map(e => NumCast(e.l.backgroundColor)); - return super.Update(); - } -} - - diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts deleted file mode 100644 index 12b8d8e77..000000000 --- a/src/client/northstar/utils/ArrayUtil.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Exception } from "../model/idea/idea"; - -export class ArrayUtil { - - public static Contains(arr1: any[], arr2: any): boolean { - if (arr1.length === 0) { - return false; - } - let isComplex = typeof arr1[0] === "object"; - for (const ele of arr1) { - if (isComplex && "Equals" in ele) { - if (ele.Equals(arr2)) { - return true; - } - } - else { - if (ele === arr2) { - return true; - } - } - } - return false; - } - - - public static RemoveMany(arr: any[], elements: Object[]) { - elements.forEach(e => ArrayUtil.Remove(arr, e)); - } - - public static AddMany(arr: any[], others: Object[]) { - arr.push(...others); - } - - public static Clear(arr: any[]) { - arr.splice(0, arr.length); - } - - - public static Remove(arr: any[], other: Object) { - const index = ArrayUtil.IndexOfWithEqual(arr, other); - if (index === -1) { - return; - } - arr.splice(index, 1); - } - - - public static First<T>(arr: T[], predicate: (x: any) => boolean): T { - let filtered = arr.filter(predicate); - if (filtered.length > 0) { - return filtered[0]; - } - throw new Exception(); - } - - public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined { - let filtered = arr.filter(predicate); - if (filtered.length > 0) { - return filtered[0]; - } - return undefined; - } - - public static Distinct(arr: any[]): any[] { - let ret = []; - for (const ele of arr) { - if (!ArrayUtil.Contains(ret, ele)) { - ret.push(ele); - } - } - return ret; - } - - public static IndexOfWithEqual(arr: any[], other: any): number { - for (let i = 0; i < arr.length; i++) { - let isComplex = typeof arr[0] === "object"; - if (isComplex && "Equals" in arr[i]) { - if (arr[i].Equals(other)) { - return i; - } - } - else { - if (arr[i] === other) { - return i; - } - } - } - return -1; - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts deleted file mode 100644 index df14d4da0..000000000 --- a/src/client/northstar/utils/Extensions.ts +++ /dev/null @@ -1,29 +0,0 @@ -interface String { - ReplaceAll(toReplace: string, replacement: string): string; - Truncate(length: number, replacement: string): String; -} - -String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string { - var target = this; - return target.split(toReplace).join(replacement); -}; - -String.prototype.Truncate = function (length: number, replacement: string): String { - var target = this; - if (target.length >= length) { - target = target.slice(0, Math.max(0, length - replacement.length)) + replacement; - } - return target; -}; - -interface Math { - log10(val: number): number; -} - -Math.log10 = function (val: number): number { - return Math.log(val) / Math.LN10; -}; - -declare interface ObjectConstructor { - assign(...objects: Object[]): Object; -} diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts deleted file mode 100644 index d5220c479..000000000 --- a/src/client/northstar/utils/GeometryUtil.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { MathUtil, PIXIRectangle, PIXIPoint } from "./MathUtil"; - - -export class GeometryUtil { - - public static ComputeBoundingBox(points: { x: number, y: number }[], scale = 1, padding: number = 0): PIXIRectangle { - let minX: number = Number.MAX_VALUE; - let minY: number = Number.MAX_VALUE; - let maxX: number = Number.MIN_VALUE; - let maxY: number = Number.MIN_VALUE; - for (const point of points) { - if (point.x < minX) { - minX = point.x; - } - if (point.y < minY) { - minY = point.y; - } - if (point.x > maxX) { - maxX = point.x; - } - if (point.y > maxY) { - maxY = point.y; - } - } - return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2); - } - - public static RectangleOverlap(rect1: PIXIRectangle, rect2: PIXIRectangle) { - let x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left)); - let y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top)); - return x_overlap * y_overlap; - } - - public static RotatePoints(center: { x: number, y: number }, points: { x: number, y: number }[], angle: number): PIXIPoint[] { - const rotate = (cx: number, cy: number, x: number, y: number, angle: number) => { - const radians = angle, - cos = Math.cos(radians), - sin = Math.sin(radians), - nx = (cos * (x - cx)) + (sin * (y - cy)) + cx, - ny = (cos * (y - cy)) - (sin * (x - cx)) + cy; - return new PIXIPoint(nx, ny); - }; - return points.map(p => rotate(center.x, center.y, p.x, p.y, angle)); - } - - public static LineByLeastSquares(points: { x: number, y: number }[]): PIXIPoint[] { - let sum_x: number = 0; - let sum_y: number = 0; - let sum_xy: number = 0; - let sum_xx: number = 0; - let count: number = 0; - - let x: number = 0; - let y: number = 0; - - - if (points.length === 0) { - return []; - } - - for (const point of points) { - x = point.x; - y = point.y; - sum_x += x; - sum_y += y; - sum_xx += x * x; - sum_xy += x * y; - count++; - } - - let m = (count * sum_xy - sum_x * sum_y) / (count * sum_xx - sum_x * sum_x); - let b = (sum_y / count) - (m * sum_x) / count; - let result: PIXIPoint[] = new Array<PIXIPoint>(); - - for (const point of points) { - x = point.x; - y = x * m + b; - result.push(new PIXIPoint(x, y)); - } - return result; - } - - // public static PointInsidePolygon(vs:Point[], x:number, y:number):boolean { - // // ray-casting algorithm based on - // // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - - // var inside = false; - // for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { - // var xi = vs[i].x, yi = vs[i].y; - // var xj = vs[j].x, yj = vs[j].y; - - // var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - // if (intersect) - // inside = !inside; - // } - - // return inside; - // }; - - public static IntersectLines(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean { - let a1: number, a2: number, b1: number, b2: number, c1: number, c2: number; - let r1: number, r2: number, r3: number, r4: number; - let denom: number, offset: number, num: number; - - a1 = y2 - y1; - b1 = x1 - x2; - c1 = (x2 * y1) - (x1 * y2); - r3 = ((a1 * x3) + (b1 * y3) + c1); - r4 = ((a1 * x4) + (b1 * y4) + c1); - - if ((r3 !== 0) && (r4 !== 0) && (MathUtil.Sign(r3) === MathUtil.Sign(r4))) { - return false; - } - - a2 = y4 - y3; - b2 = x3 - x4; - c2 = (x4 * y3) - (x3 * y4); - - r1 = (a2 * x1) + (b2 * y1) + c2; - r2 = (a2 * x2) + (b2 * y2) + c2; - - if ((r1 !== 0) && (r2 !== 0) && (MathUtil.Sign(r1) === MathUtil.Sign(r2))) { - return false; - } - - denom = (a1 * b2) - (a2 * b1); - - if (denom === 0) { - return false; - } - return true; - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/IDisposable.ts b/src/client/northstar/utils/IDisposable.ts deleted file mode 100644 index 5e9843326..000000000 --- a/src/client/northstar/utils/IDisposable.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IDisposable { - Dispose(): void; -}
\ No newline at end of file diff --git a/src/client/northstar/utils/IEquatable.ts b/src/client/northstar/utils/IEquatable.ts deleted file mode 100644 index 2f81c2478..000000000 --- a/src/client/northstar/utils/IEquatable.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IEquatable { - Equals(other: Object): boolean; -}
\ No newline at end of file diff --git a/src/client/northstar/utils/KeyCodes.ts b/src/client/northstar/utils/KeyCodes.ts deleted file mode 100644 index 044569ffe..000000000 --- a/src/client/northstar/utils/KeyCodes.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Class contains the keycodes for keys on your keyboard. - * - * Useful for auto completion: - * - * ``` - * switch (event.key) - * { - * case KeyCode.UP: - * { - * // Up key pressed - * break; - * } - * case KeyCode.DOWN: - * { - * // Down key pressed - * break; - * } - * case KeyCode.LEFT: - * { - * // Left key pressed - * break; - * } - * case KeyCode.RIGHT: - * { - * // Right key pressed - * break; - * } - * default: - * { - * // ignore - * break; - * } - * } - * ``` - */ -export class KeyCodes -{ - public static TAB:number = 9; - public static CAPS_LOCK:number = 20; - public static SHIFT:number = 16; - public static CONTROL:number = 17; - public static SPACE:number = 32; - public static DOWN:number = 40; - public static UP:number = 38; - public static LEFT:number = 37; - public static RIGHT:number = 39; - public static ESCAPE:number = 27; - public static F1:number = 112; - public static F2:number = 113; - public static F3:number = 114; - public static F4:number = 115; - public static F5:number = 116; - public static F6:number = 117; - public static F7:number = 118; - public static F8:number = 119; - public static F9:number = 120; - public static F10:number = 121; - public static F11:number = 122; - public static F12:number = 123; - public static INSERT:number = 45; - public static HOME:number = 36; - public static PAGE_UP:number = 33; - public static PAGE_DOWN:number = 34; - public static DELETE:number = 46; - public static END:number = 35; - public static ENTER:number = 13; - public static BACKSPACE:number = 8; - public static NUMPAD_0:number = 96; - public static NUMPAD_1:number = 97; - public static NUMPAD_2:number = 98; - public static NUMPAD_3:number = 99; - public static NUMPAD_4:number = 100; - public static NUMPAD_5:number = 101; - public static NUMPAD_6:number = 102; - public static NUMPAD_7:number = 103; - public static NUMPAD_8:number = 104; - public static NUMPAD_9:number = 105; - public static NUMPAD_DIVIDE:number = 111; - public static NUMPAD_ADD:number = 107; - public static NUMPAD_ENTER:number = 13; - public static NUMPAD_DECIMAL:number = 110; - public static NUMPAD_SUBTRACT:number = 109; - public static NUMPAD_MULTIPLY:number = 106; - public static SEMICOLON:number = 186; - public static EQUAL:number = 187; - public static COMMA:number = 188; - public static MINUS:number = 189; - public static PERIOD:number = 190; - public static SLASH:number = 191; - public static BACKQUOTE:number = 192; - public static LEFTBRACKET:number = 219; - public static BACKSLASH:number = 220; - public static RIGHTBRACKET:number = 221; - public static QUOTE:number = 222; - public static ALT:number = 18; - public static COMMAND:number = 15; - public static NUMPAD:number = 21; - public static A:number = 65; - public static B:number = 66; - public static C:number = 67; - public static D:number = 68; - public static E:number = 69; - public static F:number = 70; - public static G:number = 71; - public static H:number = 72; - public static I:number = 73; - public static J:number = 74; - public static K:number = 75; - public static L:number = 76; - public static M:number = 77; - public static N:number = 78; - public static O:number = 79; - public static P:number = 80; - public static Q:number = 81; - public static R:number = 82; - public static S:number = 83; - public static T:number = 84; - public static U:number = 85; - public static V:number = 86; - public static W:number = 87; - public static X:number = 88; - public static Y:number = 89; - public static Z:number = 90; - public static NUM_0:number = 48; - public static NUM_1:number = 49; - public static NUM_2:number = 50; - public static NUM_3:number = 51; - public static NUM_4:number = 52; - public static NUM_5:number = 53; - public static NUM_6:number = 54; - public static NUM_7:number = 55; - public static NUM_8:number = 56; - public static NUM_9:number = 57; - public static SUBSTRACT:number = 189; - public static ADD:number = 187; -}
\ No newline at end of file diff --git a/src/client/northstar/utils/LABColor.ts b/src/client/northstar/utils/LABColor.ts deleted file mode 100644 index 72e46fb7f..000000000 --- a/src/client/northstar/utils/LABColor.ts +++ /dev/null @@ -1,90 +0,0 @@ - -export class LABColor { - public L: number; - public A: number; - public B: number; - - // constructor - takes three floats for lightness and color-opponent dimensions - constructor(l: number, a: number, b: number) { - this.L = l; - this.A = a; - this.B = b; - } - - // static function for linear interpolation between two LABColors - public static Lerp(a: LABColor, b: LABColor, t: number): LABColor { - return new LABColor(LABColor.LerpNumber(a.L, b.L, t), LABColor.LerpNumber(a.A, b.A, t), LABColor.LerpNumber(a.B, b.B, t)); - } - - public static LerpNumber(a: number, b: number, percent: number): number { - return a + percent * (b - a); - } - - static hexToRGB(hex: number, alpha: number): number[] { - var r = (hex & (0xff << 16)) >> 16; - var g = (hex & (0xff << 8)) >> 8; - var b = (hex & (0xff << 0)) >> 0; - return [r, g, b]; - } - static RGBtoHex(red: number, green: number, blue: number): number { - return blue | (green << 8) | (red << 16); - } - - public static RGBtoHexString(rgb: number): string { - let str = "#" + this.hex((rgb & (0xff << 16)) >> 16) + this.hex((rgb & (0xff << 8)) >> 8) + this.hex((rgb & (0xff << 0)) >> 0); - return str; - } - - static hex(x: number): string { - var hexDigits = new Array - ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); - return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16]; - } - - public static FromColor(c: number): LABColor { - var rgb = LABColor.hexToRGB(c, 0); - var r = LABColor.d3_rgb_xyz(rgb[0] * 255); - var g = LABColor.d3_rgb_xyz(rgb[1] * 255); - var b = LABColor.d3_rgb_xyz(rgb[2] * 255); - - var x = LABColor.d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LABColor.d3_lab_X); - var y = LABColor.d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LABColor.d3_lab_Y); - var z = LABColor.d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LABColor.d3_lab_Z); - var lab = new LABColor(116 * y - 16, 500 * (x - y), 200 * (y - z)); - return lab; - } - - private static d3_lab_X: number = 0.950470; - private static d3_lab_Y: number = 1; - private static d3_lab_Z: number = 1.088830; - - public static d3_lab_xyz(x: number): number { - return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037; - } - - public static d3_xyz_rgb(r: number): number { - return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055)); - } - - public static d3_rgb_xyz(r: number): number { - return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); - } - - public static d3_xyz_lab(x: number): number { - return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; - } - - public static ToColor(lab: LABColor): number { - var y = (lab.L + 16) / 116; - var x = y + lab.A / 500; - var z = y - lab.B / 200; - x = LABColor.d3_lab_xyz(x) * LABColor.d3_lab_X; - y = LABColor.d3_lab_xyz(y) * LABColor.d3_lab_Y; - z = LABColor.d3_lab_xyz(z) * LABColor.d3_lab_Z; - - return LABColor.RGBtoHex( - LABColor.d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z) / 255, - LABColor.d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z) / 255, - LABColor.d3_xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z) / 255); - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts deleted file mode 100644 index 5def5e704..000000000 --- a/src/client/northstar/utils/MathUtil.ts +++ /dev/null @@ -1,249 +0,0 @@ - - -export class PIXIPoint { - public get x() { return this.coords[0]; } - public get y() { return this.coords[1]; } - public set x(value: number) { this.coords[0] = value; } - public set y(value: number) { this.coords[1] = value; } - public coords: number[] = [0, 0]; - constructor(x: number, y: number) { - this.coords[0] = x; - this.coords[1] = y; - } -} - -export class PIXIRectangle { - public x: number; - public y: number; - public width: number; - public height: number; - public get left() { return this.x; } - public get right() { return this.x + this.width; } - public get top() { return this.y; } - public get bottom() { return this.top + this.height; } - public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } - constructor(x: number, y: number, width: number, height: number) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } -} - -export class MathUtil { - - public static EPSILON: number = 0.001; - - public static Sign(value: number): number { - return value >= 0 ? 1 : -1; - } - - public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x += p2.x; - p1.y += p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); - } - } - - public static Perp(p1: PIXIPoint): PIXIPoint { - return new PIXIPoint(-p1.y, p1.x); - } - - public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x /= by; - p1.y /= by; - return p1; - } - else { - return new PIXIPoint(p1.x / by, p1.y / by); - } - } - - public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { - if (inline) { - p1.x *= by; - p1.y *= by; - return p1; - } - else { - return new PIXIPoint(p1.x * by, p1.y * by); - } - } - - public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x -= p2.x; - p1.y -= p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); - } - } - - public static Area(rect: PIXIRectangle): number { - return rect.width * rect.height; - } - - public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { - // Return minimum distance between line segment vw and point p - const l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt - if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case - // Consider the line extending the segment, parameterized as v + t (w - v). - // We find projection of point p onto the line. - // It falls where t = [(p-v) . (w-v)] / |w-v|^2 - // We clamp t from [0,1] to handle points outside the segment vw. - const dot = MathUtil.Dot( - MathUtil.SubtractPoint(p, v), - MathUtil.SubtractPoint(w, v)) / l2; - const t = Math.max(0, Math.min(1, dot)); - // Projection falls on the segment - const projection = MathUtil.AddPoint(v, - MathUtil.MultiplyConstant( - MathUtil.SubtractPoint(w, v), t)); - return MathUtil.Dist(p, projection); - } - - public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { - const a1 = pe1.y - ps1.y; - const b1 = ps1.x - pe1.x; - - const a2 = pe2.y - ps2.y; - const b2 = ps2.x - pe2.x; - - const delta = a1 * b2 - a2 * b1; - if (delta === 0) { - return undefined; - } - const c2 = a2 * ps2.x + b2 * ps2.y; - const c1 = a1 * ps1.x + b1 * ps1.y; - const invdelta = 1 / delta; - return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); - } - - public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { - if (p.x < rect.left - this.EPSILON) { - return false; - } - if (p.x > rect.right + this.EPSILON) { - return false; - } - if (p.y < rect.top - this.EPSILON) { - return false; - } - if (p.y > rect.bottom + this.EPSILON) { - return false; - } - - return true; - } - - public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> { - const r1 = new PIXIPoint(rect.left, rect.top); - const r2 = new PIXIPoint(rect.right, rect.top); - const r3 = new PIXIPoint(rect.right, rect.bottom); - const r4 = new PIXIPoint(rect.left, rect.bottom); - const ret = new Array<PIXIPoint>(); - const dist = this.Dist(lineFrom, lineTo); - let inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - return ret; - } - - public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { - const left = Math.max(rect1.x, rect2.x); - const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); - const top = Math.max(rect1.y, rect2.y); - const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); - return new PIXIRectangle(left, top, right - left, bottom - top); - } - - public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { - return Math.sqrt(MathUtil.DistSquared(p1, p2)); - } - - public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { - return p1.x * p2.x + p1.y * p2.y; - } - - public static Normalize(p1: PIXIPoint) { - const d = this.Length(p1); - return new PIXIPoint(p1.x / d, p1.y / d); - } - - public static Length(p1: PIXIPoint): number { - return Math.sqrt(p1.x * p1.x + p1.y * p1.y); - } - - public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { - const a = p1.x - p2.x; - const b = p1.y - p2.y; - return (a * a + b * b); - } - - public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { - return !(r2.x > r1.x + r1.width || - r2.x + r2.width < r1.x || - r2.y > r1.y + r1.height || - r2.y + r2.height < r1.y); - } - - public static ArgMin(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] < value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static ArgMax(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] > value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static Combinations<T>(chars: T[]) { - const result = new Array<T>(); - const f = (prefix: any, chars: any) => { - for (let i = 0; i < chars.length; i++) { - result.push(prefix.concat(chars[i])); - f(prefix.concat(chars[i]), chars.slice(i + 1)); - } - }; - f([], chars); - return result; - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/PartialClass.ts b/src/client/northstar/utils/PartialClass.ts deleted file mode 100644 index 2f20de96f..000000000 --- a/src/client/northstar/utils/PartialClass.ts +++ /dev/null @@ -1,7 +0,0 @@ - -export class PartialClass<T> { - - constructor(data?: Partial<T>) { - Object.assign(this, data); - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts deleted file mode 100644 index a52890ed9..000000000 --- a/src/client/northstar/utils/SizeConverter.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { PIXIPoint } from "./MathUtil"; -import { VisualBinRange } from "../model/binRanges/VisualBinRange"; -import { Bin, DoubleValueAggregateResult, AggregateKey } from "../model/idea/idea"; -import { ModelHelpers } from "../model/ModelHelpers"; -import { observable, action, computed } from "mobx"; - -export class SizeConverter { - public DataMins: Array<number> = new Array<number>(2); - public DataMaxs: Array<number> = new Array<number>(2); - public DataRanges: Array<number> = new Array<number>(2); - public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2); - public RenderDimension: number = 300; - - @observable _leftOffset: number = 40; - @observable _rightOffset: number = 20; - @observable _topOffset: number = 20; - @observable _bottomOffset: number = 45; - @observable _labelAngle: number = 0; - @observable _isSmall: boolean = false; - @observable public Initialized = 0; - - @action public SetIsSmall(isSmall: boolean) { this._isSmall = isSmall; } - @action public SetLabelAngle(angle: number) { this._labelAngle = angle; } - @computed public get IsSmall() { return this._isSmall; } - @computed public get LabelAngle() { return this._labelAngle; } - @computed public get LeftOffset() { return this.IsSmall ? 5 : this._leftOffset; } - @computed public get RightOffset() { return this.IsSmall ? 5 : !this._labelAngle ? this._bottomOffset : Math.max(this._rightOffset, Math.cos(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)); } - @computed public get TopOffset() { return this.IsSmall ? 5 : this._topOffset; } - @computed public get BottomOffset() { return this.IsSmall ? 25 : !this._labelAngle ? this._bottomOffset : Math.max(this._bottomOffset, Math.sin(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)) + 18; } - - public SetVisualBinRanges(visualBinRanges: Array<VisualBinRange>) { - this.Initialized++; - var xLabels = visualBinRanges[0].GetLabels(); - var yLabels = visualBinRanges[1].GetLabels(); - var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; }); - var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; }); - - var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number, - //xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS)); - var metricsY = { width: 22 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number, - // yLabelStrings[0]!.slice(0, 20)); // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS)); - this.MaxLabelSizes[0] = new PIXIPoint(metricsX.width, 12);// FontStyles.AxisLabel.fontSize as number); - this.MaxLabelSizes[1] = new PIXIPoint(metricsY.width, 12); // FontStyles.AxisLabel.fontSize as number); - - this._leftOffset = Math.max(10, metricsY.width + 10 + 20); - - this.DataMins[0] = xLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE); - this.DataMins[1] = yLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE); - this.DataMaxs[0] = xLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE); - this.DataMaxs[1] = yLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE); - - this.DataRanges[0] = this.DataMaxs[0] - this.DataMins[0]; - this.DataRanges[1] = this.DataMaxs[1] - this.DataMins[1]; - } - - public DataToScreenNormalizedRange(dataValue: number, normalization: number, axis: number, binBrushMaxAxis: number) { - var value = normalization !== 1 - axis || binBrushMaxAxis === 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis]; - var from = this.DataToScreenCoord(Math.min(0, value), axis); - var to = this.DataToScreenCoord(Math.max(0, value), axis); - return [from, value, to]; - } - - public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) { - var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult; - if (value && value.hasResult) { - return [this.DataToScreenCoord(value.result!, axis) - 5, - this.DataToScreenCoord(value.result!, axis) + 5]; - } - return [undefined, undefined]; - } - - public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { - var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]); - return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))]; - } - public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { - var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]); - return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))]; - } - - public DataToScreenX(x: number): number { - return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension; - } - public DataToScreenY(y: number, flip: boolean = true) { - var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * this.RenderDimension; - return flip ? (this.RenderDimension) - retY : retY; - } - public DataToScreenCoord(v: number, axis: number) { - if (axis === 0) { - return this.DataToScreenX(v); - } - return this.DataToScreenY(v); - } - public DataToScreenRange(minVal: number, maxVal: number, axis: number) { - let xFrom = this.DataToScreenX(axis === 0 ? minVal : this.DataMins[0]); - let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]); - let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]); - let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]); - return { xFrom, yFrom, xTo, yTo }; - } -}
\ No newline at end of file diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts deleted file mode 100644 index e9b6e0297..000000000 --- a/src/client/northstar/utils/StyleContants.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { PIXIPoint } from "./MathUtil"; - -export class StyleConstants { - - static DEFAULT_FONT: string = "Roboto Condensed"; - - static MENU_SUBMENU_WIDTH: number = 85; - static MENU_SUBMENU_HEIGHT: number = 400; - static MENU_BOX_SIZE: PIXIPoint = new PIXIPoint(80, 35); - static MENU_BOX_PADDING: number = 10; - - static OPERATOR_MENU_LARGE: number = 35; - static OPERATOR_MENU_SMALL: number = 25; - static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd]; - static GAP: number = 3; - - static BACKGROUND_COLOR: number = 0xF3F3F3; - static TOOL_TIP_BACKGROUND_COLOR: number = 0xffffff; - static LIGHT_TEXT_COLOR: number = 0xffffff; - static LIGHT_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.LIGHT_TEXT_COLOR); - static DARK_TEXT_COLOR: number = 0x282828; - static HIGHLIGHT_TEXT_COLOR: number = 0xffcc00; - static FPS_TEXT_COLOR: number = StyleConstants.DARK_TEXT_COLOR; - static CORRELATION_LABEL_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR); - static LOADING_SCREEN_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR); - static ERROR_COLOR: number = 0x540E25; - static WARNING_COLOR: number = 0xE58F24; - static LOWER_THAN_NAIVE_COLOR: number = 0xee0000; - static HIGHLIGHT_COLOR: number = 0x82A8D9; - static HIGHLIGHT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR); - static OPERATOR_BACKGROUND_COLOR: number = 0x282828; - static LOADING_ANIMATION_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR; - static MENU_COLOR: number = 0x282828; - static MENU_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR; - static MENU_SELECTED_COLOR: number = StyleConstants.HIGHLIGHT_COLOR; - static MENU_SELECTED_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR; - static BRUSH_COLOR: number = 0xff0000; - static DROP_ACCEPT_COLOR: number = StyleConstants.HIGHLIGHT_COLOR; - static SELECTED_COLOR: number = 0xffffff; - static SELECTED_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.SELECTED_COLOR); - static PROGRESS_BACKGROUND_COLOR: number = 0x595959; - static GRID_LINES_COLOR: number = 0x3D3D3D; - static GRID_LINES_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.GRID_LINES_COLOR); - - static MAX_CHAR_FOR_HISTOGRAM_LABELS: number = 20; - - static OVERLAP_COLOR: number = 0x0000ff;//0x540E25; - static BRUSH_COLORS: Array<number> = new Array<number>( - 0xFFDA7E, 0xFE8F65, 0xDA5655, 0x8F2240 - ); - - static MIN_VALUE_COLOR: number = 0x373d43; //32343d, 373d43, 3b4648 - static MARGIN_BARS_COLOR: number = 0xffffff; - static MARGIN_BARS_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.MARGIN_BARS_COLOR); - - static HISTOGRAM_WIDTH: number = 200; - static HISTOGRAM_HEIGHT: number = 150; - static PREDICTOR_WIDTH: number = 150; - static PREDICTOR_HEIGHT: number = 100; - static RAWDATA_WIDTH: number = 150; - static RAWDATA_HEIGHT: number = 100; - static FREQUENT_ITEM_WIDTH: number = 180; - static FREQUENT_ITEM_HEIGHT: number = 100; - static CORRELATION_WIDTH: number = 555; - static CORRELATION_HEIGHT: number = 390; - static PROBLEM_FINDER_WIDTH: number = 450; - static PROBLEM_FINDER_HEIGHT: number = 150; - static PIPELINE_OPERATOR_WIDTH: number = 300; - static PIPELINE_OPERATOR_HEIGHT: number = 120; - static SLICE_WIDTH: number = 150; - static SLICE_HEIGHT: number = 45; - static BORDER_MENU_ITEM_WIDTH: number = 50; - static BORDER_MENU_ITEM_HEIGHT: number = 30; - - - static SLICE_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR); - static SLICE_EMPTY_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR; - static SLICE_OCCUPIED_COLOR: number = 0xffffff; - static SLICE_OCCUPIED_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR); - static SLICE_HOVER_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR); - static SLICE_HOVER_COLOR: number = 0xffffff; - - static HexToHexString(hex: number): string { - if (hex === undefined) { - return "#000000"; - } - var s = hex.toString(16); - while (s.length < 6) { - s = "0" + s; - } - return "#" + s; - } - - -} diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts deleted file mode 100644 index d071dec62..000000000 --- a/src/client/northstar/utils/Utils.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { IBaseBrushable } from '../core/brusher/IBaseBrushable'; -import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer'; -import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider'; -import { AggregateFunction } from '../model/idea/idea'; - -export class Utils { - - public static EqualityHelper(a: Object, b: Object): boolean { - if (a === b) return true; - if (a === undefined && b !== undefined) return false; - if (a === null && b !== null) return false; - if (b === undefined && a !== undefined) return false; - if (b === null && a !== null) return false; - if ((<any>a).constructor.name !== (<any>b).constructor.name) return false; - return true; - } - - public static LowercaseFirstLetter(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); - } - - // - // this Type Guard tests if dropTarget is an IDropTarget. If it is, it coerces the compiler - // to treat the dropTarget parameter as an IDropTarget *ouside* this function scope (ie, in - // the scope of where this function is called from). - // - - public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> { - let typed = <IBaseBrushable<T>>obj; - return typed !== null && typed.BrusherModels !== undefined; - } - - public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider { - let typed = <IBaseFilterProvider>obj; - return typed !== null && typed.FilterModels !== undefined; - } - - public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer { - let typed = <IBaseFilterConsumer>obj; - return typed !== null && typed.FilterOperand !== undefined; - } - - public static EncodeQueryData(data: any): string { - const ret = []; - for (let d in data) { - ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d])); - } - return ret.join("&"); - } - - public static ToVegaAggregationString(agg: AggregateFunction): string { - if (agg === AggregateFunction.Avg) { - return "average"; - } - else if (agg === AggregateFunction.Count) { - return "count"; - } - else { - return ""; - } - } - - public static GetQueryVariable(variable: string) { - let query = window.location.search.substring(1); - let vars = query.split("&"); - for (const variable of vars) { - let pair = variable.split("="); - if (decodeURIComponent(pair[0]) === variable) { - return decodeURIComponent(pair[1]); - } - } - return undefined; - } -} - diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 569c1ef6d..b3295ece0 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -10,7 +10,6 @@ import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; -import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { Utils } from "../../Utils"; import { RichTextField } from "../../new_fields/RichTextField"; import { DictationOverlay } from "../views/DictationOverlay"; @@ -282,9 +281,8 @@ export namespace DictationManager { [DocumentType.COL, listSpec(Doc)], [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], - [DocumentType.HIST, HistogramField], [DocumentType.IMPORT, listSpec(Doc)], - [DocumentType.TEXT, "string"] + [DocumentType.RTF, "string"] ]); const tryCast = (view: DocumentView, type: DocumentType) => { @@ -377,7 +375,7 @@ export namespace DictationManager { { expression: /view as (freeform|stacking|masonry|schema|tree)/g, action: (target: DocumentView, matches: RegExpExecArray) => { - const mode = CollectionViewType.valueOf(matches[1]); + const mode = matches[1]; mode && (target.props.Document._viewType = mode); }, restrictTo: [DocumentType.COL] diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index c639f07f5..3d5306841 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,16 +1,16 @@ import { action, computed, observable } from 'mobx'; import { Doc, DocListCastAsync, DocListCast, Opt } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; -import { List } from '../../new_fields/List'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; -import { DocumentView } from '../views/nodes/DocumentView'; +import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { Scripting } from './Scripting'; import { SelectionManager } from './SelectionManager'; import { DocumentType } from '../documents/DocumentTypes'; +export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; export class DocumentManager { @@ -28,7 +28,6 @@ export class DocumentManager { //private constructor so no other class can create a nodemanager private constructor() { - // this.DocumentViews = new Array<DocumentView>(); } //gets all views @@ -55,7 +54,7 @@ export class DocumentManager { } public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined { - + if (!id) return undefined; let toReturn: DocumentView | undefined; const passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; @@ -85,17 +84,20 @@ export class DocumentManager { return this.getDocumentViewById(toFind[Id], preferredCollection); } - public getFirstDocumentView(toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined { - const views = this.getDocumentViews(toFind); - return views?.find(view => view.props.Document !== originatingDoc); + public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => { + return this.getDocumentViews(toFind)?.find(view => view.props.Document !== originatingDoc); } public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; + const docViews = DocumentManager.Instance.DocumentViews; - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.Document === toFind && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + // heuristic to return the "best" documents first: + // choose an exact match over an alias match + // choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth) + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); return toReturn; } @@ -127,98 +129,107 @@ export class DocumentManager { return pairs; } - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false, originatingDoc: Opt<Doc> = undefined): Promise<void> => { + static addRightSplit = (doc: Doc, finished?: () => void) => { + CollectionDockingView.AddRightSplit(doc); + finished?.(); + } + public jumpToDocument = async ( + targetDoc: Doc, + willZoom: boolean, + createViewFunc = DocumentManager.addRightSplit, + docContext?: Doc, + linkId?: string, + closeContextIfNotFound: boolean = false, + originatingDoc: Opt<Doc> = undefined, + finished?: () => void + ): Promise<void> => { + const getFirstDocView = DocumentManager.Instance.getFirstDocumentView; + const focusAndFinish = () => { finished?.(); return false; }; const highlight = () => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - finalDocView && (finalDocView.Document.scrollToLinkID = linkId); - finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); + const finalDocView = getFirstDocView(targetDoc); + if (finalDocView) { + finalDocView.Document.scrollToLinkID = linkId; + Doc.linkFollowHighlight(finalDocView.props.Document); + } }; - const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc, originatingDoc); - let annotatedDoc = await Cast(docView?.props.Document.annotationOn, Doc); + const docView = getFirstDocView(targetDoc, originatingDoc); + let annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (annotatedDoc) { - const first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc); + const first = getFirstDocView(annotatedDoc); if (first) annotatedDoc = first.props.Document; } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? - docView.props.focus(docView.props.Document, willZoom); + docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish); highlight(); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; - const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; - const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc); + const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; + const targetDocContext = annotatedDoc || contextDoc; if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default - (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); + createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); highlight(); - } else { - const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + } else { // otherwise try to get a view of the context of the target + const targetDocContextView = getFirstDocView(targetDocContext); targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling - if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context + if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext.panTransformType = "Ease"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); // now find the target document within the context - setTimeout(() => { - const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); - if (retryDocView) { - retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context - } else { - if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); - targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target - } - highlight(); - }, 0); + if (targetDoc.displayTimecode) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; + targetDocContext.currentTimecode = targetDoc.displayTimecode; + finished?.(); + } else { // no timecode means we need to find the context view and focus on our target + setTimeout(() => { + const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { // we found the target in the context + retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context + } else { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. + if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document); + targetDoc.layout && createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } + highlight(); + }, 0); + } } else { // there's no context view so we need to create one first and try again - (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext); + createViewFunc(targetDocContext); // so first we create the target, but don't pass finished because we still need to create the target setTimeout(() => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const finalDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + const finalDocView = getFirstDocView(targetDoc); + const finalDocContextView = getFirstDocView(targetDocContext); setTimeout(() => // if not, wait a bit to see if the context can be loaded (e.g., a PDF). wait interval heurisitic tries to guess how we're animating based on what's just become visible - this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. + this.jumpToDocument(targetDoc, willZoom, createViewFunc, undefined, linkId, true, undefined, finished), // pass true this time for closeContextIfNotFound + finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. }, 0); } } } } - public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { 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)); - const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; - const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; - const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined; - const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; - const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined]; - const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined]; - if (linkFollowDocs && linkDoc) { - const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); - const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; - 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], undefined, doc); - } else if (link) { - DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined); - } - } - - @action - zoomIntoScale = (docDelegate: Doc, scale: number) => { - const docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate)); - docView?.props.zoomToScale(scale); - } - - getScaleOfDocView = (docDelegate: Doc) => { - const doc = Doc.GetProto(docDelegate); - - const docView = DocumentManager.Instance.getDocumentView(doc); - if (docView) { - return docView.props.getScale(); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); // link docs where 'doc' is anchor2 + const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; + const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); + const linkDoc = linkDocList.length && linkDocList[0]; + if (linkDoc) { + const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + if (target) { + const containerDoc = (await Cast(target.annotationOn, Doc)) || target; + containerDoc.currentTimecode !== undefined && (containerDoc.currentTimecode = NumCast(target?.timecode)); + const targetContext = await target?.context as Doc; + const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc[Id], undefined, doc, finished); + } else { + finished?.(); + } } else { - return 1; + finished?.(); } } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 2877d5fd7..c856bbb1e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -7,15 +7,19 @@ import { DocumentManager } from "./DocumentManager"; import { LinkManager } from "./LinkManager"; import { SelectionManager } from "./SelectionManager"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { Docs } from "../documents/Documents"; +import { Docs, DocUtils } from "../documents/Documents"; import { ScriptField } from "../../new_fields/ScriptField"; import { List } from "../../new_fields/List"; import { PrefetchProxy } from "../../new_fields/Proxy"; import { listSpec } from "../../new_fields/Schema"; import { Scripting } from "./Scripting"; import { convertDropDataToButtons } from "./DropConverter"; +import { AudioBox } from "../views/nodes/AudioBox"; +import { DateField } from "../../new_fields/DateField"; +import { DocumentView } from "../views/nodes/DocumentView"; +import { UndoManager } from "./UndoManager"; -export type dropActionType = "alias" | "copy" | undefined; +export type dropActionType = "place" | "alias" | "copy" | undefined; export function SetupDrag( _reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc> | undefined, @@ -130,6 +134,7 @@ export namespace DragManager { dontHideOnDrop?: boolean; offset: number[]; dropAction: dropActionType; + removeDropProperties?: string[]; userDropAction: dropActionType; embedDoc?: boolean; moveDocument?: MoveFunction; @@ -143,6 +148,7 @@ export namespace DragManager { linkSourceDocument: Doc; dontClearTextBox?: boolean; linkDocument?: Doc; + linkDropCallback?: (data: LinkDragData) => void; } export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -179,7 +185,7 @@ export namespace DragManager { ); } element.dataset.canDrop = "true"; - const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail); + const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); }; element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); @@ -189,17 +195,22 @@ export namespace DragManager { // 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) { + const addAudioTag = (dropDoc: any) => { + dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); + dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline")); + return dropDoc; + }; + const batch = UndoManager.StartBatch("dragging"); 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.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(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?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined) ); + batch.end(); }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); @@ -208,10 +219,9 @@ export namespace DragManager { // 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 finishDrag = (e: DragCompleteEvent) => { - const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: title }); - bd.onClick = ScriptField.MakeScript(script); + const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, 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); + initialize?.(bd); bd.buttonParams = new List<string>(params); e.docDragData && (e.docDragData.droppedDocuments = [bd]); }; @@ -219,7 +229,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[]) { + export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; if (draggedDocs.length) { @@ -231,12 +241,11 @@ export namespace DragManager { 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); + docView.props.removeDocument?.(doc); addDocument(doc); return true; }; - const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined; + const containingView = docView.props.ContainingCollectionView; const finishDrag = (e: DragCompleteEvent) => e.docDragData && (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => { @@ -268,6 +277,10 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } + export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) { + StartDrag([ele], {}, downX, downY); + } + 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) { @@ -341,7 +354,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey ? "alias" : undefined; + dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined; } if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) { AbortDrag(); @@ -382,8 +395,8 @@ export namespace DragManager { hideDragShowOriginalElements(); dispatchDrag(eles, e, dragData, options, finishDrag); SelectionManager.SetIsDragging(false); - options?.dragComplete?.(new DragCompleteEvent(false, dragData)); endDrag(); + options?.dragComplete?.(new DragCompleteEvent(false, dragData)); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); @@ -391,20 +404,22 @@ export namespace DragManager { function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) { const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => { - const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height }; + const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow }; dragEle.style.width = "0"; dragEle.style.height = "0"; + dragEle.style.overflow = "hidden"; return ret; }); const target = document.elementFromPoint(e.x, e.y); removed.map(r => { r.ele.style.width = r.w; r.ele.style.height = r.h; + r.ele.style.overflow = r.o; }); if (target) { const complete = new DragCompleteEvent(false, dragData); finishDrag?.(complete); - + console.log(complete.aborted); target.dispatchEvent( new CustomEvent<DropEvent>("dashOnDrop", { bubbles: true, diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 3c7caa60b..d572e64a6 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,5 +1,5 @@ import { DragManager } from "./DragManager"; -import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Doc, DocListCast, Opt } from "../../new_fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; import { ObjectField } from "../../new_fields/ObjectField"; import { StrCast } from "../../new_fields/Types"; @@ -8,15 +8,29 @@ import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; import { RichTextField } from "../../new_fields/RichTextField"; import { ImageField } from "../../new_fields/URLField"; -export function makeTemplate(doc: Doc, first: boolean = true): boolean { +// +// converts 'doc' into a template that can be used to render other documents. +// the title of doc is used to determine which field is being templated, so +// passing a value for 'rename' allows the doc to be given a meangingful name +// after it has been converted to +export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; + if (layoutDoc.layout instanceof Doc) { // its already a template + return true; + } const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); const docs = DocListCast(layoutDoc[fieldKey]); let any = false; docs.forEach(d => { if (!StrCast(d.title).startsWith("-")) { - any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; + const params = StrCast(d.title).match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", ""); + if (params) { + any = makeTemplate(d, false) || any; + d.PARAMS = params; + } else { + any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; + } } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) { any = makeTemplate(d, false) || any; } @@ -29,21 +43,25 @@ export function makeTemplate(doc: Doc, first: boolean = true): boolean { any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc)); } } + rename && (doc.title = rename); return any; } export function convertDropDataToButtons(data: DragManager.DocumentDragData) { data && data.draggedDocuments.map((doc, i) => { let dbox = doc; // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant - if (!doc.onDragStart && !doc.onClick && !doc.isButtonBar) { + if (!doc.onDragStart && !doc.isButtonBar) { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; - if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) { - makeTemplate(layoutDoc); + if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.RTF || layoutDoc.type === DocumentType.IMG) { + !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); } else { (layoutDoc.layout instanceof Doc) && !data.userDropAction; } layoutDoc.isTemplateDoc = true; - dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); + dbox = Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, + backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), icon: layoutDoc.isTemplateDoc ? "font" : "bolt" + }); dbox.dragFactory = layoutDoc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 7194feb2e..f2d569cf3 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,3 +1,5 @@ +import React = require("react"); + export namespace InteractionUtils { export const MOUSETYPE = "mouse"; export const TOUCHTYPE = "touch"; @@ -8,20 +10,6 @@ export namespace InteractionUtils { const REACT_POINTER_PEN_BUTTON = 0; const ERASER_BUTTON = 5; - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); - return ( - <polyline - points={pts} - style={{ - fill: "none", - stroke: color, - strokeWidth: width - }} - /> - ); - } - export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> { constructor( readonly fingers: number, @@ -37,7 +25,7 @@ export namespace InteractionUtils { export function MakeMultiTouchTarget( element: HTMLElement, - startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void, + startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void ): MultiTouchEventDisposer { const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail); // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined; @@ -60,6 +48,17 @@ export namespace InteractionUtils { }; } + export function MakeHoldTouchTarget( + element: HTMLElement, + func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void + ): MultiTouchEventDisposer { + const handler = (e: Event) => func(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail); + element.addEventListener("dashOnTouchHoldStart", handler); + return () => { + element.removeEventListener("dashOnTouchHoldStart", handler); + }; + } + export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] { const myTouches = new Array<React.Touch>(); for (const pt of mte.touches) { @@ -79,6 +78,21 @@ export namespace InteractionUtils { return myTouches; } + // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view + export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) { + const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + return ( + <polyline + points={pts} + style={{ + fill: "none", + stroke: color, + strokeWidth: width + }} + /> + ); + } + export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { switch (type) { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts new file mode 100644 index 000000000..cacb72a57 --- /dev/null +++ b/src/client/util/KeyCodes.ts @@ -0,0 +1,136 @@ +/** + * Class contains the keycodes for keys on your keyboard. + * + * Useful for auto completion: + * + * ``` + * switch (event.key) + * { + * case KeyCode.UP: + * { + * // Up key pressed + * break; + * } + * case KeyCode.DOWN: + * { + * // Down key pressed + * break; + * } + * case KeyCode.LEFT: + * { + * // Left key pressed + * break; + * } + * case KeyCode.RIGHT: + * { + * // Right key pressed + * break; + * } + * default: + * { + * // ignore + * break; + * } + * } + * ``` + */ +export class KeyCodes { + public static TAB: number = 9; + public static CAPS_LOCK: number = 20; + public static SHIFT: number = 16; + public static CONTROL: number = 17; + public static SPACE: number = 32; + public static DOWN: number = 40; + public static UP: number = 38; + public static LEFT: number = 37; + public static RIGHT: number = 39; + public static ESCAPE: number = 27; + public static F1: number = 112; + public static F2: number = 113; + public static F3: number = 114; + public static F4: number = 115; + public static F5: number = 116; + public static F6: number = 117; + public static F7: number = 118; + public static F8: number = 119; + public static F9: number = 120; + public static F10: number = 121; + public static F11: number = 122; + public static F12: number = 123; + public static INSERT: number = 45; + public static HOME: number = 36; + public static PAGE_UP: number = 33; + public static PAGE_DOWN: number = 34; + public static DELETE: number = 46; + public static END: number = 35; + public static ENTER: number = 13; + public static BACKSPACE: number = 8; + public static NUMPAD_0: number = 96; + public static NUMPAD_1: number = 97; + public static NUMPAD_2: number = 98; + public static NUMPAD_3: number = 99; + public static NUMPAD_4: number = 100; + public static NUMPAD_5: number = 101; + public static NUMPAD_6: number = 102; + public static NUMPAD_7: number = 103; + public static NUMPAD_8: number = 104; + public static NUMPAD_9: number = 105; + public static NUMPAD_DIVIDE: number = 111; + public static NUMPAD_ADD: number = 107; + public static NUMPAD_ENTER: number = 13; + public static NUMPAD_DECIMAL: number = 110; + public static NUMPAD_SUBTRACT: number = 109; + public static NUMPAD_MULTIPLY: number = 106; + public static SEMICOLON: number = 186; + public static EQUAL: number = 187; + public static COMMA: number = 188; + public static MINUS: number = 189; + public static PERIOD: number = 190; + public static SLASH: number = 191; + public static BACKQUOTE: number = 192; + public static LEFTBRACKET: number = 219; + public static BACKSLASH: number = 220; + public static RIGHTBRACKET: number = 221; + public static QUOTE: number = 222; + public static ALT: number = 18; + public static COMMAND: number = 15; + public static NUMPAD: number = 21; + public static A: number = 65; + public static B: number = 66; + public static C: number = 67; + public static D: number = 68; + public static E: number = 69; + public static F: number = 70; + public static G: number = 71; + public static H: number = 72; + public static I: number = 73; + public static J: number = 74; + public static K: number = 75; + public static L: number = 76; + public static M: number = 77; + public static N: number = 78; + public static O: number = 79; + public static P: number = 80; + public static Q: number = 81; + public static R: number = 82; + public static S: number = 83; + public static T: number = 84; + public static U: number = 85; + public static V: number = 86; + public static W: number = 87; + public static X: number = 88; + public static Y: number = 89; + public static Z: number = 90; + public static NUM_0: number = 48; + public static NUM_1: number = 49; + public static NUM_2: number = 50; + public static NUM_3: number = 51; + public static NUM_4: number = 52; + public static NUM_5: number = 53; + public static NUM_6: number = 54; + public static NUM_7: number = 55; + public static NUM_8: number = 56; + public static NUM_9: number = 57; + public static SUBSTRACT: number = 189; + public static ADD: number = 187; +}
\ No newline at end of file diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 4457f41e2..e236c7f47 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -136,12 +136,12 @@ export class LinkManager { } } public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) { - linkDoc.linkRelationship = groupDoc.linkRelationship; + Doc.GetProto(linkDoc).linkRelationship = groupDoc.linkRelationship; } // removes group doc of given group type only from given anchor on given link public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) { - linkDoc.linkRelationship = "-ungrouped-"; + Doc.GetProto(linkDoc).linkRelationship = "-ungrouped-"; } // returns map of group type to anchor's links in that group type diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index ec5d1e72a..42247f177 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -151,14 +151,14 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any }); bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { const layoutDoc = props.Document; - const originalDoc = layoutDoc.expandedTemplate || layoutDoc; + const originalDoc = layoutDoc.rootDocument || layoutDoc; if (originalDoc instanceof Doc) { const newDoc = Docs.Create.TextDocument("", { title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine), x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) }); FormattedTextBox.SelectOnLoad = newDoc[Id]; - originalDoc instanceof Doc && props.addDocument(newDoc); + props.addDocument(newDoc); } }); @@ -169,7 +169,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any }; const addTextOnRight = (force: boolean) => { const layoutDoc = props.Document; - const originalDoc = layoutDoc.expandedTemplate || layoutDoc; + const originalDoc = layoutDoc.rootDocument || layoutDoc; if (force || props.Document._singleLine) { const newDoc = Docs.Create.TextDocument("", { title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine), @@ -180,7 +180,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any return true; } return false; - } + }; bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => { return addTextOnRight(true); }); diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index 460f1fa37..3f0ec7aa5 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -146,7 +146,7 @@ export default class RichTextMenu extends AntimodeMenu { public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { if (this.view) { - const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId }); + const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). addMark(this.view.state.selection.from, this.view.state.selection.to, link)); return this.view.state.selection.$from.nodeAfter?.text || ""; diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index af3b1a81e..b0a124cb8 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -81,31 +81,46 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc new InputRule( - new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/), + new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(=[a-zA-Z_#@\? \-0-9]*)?(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const docid = match[2]?.substring(1); + const docid = match[3]?.substring(1); + const value = match[2]?.substring(1); if (!fieldKey) { if (docid) { DocServer.GetRefField(docid).then(docx => { const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid); DocUtils.Publish(target, docid, returnFalse, returnFalse); - DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal link", ""); + DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to"); }); const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); } return state.tr; } + if (value !== "" && value !== undefined) { + this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : value; + } const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), + // create an inline view of a tag stored under the '#' field + new InputRule( + new RegExp(/#([a-zA-Z_\-0-9]+)\s$/), + (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]["#"] = tag; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc new InputRule( - new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\}\}$/), + new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/), (state, match, start, end) => { - const fieldKey = match[1]; - const docid = match[2]?.substring(1); + const fieldKey = match[1] || ""; + const fieldParam = match[2]?.replace("…", "...") || ""; + const docid = match[3]?.substring(1); if (!fieldKey && !docid) return state.tr; docid && DocServer.GetRefField(docid).then(docx => { if (!(docx instanceof Doc && docx)) { @@ -114,7 +129,7 @@ export class RichTextRules { } }); const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey, float: "right", alias: Utils.GenerateGuid() }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "right", alias: Utils.GenerateGuid() }); const sm = state.storedMarks || undefined; return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 4a80a1af8..de2707d36 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -8,24 +8,24 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, Field, HeightSym, WidthSym, DocListCast } from "../../new_fields/Doc"; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; +import { List } from "../../new_fields/List"; import { ObjectField } from "../../new_fields/ObjectField"; +import { listSpec } from "../../new_fields/Schema"; +import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, NumCast, StrCast, Cast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils"; +import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; import { DocServer } from "../DocServer"; +import { Docs } from "../documents/Documents"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { Transform } from "./Transform"; import React = require("react"); -import { CollectionSchemaBooleanCell } from "../views/collections/CollectionSchemaCells"; -import { ContextMenu } from "../views/ContextMenu"; -import { ContextMenuProps } from "../views/ContextMenuItem"; -import { Docs } from "../documents/Documents"; -import { CollectionView } from "../views/collections/CollectionView"; const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -300,6 +300,7 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { href: {}, targetId: { default: "" }, + linkId: { default: "" }, showPreview: { default: true }, location: { default: null }, title: { default: null }, @@ -314,7 +315,7 @@ export const marks: { [index: string]: MarkSpec } = { toDOM(node: any) { return node.attrs.docref && node.attrs.title ? ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs, id: node.attrs.targetId, title: `${node.attrs.title}` }, 0]; + ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0]; } }, @@ -509,7 +510,7 @@ export const marks: { [index: string]: MarkSpec } = { user_mark: { attrs: { userid: { default: "" }, - modified: { default: "when?" }, // 5 second intervals since 1970 + modified: { default: "when?" }, // 1 second intervals since 1970 }, group: "inline", toDOM(node: any) { @@ -525,7 +526,7 @@ export const marks: { [index: string]: MarkSpec } = { user_tag: { attrs: { userid: { default: "" }, - modified: { default: "when?" }, // 5 second intervals since 1970 + modified: { default: "when?" }, // 1 second intervals since 1970 tag: { default: "" } }, group: "inline", @@ -722,6 +723,7 @@ export class DashDocView { _outer: HTMLElement; _dashDoc: Doc | undefined; _reactionDisposer: IReactionDisposer | undefined; + _renderDisposer: IReactionDisposer | undefined; _textBox: FormattedTextBox; getDocTransform = () => { @@ -767,13 +769,14 @@ export class DashDocView { }; const alias = node.attrs.alias; - const docid = node.attrs.docid || tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; + const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; DocServer.GetRefField(docid + alias).then(async dashDoc => { if (!(dashDoc instanceof Doc)) { alias && DocServer.GetRefField(docid).then(async dashDocBase => { if (dashDocBase instanceof Doc) { const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); - aliasedDoc.layoutKey = node.attrs.fieldKey === "layout" ? "layout" : "layout" + (node.attrs.fieldKey ? "_" + node.attrs.fieldKey : ""); + aliasedDoc.layoutKey = "layout"; + node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); self.doRender(aliasedDoc, removeDoc, node, view, getPos); } }); @@ -796,59 +799,74 @@ export class DashDocView { } doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { this._dashDoc = dashDoc; - if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); - } catch (e) { - console.log(e); - } - } const self = this; - const finalLayout = Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.Document) ? this._textBox.dataDoc : undefined); + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); else { - const layoutKey = StrCast(finalLayout.layoutKey); - const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; - if (finalLayout !== dashDoc && finalKey) { - const finalLayoutField = finalLayout[finalKey]; - if (finalLayoutField instanceof ObjectField) { - finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); - } - } this._reactionDisposer?.(); this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); }, { fireImmediately: true }); - ReactDOM.render(<DocumentView - Document={finalLayout} - DataDoc={!node.attrs.docid ? this._textBox.dataDoc : undefined} - LibraryPath={this._textBox.props.LibraryPath} - fitToBox={BoolCast(dashDoc._fitToBox)} - addDocument={returnFalse} - removeDocument={removeDoc} - ScreenToLocalTransform={this.getDocTransform} - addDocTab={this._textBox.props.addDocTab} - pinToPres={returnFalse} - renderDepth={self._textBox.props.renderDepth + 1} - PanelWidth={finalLayout[WidthSym]} - PanelHeight={finalLayout[HeightSym]} - focus={this.outerFocus} - backgroundColor={returnEmptyString} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={emptyFunction} - zoomToScale={emptyFunction} - getScale={returnOne} - dontRegisterView={false} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - ContentScaling={this.contentScaling} - />, this._dashSpan); + const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { + ReactDOM.unmountComponentAtNode(this._dashSpan); + ReactDOM.render(<DocumentView + Document={finalLayout} + DataDoc={resolvedDataDoc} + LibraryPath={this._textBox.props.LibraryPath} + fitToBox={BoolCast(dashDoc._fitToBox)} + addDocument={returnFalse} + rootSelected={this._textBox.props.isSelected} + removeDocument={removeDoc} + ScreenToLocalTransform={this.getDocTransform} + addDocTab={this._textBox.props.addDocTab} + pinToPres={returnFalse} + renderDepth={self._textBox.props.renderDepth + 1} + NativeHeight={returnZero} + NativeWidth={returnZero} + PanelWidth={finalLayout[WidthSym]} + PanelHeight={finalLayout[HeightSym]} + focus={this.outerFocus} + backgroundColor={returnEmptyString} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + dontRegisterView={false} + ContainingCollectionView={this._textBox.props.ContainingCollectionView} + ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} + ContentScaling={this.contentScaling} + />, this._dashSpan); + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + }; + this._renderDisposer?.(); + this._renderDisposer = reaction(() => { + if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { + finalLayout.rootDocument = dashDoc.aliasOf; + } + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; + }, + (res) => doReactRender(res.finalLayout, res.resolvedDataDoc), + { fireImmediately: true }); } } destroy() { + ReactDOM.unmountComponentAtNode(this._dashSpan); this._reactionDisposer?.(); } } @@ -858,6 +876,8 @@ export class DashFieldView { _fieldWrapper: HTMLDivElement; // container for label and value _labelSpan: HTMLSpanElement; // field label _fieldSpan: HTMLDivElement; // field value + _fieldCheck: HTMLInputElement; + _enumerables: HTMLDivElement; // field value _reactionDisposer: IReactionDisposer | undefined; _textBoxDoc: Doc; @observable _dashDoc: Doc | undefined; @@ -871,34 +891,81 @@ export class DashFieldView { this._fieldWrapper.style.width = node.attrs.width; this._fieldWrapper.style.height = node.attrs.height; this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.style.display = "inline-flex"; const self = this; + this._enumerables = document.createElement("div"); + this._enumerables.style.width = "10px"; + this._enumerables.style.height = "10px"; + this._enumerables.style.position = "relative"; + this._enumerables.style.display = "none"; + this._enumerables.style.background = "dimGray"; + + this._enumerables.onpointerdown = async (e) => { + e.stopPropagation(); + const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); + }; + const updateText = (forceMatch: boolean) => { + self._enumerables.style.display = "none"; + const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; + + // look for a document whose id === the fieldKey being displayed. If there's a match, then that document + // holds the different enumerated values for the field in the titles of its collected documents. + // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. + DocServer.GetRefField(self._fieldKey).then(options => { + let modText = ""; + (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + if (modText) { + self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; + Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); + } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key + else if (self._fieldSpan.innerText.startsWith(":=")) { + self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); + } else if (self._fieldSpan.innerText.startsWith("=:=")) { + Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); + } else { + self._dashDoc![self._fieldKey] = newText; + } + }); + }; + + + this._fieldCheck = document.createElement("input"); + this._fieldCheck.id = Utils.GenerateGuid(); + this._fieldCheck.type = "checkbox"; + this._fieldCheck.style.position = "relative"; + this._fieldCheck.style.display = "none"; + this._fieldCheck.style.minWidth = "12px"; + this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; + this._fieldCheck.onchange = function (e: any) { + self._dashDoc![self._fieldKey] = e.target.checked; + }; + this._fieldSpan = document.createElement("div"); this._fieldSpan.id = Utils.GenerateGuid(); this._fieldSpan.contentEditable = "true"; this._fieldSpan.style.position = "relative"; - this._fieldSpan.style.display = "inline-block"; - this._fieldSpan.style.minWidth = "50px"; + this._fieldSpan.style.display = "none"; + this._fieldSpan.style.minWidth = "12px"; this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.oncontextmenu = function (e: any) { - ContextMenu.Instance.addItem({ - description: "Show Enumeration Templates", event: () => { - e.stopPropagation(); - DocServer.GetRefField(node.attrs.fieldKey).then(collview => collview instanceof Doc && tbox.props.addDocTab(collview, "onRight")); - }, icon: "expand-arrows-alt" - }); - }; + this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; + this._fieldSpan.onblur = function (e: any) { updateText(false); }; const setDashDoc = (doc: Doc) => { self._dashDoc = doc; - if (this._dashDoc && self._options?.length && !this._dashDoc[node.attrs.fieldKey]) { - this._dashDoc[node.attrs.fieldKey] = StrCast(self._options[0].title); + if (self._options?.length && !self._dashDoc[self._fieldKey]) { + self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); } - } + // NOTE: if the field key starts with "@", then the actual field key is stored in the field 'fieldKey' (removing the @). + self._fieldKey = self._fieldKey.startsWith("@") ? StrCast(tbox.props.Document[StrCast(self._fieldKey).substring(1)]) : self._fieldKey; + this._labelSpan.innerHTML = `${self._fieldKey}: `; + const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); + this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? "inline-block" : "none"; + }; this._fieldSpan.onkeydown = function (e: any) { e.stopPropagation(); if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { @@ -912,31 +979,37 @@ export class DashFieldView { } if (e.key === "Enter") { e.preventDefault(); - if (e.ctrlKey) { - Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]); - } - let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText; - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - - // alternatively, if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - DocServer.GetRefField(node.attrs.fieldKey).then(options => { - (options instanceof Doc) && DocListCast(options.data).forEach(opt => StrCast(opt.title).startsWith(newText) && (newText = StrCast(opt.title))); - self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = newText; - if (newText.startsWith(":=") && self._dashDoc && e.data === null && !e.inputType.includes("delete")) { - Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); - } - }); + e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + updateText(true); } }; this._labelSpan = document.createElement("span"); + this._labelSpan.style.backgroundColor = "rgba(155, 155, 155, 0.44)"; this._labelSpan.style.position = "relative"; - this._labelSpan.style.display = "inline"; - this._labelSpan.style.fontWeight = "bold"; - this._labelSpan.style.fontSize = "larger"; - this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `; + this._labelSpan.style.display = "inline-block"; + this._labelSpan.style.fontSize = "small"; + this._labelSpan.title = "click to see related tags"; + this._labelSpan.onpointerdown = function (e: any) { + e.stopPropagation(); + let container = tbox.props.ContainingCollectionView; + while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { + container = container.props.ContainingCollectionView; + } + if (container) { + const alias = Doc.MakeAlias(container.props.Document); + alias.viewType = CollectionViewType.Time; + let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); + if (!list) { + alias.schemaColumns = list = new List<SchemaHeaderField>(); + } + list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); + list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); + alias._pivotField = self._fieldKey; + tbox.props.addDocTab(alias, "onRight"); + } + }; + this._labelSpan.innerHTML = `${self._fieldKey}: `; if (node.attrs.docid) { DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); } else { @@ -944,13 +1017,25 @@ export class DashFieldView { } this._reactionDisposer?.(); this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes - const dashVal = this._dashDoc?.[node.attrs.fieldKey]; - return StrCast(dashVal).startsWith(":=") || !dashVal ? Doc.Layout(tbox.props.Document)[this._fieldKey] : dashVal; - }, fval => this._fieldSpan.innerHTML = Field.toString(fval as Field) || "(null)", { fireImmediately: true }); + const dashVal = this._dashDoc?.[self._fieldKey]; + return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; + }, fval => { + const boolVal = Cast(fval, "boolean", null); + if (boolVal === true || boolVal === false) { + this._fieldCheck.checked = boolVal; + } else { + this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; + } + this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(boolVal === true || boolVal === false) ? "inline-block" : "none"; + }, { fireImmediately: true }); this._fieldWrapper.appendChild(this._labelSpan); + this._fieldWrapper.appendChild(this._fieldCheck); this._fieldWrapper.appendChild(this._fieldSpan); + this._fieldWrapper.appendChild(this._enumerables); (this as any).dom = this._fieldWrapper; + //updateText(false); } destroy() { this._reactionDisposer?.(); @@ -1011,12 +1096,12 @@ export class FootnoteView { "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), "Mod-b": toggleMark(schema.marks.strong) }), - new Plugin({ - view(newView) { - // TODO -- make this work with RichTextMenu - // return FormattedTextBox.getToolTip(newView); - } - }) + // new Plugin({ + // view(newView) { + // // TODO -- make this work with RichTextMenu + // // return FormattedTextBox.getToolTip(newView); + // } + // }) ], }), diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index ca770f897..8207b940d 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -195,14 +195,14 @@ export type Transformer = { getVars?: () => { capturedVariables: { [name: string]: Field } } }; export interface ScriptOptions { - requiredType?: string; - addReturn?: boolean; - params?: { [name: string]: string }; - capturedVariables?: { [name: string]: Field }; - typecheck?: boolean; - editable?: boolean; + requiredType?: string; // does function required a typed return value + addReturn?: boolean; // does the compiler automatically add a return statement + params?: { [name: string]: string }; // list of function parameters and their types + capturedVariables?: { [name: string]: Field }; // list of captured variables + typecheck?: boolean; // should the compiler perform typechecking + editable?: boolean; // can the script edit Docs traverser?: TraverserParam; - transformer?: Transformer; + transformer?: Transformer; // does the editor display a text label by each document that can be used as a captured document reference globals?: { [name: string]: any }; } @@ -215,6 +215,8 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; + if (options.params && !options.params.this) options.params.this = Doc.name; + if (options.params && !options.params.self) options.params.self = Doc.name; if (options.globals) { Scripting.setScriptingGlobals(options.globals); } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 8ff54d052..2026bf940 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -34,7 +34,7 @@ export namespace SearchUtil { export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>; export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { query = query || "*"; //If we just have a filter query, search for * as the query - const rpquery = Utils.prepend("/search"); + const rpquery = Utils.prepend("/dashsearch"); const gotten = await rp.get(rpquery, { qs: { ...options, q: query } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { @@ -52,7 +52,7 @@ export namespace SearchUtil { const newLines: string[][] = []; await Promise.all(fileids.map(async (tr: string, i: number) => { const docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query - const docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } })); + const docResult = JSON.parse(await rp.get(Utils.prepend("/dashsearch"), { qs: { ...options, q: docQuery } })); newIds.push(...docResult.ids); newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i])); })); @@ -64,7 +64,7 @@ export namespace SearchUtil { const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc); for (let i = 0; i < textDocs.length; i++) { const testDoc = textDocs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { theDocs.push(Doc.GetProto(testDoc)); theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase()))); } @@ -74,7 +74,7 @@ export namespace SearchUtil { const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); for (let i = 0; i < ids.length; i++) { const testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); } @@ -118,4 +118,27 @@ export namespace SearchUtil { aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids)); return contexts; } -}
\ No newline at end of file + + export async function GetAllDocs() { + const query = "*"; + const response = await rp.get(Utils.prepend('/dashsearch'), { + qs: + { start: 0, rows: 10000, q: query }, + + }); + const result: IdSearchResult = JSON.parse(response); + const { ids, numFound, highlighting } = result; + //console.log(ids.length); + const docMap = await DocServer.GetRefFields(ids); + const docs: Doc[] = []; + for (const id of ids) { + const field = docMap[id]; + if (field instanceof Doc) { + docs.push(field); + } + } + return docs; + // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); + // return docs as Doc[]; + } +} diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 4612f10f4..6c386d684 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -55,10 +55,13 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } + // computed functions, such as used in IsSelected generate errors if they're called outside of a + // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature + // to avoid unnecessary mobx invalidations when running inside a reaction. export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean { return outsideReaction ? - manager.SelectedDocuments.get(doc) ? true : false : - computedFn(function isSelected(doc: DocumentView) { + manager.SelectedDocuments.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() + computedFn(function isSelected(doc: DocumentView) { // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed return manager.SelectedDocuments.get(doc) ? true : false; })(doc); } diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss new file mode 100644 index 000000000..e2149e9c1 --- /dev/null +++ b/src/client/util/TooltipTextMenu.scss @@ -0,0 +1,372 @@ +@import "../views/globalCssVariables"; +.ProseMirror-menu-dropdown-wrap { + display: inline-block; + position: relative; +} + +.ProseMirror-menu-dropdown { + vertical-align: 1px; + cursor: pointer; + position: relative; + padding: 0 15px 0 4px; + background: white; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } +} + +.ProseMirror-menu-submenu-wrap { + position: relative; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; + border: 1px solid rgb(223, 223, 223); + min-width: 40px; + z-index: 50000; + position: absolute; + box-sizing: content-box; + + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + + +.ProseMirror-menu-submenu-label:after { + content: ""; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 4px); +} + + .ProseMirror-icon { + display: inline-block; + // line-height: .8; + // vertical-align: -2px; /* Compensate for padding */ + // padding: 2px 8px; + cursor: pointer; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +.wrapper { + position: absolute; + pointer-events: all; + display: flex; + align-items: center; + transform: translateY(-85px); + + height: 35px; + background: #323232; + border-radius: 6px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; + pointer-events: all; + padding: 3px; + padding-bottom: 5px; + display: flex; + align-items: center; + + .ProseMirror-example-setup-style hr { + padding: 2px 10px; + border: none; + margin: 1em 0; + } + + .ProseMirror-example-setup-style hr:after { + content: ""; + display: block; + height: 1px; + background-color: silver; + line-height: 2px; + } +} + +.menuicon { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: white; + width: 18px; + height: 18px; + } +} + +.menuicon-active { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + // margin-top: 11px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 13px; +} + + .font-size-indicator { + font-size: 12px; + padding-right: 0px; + } + .summarize{ + color: white; + height: 20px; + text-align: center; + } + + +.brush{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; + margin-right: 15px; +} + +.brush-active{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 3; + fill: greenyellow; + margin-right: 15px; +} + +.dragger-wrapper { + color: #eee; + height: 22px; + padding: 0 5px; + box-sizing: content-box; + cursor: grab; + + .dragger { + width: 18px; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + border: 1px solid #4d4d4d; + color: $light-color-secondary; + // border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 97f6b79fb..08aec3724 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -195,7 +195,6 @@ interface DocumentOptions { } declare const Docs: { ImageDocument(url: string, options?: DocumentOptions): Doc; VideoDocument(url: string, options?: DocumentOptions): Doc; - // HistogramDocument(url:string, options?:DocumentOptions); TextDocument(options?: DocumentOptions): Doc; PdfDocument(url: string, options?: DocumentOptions): Doc; WebDocument(url: string, options?: DocumentOptions): Doc; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index f4e830a48..bbba2712e 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -6,9 +6,10 @@ import { listSpec } from '../../new_fields/Schema'; import { InkingControl } from './InkingControl'; import { InkTool } from '../../new_fields/InkField'; import { PositionDocument } from '../../new_fields/documentSchemas'; +import { InteractionUtils } from '../util/InteractionUtils'; -/// DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, ButtonBox) +/// DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, LabelBox) interface DocComponentProps { Document: Doc; LayoutDoc?: () => Opt<Doc>; @@ -18,6 +19,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { return schemaCtor(this.props.Document); } @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document, this.props.LayoutDoc?.())); } + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; } @@ -29,6 +31,7 @@ interface DocExtendableProps { fieldKey: string; isSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; + rootSelected: (outsideReaction?: boolean) => boolean; } export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) { class Component extends Touchable<P> { @@ -36,7 +39,8 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt @computed get Document(): T { return schemaCtor(this.props.Document); } @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; } - active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools + active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; } @@ -50,6 +54,7 @@ export interface DocAnnotatableProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; + rootSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; } export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) { @@ -60,6 +65,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; } + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; _annotationKey: string = "annotations"; public set annotationKey(val: string) { this._annotationKey = val; } @@ -80,13 +86,13 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema } @action.bound addDocument(doc: Doc): boolean { - Doc.GetProto(doc).annotationOn = this.props.Document; + doc.context = Doc.GetProto(doc).annotationOn = this.props.Document; return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false; } whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) && - (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a3d313224..b95cc6627 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,15 +1,13 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../new_fields/Doc"; -import { Id } from '../../new_fields/FieldSymbols'; import { RichTextField } from '../../new_fields/RichTextField'; import { NumCast, StrCast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; -import RichTextMenu from '../util/RichTextMenu'; import { UndoManager } from "../util/UndoManager"; import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView'; import { ParentDocSelector } from './collections/ParentDocumentSelector'; @@ -25,7 +23,6 @@ import { DragManager } from '../util/DragManager'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; -import { ComputedField } from '../../new_fields/ScriptField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -40,6 +37,7 @@ library.add(faCheckCircle); library.add(faCloudUploadAlt); library.add(faSyncAlt); library.add(faShare); +library.add(faPhotoVideo); const cloud: IconProp = "cloud-upload-alt"; const fetch: IconProp = "sync-alt"; @@ -108,7 +106,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | this._pullColorAnimating = false; }); - get view0() { return this.props.views && this.props.views.length ? this.props.views[0] : undefined; } + get view0() { return this.props.views?.[0]; } @action onLinkButtonMoved = (e: PointerEvent): void => { @@ -120,15 +118,10 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | dragComplete: dropEv => { const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop if (this.view0 && linkDoc) { - const proto = Doc.GetProto(linkDoc); - proto.anchor1Context = this.view0.props.ContainingCollectionDoc; - - const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; - const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; - const text = RichTextMenu.Instance.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", anchor2Id); - if (linkDoc.anchor2 instanceof Doc && !proto.title) { - proto.title = Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" " + (this.linkRelationship||"to") +" " + this.anchor2.title'); - } + Doc.GetProto(linkDoc).linkRelationship = "hyperlink"; + dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData); + runInAction(() => this.view0!._link = linkDoc); + setTimeout(action(() => this.view0!._link = undefined), 0); } linkDrag?.end(); }, @@ -252,7 +245,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | @computed get contextButton() { - return !this.view0 ? (null) : <ParentDocSelector Views={this.props.views.filter(v => v).map(v => v as DocumentView)} Document={this.view0.props.Document} addDocTab={(doc, where) => { + return !this.view0 ? (null) : <ParentDocSelector Document={this.view0.props.Document} addDocTab={(doc, where) => { where === "onRight" ? CollectionDockingView.AddRightSplit(doc) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc) : this.view0?.props.addDocTab(doc, "onRight"); @@ -316,7 +309,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | render() { if (!this.view0) return (null); - const isText = this.view0.props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field. need to add fieldKey to DocumentView + const isText = this.view0.props.Document[Doc.LayoutFieldKey(this.view0.props.Document)] instanceof RichTextField; const considerPull = isText && this.considerGoogleDocsPull; const considerPush = isText && this.considerGoogleDocsPush; return <div className="documentButtonBar"> diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 1992c5efa..353520026 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -27,6 +27,17 @@ $linkGap : 3px; opacity: 1; } + .documentDecorations-selector { + pointer-events: auto; + height: 15px; + width: 15px; + left: -20px; + top: 20px; + display: inline-block; + position: absolute; + opacity: 0.5; + } + .documentDecorations-radius { pointer-events: auto; background: black; @@ -69,6 +80,7 @@ $linkGap : 3px; #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { cursor: nwse-resize; + background: dimGray; } #documentDecorations-bottomRightResizer { @@ -78,6 +90,7 @@ $linkGap : 3px; #documentDecorations-topRightResizer, #documentDecorations-bottomLeftResizer { cursor: nesw-resize; + background: dimGray; } #documentDecorations-topResizer, @@ -147,6 +160,7 @@ $linkGap : 3px; padding-top: 5px; width: $MINIMIZED_ICON_SIZE; height: $MINIMIZED_ICON_SIZE; + max-height: 20px; } .documentDecorations-background { @@ -177,7 +191,7 @@ $linkGap : 3px; height: auto; display: flex; flex-direction: row; - z-index: 5; + z-index: 998; position: absolute; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4922411e8..bd72385ef 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -8,7 +8,7 @@ import { PositionDocument } from '../../new_fields/documentSchemas'; import { ScriptField } from '../../new_fields/ScriptField'; import { Cast, StrCast, NumCast } from "../../new_fields/Types"; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { Utils, setupMoveUpEvents } from "../../Utils"; +import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils"; import { DocUtils } from "../documents/Documents"; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from "../util/DragManager"; @@ -43,7 +43,7 @@ export type CloseCall = (toBeDeleted: DocumentView[]) => void; export class DocumentDecorations extends React.Component<{}, { value: string }> { static Instance: DocumentDecorations; private _resizeHdlId = ""; - private _keyinput: React.RefObject<HTMLInputElement>; + private _keyinput = React.createRef<HTMLInputElement>(); private _resizeBorderWidth = 16; private _linkBoxHeight = 20 + 3; // link button height + margin private _titleHeight = 20; @@ -62,7 +62,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> constructor(props: Readonly<{}>) { super(props); DocumentDecorations.Instance = this; - this._keyinput = React.createRef(); reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this.titleBlur(false)); } @@ -77,7 +76,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> var [sptX, sptY] = transform.transformPoint(0, 0); let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); if (documentView.props.Document.type === DocumentType.LINK) { - const docuBox = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont"); + const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont"); if (docuBox.length) { const rect = docuBox[0].getBoundingClientRect(); sptX = rect.left; @@ -245,6 +244,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } @action + onSelectorUp = (e: React.PointerEvent): void => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e) => { + const selDoc = SelectionManager.SelectedDocuments()?.[0]; + if (selDoc) { + selDoc.props.ContainingCollectionView?.props.select(false); + } + })); + } + + @action onRadiusDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, this.onRadiusMove, (e) => this._resizeUndo?.end(), (e) => { }); if (e.button === 0) { @@ -320,6 +329,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const width = (layoutDoc._width || 0); const height = (layoutDoc._height || (nheight / nwidth * width)); const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); + if (nwidth && nheight) { + if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; + else dW = dH * nwidth / nheight; + } const actualdW = Math.max(width + (dW * scale), 20); const actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); @@ -411,7 +424,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined; const bounds = this.Bounds; const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; - if (SelectionManager.GetIsDragging() || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { + if (SelectionManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } const minimal = bounds.r - bounds.x < 100 ? true : false; @@ -496,6 +509,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> + {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : <div id="documentDecorations-levelSelector" className="documentDecorations-selector" title="tap to select containing document" + onPointerDown={this.onSelectorUp} onContextMenu={(e) => e.preventDefault()}> + <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" /> + </div>} <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div> diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 4a27425e8..2219966e5 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -4,10 +4,7 @@ import { observer } from 'mobx-react'; import * as Autosuggest from 'react-autosuggest'; import { ObjectField } from '../../new_fields/ObjectField'; import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; -import { ContextMenu } from './ContextMenu'; -import { ContextMenuProps } from './ContextMenuItem'; import "./EditableView.scss"; -import { CollectionTreeView } from './collections/CollectionTreeView'; export interface EditableProps { /** @@ -88,12 +85,12 @@ export class EditableView extends React.Component<EditableProps> { onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === "Tab") { e.stopPropagation(); - this.finalizeEdit(e.currentTarget.value, e.shiftKey); + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false); this.props.OnTab && this.props.OnTab(e.shiftKey); } else if (e.key === "Enter") { e.stopPropagation(); if (!e.ctrlKey) { - this.finalizeEdit(e.currentTarget.value, e.shiftKey); + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false); } else if (this.props.OnFillDown) { this.props.OnFillDown(e.currentTarget.value); this._editing = false; @@ -123,10 +120,17 @@ export class EditableView extends React.Component<EditableProps> { } @action - private finalizeEdit(value: string, shiftDown: boolean) { - this._editing = false; + private finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean) { if (this.props.SetValue(value, shiftDown)) { + this._editing = false; + this.props.isEditingCallback?.(false); + } else { + this._editing = false; this.props.isEditingCallback?.(false); + !lostFocus && setTimeout(action(() => { + this._editing = true; + this.props.isEditingCallback?.(true); + }), 0); } } @@ -151,7 +155,7 @@ export class EditableView extends React.Component<EditableProps> { className: "editableView-input", onKeyDown: this.onKeyDown, autoFocus: true, - onBlur: e => this.finalizeEdit(e.currentTarget.value, false), + onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true), onPointerDown: this.stopPropagation, onClick: this.stopPropagation, onPointerUp: this.stopPropagation, @@ -163,7 +167,7 @@ export class EditableView extends React.Component<EditableProps> { defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus={true} - onBlur={e => this.finalizeEdit(e.currentTarget.value, false)} + onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} style={{ display: this.props.display, fontSize: this.props.fontSize }} />; diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index d980b0a91..107077792 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -5,6 +5,21 @@ top: 0; left: 0; touch-action: none; + + .pointerBubbles { + width: 100%; + height: 100%; + position: absolute; + pointer-events: none; + + .bubble { + position: absolute; + width: 15px; + height: 15px; + border-radius: 100%; + border: .5px solid grey; + } + } } .clipboardDoc-cont { @@ -13,6 +28,35 @@ height: 300px; } +.inkToTextDoc-cont { + position: absolute; + width: 300px; + overflow: hidden; + pointer-events: none; + + .inkToTextDoc-scroller { + overflow: visible; + position: absolute; + width: 100%; + + .menuItem-cont { + width: 100%; + height: 25px; + padding: 2.5px; + border-bottom: .5px solid black; + } + } + + .shadow { + width: 100%; + height: calc(100% - 25px); + position: absolute; + top: 25px; + background-color: black; + opacity: 0.2; + } +} + .filter-cont { position: absolute; background-color: transparent; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a8cf8c197..69aa8dbaa 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,49 +2,72 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; import "./GestureOverlay.scss"; -import { computed, observable, action, runInAction, IReactionDisposer, reaction } from "mobx"; +import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow, trace } from "mobx"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; -import { InkTool } from "../../new_fields/InkField"; +import { InkTool, InkData } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { LinkManager } from "../util/LinkManager"; -import { DocUtils } from "../documents/Documents"; +import { DocUtils, Docs } from "../documents/Documents"; import { undoBatch } from "../util/UndoManager"; import { Scripting } from "../util/Scripting"; import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; -import Palette from "./Palette"; -import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils"; +import HorizontalPalette from "./Palette"; +import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils"; import { DocumentView } from "./nodes/DocumentView"; import { Transform } from "../util/Transform"; import { DocumentContentsView } from "./nodes/DocumentContentsView"; +import { CognitiveServices } from "../cognitive_services/CognitiveServices"; +import { DocServer } from "../DocServer"; +import htmlToImage from "html-to-image"; +import { ScriptField } from "../../new_fields/ScriptField"; +import { listSpec } from "../../new_fields/Schema"; +import { List } from "../../new_fields/List"; +import { CollectionViewType } from "./collections/CollectionView"; +import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; +import MobileInterface from "../../mobile/MobileInterface"; +import { MobileInkOverlayContent } from "../../server/Message"; +import MobileInkOverlay from "../../mobile/MobileInkOverlay"; +import { RadialMenu } from "./nodes/RadialMenu"; +import { SelectionManager } from "../util/SelectionManager"; + @observer export default class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public Color: string = "rgb(244, 67, 54)"; - @observable public Width: number = 5; + @observable public Color: string = "rgb(0, 0, 0)"; + @observable public Width: number = 2; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @observable private _thumbX?: number; @observable private _thumbY?: number; + @observable private _selectedIndex: number = -1; + @observable private _menuX: number = -300; + @observable private _menuY: number = -300; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; + @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; + @observable private _possibilities: JSX.Element[] = []; - @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } + @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); } @computed private get showBounds() { return this.Tool !== ToolglassTools.None; } + @observable private showMobileInkOverlay: boolean = false; + private _d1: Doc | undefined; + private _inkToTextDoc: Doc | undefined; private _thumbDoc: Doc | undefined; private thumbIdentifier?: number; private pointerIdentifier?: number; private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>(); + private _holdTimer: NodeJS.Timeout | undefined; protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -54,6 +77,11 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } + componentDidMount = () => { + this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc)); + this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc)); + } + getNewTouches(e: React.TouchEvent | TouchEvent) { const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches); const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches); @@ -84,6 +112,15 @@ export default class GestureOverlay extends Touchable { } onReactTouchStart = (te: React.TouchEvent) => { + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + if (RadialMenu.Instance._display === true) { + te.preventDefault(); + te.stopPropagation(); + RadialMenu.Instance.closeMenu(); + return; + } + const actualPts: React.Touch[] = []; for (let i = 0; i < te.touches.length; i++) { const pt: any = te.touches.item(i); @@ -107,8 +144,6 @@ export default class GestureOverlay extends Touchable { ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); const nts = this.getNewTouches(te); - console.log(nts.nt.length); - if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); target?.dispatchEvent( @@ -125,6 +160,41 @@ export default class GestureOverlay extends Touchable { } ) ); + if (nts.nt.length === 1) { + console.log("started"); + this._holdTimer = setTimeout(() => { + console.log("hold"); + const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + const pt: any = te.touches[te.touches.length - 1]; + if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) { + target?.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchHoldStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te + } + } + ) + ); + this._holdTimer = undefined; + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.addEventListener("touchmove", this.onReactHoldTouchMove); + document.addEventListener("touchend", this.onReactHoldTouchEnd); + } + + }, (500)); + } + else { + this._holdTimer && clearTimeout(this._holdTimer); + } document.removeEventListener("touchmove", this.onReactTouchMove); document.removeEventListener("touchend", this.onReactTouchEnd); document.addEventListener("touchmove", this.onReactTouchMove); @@ -137,8 +207,72 @@ export default class GestureOverlay extends Touchable { } } + onReactHoldTouchMove = (e: TouchEvent) => { + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.addEventListener("touchmove", this.onReactHoldTouchMove); + document.addEventListener("touchend", this.onReactHoldTouchEnd); + const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + } + document.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldMove", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e + } + }) + ); + } + + onReactHoldTouchEnd = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + this._holdTimer = undefined; + } + document.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldEnd", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e + } + }) + ); + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt) { + if (this.prevPoints.has(pt.identifier)) { + this.prevPoints.delete(pt.identifier); + } + } + } + + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + + e.stopPropagation(); + } + + onReactTouchMove = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); + this._holdTimer && clearTimeout(this._holdTimer); + this._holdTimer = undefined; + document.dispatchEvent( new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchMove", { @@ -156,6 +290,9 @@ export default class GestureOverlay extends Touchable { onReactTouchEnd = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); + this._holdTimer && clearTimeout(this._holdTimer); + this._holdTimer = undefined; + document.dispatchEvent( new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchEnd", { @@ -186,6 +323,7 @@ export default class GestureOverlay extends Touchable { } handleHandDown = async (e: React.TouchEvent) => { + this._holdTimer && clearTimeout(this._holdTimer); const fingers = new Array<React.Touch>(); for (let i = 0; i < e.touches.length; i++) { const pt: any = e.touches.item(i); @@ -216,13 +354,16 @@ export default class GestureOverlay extends Touchable { console.log("not hand"); } this.pointerIdentifier = pointer?.identifier; - runInAction(() => this._pointerY = pointer?.clientY); - if (thumb.identifier === this.thumbIdentifier) { - this._thumbX = thumb.clientX; - this._thumbY = thumb.clientY; - this._hands.set(thumb.identifier, fingers); - return; - } + runInAction(() => { + this._pointerY = pointer?.clientY; + if (thumb.identifier === this.thumbIdentifier) { + this._thumbX = thumb.clientX; + this._thumbY = thumb.clientY; + this._hands.set(thumb.identifier, fingers); + return; + } + }); + this.thumbIdentifier = thumb?.identifier; this._hands.set(thumb.identifier, fingers); const others = fingers.filter(f => f !== thumb); @@ -232,10 +373,14 @@ export default class GestureOverlay extends Touchable { const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc); if (thumbDoc) { runInAction(() => { + RadialMenu.Instance._display = false; + this._inkToTextDoc = FieldValue(Cast(thumbDoc.inkToTextDoc, Doc)); this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; this._thumbY = thumb.clientY; - this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />; + this._menuX = thumb.clientX + 50; + this._menuY = thumb.clientY; + this._palette = <HorizontalPalette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />; }); } @@ -273,11 +418,33 @@ export default class GestureOverlay extends Touchable { for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && pt.identifier === this.thumbIdentifier && this._thumbX && this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > 20) { - this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); - this._thumbX = pt.clientX; + if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) { + if (this._thumbX && this._thumbY) { + const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY); + if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) { + if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) { + this._selectedIndex = Math.min(Math.max(-1, (-Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1)), this._possibilities.length - 1); + } + } + else if (this._thumbDoc) { + if (Math.abs(pt.clientX - this._thumbX) > (15 * window.devicePixelRatio)) { + this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + this._thumbX = pt.clientX; + } + } } + + // if (this._thumbX && this._thumbDoc) { + // if (Math.abs(pt.clientX - this._thumbX) > 30) { + // this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + // this._thumbX = pt.clientX; + // } + // } + // if (this._thumbY && this._inkToTextDoc) { + // if (Math.abs(pt.clientY - this._thumbY) > 20) { + // this._selectedIndex = Math.min(Math.max(0, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1); + // } + // } } if (pt && pt.identifier === this.pointerIdentifier) { this._pointerY = pt.clientY; @@ -293,6 +460,24 @@ export default class GestureOverlay extends Touchable { this._palette = undefined; this.thumbIdentifier = undefined; this._thumbDoc = undefined; + + let scriptWorked = false; + if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) { + const selectedButton = this._possibilities[this._selectedIndex]; + if (selectedButton) { + selectedButton.props.onClick(); + scriptWorked = true; + } + } + + if (!scriptWorked) { + this._strokes.forEach(s => { + this.dispatchGesture(GestureUtils.Gestures.Stroke, s); + }); + } + this._strokes = []; + this._points = []; + this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } } @@ -317,6 +502,22 @@ export default class GestureOverlay extends Touchable { this._points.push({ X: e.clientX, Y: e.clientY }); e.stopPropagation(); e.preventDefault(); + + + if (this._points.length > 1) { + const B = this.svgBounds; + const initialPoint = this._points[0.]; + const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { + switch (this.Tool) { + case ToolglassTools.RadialMenu: + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + //this.handle1PointerHoldStart(e); + } + } + } } } @@ -333,8 +534,10 @@ export default class GestureOverlay extends Touchable { this._d1 = doc; } else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) { - DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link"); - actionPerformed = true; + if (this._d1.type !== "ink" && doc.type !== "ink") { + DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link"); + actionPerformed = true; + } } }; const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture", @@ -358,12 +561,51 @@ export default class GestureOverlay extends Touchable { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - const xInGlass = points[0].X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && points[0].X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; - const yInGlass = points[0].Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && points[0].Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) { + const { selectedColor, selectedWidth } = InkingControl.Instance; + DocServer.Mobile.dispatchGesturePoints({ + points: this._points, + bounds: B, + color: selectedColor, + width: selectedWidth + }); + } + + const initialPoint = this._points[0.]; + const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { case ToolglassTools.InkToText: + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this._strokes.push(new Array(...this._points)); + this._points = []; + CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { + console.log(results); + const wordResults = results.filter((r: any) => r.category === "line"); + const possibilities: string[] = []; + for (const wR of wordResults) { + console.log(wR); + if (wR?.recognizedText) { + possibilities.push(wR?.recognizedText); + } + possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString)); + } + console.log(possibilities); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + runInAction(() => { + this._possibilities = possibilities.map(p => + <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />); + }); + }); + break; + case ToolglassTools.IgnoreGesture: + this.dispatchGesture(GestureUtils.Gestures.Stroke); + this._points = []; break; } } @@ -373,16 +615,15 @@ export default class GestureOverlay extends Touchable { if (result && result.Score > 0.7) { switch (result.Name) { case GestureUtils.Gestures.Box: - const target = document.elementFromPoint(this._points[0].X, this._points[0].Y); - target?.dispatchEvent(new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture", - { - bubbles: true, - detail: { - points: this._points, - gesture: GestureUtils.Gestures.Box, - bounds: B - } - })); + this.dispatchGesture(GestureUtils.Gestures.Box); + actionPerformed = true; + break; + case GestureUtils.Gestures.StartBracket: + this.dispatchGesture(GestureUtils.Gestures.StartBracket); + actionPerformed = true; + break; + case GestureUtils.Gestures.EndBracket: + this.dispatchGesture("endbracket"); actionPerformed = true; break; case GestureUtils.Gestures.Line: @@ -398,19 +639,7 @@ export default class GestureOverlay extends Touchable { } if (!actionPerformed) { - const target = document.elementFromPoint(this._points[0].X, this._points[0].Y); - target?.dispatchEvent( - new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture", - { - bubbles: true, - detail: { - points: this._points, - gesture: GestureUtils.Gestures.Stroke, - bounds: B - } - } - ) - ); + this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; } } @@ -419,9 +648,26 @@ export default class GestureOverlay extends Touchable { document.removeEventListener("pointerup", this.onPointerUp); } - @computed get svgBounds() { - const xs = this._points.map(p => p.X); - const ys = this._points.map(p => p.Y); + dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => { + const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y); + target?.dispatchEvent( + new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture", + { + bubbles: true, + detail: { + points: stroke ?? this._points, + gesture: gesture as any, + bounds: this.getBounds(stroke ?? this._points), + text: data + } + } + ) + ); + } + + getBounds = (stroke: InkData) => { + const xs = stroke.map(p => p.X); + const ys = stroke.map(p => p.Y); const right = Math.max(...xs); const left = Math.min(...xs); const bottom = Math.max(...ys); @@ -429,28 +675,28 @@ export default class GestureOverlay extends Touchable { return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; } - @computed get currentStroke() { - if (this._points.length <= 1) { - return (null); - } - - const B = this.svgBounds; - - return ( - <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}> - {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} - </svg> - ); + @computed get svgBounds() { + return this.getBounds(this._points); } @computed get elements() { + const B = this.svgBounds; return [ this.props.children, this._palette, - this.currentStroke + [this._strokes.map(l => { + const b = this.getBounds(l); + return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}> + {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)} + </svg>; + }), + this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}> + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)} + </svg>] ]; } - + screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1); + return300 = () => 300; @action public openFloatingDoc = (doc: Doc) => { this._clipboardDoc = @@ -460,13 +706,16 @@ export default class GestureOverlay extends Touchable { LibraryPath={emptyPath} addDocument={undefined} addDocTab={returnFalse} + rootSelected={returnTrue} pinToPres={emptyFunction} onClick={undefined} removeDocument={undefined} - ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)} + ScreenToLocalTransform={this.screenToLocalTransform} ContentScaling={returnOne} - PanelWidth={() => 300} - PanelHeight={() => 300} + PanelWidth={this.return300} + PanelHeight={this.return300} + NativeHeight={returnZero} + NativeWidth={returnZero} renderDepth={0} backgroundColor={returnEmptyString} focus={emptyFunction} @@ -475,8 +724,6 @@ export default class GestureOverlay extends Touchable { bringToFront={emptyFunction} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} />; } @@ -485,21 +732,28 @@ export default class GestureOverlay extends Touchable { this._clipboardDoc = undefined; } + @action + enableMobileInkOverlay = (content: MobileInkOverlayContent) => { + this.showMobileInkOverlay = content.enableOverlay; + } + render() { return ( <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}> + {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>} {this.elements} + <div className="clipboardDoc-cont" style={{ - transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`, height: this.height, width: this.height, pointerEvents: this._clipboardDoc ? "unset" : "none", touchAction: this._clipboardDoc ? "unset" : "none", + transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`, }}> {this._clipboardDoc} </div> <div className="filter-cont" style={{ - transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`, + transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height}px)`, height: this.height, width: this.height, pointerEvents: "none", @@ -507,12 +761,20 @@ export default class GestureOverlay extends Touchable { display: this.showBounds ? "unset" : "none", }}> </div> - </div >); + <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} /> + {/* <div className="pointerBubbles"> + {this._pointers.map(p => <div className="bubble" style={{ translate: `transform(${p.clientX}px, ${p.clientY}px)` }}></div>)} + </div> */} + </div>); } } +// export class + export enum ToolglassTools { InkToText = "inktotext", + IgnoreGesture = "ignoregesture", + RadialMenu = "radialmenu", None = "none", } @@ -530,7 +792,10 @@ Scripting.addGlobal(function setPen(width: any, color: any) { }); Scripting.addGlobal(function resetPen() { runInAction(() => { - GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)"; - GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5; + GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)"; + GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 2; }); +}); +Scripting.addGlobal(function createText(text: any, x: any, y: any) { + GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); });
\ No newline at end of file diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d0900251d..52801b570 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -79,6 +79,7 @@ export default class KeyManager { } SelectionManager.DeselectAll(); DictationManager.Controls.stop(); + // RecommendationsBox.Instance.closeMenu(); SharingManager.Instance.close(); break; case "delete": @@ -105,14 +106,14 @@ export default class KeyManager { }); private shift = async (keyname: string) => { - let stopPropagation = false; - let preventDefault = false; + const stopPropagation = false; + const preventDefault = false; switch (keyname) { - case "~": - DictationManager.Controls.listen({ useOverlay: true, tryExecute: true }); - stopPropagation = true; - preventDefault = true; + // case "~": + // DictationManager.Controls.listen({ useOverlay: true, tryExecute: true }); + // stopPropagation = true; + // preventDefault = true; } return { diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 5cd3c265d..645c7fa54 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -85,7 +85,6 @@ export class InkingControl { Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); }); Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); }); Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); }); -Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); }); Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); }); Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); }); Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); }); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index f315ce12a..a791eed40 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -29,13 +29,13 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu @computed get PanelHeight() { return this.props.PanelHeight(); } private analyzeStrokes = () => { - const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? []; + const data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? []; CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.Document, ["inkAnalysis", "handwriting"], [data]); } render() { TraceMobx(); - const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? []; + const data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? []; const xs = data.map(p => p.X); const ys = data.map(p => p.Y); const left = Math.min(...xs); diff --git a/src/client/views/KeyphraseQueryView.scss b/src/client/views/KeyphraseQueryView.scss new file mode 100644 index 000000000..ac715e5e7 --- /dev/null +++ b/src/client/views/KeyphraseQueryView.scss @@ -0,0 +1,8 @@ +.fading { + animation: fanOut 1s +} + +@keyframes fanOut { + from {opacity: 0;} + to {opacity: 1;} +}
\ No newline at end of file diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx new file mode 100644 index 000000000..1dc156968 --- /dev/null +++ b/src/client/views/KeyphraseQueryView.tsx @@ -0,0 +1,35 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import "./KeyphraseQueryView.scss"; + +// tslint:disable-next-line: class-name +export interface KP_Props { + keyphrases: string; +} + +@observer +export class KeyphraseQueryView extends React.Component<KP_Props>{ + constructor(props: KP_Props) { + super(props); + console.log("FIRST KEY PHRASE: ", props.keyphrases[0]); + } + + render() { + const kps = this.props.keyphrases.toString(); + const keyterms = this.props.keyphrases.split(','); + return ( + <div> + <h5>Select queries to send:</h5> + <form> + {keyterms.map((kp: string) => { + //return (<p>{"-" + kp}</p>); + return (<p><label> + <input name="query" type="radio" /> + <span>{kp}</span> + </label></p>); + })} + </form> + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index b21eb9c8f..6d705aa44 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,6 +5,7 @@ import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { DocServer } from "../DocServer"; import { AssignAllExtensions } from "../../extensions/General/Extensions"; +process.env.HANDWRITING = "61088486d76c4b12ba578775a5f55422"; AssignAllExtensions(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f30d17d94..d7c769ddd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,49 +1,46 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { - faFileAlt, faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, - faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard, faVideo, faTerminal, -} from '@fortawesome/free-solid-svg-icons'; +import { faTerminal, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; -import { Doc, DocListCast, Field, FieldResult, Opt } from '../../new_fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { List } from '../../new_fields/List'; import { listSpec } from '../../new_fields/Schema'; -import { Cast, FieldValue, StrCast, BoolCast } from '../../new_fields/Types'; +import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; +import { TraceMobx } from '../../new_fields/util'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils, emptyPath } from '../../Utils'; +import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; +import RichTextMenu from '../util/RichTextMenu'; +import { Scripting } from '../util/Scripting'; +import SettingsManager from '../util/SettingsManager'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; -import { CollectionLinearView } from './collections/CollectionLinearView'; -import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; +import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; +import { CollectionLinearView } from './collections/CollectionLinearView'; +import { CollectionView, CollectionViewType } from './collections/CollectionView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; +import GestureOverlay from './GestureOverlay'; import KeyManager from './GlobalKeyHandler'; import "./MainView.scss"; import { MainViewNotifs } from './MainViewNotifs'; +import { AudioBox } from './nodes/AudioBox'; import { DocumentView } from './nodes/DocumentView'; +import { RadialMenu } from './nodes/RadialMenu'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; -import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; -import GestureOverlay from './GestureOverlay'; -import { Scripting } from '../util/Scripting'; -import { AudioBox } from './nodes/AudioBox'; -import SettingsManager from '../util/SettingsManager'; -import { TraceMobx } from '../../new_fields/util'; -import { RadialMenu } from './nodes/RadialMenu'; -import RichTextMenu from '../util/RichTextMenu'; -import { DocumentType } from '../documents/DocumentTypes'; @observer export class MainView extends React.Component { @@ -52,6 +49,7 @@ export class MainView extends React.Component { private _flyoutSizeOnDown = 0; private _urlState: HistoryUtil.DocUrl; private _docBtnRef = React.createRef<HTMLDivElement>(); + private _mainViewRef = React.createRef<HTMLDivElement>(); @observable private _panelWidth: number = 0; @observable private _panelHeight: number = 0; @@ -103,6 +101,7 @@ export class MainView extends React.Component { } } + library.add(faTerminal); library.add(faFileAlt); library.add(faStickyNote); library.add(faFont); @@ -278,7 +277,7 @@ export class MainView extends React.Component { defaultBackgroundColors = (doc: Doc) => { if (this.darkScheme) { switch (doc.type) { - case DocumentType.TEXT || DocumentType.BUTTON: return "#2d2d2d"; + case DocumentType.RTF || DocumentType.LABEL: return "#2d2d2d"; case DocumentType.LINK: case DocumentType.COL: { if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; @@ -287,8 +286,8 @@ export class MainView extends React.Component { } } else { switch (doc.type) { - case DocumentType.TEXT: return "#f1efeb"; - case DocumentType.BUTTON: return "lightgray"; + case DocumentType.RTF: return "#f1efeb"; + case DocumentType.LABEL: return "lightgray"; case DocumentType.LINK: case DocumentType.COL: { if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray"; @@ -304,11 +303,14 @@ export class MainView extends React.Component { addDocument={undefined} addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} + rootSelected={returnTrue} onClick={undefined} backgroundColor={this.defaultBackgroundColors} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={this.getPWidth} PanelHeight={this.getPHeight} renderDepth={0} @@ -318,8 +320,6 @@ export class MainView extends React.Component { bringToFront={emptyFunction} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} />; } @computed get dockingContent() { @@ -402,12 +402,15 @@ export class MainView extends React.Component { DataDoc={undefined} LibraryPath={emptyPath} addDocument={undefined} + rootSelected={returnTrue} addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} removeDocument={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={this.flyoutWidthFunc} PanelHeight={this.getPHeight} renderDepth={0} @@ -417,10 +420,7 @@ export class MainView extends React.Component { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> - </DocumentView> + ContainingCollectionDoc={undefined} /> </div> <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${this._buttonBarHeight}px)`, width: "100%", overflow: "visible" }}> <DocumentView @@ -430,6 +430,9 @@ export class MainView extends React.Component { addDocument={undefined} addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} + NativeHeight={returnZero} + NativeWidth={returnZero} + rootSelected={returnTrue} removeDocument={returnFalse} onClick={undefined} ScreenToLocalTransform={this.mainContainerXf} @@ -443,10 +446,7 @@ export class MainView extends React.Component { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> - </DocumentView> + ContainingCollectionDoc={undefined} /> <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}> Settings </button> @@ -516,7 +516,10 @@ export class MainView extends React.Component { DataDoc={undefined} LibraryPath={emptyPath} fieldKey={"data"} + dropAction={"alias"} annotationsKey={""} + rootSelected={returnTrue} + bringToFront={emptyFunction} select={emptyFunction} active={returnFalse} isSelected={returnFalse} @@ -529,6 +532,8 @@ export class MainView extends React.Component { onClick={undefined} ScreenToLocalTransform={this.buttonBarXf} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={this.flyoutWidthFunc} PanelHeight={this.getContentsHeight} renderDepth={0} @@ -541,8 +546,16 @@ export class MainView extends React.Component { return (null); } + get mainViewElement() { + return document.getElementById("mainView-container"); + } + + get mainViewRef() { + return this._mainViewRef; + } + render() { - return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}> + return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}> <DictationOverlay /> <SharingManager /> <SettingsManager /> diff --git a/src/client/views/OCRUtils.ts b/src/client/views/OCRUtils.ts new file mode 100644 index 000000000..282ec770e --- /dev/null +++ b/src/client/views/OCRUtils.ts @@ -0,0 +1,7 @@ +// import tesseract from "node-tesseract-ocr"; +// const tesseract = require("node-tesseract"); + + +export namespace OCRUtils { + +} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 220efd4a8..4000cade5 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -174,6 +174,7 @@ export class OverlayView extends React.Component { Document={d} LibraryPath={emptyPath} ChromeHeight={returnZero} + rootSelected={returnTrue} // isSelected={returnFalse} // select={emptyFunction} // layoutKey={"layout"} @@ -181,6 +182,8 @@ export class OverlayView extends React.Component { addDocument={undefined} removeDocument={undefined} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={returnOne} PanelHeight={returnOne} ScreenToLocalTransform={Transform.Identity} @@ -192,9 +195,7 @@ export class OverlayView extends React.Component { addDocTab={returnFalse} pinToPres={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} /> + ContainingCollectionDoc={undefined}/> </div>; }); } diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss index 4513de2b0..0ec879288 100644 --- a/src/client/views/Palette.scss +++ b/src/client/views/Palette.scss @@ -1,13 +1,14 @@ .palette-container { .palette-thumb { touch-action: pan-x; - overflow: scroll; position: absolute; - width: 90px; height: 70px; + overflow: hidden; .palette-thumbContent { transition: transform .3s; + width: max-content; + overflow: hidden; .collectionView { overflow: visible; @@ -17,5 +18,13 @@ } } } + + .palette-cover { + width: 50px; + height: 50px; + position: absolute; + bottom: 0; + border: 1px solid black; + } } }
\ No newline at end of file diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 10aac96a0..63744cb50 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -1,23 +1,12 @@ +import { IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; import * as React from "react"; -import "./Palette.scss"; -import { PointData } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; -import { Docs } from "../documents/Documents"; -import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; -import { List } from "../../new_fields/List"; -import { DocumentView } from "./nodes/DocumentView"; -import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { NumCast } from "../../new_fields/Types"; +import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils"; import { Transform } from "../util/Transform"; -import { computed, action, IReactionDisposer, reaction, observable } from "mobx"; -import { FieldValue, Cast, NumCast } from "../../new_fields/Types"; -import { observer } from "mobx-react"; -import { DocumentContentsView } from "./nodes/DocumentContentsView"; -import { CollectionStackingView } from "./collections/CollectionStackingView"; -import { CollectionView } from "./collections/CollectionView"; -import { CollectionSubView, SubCollectionViewProps } from "./collections/CollectionSubView"; -import { makeInterface } from "../../new_fields/Schema"; -import { documentSchema } from "../../new_fields/documentSchemas"; +import { DocumentView } from "./nodes/DocumentView"; +import "./Palette.scss"; export interface PaletteProps { x: number; @@ -40,7 +29,7 @@ export default class Palette extends React.Component<PaletteProps> { } componentWillUnmount = () => { - this._selectedDisposer && this._selectedDisposer(); + this._selectedDisposer?.(); } render() { @@ -54,11 +43,14 @@ export default class Palette extends React.Component<PaletteProps> { LibraryPath={emptyPath} addDocument={undefined} addDocTab={returnFalse} + rootSelected={returnTrue} pinToPres={emptyFunction} removeDocument={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={() => window.screen.width} PanelHeight={() => window.screen.height} renderDepth={0} @@ -68,10 +60,8 @@ export default class Palette extends React.Component<PaletteProps> { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> - </DocumentView> + ContainingCollectionDoc={undefined} /> + <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div> </div> </div> </div> diff --git a/src/client/views/RecommendationsBox.scss b/src/client/views/RecommendationsBox.scss new file mode 100644 index 000000000..7d89042a4 --- /dev/null +++ b/src/client/views/RecommendationsBox.scss @@ -0,0 +1,69 @@ +@import "globalCssVariables"; + +.rec-content *{ + display: inline-block; + margin: auto; + width: 50; + height: 150px; + border: 1px dashed grey; + padding: 10px 10px; +} + +.rec-content { + float: left; + width: inherit; + align-content: center; +} + +.rec-scroll { + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + pointer-events: all; + // display: flex; + z-index: 10000; + box-shadow: gray 0.2vw 0.2vw 0.4vw; + // flex-direction: column; + background: whitesmoke; + padding-bottom: 10px; + padding-top: 20px; + // border-radius: 15px; + border: solid #BBBBBBBB 1px; + width: 100%; + text-align: center; + // max-height: 250px; + height: 100%; + text-transform: uppercase; + color: grey; + letter-spacing: 2px; +} + +.content { + padding: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.image-background { + pointer-events: none; + background-color: transparent; + width: 50%; + text-align: center; + margin-left: 5px; +} + +// bcz: UGH!! Can't have global settings like this!!! +// img{ +// width: 100%; +// height: 100%; +// } + +.score { + // margin-left: 15px; + width: 50%; + height: 100%; + text-align: center; + margin-left: 10px; +} diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx new file mode 100644 index 000000000..e66fd3eb4 --- /dev/null +++ b/src/client/views/RecommendationsBox.tsx @@ -0,0 +1,200 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { observable, action, computed, runInAction } from "mobx"; +import Measure from "react-measure"; +import "./RecommendationsBox.scss"; +import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { DocumentIcon } from "./nodes/DocumentIcon"; +import { StrCast, NumCast } from "../../new_fields/Types"; +import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { DocumentView } from "./nodes/DocumentView"; +import { DocumentType } from '../documents/DocumentTypes'; +import { ClientRecommender } from "../ClientRecommender"; +import { DocServer } from "../DocServer"; +import { Id } from "../../new_fields/FieldSymbols"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { DocumentManager } from "../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; +import { DocUtils } from "../documents/Documents"; + +export interface RecProps { + documents: { preview: Doc, similarity: number }[]; + node: Doc; +} + +library.add(faBullseye, faLink); + +@observer +export class RecommendationsBox extends React.Component<FieldViewProps> { + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); } + + // @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable.shallow private _docViews: JSX.Element[] = []; + // @observable private _documents: { preview: Doc, score: number }[] = []; + private previewDocs: Doc[] = []; + + constructor(props: FieldViewProps) { + super(props); + } + + @action + private DocumentIcon(doc: Doc) { + const layoutresult = StrCast(doc.type); + let renderDoc = doc; + //let box: number[] = []; + if (layoutresult.indexOf(DocumentType.COL) !== -1) { + renderDoc = Doc.MakeDelegate(renderDoc); + } + const returnXDimension = () => 150; + const returnYDimension = () => 150; + const scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); + //let scale = () => 1; + const newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + newRenderDoc.height = NumCast(this.props.Document.documentIconHeight); + newRenderDoc.autoHeight = false; + const docview = <div> + <DocumentView + fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1} + Document={newRenderDoc} + addDocument={returnFalse} + LibraryPath={emptyPath} + removeDocument={returnFalse} + rootSelected={returnFalse} + ScreenToLocalTransform={Transform.Identity} + addDocTab={returnFalse} + pinToPres={returnFalse} + renderDepth={1} + NativeHeight={returnZero} + NativeWidth={returnZero} + PanelWidth={returnXDimension} + PanelHeight={returnYDimension} + focus={emptyFunction} + backgroundColor={returnEmptyString} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + ContentScaling={scale} + /> + </div>; + return docview; + + } + + // @action + // closeMenu = () => { + // this._display = false; + // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); + // this.previewDocs = []; + // } + + // @action + // resetDocuments = () => { + // this._documents = []; + // } + + // @action + // displayRecommendations(x: number, y: number) { + // this._pageX = x; + // this._pageY = y; + // this._display = true; + // } + + static readonly buffer = 20; + + // get pageX() { + // const x = this._pageX; + // if (x < 0) { + // return 0; + // } + // const width = this._width; + // if (x + width > window.innerWidth - RecommendationsBox.buffer) { + // return window.innerWidth - RecommendationsBox.buffer - width; + // } + // return x; + // } + + // get pageY() { + // const y = this._pageY; + // if (y < 0) { + // return 0; + // } + // const height = this._height; + // if (y + height > window.innerHeight - RecommendationsBox.buffer) { + // return window.innerHeight - RecommendationsBox.buffer - height; + // } + // return y; + // } + + // get createDocViews() { + // return DocListCast(this.props.Document.data).map(doc => { + // return ( + // <div className="content"> + // <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background"> + // {this.DocumentIcon(doc)} + // </span> + // <span className="score">{NumCast(doc.score).toFixed(4)}</span> + // <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}> + // <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" /> + // </div> + // <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> + // <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" /> + // </div> + // </div> + // ); + // }); + // } + + componentDidMount() { //TODO: invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set + runInAction(() => { + if (this._docViews.length === 0) { + this._docViews = DocListCast(this.props.Document.data).map(doc => { + return ( + <div className="content"> + <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background"> + {this.DocumentIcon(doc)} + </span> + <span className="score">{NumCast(doc.score).toFixed(4)}</span> + <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}> + <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" /> + </div> + <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", undefined)}> + <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" /> + </div> + </div> + ); + }); + } + }); + } + + render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered. + // if (!this._display) { + // return null; + // } + // let style = { left: this.pageX, top: this.pageY }; + //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + let title = StrCast((this.props.Document.sourceDoc as Doc).title); + if (title.length > 15) { + title = title.substring(0, 15) + "..."; + } + return ( + <div className="rec-scroll"> + <p>Recommendations for "{title}"</p> + {this._docViews} + </div> + ); + } + // + // +}
\ No newline at end of file diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 48f4c8163..f094feb0b 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -74,7 +74,7 @@ export class ScriptBox extends DocAnnotatableComponent<FieldViewProps & ScriptBo } onBlur = () => { - this.overlayDisposer && this.overlayDisposer(); + this.overlayDisposer?.(); } @action @@ -177,6 +177,6 @@ export class ScriptBox extends DocAnnotatableComponent<FieldViewProps & ScriptBo overlayDisposer(); } }} showDocumentIcons />; - overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title }); + overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title }); } } diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx new file mode 100644 index 000000000..799fa9d85 --- /dev/null +++ b/src/client/views/SearchDocBox.tsx @@ -0,0 +1,431 @@ +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +//import "./SearchBoxDoc.scss"; +import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Id } from "../../new_fields/FieldSymbols"; +import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { returnFalse } from "../../Utils"; +import { Docs } from "../documents/Documents"; +import { SearchUtil } from "../util/SearchUtil"; +import { EditableView } from "./EditableView"; +import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { FilterBox } from "./search/FilterBox"; +import { SearchItem } from "./search/SearchItem"; +import React = require("react"); + +export interface RecProps { + documents: { preview: Doc, similarity: number }[]; + node: Doc; + +} + +library.add(faBullseye, faLink); +export const keyPlaceholder = "Query"; + +@observer +export class SearchDocBox extends React.Component<FieldViewProps> { + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchDocBox, fieldKey); } + + // @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable.shallow private _docViews: JSX.Element[] = []; + // @observable private _documents: { preview: Doc, score: number }[] = []; + private previewDocs: Doc[] = []; + + constructor(props: FieldViewProps) { + super(props); + this.editingMetadata = this.editingMetadata || false; + //SearchBox.Instance = this; + this.resultsScrolled = this.resultsScrolled.bind(this); + } + + + @computed + private get editingMetadata() { + return BoolCast(this.props.Document.editingMetadata); + } + + private set editingMetadata(value: boolean) { + this.props.Document.editingMetadata = value; + } + + static readonly buffer = 20; + + componentDidMount() { + runInAction(() => { + console.log("didit" + ); + this.query = StrCast(this.props.Document.searchText); + this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query })); + + }); + if (this.inputRef.current) { + this.inputRef.current.focus(); + runInAction(() => { + this._searchbarOpen = true; + }); + } + } + + @observable + private content: Doc | undefined; + + @action + updateKey = async (newKey: string) => { + this.query = newKey; + if (newKey.length > 1) { + const newdocs = await this.getAllResults(this.query); + const things = newdocs.docs; + console.log(things); + console.log(this.content); + runInAction(() => { + this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }); + }); + console.log(this.content); + } + + + //this.keyRef.current && this.keyRef.current.setIsFocused(false); + //this.query.length === 0 && (this.query = keyPlaceholder); + return true; + } + + @computed + public get query() { + return StrCast(this.props.Document.query); + } + + public set query(value: string) { + this.props.Document.query = value; + } + + @observable private _searchString: string = ""; + @observable private _resultsOpen: boolean = false; + @observable private _searchbarOpen: boolean = false; + @observable private _results: [Doc, string[], string[]][] = []; + private _resultsSet = new Map<Doc, number>(); + @observable private _openNoResults: boolean = false; + @observable private _visibleElements: JSX.Element[] = []; + + private resultsRef = React.createRef<HTMLDivElement>(); + public inputRef = React.createRef<HTMLInputElement>(); + + private _isSearch: ("search" | "placeholder" | undefined)[] = []; + private _numTotalResults = -1; + private _endIndex = -1; + + + private _maxSearchIndex: number = 0; + private _curRequest?: Promise<any> = undefined; + + @action + getViews = async (doc: Doc) => { + const results = await SearchUtil.GetViewsOfDocument(doc); + let toReturn: Doc[] = []; + await runInAction(() => { + toReturn = results; + }); + return toReturn; + } + + @action.bound + onChange(e: React.ChangeEvent<HTMLInputElement>) { + this._searchString = e.target.value; + + this._openNoResults = false; + this._results = []; + this._resultsSet.clear(); + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + this._maxSearchIndex = 0; + } + + enter = async (e: React.KeyboardEvent) => { + console.log(e.key); + if (e.key === "Enter") { + const newdocs = await this.getAllResults(this.query); + console.log(newdocs.docs); + this.content = Docs.Create.TreeDocument(newdocs.docs, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` }); + + } + } + + + @action + submitSearch = async () => { + let query = this._searchString; + query = FilterBox.Instance.getFinalQuery(query); + this._results = []; + this._resultsSet.clear(); + this._isSearch = []; + this._visibleElements = []; + FilterBox.Instance.closeFilter(); + + //if there is no query there should be no result + if (query === "") { + return; + } + else { + this._endIndex = 12; + this._maxSearchIndex = 0; + this._numTotalResults = -1; + await this.getResults(query); + } + + runInAction(() => { + this._resultsOpen = true; + this._searchbarOpen = true; + this._openNoResults = true; + this.resultsScrolled(); + }); + } + + getAllResults = async (query: string) => { + return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); + } + + private get filterQuery() { + const types = FilterBox.Instance.filterTypes; + const includeDeleted = FilterBox.Instance.getDataStatus(); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : ""); + } + + + private NumResults = 25; + private lockPromise?: Promise<void>; + getResults = async (query: string) => { + if (this.lockPromise) { + await this.lockPromise; + } + this.lockPromise = new Promise(async res => { + while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { + this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { + + // happens at the beginning + if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { + this._numTotalResults = res.numFound; + } + + const highlighting = res.highlighting || {}; + const highlightList = res.docs.map(doc => highlighting[doc[Id]]); + const lines = new Map<string, string[]>(); + res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i])); + const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); + const highlights: typeof res.highlighting = {}; + docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); + const filteredDocs = FilterBox.Instance.filterDocsByType(docs); + runInAction(() => { + // this._results.push(...filteredDocs); + filteredDocs.forEach(doc => { + const index = this._resultsSet.get(doc); + const highlight = highlights[doc[Id]]; + const line = lines.get(doc[Id]) || []; + const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); + } + }); + }); + + this._curRequest = undefined; + })); + this._maxSearchIndex += this.NumResults; + + await this._curRequest; + } + this.resultsScrolled(); + res(); + }); + return this.lockPromise; + } + + collectionRef = React.createRef<HTMLSpanElement>(); + startDragCollection = async () => { + const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString)); + const filtered = FilterBox.Instance.filterDocsByType(res.docs); + // console.log(this._results) + const docs = filtered.map(doc => { + const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); + if (isProto) { + return Doc.MakeDelegate(doc); + } else { + return Doc.MakeAlias(doc); + } + }); + let x = 0; + let y = 0; + for (const doc of docs.map(d => Doc.Layout(d))) { + doc.x = x; + doc.y = y; + const size = 200; + const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1); + if (aspect > 1) { + doc._height = size; + doc._width = size / aspect; + } else if (aspect > 0) { + doc._width = size; + doc._height = size * aspect; + } else { + doc._width = size; + doc._height = size; + } + x += 250; + if (x > 1000) { + x = 0; + y += 300; + } + } + //return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); + return Docs.Create.QueryDocument({ _width: 200, _height: 400, searchText: this._searchString, title: `Query Docs: "${this._searchString}"` }); + } + + @action.bound + openSearch(e: React.SyntheticEvent) { + e.stopPropagation(); + this._openNoResults = false; + FilterBox.Instance.closeFilter(); + this._resultsOpen = true; + this._searchbarOpen = true; + FilterBox.Instance._pointerTime = e.timeStamp; + } + + @action.bound + closeSearch = () => { + FilterBox.Instance.closeFilter(); + this.closeResults(); + this._searchbarOpen = false; + } + + @action.bound + closeResults() { + this._resultsOpen = false; + this._results = []; + this._resultsSet.clear(); + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + } + + @action + resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { + if (!this.resultsRef.current) return; + const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0; + const itemHght = 53; + const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); + const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght))); + + this._endIndex = endIndex === -1 ? 12 : endIndex; + + if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { + this._visibleElements = [<div className="no-result">No Search Results</div>]; + return; + } + + if (this._numTotalResults <= this._maxSearchIndex) { + this._numTotalResults = this._results.length; + } + + // only hit right at the beginning + // visibleElements is all of the elements (even the ones you can't see) + else if (this._visibleElements.length !== this._numTotalResults) { + // undefined until a searchitem is put in there + this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + // indicates if things are placeholders + this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + } + + for (let i = 0; i < this._numTotalResults; i++) { + //if the index is out of the window then put a placeholder in + //should ones that have already been found get set to placeholders? + if (i < startIndex || i > endIndex) { + if (this._isSearch[i] !== "placeholder") { + this._isSearch[i] = "placeholder"; + this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; + } + } + else { + if (this._isSearch[i] !== "search") { + let result: [Doc, string[], string[]] | undefined = undefined; + if (i >= this._results.length) { + this.getResults(this._searchString); + if (i < this._results.length) result = this._results[i]; + if (result) { + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; + this._isSearch[i] = "search"; + } + } + else { + result = this._results[i]; + if (result) { + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; + this._isSearch[i] = "search"; + } + } + } + } + } + if (this._maxSearchIndex >= this._numTotalResults) { + this._visibleElements.length = this._results.length; + this._isSearch.length = this._results.length; + } + } + + @computed + get resFull() { return this._numTotalResults <= 8; } + + @computed + get resultHeight() { return this._numTotalResults * 70; } + + render() { + const isEditing = this.editingMetadata; + return !this.content ? (null) : ( + <div style={{ pointerEvents: "all" }}> + <ContentFittingDocumentView {...this.props} + Document={this.content} + rootSelected={returnFalse} + getTransform={this.props.ScreenToLocalTransform}> + </ContentFittingDocumentView> + <div + style={{ + position: "absolute", + right: 0, + width: 20, + height: 20, + background: "black", + pointerEvents: "all", + opacity: 1, + transition: "0.4s opacity ease", + zIndex: 99, + top: 0, + }} + title={"Add Metadata"} + onClick={action(() => this.editingMetadata = !this.editingMetadata)} + /> + <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease", position: "absolute", top: 0, left: 0, height: 20, width: "-webkit-fill-available" }}> + <EditableView + contents={this.query} + SetValue={this.updateKey} + GetValue={() => ""} + /> + </div> + </div > + ); + } + +}
\ No newline at end of file diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 5029b4074..8fb8e7516 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,4 +1,4 @@ -import { action, observable, runInAction, ObservableSet } from "mobx"; +import { action, observable, runInAction, ObservableSet, trace, computed } from "mobx"; import { observer } from "mobx-react"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; @@ -7,8 +7,13 @@ import { DocumentView } from "./nodes/DocumentView"; import { Template } from "./Templates"; import React = require("react"); import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Docs, } from "../documents/Documents"; import { StrCast, Cast } from "../../new_fields/Types"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { CollectionTreeView } from "./collections/CollectionTreeView"; +import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; +import { Scripting } from "../util/Scripting"; @observer class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> { @@ -50,7 +55,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @observable private _hidden: boolean = true; toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => { - this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout));//.setCustomView(e.target.checked, layout)); + this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout)); + } + toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => { + this.props.docViews.map(dv => dv.switchViews(false, "layout")); } toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => { @@ -94,27 +102,84 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))). filter(key => key.startsWith("layout_")). map(key => runInAction(() => this._addedKeys.add(key.replace("layout_", "")))); - DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => { - if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title)) { - runInAction(() => this._addedKeys.add(StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title))); - } - }); } + return100 = () => 100; + @computed get scriptField() { + return ScriptField.MakeScript("switchView(firstDoc, this)", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name }, + { firstDoc: this.props.docViews[0].props.Document }); + } render() { - const layout = Doc.Layout(this.props.docViews[0].Document); + const firstDoc = this.props.docViews[0].props.Document; + const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", ""); + const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null); + const noteTypes = DocListCast(noteTypesDoc?.data); + const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data); + const layout = Doc.Layout(firstDoc); const templateMenu: Array<JSX.Element> = []; this.props.templates.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />)); - templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={this.props.docViews[0].Document._showAudio ? true : false} toggle={this.toggleAudio} />); - templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docViews[0].Document.z ? true : false} toggle={this.toggleFloat} />); + templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />); + templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />); templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />); - this._addedKeys && Array.from(this._addedKeys).map(layout => - templateMenu.push(<OtherToggle key={layout} name={layout} checked={StrCast(this.props.docViews[0].Document.layoutKey, "layout") === "layout_" + layout} toggle={e => this.toggleLayout(e, layout)} />) - ); + templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />); + if (noteTypesDoc) { + addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(this)`)); + this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push( + <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />)); + } return <ul className="template-list" style={{ display: "block" }}> + <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} /> {templateMenu} - <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}></input> + <CollectionTreeView + Document={Doc.UserDoc().templateDocs as Doc} + CollectionView={undefined} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + rootSelected={returnFalse} + onCheckedClick={this.scriptField!} + onChildClick={this.scriptField!} + LibraryPath={emptyPath} + dropAction={undefined} + active={returnTrue} + ContentScaling={returnOne} + bringToFront={emptyFunction} + focus={emptyFunction} + whenActiveChanged={emptyFunction} + ScreenToLocalTransform={Transform.Identity} + isSelected={returnFalse} + pinToPres={emptyFunction} + select={emptyFunction} + renderDepth={1} + addDocTab={returnFalse} + NativeHeight={returnZero} + NativeWidth={returnZero} + PanelWidth={this.return100} + PanelHeight={this.return100} + treeViewHideHeaderFields={true} + annotationsKey={""} + dontRegisterView={true} + fieldKey={"data"} + moveDocument={(doc: Doc) => false} + removeDocument={(doc: Doc) => false} + addDocument={(doc: Doc) => false} /> </ul>; } -}
\ No newline at end of file +} + +Scripting.addGlobal(function switchView(doc: Doc, template: Doc) { + if (template.dragFactory) { + template = Cast(template.dragFactory, Doc, null); + } + const templateTitle = StrCast(template?.title); + return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template); +}); + +Scripting.addGlobal(function templateIsUsed(templateDoc: Doc) { + const firstDoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0].props.Document : undefined; + if (firstDoc) { + const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title); + return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked'; + } + return false; +});
\ No newline at end of file diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx new file mode 100644 index 000000000..969605be9 --- /dev/null +++ b/src/client/views/TouchScrollableMenu.tsx @@ -0,0 +1,59 @@ +import React = require("react"); +import { computed } from "mobx"; +import { observer } from "mobx-react"; + +export interface TouchScrollableMenuProps { + options: JSX.Element[]; + bounds: { + right: number; + left: number; + bottom: number; + top: number; + width: number; + height: number; + }; + selectedIndex: number; + x: number; + y: number; +} + +export interface TouchScrollableMenuItemProps { + text: string; + onClick: () => any; +} + +@observer +export default class TouchScrollableMenu extends React.Component<TouchScrollableMenuProps> { + + @computed + private get possibilities() { return this.props.options; } + + @computed + private get selectedIndex() { return this.props.selectedIndex; } + + render() { + return ( + <div className="inkToTextDoc-cont" style={{ + transform: `translate(${this.props.x}px, ${this.props.y}px)`, + width: 300, + height: this.possibilities.length * 25 + }}> + <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-this.selectedIndex * 25}px)` }}> + {this.possibilities} + </div> + <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}> + </div> + </div> + ); + } +} + +export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps>{ + render() { + return ( + <div className="menuItem-cont" onClick={this.props.onClick}> + {this.props.text} + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 7800c4019..08310786b 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -8,9 +8,11 @@ const HOLD_DURATION = 1000; export abstract class Touchable<T = {}> extends React.Component<T> { //private holdTimer: NodeJS.Timeout | undefined; - private holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer; + protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _touchDrag: boolean = false; @@ -26,6 +28,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => { + const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen @@ -39,7 +42,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) { // pen is also a touch, but with a radius of 0.5 (at least with the surface pens) // and this seems to be the only way of differentiating pen and touch on touch events - if (pt.radiusX > 1 && pt.radiusY > 1) { + if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) { this.prevPoints.set(pt.identifier, pt); } } @@ -65,8 +68,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION); - // e.stopPropagation(); + // console.log(this.holdTimer); // console.log(this.holdTimer); break; case 2: @@ -91,11 +93,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> { // if we're not actually moving a lot, don't consider it as dragging yet if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; - if (this.holdTimer) { - console.log("CLEAR"); - clearTimeout(this.holdTimer); - // this.holdTimer = undefined; - } // console.log(myTouches.length); switch (myTouches.length) { case 1: @@ -127,10 +124,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> { } } } - if (this.holdTimer) { - clearTimeout(this.holdTimer); - console.log("clear"); - } this._touchDrag = false; te.stopPropagation(); @@ -174,10 +167,16 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this.addEndListeners(); } - handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { e.stopPropagation(); - e.preventDefault(); + me.touchEvent.stopPropagation(); this.removeMoveListeners(); + this.removeEndListeners(); + this.removeHoldMoveListeners(); + this.removeHoldEndListeners(); + this.addHoldMoveListeners(); + this.addHoldEndListeners(); + } addMoveListeners = () => { @@ -200,6 +199,44 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this.endDisposer && this.endDisposer(); } + addHoldMoveListeners = () => { + const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); + document.addEventListener("dashOnTouchHoldMove", handler); + this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler); + } + + addHoldEndListeners = () => { + const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); + document.addEventListener("dashOnTouchHoldEnd", handler); + this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler); + } + + removeHoldMoveListeners = () => { + this.holdMoveDisposer && this.holdMoveDisposer(); + } + + removeHoldEndListeners = () => { + this.holdEndDisposer && this.holdEndDisposer(); + } + + + handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { + // e.stopPropagation(); + // me.touchEvent.stopPropagation(); + } + + + handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { + e.stopPropagation(); + me.touchEvent.stopPropagation(); + this.removeHoldMoveListeners(); + this.removeHoldEndListeners(); + + me.touchEvent.stopPropagation(); + me.touchEvent.preventDefault(); + } + + handleHandDown = (e: React.TouchEvent) => { // e.stopPropagation(); // e.preventDefault(); diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index fd1296286..a9a1898f5 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -13,6 +13,7 @@ height: calc(100% - 50px); display: inline-block; width: 100%; + user-select: none; } } .carouselView-back, .carouselView-fwd { diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index a0cb1fe19..9e7248db2 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -80,10 +80,33 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) }); } } + _downX = 0; + _downY = 0; + onPointerDown = (e: React.PointerEvent) => { + this._downX = e.clientX; + this._downY = e.clientY; + console.log("CAROUSEL down"); + document.addEventListener("pointerup", this.onpointerup); + } + private _lastTap: number = 0; + private _doubleTap = false; + onpointerup = (e: PointerEvent) => { + console.log("CAROUSEL up"); + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); + this._lastTap = Date.now(); + } + + onClick = (e: React.MouseEvent) => { + if (this._doubleTap) { + e.stopPropagation(); + this.props.Document.isLightboxOpen = true; + } + } + render() { - return <div className="collectionCarouselView-outer" ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}> + return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}> {this.content} - {this.buttons} + {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} </div>; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1e38a8927..28aaf0c57 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faFile } from '@fortawesome/free-solid-svg-icons'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, computed, Lambda, observable, reaction, runInAction } from "mobx"; +import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; @@ -15,12 +15,12 @@ import { FieldId } from "../../../new_fields/RefField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from '../../../new_fields/util'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { emptyFunction, returnOne, returnTrue, Utils } from "../../../Utils"; +import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager } from "../../util/DragManager"; +import { DragManager, dropActionType } from "../../util/DragManager"; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; @@ -127,36 +127,22 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @undoBatch @action public static CloseRightSplit(document: Opt<Doc>): boolean { - if (!CollectionDockingView.Instance) return false; const instance = CollectionDockingView.Instance; - let retVal = false; - if (instance._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { - if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && - DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) && - ((!document && DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document.isDisplayPanel) || - (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)))) { - child.contentItems[0].remove(); + const tryClose = (childItem: any) => { + if (childItem.config?.component === "DocumentFrameRenderer") { + const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId); + if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) { + childItem.remove(); instance.layoutChanged(document); return true; - } else { - Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { - if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) && - ((!document && DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document.isDisplayPanel) || - (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)))) { - child.contentItems[j].remove(); - child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - return true; - } - return false; - }); } - return false; - }); - } - if (retVal) { - instance.stateChanged(); - } + } + return false; + }; + const retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false : + Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose)); + + retVal && instance.stateChanged(); return retVal; } @@ -238,6 +224,75 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return true; } + + // + // Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side + // + @undoBatch + @action + public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) { + if (!CollectionDockingView.Instance) return false; + const instance = CollectionDockingView.Instance; + const newItemStackConfig = { + type: 'stack', + content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)] + }; + + const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + + if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns + instance._goldenLayout.root.addChild(newContentItem); + } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row + if (pullSide === "left") { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); + } else if (pullSide === "right") { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem); + } else if (pullSide === "top" || pullSide === "bottom") { + // if not going in a row layout, must add already existing content into column + const rowlayout = instance._goldenLayout.root.contentItems[0]; + const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout); + rowlayout.parent.replaceChild(rowlayout, newColumn); + if (pullSide === "top") { + newColumn.addChild(rowlayout, undefined, true); + newColumn.addChild(newContentItem, 0, true); + } else if (pullSide === "bottom") { + newColumn.addChild(newContentItem, undefined, true); + newColumn.addChild(rowlayout, 0, true); + } + + rowlayout.config.height = 50; + newContentItem.config.height = 50; + } + } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column + if (pullSide === "top") { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); + } else if (pullSide === "bottom") { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem); + } else if (pullSide === "left" || pullSide === "right") { + // if not going in a row layout, must add already existing content into column + const collayout = instance._goldenLayout.root.contentItems[0]; + const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); + collayout.parent.replaceChild(collayout, newRow); + + if (pullSide === "left") { + newRow.addChild(collayout, undefined, true); + newRow.addChild(newContentItem, 0, true); + } else if (pullSide === "right") { + newRow.addChild(newContentItem, undefined, true); + newRow.addChild(collayout, 0, true); + } + + collayout.config.width = 50; + newContentItem.config.width = 50; + } + } + + newContentItem.callDownwards('_$init'); + instance.layoutChanged(); + return true; + } + + // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @@ -434,6 +489,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp }; tab.titleElement[0].size = StrCast(doc.title).length + 1; tab.titleElement[0].value = doc.title; + tab.titleElement[0].style["max-width"] = "100px"; const gearSpan = document.createElement("span"); gearSpan.className = "collectionDockingView-gear"; gearSpan.style.position = "relative"; @@ -449,15 +505,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } tab.setActive(true); }; - ReactDOM.render(<span title="Drag as document" - className="collectionDockingView-dragAsDocument" - onPointerDown={e => { + const onDown = (e: React.PointerEvent) => { + if (!(e.nativeEvent as any).defaultPrevented) { e.preventDefault(); e.stopPropagation(); const dragData = new DragManager.DocumentDragData([doc]); - dragData.dropAction = doc.dropAction === "alias" ? "alias" : doc.dropAction === "copy" ? "copy" : undefined; + dragData.dropAction = doc.dropAction as dropActionType; DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY); - }}><DockingViewButtonSelector Document={doc} Stack={stack} /></span>, gearSpan); + } + }; + let rendered = false; + tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)), + (views) => { + !rendered && ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} > + <DockingViewButtonSelector views={views} Stack={stack} /> + </span>, + gearSpan); + rendered = true; + }); + tab.reactComponents = [gearSpan]; tab.element.append(gearSpan); tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { @@ -471,7 +537,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } tab.closeElement.off('click') //unbind the current click handler .click(async function () { - tab.reactionDisposer && tab.reactionDisposer(); + tab.reactionDisposer?.(); + tab.buttonDisposer?.(); const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId); if (doc instanceof Doc) { const theDoc = doc; @@ -721,11 +788,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { Document={document} DataDoc={resolvedDataDoc} bringToFront={emptyFunction} + rootSelected={returnTrue} addDocument={undefined} removeDocument={undefined} ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} + NativeHeight={returnZero} + NativeWidth={returnZero} ScreenToLocalTransform={this.ScreenToLocalTransform} renderDepth={0} parentActive={returnTrue} @@ -735,9 +805,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { addDocTab={this.addDocTab} pinToPres={DockedFrameRenderer.PinDoc} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} />; + ContainingCollectionDoc={undefined} />; } render() { diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss index eae9e0220..123a27deb 100644 --- a/src/client/views/collections/CollectionLinearView.scss +++ b/src/client/views/collections/CollectionLinearView.scss @@ -8,6 +8,8 @@ display:flex; height: 100%; >label { + margin-top: "auto"; + margin-bottom: "auto"; background: $dark-color; color: $light-color; display: inline-block; diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 4a14a30cd..cb0206260 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -3,8 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc'; import { makeInterface } from '../../../new_fields/Schema'; -import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types'; -import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils'; +import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types'; +import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; import "./CollectionLinearView.scss"; @@ -13,7 +13,6 @@ import { CollectionSubView } from './CollectionSubView'; import { DocumentView } from '../nodes/DocumentView'; import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; -import { ScriptField } from '../../../new_fields/ScriptField'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -28,12 +27,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { private _selectedDisposer?: IReactionDisposer; componentWillUnmount() { - this._dropDisposer && this._dropDisposer(); - this._widthDisposer && this._widthDisposer(); - this._selectedDisposer && this._selectedDisposer(); - this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { - Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log); - }); + this._dropDisposer?.(); + this._widthDisposer?.(); + this._selectedDisposer?.(); + this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log)); } componentDidMount() { @@ -48,17 +45,17 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (i) => runInAction(() => { this._selectedIndex = i; let selected: any = undefined; - this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => { + this.childLayoutPairs.map(async (pair, ind) => { const isSelected = this._selectedIndex === ind; if (isSelected) { selected = pair; } else { - Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log); + ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); } }); if (selected && selected.layout) { - Cast(selected.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: selected.layout.proto }, console.log); + ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); } }), { fireImmediately: true } @@ -71,32 +68,37 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } } - public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } - dimension = () => NumCast(this.props.Document._height); // 2 * the padding getTransform = (ele: React.RefObject<HTMLDivElement>) => () => { if (!ele.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); - return new Transform(-translateX, -translateY, 1 / scale); + return new Transform(-translateX, -translateY, 1); } render() { const guid = Utils.GenerateGuid(); + const flexDir: any = StrCast(this.Document.flexDirection); + const backgroundColor = StrCast(this.props.Document.backgroundColor, "black"); + const color = StrCast(this.props.Document.color, "white"); return <div className="collectionLinearView-outer"> <div className="collectionLinearView" ref={this.createDashEventsTarget} > + <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }} + onPointerDown={e => e.stopPropagation()} > + <p>+</p> + </label> <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> - <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label> - <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}> - {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { + <div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}> + {this.childLayoutPairs.map((pair, ind) => { const nested = pair.layout._viewType === CollectionViewType.Linear; const dref = React.createRef<HTMLDivElement>(); const nativeWidth = NumCast(pair.layout._nativeWidth, this.dimension()); const deltaSize = nativeWidth * .15 / 2; - return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref} + const scalable = pair.layout.onClick || pair.layout.onDragStart; + return <div className={`collectionLinearView-docBtn` + (scalable ? "-scalable" : "")} key={pair.layout[Id]} ref={dref} style={{ - width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize, + width: scalable ? (nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize) : undefined, height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize, }} > <DocumentView @@ -107,10 +109,13 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { moveDocument={this.props.moveDocument} addDocTab={this.props.addDocTab} pinToPres={emptyFunction} + rootSelected={this.props.isSelected} removeDocument={this.props.removeDocument} onClick={undefined} ScreenToLocalTransform={this.getTransform(dref)} ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()} renderDepth={this.props.renderDepth + 1} @@ -120,10 +125,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> - </DocumentView> + ContainingCollectionDoc={undefined} /> </div>; })} </div> diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index c9b7ca221..b272151c1 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -8,7 +8,7 @@ import Measure from "react-measure"; import { Doc } from "../../../new_fields/Doc"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { StrCast } from "../../../new_fields/Types"; +import { StrCast, NumCast } from "../../../new_fields/Types"; import { numberRange } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; @@ -36,6 +36,9 @@ interface CMVFieldRowProps { createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; setDocHeight: (key: string, thisHeight: number) => void; + observeHeight: (myref: any) => void; + unobserveHeight: (myref: any) => void; + showHandle: boolean; } @observer @@ -53,14 +56,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr private _contRef: React.RefObject<HTMLDivElement> = React.createRef(); private _sensitivity: number = 16; private _counter: number = 0; - + private _ele: any; createRowDropRef = (ele: HTMLDivElement | null) => { this._dropDisposer && this._dropDisposer(); if (ele) { + this._ele = ele; + this.props.observeHeight(ele); this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } } + componentWillUnmount() { + this.props.unobserveHeight(this._ele); + } getTrueHeight = () => { if (this._collapsed) { @@ -75,11 +83,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @undoBatch rowDrop = action((e: Event, de: DragManager.DropEvent) => { + console.log("masronry row drop"); this._createAliasSelected = false; if (de.complete.docDragData) { (this.props.parent.Document.dropConverter instanceof ScriptField) && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData }); - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(this._heading); de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue); this.props.parent.onInternalDrop(e, de); @@ -98,7 +107,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action headingChanged = (value: string, shiftDown?: boolean) => { this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(value); if (castedValue) { if (this.props.parent.sectionHeaders) { @@ -137,15 +146,16 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action addDocument = (value: string, shiftDown?: boolean) => { this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document.sectionFilter); - const newDoc = Docs.Create.TextDocument("", { _height: 18, _width: 200, title: value }); + const key = StrCast(this.props.parent.props.Document._pivotField); + const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value }); newDoc[key] = this.getValue(this.props.heading); - return this.props.parent.props.addDocument(newDoc); + const docs = this.props.parent.childDocList; + return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument(newDoc); } deleteRow = undoBatch(action(() => { this._createAliasSelected = false; - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); this.props.docList.forEach(d => d[key] = undefined); if (this.props.parent.sectionHeaders && this.props.headingObject) { const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject); @@ -167,7 +177,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { const alias = Doc.MakeAlias(this.props.parent.props.Document); - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; const script = `return doc.${key} === ${value}`; @@ -273,6 +283,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr }; return collapsed ? (null) : <div style={{ position: "relative" }}> + {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? + <div className="collectionStackingView-addDocumentButton" + style={{ + width: style.columnWidth / style.numGroupColumns, + padding: NumCast(this.props.parent.layoutDoc._yPadding) + }}> + <EditableView {...newEditableViewProps} /> + </div> : null + } <div className={`collectionStackingView-masonryGrid`} ref={this._contRef} style={{ @@ -282,20 +301,14 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""), }}> {this.props.parent.children(this.props.docList)} - {this.props.parent.columnDragger} + {this.props.showHandle && this.props.parent.props.active() ? this.props.parent.columnDragger : (null)} </div> - {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? - <div className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / style.numGroupColumns }}> - <EditableView {...newEditableViewProps} /> - </div> : null - } </div>; } @computed get headingView() { const heading = this._heading; - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const headerEditableViewProps = { GetValue: () => evContents, @@ -316,7 +329,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} title={evContents === `NO ${key.toUpperCase()} VALUE` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""} - style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey", }}> + style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey" }}> <EditableView {...headerEditableViewProps} /> {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <div className="collectionStackingView-sectionColor"> diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index facde3648..82204ca7b 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -4,8 +4,9 @@ import { observer } from "mobx-react"; import { CellInfo } from "react-table"; import "react-table/react-table.css"; import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils"; -import { Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; +import { KeyCodes } from "../../util/KeyCodes"; import { SetupDrag, DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; @@ -21,7 +22,6 @@ import { SelectionManager } from "../../util/SelectionManager"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faExpand } from '@fortawesome/free-solid-svg-icons'; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { KeyCodes } from "../../northstar/utils/KeyCodes"; import { undoBatch } from "../../util/UndoManager"; library.add(faExpand); @@ -76,17 +76,28 @@ export class CollectionSchemaCell extends React.Component<CellProps> { @action isEditingCallback = (isEditing: boolean): void => { - document.addEventListener("keydown", this.onKeyDown); + document.removeEventListener("keydown", this.onKeyDown); + isEditing && document.addEventListener("keydown", this.onKeyDown); this._isEditing = isEditing; this.props.setIsEditing(isEditing); this.props.changeFocusedCellByIndex(this.props.row, this.props.col); } @action - onPointerDown = (e: React.PointerEvent): void => { + onPointerDown = async (e: React.PointerEvent): Promise<void> => { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); + let url: string; + if (url = StrCast(this.props.rowProps.row.href)) { + try { + new URL(url); + const temp = window.open(url)!; + temp.blur(); + window.focus(); + } catch { } + } + // this._isEditing = true; // this.props.setIsEditing(true); @@ -145,6 +156,9 @@ export class CollectionSchemaCell extends React.Component<CellProps> { Document: this.props.rowProps.original, DataDoc: this.props.rowProps.original, LibraryPath: [], + dropAction: "alias", + bringToFront: emptyFunction, + rootSelected: returnFalse, fieldKey: this.props.rowProps.column.id as string, ContainingCollectionView: this.props.CollectionView, ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, @@ -157,6 +171,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { whenActiveChanged: emptyFunction, PanelHeight: returnZero, PanelWidth: returnZero, + NativeHeight: returnZero, + NativeWidth: returnZero, addDocTab: this.props.addDocTab, pinToPres: this.props.pinToPres, ContentScaling: returnOne diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 6eeceb552..57be77fdd 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -12,9 +12,8 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { Docs, DocumentOptions } from "../../documents/Documents"; -import { Gateway } from "../../northstar/manager/Gateway"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; @@ -28,7 +27,8 @@ import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionView } from "./CollectionView"; import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { setupMoveUpEvents, emptyFunction } from "../../../Utils"; +import { setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils"; +import { DocumentView } from "../nodes/DocumentView"; library.add(faCog, faPlus, faSortUp, faSortDown); library.add(faTable); @@ -51,8 +51,7 @@ const columnTypes: Map<string, ColumnType> = new Map([ @observer export class CollectionSchemaView extends CollectionSubView(doc => doc) { - private _mainCont?: HTMLDivElement; - private _startPreviewWidth = 0; + private _previewCont?: HTMLDivElement; private DIVIDER_WIDTH = 4; @observable previewDoc: Doc | undefined = undefined; @@ -64,7 +63,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } private createTarget = (ele: HTMLDivElement) => { - this._mainCont = ele; + this._previewCont = ele; super.CreateDropTarget(ele); } @@ -81,12 +80,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } onDividerDown = (e: React.PointerEvent) => { - this._startPreviewWidth = this.previewWidth(); setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, action(() => this.toggleExpander())); } @action onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { - const nativeWidth = this._mainCont!.getBoundingClientRect(); + const nativeWidth = this._previewCont!.getBoundingClientRect(); const minWidth = 40; const maxWidth = 1000; const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; @@ -119,27 +117,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get previewPanel() { - const layoutDoc = this.previewDocument ? Doc.expandTemplateLayout(this.previewDocument, this.props.DataDoc) : undefined; - return <div ref={this.createTarget}> - <ContentFittingDocumentView - Document={layoutDoc} - DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined} - LibraryPath={this.props.LibraryPath} - childDocs={this.childDocs} - renderDepth={this.props.renderDepth} - PanelWidth={this.previewWidth} - PanelHeight={this.previewHeight} - getTransform={this.getPreviewTransform} - CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document} - CollectionView={this.props.CollectionView} - moveDocument={this.props.moveDocument} - addDocument={this.props.addDocument} - removeDocument={this.props.removeDocument} - active={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - /> + return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}> + {!this.previewDocument ? (null) : + <ContentFittingDocumentView + Document={this.previewDocument} + DataDocument={undefined} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={true} + FreezeDimensions={true} + focus={emptyFunction} + LibraryPath={this.props.LibraryPath} + renderDepth={this.props.renderDepth} + rootSelected={this.rootSelected} + PanelWidth={this.previewWidth} + PanelHeight={this.previewHeight} + getTransform={this.getPreviewTransform} + CollectionDoc={this.props.CollectionView?.props.Document} + CollectionView={this.props.CollectionView} + moveDocument={this.props.moveDocument} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + active={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + />} </div>; } @@ -182,7 +185,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { render() { return <div className="collectionSchemaView-container"> - <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}> + <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}> {this.schemaTable} </div> {this.dividerDragger} @@ -669,27 +672,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } } - @action - makeDB = async () => { - let csv: string = this.columns.reduce((val, col) => val + col + ",", ""); - csv = csv.substr(0, csv.length - 1) + "\n"; - const self = this; - this.childDocs.map(doc => { - csv += self.columns.reduce((val, col) => val + (doc[col.heading] ? doc[col.heading]!.toString() : "0") + ",", ""); - csv = csv.substr(0, csv.length - 1) + "\n"; - }); - csv.substring(0, csv.length - 1); - const dbName = StrCast(this.props.Document.title); - const res = await Gateway.Instance.PostSchema(csv, dbName); - if (self.props.CollectionView && self.props.CollectionView.props.addDocument) { - const schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); - if (schemaDoc) { - //self.props.CollectionView.props.addDocument(schemaDoc, false); - self.props.Document.schemaDoc = schemaDoc; - } - } - } - getField = (row: number, col?: number) => { const docs = this.childDocs; diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index bfa5ea278..47faa9239 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -26,6 +26,9 @@ position: relative; display: block; } + .collectionStackingViewFieldColumn { + height:max-content; + } .collectionSchemaView-previewDoc { height: 100%; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d1f45af90..da53888fc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,8 +11,8 @@ import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; -import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils"; -import { DragManager } from "../../util/DragManager"; +import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils"; +import { DragManager, dropActionType } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; @@ -24,35 +24,37 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; -import { Docs } from "../../documents/Documents"; +import { SelectionManager } from "../../util/SelectionManager"; +const _global = (window /* browser */ || global /* node */) as any; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); - _heightDisposer?: IReactionDisposer; - _sectionFilterDisposer?: IReactionDisposer; + _pivotFieldDisposer?: IReactionDisposer; _docXfs: any[] = []; _columnStart: number = 0; @observable _heightMap = new Map<string, number>(); @observable _cursor: CursorProperty = "grab"; @observable _scroll = 0; // used to force the document decoration to update when scrolling @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } - @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); } + @computed get pivotField() { return StrCast(this.props.Document._pivotField); } @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); } @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); } @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); } @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } - @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); } + @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); } @computed get columnWidth() { + TraceMobx(); return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250)); } @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } children(docs: Doc[], columns?: number) { + TraceMobx(); this._docXfs.length = 0; return docs.map((d, i) => { const height = () => this.getDocHeight(d); @@ -63,7 +65,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); const style = this.isStackingView ? { width: width(), marginTop: this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > - {this.getDisplayDoc(d, this.props.DataDoc, dxf, width)} + {this.getDisplayDoc(d, (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc, dxf, width)} </div>; }); } @@ -73,7 +75,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } get Sections() { - if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); + if (!this.pivotField || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); if (this.sectionHeaders === undefined) { setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0); @@ -83,66 +85,60 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); let changed = false; this.filteredChildren.map(d => { - const sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object; + const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object; // the next five lines ensures that floating point rounding errors don't create more than one section -syip const parsed = parseInt(sectionValue.toString()); const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; // look for if header exists already - const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`)); + const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`)); if (existingHeader) { fields.get(existingHeader)!.push(d); } else { - const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`); + const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`); fields.set(newSchemaHeader, [d]); sectionHeaders.push(newSchemaHeader); changed = true; } }); + // remove all empty columns if hideHeadings is set + if (this.props.Document.hideHeadings) { + Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => { + fields.delete(header); + sectionHeaders.splice(sectionHeaders.indexOf(header), 1); + changed = true; + }); + } changed && setTimeout(action(() => { if (this.sectionHeaders) { this.sectionHeaders.length = 0; this.sectionHeaders.push(...sectionHeaders); } }), 0); return fields; } + getSimpleDocHeight(d?: Doc) { + if (!d) return 0; + const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const nw = NumCast(layoutDoc._nativeWidth); + const nh = NumCast(layoutDoc._nativeHeight); + let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); + if (!layoutDoc._fitWidth && nw && nh) { + const aspect = nw && nh ? nh / nw : 1; + if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); + return wid * aspect; + } + return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym](); + } componentDidMount() { super.componentDidMount(); - this._heightDisposer = reaction(() => { - if (this.props.Document._autoHeight) { - const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); - if (this.isStackingView) { - const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => { - const r1 = Math.max(maxHght, - (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => { - const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap); - return val; - }, this.yMargin)); - return r1; - }, 0); - return res; - } else { - const sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0); - return this.props.ContentScaling() * (sum + (this.Sections.size ? (this.props.Document.miniHeaders ? 20 : 85) : -15)); - } - } - return -1; - }, - (hgt: number) => { - const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; - doc && hgt > 0 && (Doc.Layout(doc)._height = hgt); - }, - { fireImmediately: true } - ); // reset section headers when a new filter is inputted - this._sectionFilterDisposer = reaction( - () => this.sectionFilter, + this._pivotFieldDisposer = reaction( + () => this.pivotField, () => this.props.Document.sectionHeaders = new List() ); } componentWillUnmount() { super.componentWillUnmount(); - this._heightDisposer && this._heightDisposer(); - this._sectionFilterDisposer && this._sectionFilterDisposer(); + this._pivotFieldDisposer?.(); } @action @@ -162,18 +158,23 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const height = () => this.getDocHeight(doc); return <ContentFittingDocumentView Document={doc} - DataDocument={doc[DataSym] !== doc && doc[DataSym]} + DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])} backgroundColor={this.props.backgroundColor} LayoutDoc={this.props.childLayoutTemplate} LibraryPath={this.props.LibraryPath} + FreezeDimensions={this.props.freezeChildDimensions} renderDepth={this.props.renderDepth + 1} - fitToBox={this.props.fitToBox} - onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler} PanelWidth={width} PanelHeight={height} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + rootSelected={this.rootSelected} + dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} + onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler} getTransform={dxf} focus={this.props.focus} - CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document} + CollectionDoc={this.props.CollectionView?.props.Document} CollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} @@ -276,9 +277,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } }); } - headings = () => Array.from(this.Sections.keys()); + headings = () => Array.from(this.Sections); + refList: any[] = []; sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - const key = this.sectionFilter; + const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { @@ -287,6 +289,19 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); return <CollectionStackingViewFieldColumn + unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={(ref) => { + if (ref) { + this.refList.push(ref); + const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; + this.observer = new _global.ResizeObserver(action((entries: any) => { + if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) { + Doc.Layout(doc)._height = Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))); + } + })); + this.observer.observe(ref); + } + }} key={heading ? heading.heading : ""} cols={cols} headings={this.headings} @@ -305,15 +320,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref); const outerXf = Utils.GetScreenTransform(this._masonryGridRef!); - const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]()); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2; const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0); - return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling); + return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety); } - sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - const key = this.sectionFilter; + sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { + const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { @@ -322,6 +335,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); return <CollectionMasonryViewFieldRow + showHandle={first} + unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={(ref) => { + if (ref) { + this.refList.push(ref); + const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; + this.observer = new _global.ResizeObserver(action((entries: any) => { + if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) { + Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); + } + })); + this.observer.observe(ref); + } + }} key={heading ? heading.heading : ""} rows={rows} headings={this.headings} @@ -341,7 +368,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { if (value && this.sectionHeaders) { const schemaHdrField = new SchemaHeaderField(value); this.sectionHeaders.push(schemaHdrField); - Doc.addEnumerationToTextField(undefined, this.sectionFilter, [Docs.Create.TextDocument(value, { title: value, _backgroundColor: schemaHdrField.color })]); + Doc.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]); return true; } return false; @@ -370,15 +397,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get renderedSections() { TraceMobx(); let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; - if (this.sectionFilter) { + if (this.pivotField) { const entries = Array.from(this.Sections.entries()); sections = entries.sort(this.sortFunc); } - return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1])); + return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); } - @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); } + @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth) || this.props.NativeWidth() || 0; } + @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight) || this.props.NativeHeight() || 0; } + + @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } + + observer: any; render() { TraceMobx(); const editableViewProps = { @@ -407,7 +439,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> <EditableView {...editableViewProps} /> </div>} - {this.props.Document._chromeStatus !== 'disabled' ? <Switch + {this.props.Document._chromeStatus !== 'disabled' && this.props.isSelected() ? <Switch onChange={this.onToggle} onClick={this.onToggle} defaultChecked={this.props.Document._chromeStatus !== 'view-mode'} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 516e583d4..5d926b7c7 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -23,7 +23,6 @@ import { CollectionStackingView } from "./CollectionStackingView"; import { setupMoveUpEvents, emptyFunction } from "../../../Utils"; import "./CollectionStackingView.scss"; import { listSpec } from "../../../new_fields/Schema"; -import { Schema } from "prosemirror-model"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -40,6 +39,8 @@ interface CSVFieldColumnProps { type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; + observeHeight: (myref: any) => void; + unobserveHeight: (myref: any) => void; } @observer @@ -51,18 +52,24 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + _ele: HTMLElement | null = null; createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) { + this._ele = ele; + this.props.observeHeight(ele); this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); } } + componentWillUnmount() { + this.props.unobserveHeight(this._ele); + } @undoBatch columnDrop = action((e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(this._heading); de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, false)); this.props.parent.onInternalDrop(e, de); @@ -85,7 +92,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action headingChanged = (value: string, shiftDown?: boolean) => { - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(value); if (castedValue) { if (this.props.parent.sectionHeaders) { @@ -126,18 +133,19 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action addDocument = (value: string, shiftDown?: boolean) => { if (!value) return false; - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; - return this.props.parent.props.addDocument(newDoc); + this.props.parent.props.addDocument(newDoc); + return false; } @action deleteColumn = () => { - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); this.props.docList.forEach(d => d[key] = undefined); if (this.props.parent.sectionHeaders && this.props.headingObject) { const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject); @@ -161,8 +169,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC startDrag = (e: PointerEvent, down: number[], delta: number[]) => { const alias = Doc.MakeAlias(this.props.parent.props.Document); alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.props.Document.sectionHeaders, listSpec(SchemaHeaderField))?.length || 1); - alias.sectionFilter = undefined; - const key = StrCast(this.props.parent.props.Document.sectionFilter); + alias._pivotField = undefined; + const key = StrCast(this.props.parent.props.Document._pivotField); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); @@ -277,7 +285,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC render() { TraceMobx(); const cols = this.props.cols(); - const key = StrCast(this.props.parent.props.Document.sectionFilter); + const key = StrCast(this.props.parent.props.Document._pivotField); let templatecols = ""; const headings = this.props.headings(); const heading = this._heading; @@ -353,7 +361,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; const chromeStatus = this.props.parent.props.Document._chromeStatus; return ( - <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }} + <div className="collectionStackingViewFieldColumn" key={heading} + style={{ + width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, + height: SelectionManager.GetIsDragging() ? "100%" : undefined, + background: this._background + }} ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}> {this.props.parent.Document.hideHeadings ? (null) : headingView} { diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx index 8c7e113b2..5b9a69bf7 100644 --- a/src/client/views/collections/CollectionStaffView.tsx +++ b/src/client/views/collections/CollectionStaffView.tsx @@ -1,22 +1,20 @@ import { CollectionSubView } from "./CollectionSubView"; -import { Transform } from "../../util/Transform"; import React = require("react"); import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx"; -import { Doc } from "../../../new_fields/Doc"; import { NumCast } from "../../../new_fields/Types"; import "./CollectionStaffView.scss"; import { observer } from "mobx-react"; @observer export class CollectionStaffView extends CollectionSubView(doc => doc) { - private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(0, -this._mainCont.current!.scrollTop); - private _mainCont = React.createRef<HTMLDivElement>(); private _reactionDisposer: IReactionDisposer | undefined; @observable private _staves = NumCast(this.props.Document.staves); + componentWillUnmount() { + this._reactionDisposer?.(); + } componentDidMount = () => { - this._reactionDisposer = reaction( - () => NumCast(this.props.Document.staves), + this._reactionDisposer = reaction(() => NumCast(this.props.Document.staves), (staves) => runInAction(() => this._staves = staves) ); @@ -47,7 +45,7 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) { } render() { - return <div className="collectionStaffView" ref={this._mainCont}> + return <div className="collectionStaffView"> {this.staves} {this.addStaffButton} </div>; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 6ad187e5c..41f4fb3f0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,12 +1,11 @@ import { action, computed, IReactionDisposer, reaction } from "mobx"; -import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast } from "../../../new_fields/Types"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -35,30 +34,36 @@ export interface CollectionViewProps extends FieldViewProps { PanelHeight: () => number; VisibleHeight?: () => number; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; + rootSelected: (outsideReaction?: boolean) => boolean; fieldKey: string; + NativeWidth: () => number; + NativeHeight: () => number; } export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: Opt<CollectionView>; children?: never | (() => JSX.Element[]) | React.ReactNode; - overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explict list (see LinkBox) + freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height + overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox) isAnnotationOverlay?: boolean; annotationsKey: string; layoutEngine?: () => string; } -export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { - class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { +export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: X) { + class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; private _childLayoutDisposer?: IReactionDisposer; + protected _mainCont?: HTMLDivElement; protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer?.(); this.gestureDisposer?.(); this.multiTouchDisposer?.(); if (ele) { + this._mainCont = ele; this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); @@ -90,10 +95,14 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { } @computed get dataDoc() { - return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : + return (this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template } + rootSelected = (outsideReaction?: boolean) => { + return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false); + } + // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through // to its children which may be templates. @@ -105,15 +114,62 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { get childLayoutPairs(): { layout: Doc; data: Doc; }[] { const { Document, DataDoc } = this.props; const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout); - return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types + return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } get childDocList() { return Cast(this.dataField, listSpec(Doc)); } - get childDocs() { - const docs = DocListCast(this.dataField); + @computed get childDocs() { + const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []); + const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []); + const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields + for (let i = 0; i < docFilters.length; i += 3) { + const [key, value, modifiers] = docFilters.slice(i, i + 3); + if (!filterFacets[key]) { + filterFacets[key] = {}; + } + filterFacets[key][value] = modifiers; + } + + let rawdocs: (Doc | Promise<Doc>)[] = []; + if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + rawdocs = [this.dataField]; + } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. + rawdocs = Cast(this.dataField, listSpec(Doc), null); + } else { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. + // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection. + // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image. + const rootDoc = Cast(this.props.Document.rootDocument, Doc, null); + rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; + } + const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + + const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => { + for (const facetKey of Object.keys(filterFacets)) { + const facet = filterFacets[facetKey]; + const satisfiesFacet = Object.keys(facet).some(value => + (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value)); + if (!satisfiesFacet) { + return false; + } + } + return true; + }) : childDocs; + const rangeFilteredDocs = filteredDocs.filter(d => { + for (let i = 0; i < docRangeFilters.length; i += 3) { + const key = docRangeFilters[i]; + const min = Number(docRangeFilters[i + 1]); + const max = Number(docRangeFilters[i + 2]); + const val = Cast(d[key], "number", null); + if (val !== undefined && (val < min || val > max)) { + return false; + } + } + return true; + }); + return rangeFilteredDocs; } @action @@ -150,7 +206,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { @undoBatch protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { - } @undoBatch @@ -161,9 +216,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this if (docDragData) { let added = false; - if (this.props.Document._freezeOnDrop) { - de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]())); - } if (docDragData.dropAction || docDragData.userDropAction) { added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (docDragData.moveDocument) { @@ -231,7 +283,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { if (!html.startsWith("<a")) { const tags = html.split("<"); if (tags[0] === "") tags.splice(0, 1); - const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : ""; + let img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : ""; + const cors = img.includes("corsProxy") ? img.match(/http.*corsProxy\//)![0] : ""; + img = cors ? img.replace(cors, "") : img; if (img) { const split = img.split("src=\"")[1].split("\"")[0]; let source = split; @@ -239,9 +293,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); source = Utils.prepend(accessPaths.agnostic.client); } - const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); - ImageUtils.ExtractExif(doc); - addDocument(doc); + if (source.startsWith("http")) { + const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); + ImageUtils.ExtractExif(doc); + addDocument(doc); + } return; } else { const path = window.location.origin + "/doc/"; @@ -309,7 +365,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { const item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.includes("uri")) { const stringContents = await new Promise<string>(resolve => item.getAsString(resolve)); - const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"]; + const type = "html";// (await rp.head(Utils.CorsProxy(stringContents)))["content-type"]; if (type) { const doc = await Docs.Get.DocumentFromType(type, stringContents, options); doc && generatedDocuments.push(doc); diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss index 865fc3cd2..fa7c87f4e 100644 --- a/src/client/views/collections/CollectionTimeView.scss +++ b/src/client/views/collections/CollectionTimeView.scss @@ -47,7 +47,8 @@ .collectionTimeView-flyout { width: 400px; - display: inline-block; + display: block; + text-align: left; .collectionTimeView-flyout-item { background-color: lightgray; @@ -72,62 +73,11 @@ } } - .collectionTimeView-treeView { - display: flex; - flex-direction: column; - width: 200px; - height: 100%; - - .collectionTimeView-addfacet { - display: inline-block; - width: 200px; - height: 30px; - background: darkGray; - text-align: center; - - .collectionTimeView-button { - align-items: center; - display: flex; - width: 100%; - height: 100%; - - .collectionTimeView-span { - margin: auto; - } - } - - >div, - >div>div { - width: 100%; - height: 100%; - text-align: center; - } - } - - .collectionTimeView-tree { - display: inline-block; - width: 100%; - height: calc(100% - 30px); - } - } - .collectionTimeView-innards { display: inline-block; width: calc(100% - 200px); height: 100%; } - - .collectionTimeView-dragger { - background-color: lightgray; - height: 40px; - width: 20px; - position: absolute; - border-radius: 10px; - top: 55%; - border: 1px black solid; - z-index: 2; - left: -10px; - } } .collectionTimeView-pivot { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 50e297f0b..53de2fbbe 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,16 +1,12 @@ -import { faEdit } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction, trace } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; import { RichTextField } from "../../../new_fields/RichTextField"; -import { listSpec } from "../../../new_fields/Schema"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Docs } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; +import { NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; +import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -19,7 +15,6 @@ import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngi import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTimeView.scss"; -import { CollectionTreeView } from "./CollectionTreeView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -29,250 +24,55 @@ import React = require("react"); export class CollectionTimeView extends CollectionSubView(doc => doc) { _changing = false; @observable _layoutEngine = "pivot"; - + @observable _collapsed: boolean = false; + componentWillUnmount() { + this.props.Document.onChildClick = undefined; + } componentDidMount() { - this.props.Document._freezeOnDrop = true; const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked; - if (!this.props.Document._facetCollection) { - const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true }); - facetCollection.target = this.props.Document; - this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]); - - const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)"; - const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); "; - facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name }); - this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name, shiftKey: "boolean" }); - this.props.Document._facetCollection = facetCollection; - this.props.Document._fitToBox = true; - } + const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(thisContainer.childDetailed, alias, 'layout_detailView'); alias.layoutKey='layout_detailedView'; alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); "; + this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", thisContainer: Doc.name, shiftKey: "boolean" }); + this.props.Document._fitToBox = true; if (!this.props.Document.onViewDefClick) { this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); } } - bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth; - getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0); - - @computed get _allFacets() { - const facets = new Set<string>(); - this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); - return Array.from(facets); - } - - /** - * Responds to clicking the check box in the flyout menu - */ - facetClick = (facetHeader: string) => { - const facetCollection = this.props.Document._facetCollection; - if (facetCollection instanceof Doc) { - const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader); - if (found !== -1) { - (facetCollection.data as List<Doc>).splice(found, 1); - const docFilter = Cast(this.props.Document._docFilters, listSpec("string")); - if (docFilter) { - let index: number; - while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { - docFilter.splice(index, 3); - } - } - const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string")); - if (docRangeFilters) { - let index: number; - while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { - docRangeFilters.splice(index, 3); - } - } - } else { - const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]); - const facetValues = Array.from(allCollectionDocs.reduce((set, child) => - set.add(Field.toString(child[facetHeader] as Field)), new Set<string>())); - - let nonNumbers = 0; - let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; - facetValues.map(val => { - const num = Number(val); - if (Number.isNaN(num)) { - nonNumbers++; - } else { - minVal = Math.min(num, minVal); - maxVal = Math.max(num, maxVal); - } - }); - if (nonNumbers / allCollectionDocs.length < .1) { - const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader); - const newFacet = Docs.Create.SliderDocument({ title: facetHeader }); - Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - newFacet.treeViewExpandedView = "layout"; - newFacet.treeViewOpen = true; - newFacet._sliderMin = ranged === undefined ? minVal : ranged[0]; - newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1]; - newFacet._sliderMinThumb = minVal; - newFacet._sliderMaxThumb = maxVal; - newFacet.target = this.props.Document; - const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`; - newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); - - Doc.AddDocToList(facetCollection, "data", newFacet); - } else { - const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); - const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; - const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; - newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); - Doc.AddDocToList(facetCollection, "data", newFacet); - } - } - } - } - _canClick = false; - _facetWidthOnDown = 0; - @observable _facetWidth = 0; - onPointerDown = (e: React.PointerEvent) => { - this._canClick = true; - this._facetWidthOnDown = e.screenX; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - e.preventDefault(); - } - - - @action - onPointerMove = (e: PointerEvent) => { - this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0); - Math.abs(e.movementX) > 6 && (this._canClick = false); - } - @action - onPointerUp = (e: PointerEvent) => { - if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) { - this._facetWidth = this._facetWidth < 15 ? 200 : 0; - } - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - - menuCallback = (x: number, y: number) => { - ContextMenu.Instance.clearItems(); - const docItems: ContextMenuProps[] = []; - const keySet: Set<string> = new Set(); - - this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey => - pair.layout[fieldKey] instanceof RichTextField || - typeof (pair.layout[fieldKey]) === "number" || - typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey))); - Array.from(keySet).map(fieldKey => - docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" })); - docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); - const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); - ContextMenu.Instance.displayMenu(x, y, ":"); - } - - @observable private collapsed: boolean = false; - private toggleVisibility = action(() => this.collapsed = !this.collapsed); + layoutEngine = () => this._layoutEngine; + toggleVisibility = action(() => this._collapsed = !this._collapsed); - _downX = 0; onMinDown = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onMinMove); - document.removeEventListener("pointerup", this.onMinUp); - document.addEventListener("pointermove", this.onMinMove); - document.addEventListener("pointerup", this.onMinUp); - this._downX = e.clientX; - e.stopPropagation(); - e.preventDefault(); - } - @action - onMinMove = (e: PointerEvent) => { - const delta = e.clientX - this._downX; - this._downX = e.clientX; - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; - } - onMinUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onMinMove); - document.removeEventListener("pointermove", this.onMinUp); + setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); + this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; + return false; + }), returnFalse, emptyFunction); } onMaxDown = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onMaxMove); - document.removeEventListener("pointermove", this.onMaxUp); - document.addEventListener("pointermove", this.onMaxMove); - document.addEventListener("pointerup", this.onMaxUp); - this._downX = e.clientX; - e.stopPropagation(); - e.preventDefault(); - } - @action - onMaxMove = (e: PointerEvent) => { - const delta = e.clientX - this._downX; - this._downX = e.clientX; - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; - } - onMaxUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onMaxMove); - document.removeEventListener("pointermove", this.onMaxUp); + setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); + this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth(); + return false; + }), returnFalse, emptyFunction); } onMidDown = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onMidMove); - document.removeEventListener("pointermove", this.onMidUp); - document.addEventListener("pointermove", this.onMidMove); - document.addEventListener("pointerup", this.onMidUp); - this._downX = e.clientX; - e.stopPropagation(); - e.preventDefault(); - } - @action - onMidMove = (e: PointerEvent) => { - const delta = e.clientX - this._downX; - this._downX = e.clientX; - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq - (maxReq - minReq) * delta / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq - (maxReq - minReq) * delta / this.props.PanelWidth(); - } - onMidUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onMidMove); - document.removeEventListener("pointermove", this.onMidUp); + setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); + this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth(); + return false; + }), returnFalse, emptyFunction); } - layoutEngine = () => this._layoutEngine; @computed get contents() { - return <div className="collectionTimeView-innards" key="timeline" style={{ width: this.bodyPanelWidth() }}> - <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} /> - </div>; - } - @computed get filterView() { - trace(); - const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null); - const flyout = ( - <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30, display: "block" }} onWheel={e => e.stopPropagation()}> - {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> - <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} /> - <span className="checkmark" /> - {facet} - </label>)} - </div> - ); - return <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}> - <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> - <div className="collectionTimeView-button"> - <span className="collectionTimeView-span">Facet Filters</span> - <FontAwesomeIcon icon={faEdit} size={"lg"} /> - </div> - </Flyout> - </div> - <div className="collectionTimeView-tree" key="tree"> - <CollectionTreeView {...this.props} PanelWidth={() => this._facetWidth} Document={facetCollection} /> - </div> + return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}> + <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} /> </div>; } @@ -291,8 +91,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" }); } + @computed get _allFacets() { + const facets = new Set<string>(); + this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); + Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); + return Array.from(facets); + } + menuCallback = (x: number, y: number) => { + ContextMenu.Instance.clearItems(); + const docItems: ContextMenuProps[] = []; + const keySet: Set<string> = new Set(); + + this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey => + pair.layout[fieldKey] instanceof RichTextField || + typeof (pair.layout[fieldKey]) === "number" || + typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey))); + Array.from(keySet).map(fieldKey => + docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" })); + docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" }); + ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); + const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); + ContextMenu.Instance.displayMenu(x, y, ":"); + } - render() { + @computed get pivotKeyUI() { const newEditableViewProps = { GetValue: () => "", SetValue: (value: any) => { @@ -307,7 +129,26 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { toggle: this.toggleVisibility, color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; }; + return <div className={"pivotKeyEntry"}> + <button className="collectionTimeView-backBtn" + onClick={action(() => { + let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex); + if (prevFilterIndex > 0) { + prevFilterIndex--; + this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField); + this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); + this.props.Document._prevFilterIndex = prevFilterIndex; + } else { + this.props.Document._docFilters = new List([]); + } + })}> + back + </button> + <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} /> + </div>; + } + render() { let nonNumbers = 0; this.childDocs.map(doc => { const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)]))); @@ -327,41 +168,16 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } } - - const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null); - return !facetCollection ? (null) : - <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu} - style={{ height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}> - <div className={"pivotKeyEntry"}> - <button className="collectionTimeView-backBtn" - onClick={action(() => { - let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex); - if (prevFilterIndex > 0) { - prevFilterIndex--; - this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField); - this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); - this.props.Document._prevFilterIndex = prevFilterIndex; - } else { - this.props.Document._docFilters = new List([]); - } - })}> - back - </button> - <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} /> - </div> - {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) : - <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} > - <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} /> - </div> - } - {this.filterView} - {this.contents} - {!this.props.isSelected() || !doTimeline ? (null) : <> - <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} /> - <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} /> - <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} /> - </>} - </div>; + return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu} + style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}> + {this.pivotKeyUI} + {this.contents} + {!this.props.isSelected() || !doTimeline ? (null) : <> + <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} /> + <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} /> + <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} /> + </>} + </div>; } } diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 6ebe81545..8e95f7fbe 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -22,6 +22,7 @@ ul { list-style: none; padding-left: 20px; + margin-bottom: 1px;// otherwise vertical scrollbars may pop up for no apparent reason.... } @@ -34,7 +35,9 @@ width: 15px; color: $intermediate-color; margin-top: 3px; - transform: scale(1.3, 1.3); + transform: scale(1.3, 1.3); + border: #80808030 1px solid; + border-radius: 4px; } .editableView-container { @@ -123,6 +126,9 @@ .editableView-container-editing-oneLine { min-width: 15px; } + .documentView-node-topmost { + width: unset; + } } .treeViewItem-header-above { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 7eeeb6ff1..7edda5a4f 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -6,11 +6,11 @@ import { observer } from "mobx-react"; import { Doc, DocListCast, Field, HeightSym, WidthSym, DataSym, Opt } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { Document, listSpec } from '../../../new_fields/Schema'; +import { Document, listSpec, createSchema, makeInterface } from '../../../new_fields/Schema'; import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { emptyFunction, emptyPath, returnFalse, Utils } from '../../../Utils'; +import { emptyFunction, emptyPath, returnFalse, Utils, returnOne, returnZero, returnTransparent, returnTrue } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from '../../util/DocumentManager'; @@ -29,11 +29,12 @@ import { ImageBox } from '../nodes/ImageBox'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { ScriptBox } from '../ScriptBox'; import { Templates } from '../Templates'; -import { CollectionSubView } from "./CollectionSubView"; +import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); import { CollectionViewType } from './CollectionView'; import { RichTextField } from '../../../new_fields/RichTextField'; +import { DocumentView } from '../nodes/DocumentView'; export interface TreeViewProps { @@ -45,7 +46,7 @@ export interface TreeViewProps { renderDepth: number; deleteDoc: (doc: Doc) => boolean; moveDocument: DragManager.MoveFunction; - dropAction: "alias" | "copy" | undefined; + dropAction: dropActionType; addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; pinToPres: (document: Doc) => void; panelWidth: () => number; @@ -64,6 +65,7 @@ export interface TreeViewProps { treeViewPreventOpen: boolean; renderedIds: string[]; onCheckedClick?: ScriptField; + onChildClick?: ScriptField; ignoreFields?: string[]; } @@ -92,6 +94,7 @@ class TreeView extends React.Component<TreeViewProps> { private _header?: React.RefObject<HTMLDivElement> = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef<HTMLDivElement>(); + private _tref = React.createRef<HTMLDivElement>(); get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive @@ -100,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> { set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; } @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; } @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } + @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; } @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'"); @@ -128,7 +131,7 @@ class TreeView extends React.Component<TreeViewProps> { } @undoBatch delete = () => this.props.deleteDoc(this.props.document); - @undoBatch openRight = () => this.props.addDocTab(this.props.containingCollection.childDropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath); + @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath); @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => { return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); @@ -171,19 +174,29 @@ class TreeView extends React.Component<TreeViewProps> { editableView = (key: string, style?: string) => (<EditableView oneLine={true} display={"inline-block"} - editing={this.dataDoc[Id] === EditableView.loadId} + editing={true /*this.dataDoc[Id] === EditableView.loadId*/} contents={StrCast(this.props.document[key])} height={12} fontStyle={style} fontSize={12} GetValue={() => StrCast(this.props.document[key])} - SetValue={undoBatch((value: string) => Doc.SetInPlace(this.props.document, key, value, false) || true)} + SetValue={undoBatch((value: string) => { + Doc.SetInPlace(this.props.document, key, value, false) || true; + this.props.document.editTitle = undefined; + })} OnFillDown={undoBatch((value: string) => { Doc.SetInPlace(this.props.document, key, value, false); - const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) }); - EditableView.loadId = doc[Id]; + const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) }); + //EditableView.loadId = doc[Id]; + this.props.document.editTitle = undefined; + doc.editTitle = true; return this.props.addDocument(doc); })} + onClick={() => { + SelectionManager.DeselectAll(); + Doc.UserDoc().SelectedDocs = new List([this.props.document]); + return false; + }} OnTab={undoBatch((shift?: boolean) => { EditableView.loadId = this.dataDoc[Id]; shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); @@ -231,7 +244,7 @@ class TreeView extends React.Component<TreeViewProps> { if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceDocument; const destDoc = this.props.document; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree drop link"); + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link"); e.stopPropagation(); } if (de.complete.docDragData) { @@ -254,13 +267,21 @@ class TreeView extends React.Component<TreeViewProps> { docTransform = () => { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!); const outerXf = this.props.outerXf(); + const offset = this.props.ScreenToLocalTransform().transformDirection((outerXf.translateX - translateX), outerXf.translateY - translateY); + const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); + + return finalXf; + } + getTransform = () => { + const { scale, translateX, translateY } = Utils.GetScreenTransform(this._tref.current!); + const outerXf = this.props.outerXf(); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0)); + const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); return finalXf; } docWidth = () => { const layoutDoc = Doc.Layout(this.props.document); - const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth); + const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } @@ -268,7 +289,7 @@ class TreeView extends React.Component<TreeViewProps> { const layoutDoc = Doc.Layout(this.props.document); const bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { - const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1); + const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) : @@ -296,7 +317,7 @@ class TreeView extends React.Component<TreeViewProps> { DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.ignoreFields); + [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields); } else { contentElement = <EditableView key="editableView" @@ -339,7 +360,7 @@ class TreeView extends React.Component<TreeViewProps> { this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.ignoreFields)} + [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)} </ul >; } else if (this.treeViewExpandedView === "fields") { return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}> @@ -353,8 +374,10 @@ class TreeView extends React.Component<TreeViewProps> { DataDocument={this.templateDataDoc} LibraryPath={emptyPath} renderDepth={this.props.renderDepth + 1} + rootSelected={returnTrue} backgroundColor={this.props.backgroundColor} fitToBox={this.boundsOfCollectionDocument !== undefined} + FreezeDimensions={true} PanelWidth={this.docWidth} PanelHeight={this.docHeight} getTransform={this.docTransform} @@ -390,7 +413,7 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { const checked = this.props.document.type === DocumentType.COL ? undefined : this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined; - return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: 0.4 }}> + return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}> {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />} </div>; } @@ -399,8 +422,8 @@ class TreeView extends React.Component<TreeViewProps> { */ @computed get renderTitle() { - const reference = React.createRef<HTMLDivElement>(); - const onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true); + const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true); + const editTitle = ScriptField.MakeFunction("this.editTitle=true", { this: Doc.name }); const headerElements = ( <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} @@ -419,14 +442,42 @@ class TreeView extends React.Component<TreeViewProps> { <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" /> </div>); return <> - <div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} + <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} onPointerDown={onItemDown} style={{ background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0", 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" }} > - {this.editableView("title")} + {this.props.document.editTitle ? + this.editableView("title") : + <DocumentView + Document={this.props.document} + DataDoc={undefined} + LibraryPath={this.props.libraryPath || []} + addDocument={undefined} + addDocTab={this.props.addDocTab} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={this.props.onChildClick || editTitle} + dropAction={this.props.dropAction} + moveDocument={this.props.moveDocument} + removeDocument={undefined} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={returnZero} + PanelHeight={returnZero} + NativeHeight={returnZero} + NativeWidth={returnZero} + renderDepth={1} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + />} </div > {this.props.treeViewHideHeaderFields() ? (null) : headerElements} {openRight} @@ -473,6 +524,7 @@ class TreeView extends React.Component<TreeViewProps> { renderedIds: string[], libraryPath: Doc[] | undefined, onCheckedClick: ScriptField | undefined, + onChildClick: ScriptField | undefined, ignoreFields: string[] | undefined ) { const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); @@ -563,6 +615,7 @@ class TreeView extends React.Component<TreeViewProps> { indentDocument={indent} outdentDocument={outdent} onCheckedClick={onCheckedClick} + onChildClick={onChildClick} renderDepth={renderDepth} deleteDoc={remove} addDocument={addDocument} @@ -586,8 +639,15 @@ class TreeView extends React.Component<TreeViewProps> { } } +export type collectionTreeViewProps = { + treeViewHideTitle?: boolean; + treeViewHideHeaderFields?: boolean; + onCheckedClick?: ScriptField; + onChildClick?: ScriptField; +}; + @observer -export class CollectionTreeView extends CollectionSubView(Document) { +export class CollectionTreeView extends CollectionSubView(Document, undefined as any as collectionTreeViewProps) { private treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; @@ -619,7 +679,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { const doAddDoc = () => Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false); if (this.props.Document.resolvedDataDoc instanceof Promise) { - this.props.Document.resolvedDataDoc.then(resolved => doAddDoc()); + this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc()); } else { doAddDoc(); } @@ -716,18 +776,23 @@ export class CollectionTreeView extends CollectionSubView(Document) { } render() { - const dropAction = StrCast(this.props.Document.dropAction) as dropActionType; + const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType; const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs; return !childDocs ? (null) : ( <div className="collectionTreeView-dropTarget" id="body" - style={{ background: this.props.backgroundColor?.(this.props.Document), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }} + style={{ + background: this.props.backgroundColor?.(this.props.Document), + paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`, + paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`, + paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px` + }} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> - {(this.props.Document.treeViewHideTitle ? (null) : <EditableView + {(this.props.treeViewHideTitle || this.props.Document.treeViewHideTitle ? (null) : <EditableView contents={this.dataDoc.title} editing={false} display={"block"} @@ -746,8 +811,9 @@ export class CollectionTreeView extends CollectionSubView(Document) { { TreeView.GetChildElements(childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, - this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields), - BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick), this.props.ignoreFields) + this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.props.Document.treeViewHideHeaderFields), + BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick || ScriptCast(this.props.Document.onCheckedClick), + this.props.onChildClick || ScriptCast(this.props.Document.onChildClick), this.props.ignoreFields) } </ul> </div > diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss index 1c46081a1..b92c5fdd1 100644 --- a/src/client/views/collections/CollectionView.scss +++ b/src/client/views/collections/CollectionView.scss @@ -10,6 +10,59 @@ width: 100%; height: 100%; overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying... + + + .collectionTimeView-dragger { + background-color: lightgray; + height: 40px; + width: 20px; + position: absolute; + border-radius: 10px; + top: 55%; + border: 1px black solid; + z-index: 2; + left: -10px; + } + .collectionTimeView-treeView { + display: flex; + flex-direction: column; + width: 200px; + height: 100%; + position: absolute; + left: 0; + top: 0; + + .collectionTimeView-addfacet { + display: inline-block; + width: 200px; + height: 30px; + background: darkGray; + text-align: left; + + .collectionTimeView-button { + align-items: center; + display: flex; + width: 100%; + height: 100%; + + .collectionTimeView-span { + margin: auto; + } + } + + >div, + >div>div { + width: 100%; + height: 100%; + } + } + + .collectionTimeView-tree { + display: inline-block; + width: 100%; + height: calc(100% - 30px); + } + } } #google-tags { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 157229ec8..8192e6751 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,20 +1,19 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEye } from '@fortawesome/free-regular-svg-icons'; +import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction, runInAction, computed } from 'mobx'; +import { action, observable, computed } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, DataSym } from '../../../new_fields/Doc'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types'; +import { DataSym, Doc, DocListCast, Field, Opt } from '../../../new_fields/Doc'; +import { List } from '../../../new_fields/List'; +import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types'; import { ImageField } from '../../../new_fields/URLField'; import { TraceMobx } from '../../../new_fields/util'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { Utils } from '../../../Utils'; +import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; @@ -23,59 +22,49 @@ import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { ScriptBox } from '../ScriptBox'; import { Touchable } from '../Touchable'; +import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from "./CollectionDockingView"; import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionLinearView } from './CollectionLinearView'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; +import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionStackingView } from './CollectionStackingView'; import { CollectionStaffView } from './CollectionStaffView'; +import { SubCollectionViewProps } from './CollectionSubView'; +import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import { CollectionViewBaseChrome } from './CollectionViewChromes'; -import { CollectionTimeView } from './CollectionTimeView'; -import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; -import { List } from '../../../new_fields/List'; -import { SubCollectionViewProps } from './CollectionSubView'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { Id } from '../../../new_fields/FieldSymbols'; +import { listSpec } from '../../../new_fields/Schema'; +import { Docs } from '../../documents/Documents'; +import { ScriptField, ComputedField } from '../../../new_fields/ScriptField'; +import { InteractionUtils } from '../../util/InteractionUtils'; +import { ObjectField } from '../../../new_fields/ObjectField'; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree, - Stacking, - Masonry, - Multicolumn, - Multirow, - Time, - Carousel, - Linear, - Staff -} - -export namespace CollectionViewType { - const stringMapping = new Map<string, CollectionViewType>([ - ["invalid", CollectionViewType.Invalid], - ["freeform", CollectionViewType.Freeform], - ["schema", CollectionViewType.Schema], - ["docking", CollectionViewType.Docking], - ["tree", CollectionViewType.Tree], - ["stacking", CollectionViewType.Stacking], - ["masonry", CollectionViewType.Masonry], - ["multicolumn", CollectionViewType.Multicolumn], - ["multirow", CollectionViewType.Multirow], - ["time", CollectionViewType.Time], - ["carousel", CollectionViewType.Carousel], - ["linear", CollectionViewType.Linear], - ]); - - export const valueOf = (value: string) => stringMapping.get(value.toLowerCase()); + Invalid = "invalid", + Freeform = "freeform", + Schema = "schema", + Docking = "docking", + Tree = 'tree', + Stacking = "stacking", + Masonry = "masonry", + Multicolumn = "multicolumn", + Multirow = "multirow", + Time = "time", + Carousel = "carousel", + Linear = "linear", + Staff = "staff", } export interface CollectionRenderProps { @@ -84,6 +73,7 @@ export interface CollectionRenderProps { moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; active: () => boolean; whenActiveChanged: (isActive: boolean) => void; + PanelWidth: () => number; } @observer @@ -91,13 +81,16 @@ export class CollectionView extends Touchable<FieldViewProps> { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _isChildActive = false; //TODO should this be observable? - @observable private _isLightboxOpen = false; + get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); } + set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; } @observable private _curLightboxImg = 0; @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + get collectionViewType(): CollectionViewType | undefined { - const viewField = NumCast(this.props.Document._viewType); + const viewField = StrCast(this.props.Document._viewType); if (CollectionView._safeMode) { if (viewField === CollectionViewType.Freeform) { return CollectionViewType.Tree; @@ -106,18 +99,20 @@ export class CollectionView extends Touchable<FieldViewProps> { return CollectionViewType.Freeform; } } - return viewField; + return viewField as any as CollectionViewType; } - active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; + active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0; - whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; + whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); @action.bound addDocument(doc: Doc): boolean { const targetDataDoc = this.props.Document[DataSym]; - targetDataDoc[this.props.fieldKey] = new List<Doc>([...DocListCast(targetDataDoc[this.props.fieldKey]), doc]); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there + const docList = DocListCast(targetDataDoc[this.props.fieldKey]); + !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + doc.context = this.props.Document; targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); Doc.GetProto(doc).lastOpened = new DateField; return true; @@ -132,7 +127,8 @@ export class CollectionView extends Touchable<FieldViewProps> { let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - ContextMenu.Instance.clearItems(); + doc.context = undefined; + ContextMenu.Instance?.clearItems(); if (index !== -1) { value.splice(index, 1); targetDataDoc[this.props.fieldKey] = new List<Doc>(value); @@ -187,7 +183,7 @@ export class CollectionView extends Touchable<FieldViewProps> { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) : - <CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />; + <CollectionViewBaseChrome CollectionView={this} key="chrome" PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />; return [chrome, this.SubViewHelper(type, renderProps)]; } @@ -233,6 +229,8 @@ export class CollectionView extends Touchable<FieldViewProps> { if (this.props.Document.childDetailed instanceof Doc) { layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" }); } + layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); + !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); const open = ContextMenu.Instance.findByDescription("Open..."); @@ -241,14 +239,21 @@ export class CollectionView extends Touchable<FieldViewProps> { const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) }); + const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }]; + DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick => + funcs.push({ key: "onChildClick", name: StrCast(childClick.title), script: ScriptCast(childClick.script) })); + funcs.map(func => onClicks.push({ + description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { + func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script)); + ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }); + } + })); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); const more = ContextMenu.Instance.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); - } } @@ -268,6 +273,167 @@ export class CollectionView extends Touchable<FieldViewProps> { onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); } + @observable _facetWidth = 0; + + bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth(); + getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0); + facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth)); + + @computed get dataDoc() { + return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : + this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template + } + // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. + // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through + // to its children which may be templates. + // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' + @computed get dataField() { + return this.dataDoc[this.props.fieldKey]; + } + + get childLayoutPairs(): { layout: Doc; data: Doc; }[] { + const { Document, DataDoc } = this.props; + const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout); + return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types + } + get childDocList() { + return Cast(this.dataField, listSpec(Doc)); + } + get childDocs() { + const dfield = this.dataField; + const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []); + const docs = rawdocs.filter(d => d && !(d instanceof Promise)).map(d => d as Doc); + const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript); + return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + } + @computed get _allFacets() { + const facets = new Set<string>(); + this.childDocs.filter(child => child).forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); + Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key))); + return Array.from(facets); + } + + /** + * Responds to clicking the check box in the flyout menu + */ + facetClick = (facetHeader: string) => { + const facetCollection = this.props.Document; + const found = DocListCast(facetCollection[this.props.fieldKey + "-filter"]).findIndex(doc => doc.title === facetHeader); + if (found !== -1) { + (facetCollection[this.props.fieldKey + "-filter"] as List<Doc>).splice(found, 1); + const docFilter = Cast(this.props.Document._docFilters, listSpec("string")); + if (docFilter) { + let index: number; + while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { + docFilter.splice(index, 3); + } + } + const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string")); + if (docRangeFilters) { + let index: number; + while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { + docRangeFilters.splice(index, 3); + } + } + } else { + const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]); + const facetValues = Array.from(allCollectionDocs.reduce((set, child) => + set.add(Field.toString(child[facetHeader] as Field)), new Set<string>())); + + let nonNumbers = 0; + let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; + facetValues.map(val => { + const num = Number(val); + if (Number.isNaN(num)) { + nonNumbers++; + } else { + minVal = Math.min(num, minVal); + maxVal = Math.max(num, maxVal); + } + }); + let newFacet: Opt<Doc>; + if (nonNumbers / allCollectionDocs.length < .1) { + newFacet = Docs.Create.SliderDocument({ title: facetHeader }); + const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader); + Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox + newFacet.treeViewExpandedView = "layout"; + newFacet.treeViewOpen = true; + newFacet._sliderMin = ranged === undefined ? minVal : ranged[0]; + newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1]; + newFacet._sliderMinThumb = minVal; + newFacet._sliderMaxThumb = maxVal; + newFacet.target = this.props.Document; + const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`; + newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); + + Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); + } else { + newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); + const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; + const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; + newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); + } + newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); + } + } + + + onPointerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0); + return false; + }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0)); + } + filterBackground = () => "dimGray"; + @computed get scriptField() { + const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)"; + return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); + } + @computed get treeIgnoreFields() { return ["_facetCollection", "_docFilters"]; } + @computed get filterView() { + const facetCollection = this.props.Document; + const flyout = ( + <div className="collectionTimeView-flyout" style={{ width: `${this.facetWidth()}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> + {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> + <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey + "-filter"]).some(d => d.title === facet)} /> + <span className="checkmark" /> + {facet} + </label>)} + </div> + ); + return !this._facetWidth || this.props.dontRegisterView ? (null) : + <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}> + <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> + <div className="collectionTimeView-button"> + <span className="collectionTimeView-span">Facet Filters</span> + <FontAwesomeIcon icon={faEdit} size={"lg"} /> + </div> + </Flyout> + </div> + <div className="collectionTimeView-tree" key="tree"> + <CollectionTreeView {...this.props} + CollectionView={this} + treeViewHideTitle={true} + NativeHeight={returnZero} + NativeWidth={returnZero} + treeViewHideHeaderFields={true} + onCheckedClick={this.scriptField!} + ignoreFields={this.treeIgnoreFields} + annotationsKey={""} + dontRegisterView={true} + PanelWidth={this.facetWidth} + DataDoc={facetCollection} + Document={facetCollection} + backgroundColor={this.filterBackground} + fieldKey={`${this.props.fieldKey}-filter`} + moveDocument={returnFalse} + removeDocument={returnFalse} + addDocument={returnFalse} /> + </div> + </div>; + } + render() { TraceMobx(); const props: CollectionRenderProps = { @@ -276,6 +442,7 @@ export class CollectionView extends Touchable<FieldViewProps> { moveDocument: this.moveDocument, active: this.active, whenActiveChanged: this.whenActiveChanged, + PanelWidth: this.bodyPanelWidth }; return (<div className={"collectionView"} style={{ @@ -285,13 +452,21 @@ export class CollectionView extends Touchable<FieldViewProps> { }} onContextMenu={this.onContextMenu}> {this.showIsTagged()} - {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} + <div style={{ width: `calc(100% - ${this.facetWidth()}px)`, marginLeft: `${this.facetWidth()}px` }}> + {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} + </div> {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => Cast(d.data, ImageField) ? (Cast(d.data, ImageField)!.url.href.indexOf(window.location.origin) === -1) ? Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href : ""))} + {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) : + <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this.facetWidth()}px, 0px)` }} > + <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} /> + </div> + } + {this.filterView} </div>); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 0c96a662c..a691b4805 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -27,7 +27,6 @@ outline-color: black; border: none; padding: 12px 10px 11px 10px; - margin-left: 50px; } .collectionViewBaseChrome-viewPicker:active { @@ -59,10 +58,10 @@ position: absolute; width: 40px; transform-origin: top left; + pointer-events: all; // margin-top: 10px; } .collectionViewBaseChrome-template { - margin-left: 10px; display: grid; background: rgb(238, 238, 238); color:grey; @@ -169,23 +168,26 @@ } - .collectionStackingViewChrome-sectionFilter-cont, - .collectionTreeViewChrome-sectionFilter-cont { + .collectionStackingViewChrome-pivotField-cont, + .collectionTreeViewChrome-pivotField-cont { justify-self: right; display: flex; font-size: 75%; letter-spacing: 2px; - .collectionStackingViewChrome-sectionFilter-label, - .collectionTreeViewChrome-sectionFilter-label { + .collectionStackingViewChrome-pivotField-label, + .collectionTreeViewChrome-pivotField-label { vertical-align: center; - padding: 10px; + padding-left: 10px; + padding-top: 10px; + padding-bottom: 10px; } - .collectionStackingViewChrome-sectionFilter, - .collectionTreeViewChrome-sectionFilter { + .collectionStackingViewChrome-pivotField, + .collectionTreeViewChrome-pivotField { color: white; - width: 100px; + width:100%; + min-width: 100px; text-align: center; background: rgb(238, 238, 238); @@ -210,8 +212,8 @@ } } - .collectionStackingViewChrome-sectionFilter:hover, - .collectionTreeViewChrome-sectionFilter:hover { + .collectionStackingViewChrome-pivotField:hover, + .collectionTreeViewChrome-pivotField:hover { cursor: text; } } @@ -278,7 +280,7 @@ display:flex; flex-direction: row; width: 150px; - margin: auto 0 auto auto; + margin: auto auto auto auto; } .react-autosuggest__container { diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 49a9e8200..b2ca3c19f 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -8,7 +8,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils, emptyFunction } from "../../../Utils"; +import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; @@ -24,6 +24,7 @@ interface CollectionViewChromeProps { CollectionView: CollectionView; type: CollectionViewType; collapse?: (value: boolean) => any; + PanelWidth: () => number; } interface Filter { @@ -38,25 +39,30 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> { //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) + get target() { return this.props.CollectionView.props.Document; } _templateCommand = { - title: "=> item view", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"], + params: ["target", "source"], title: "=> item view", + script: "this.target.childLayout = getDocTemplate(this.source?.[0])", + immediate: (source: Doc[]) => this.target.childLayout = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, - immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined) }; _narrativeCommand = { - title: "=> click item view", script: "setChildDetailedLayout(this.target, this.source?.[0])", params: ["target", "source"], + params: ["target", "source"], title: "=> click item view", + script: "this.target.childDetailed = getDocTemplate(this.source?.[0])", + immediate: (source: Doc[]) => this.target.childDetailed = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, - immediate: (draggedDocs: Doc[]) => Doc.setChildDetailedLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined) }; _contentCommand = { - title: "=> content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"], + params: ["target", "source"], title: "=> content", + script: "getProto(this.target).data = copyField(this.source);", + immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source), // Doc.aliasDocs(source), initialize: emptyFunction, - immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d))) }; _viewCommand = { - title: "=> saved view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"], - initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document._panX; button.restoredPanY = this.props.CollectionView.props.Document._panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }, - immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document._panX = 0; this.props.CollectionView.props.Document._panY = 0; this.props.CollectionView.props.Document.scale = 1; }, + params: ["target"], title: "=> saved view", + script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", + immediate: (source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }, + initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; }, }; _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand]; _stacking_commands = [this._contentCommand, this._templateCommand]; @@ -77,6 +83,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro } private _picker: any; private _commandRef = React.createRef<HTMLInputElement>(); + private _viewRef = React.createRef<HTMLInputElement>(); private _autosuggestRef = React.createRef<Autosuggest>(); @observable private _currentKey: string = ""; @observable private _viewSpecsOpen: boolean = false; @@ -151,7 +158,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro @undoBatch viewChanged = (e: React.ChangeEvent) => { //@ts-ignore - this.props.CollectionView.props.Document._viewType = parseInt(e.target.selectedOptions[0].value); + this.props.CollectionView.props.Document._viewType = e.target.selectedOptions[0].value; } commandChanged = (e: React.ChangeEvent) => { @@ -260,10 +267,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; if (collapsed) return null; switch (this.props.type) { - case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); - case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); - case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); - case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); default: return null; } } @@ -339,8 +346,21 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; private _sensitivity: number = 16; + dragViewDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e, down, delta) => { + const vtype = this.props.CollectionView.collectionViewType; + const c = { + params: ["target"], title: vtype, + script: `this.target._viewType = ${StrCast(this.props.CollectionView.props.Document._viewType)}`, + immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]), + initialize: emptyFunction, + }; + DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), + { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY); + return true; + }, emptyFunction, emptyFunction); + } dragCommandDown = (e: React.PointerEvent) => { - this._startDragPosition = { x: e.clientX, y: e.clientY }; document.addEventListener("pointermove", this.dragPointerMove); document.addEventListener("pointerup", this.dragPointerUp); @@ -363,41 +383,52 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro dragPointerUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.dragPointerMove); document.removeEventListener("pointerup", this.dragPointerUp); - } render() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; return ( - <div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined }}> - <div className="collectionViewChrome" style={{ border: "unset" }}> + <div className="collectionViewChrome-cont" style={{ + top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined, + transform: collapsed ? "" : `scale(${Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)})`, + transformOrigin: "top left", + width: `${this.props.PanelWidth() / Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)}px` + }}> + <div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}> <div className="collectionViewBaseChrome"> <button className="collectionViewBaseChrome-collapse" style={{ top: collapsed ? 70 : 10, - transform: `rotate(${collapsed ? 180 : 0}deg) scale(${collapsed ? 0.5 : 1}) translate(${collapsed ? "-100%, -100%" : "0, 0"})`, - opacity: (collapsed && !this.props.CollectionView.props.isSelected()) ? 0 : 0.9, + transform: `rotate(${collapsed ? 180 : 0}deg) scale(0.5) translate(${collapsed ? "-100%, -100%" : "0, 0"})`, + opacity: 0.9, + display: (collapsed && !this.props.CollectionView.props.isSelected()) ? "none" : undefined, left: (collapsed ? 0 : "unset"), }} title="Collapse collection chrome" onClick={this.toggleCollapse}> <FontAwesomeIcon icon="caret-up" size="2x" /> </button> - <select - className="collectionViewBaseChrome-viewPicker" - onPointerDown={stopPropagation} - onChange={this.viewChanged} - style={{ display: collapsed ? "none" : undefined }} - value={NumCast(this.props.CollectionView.props.Document._viewType)}> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">MultiCol</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">MultiRow</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Pivot/Time</option> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Carousel</option> - </select> + <div className="collectionViewBaseChrome-template" style={{ marginLeft: 25, display: collapsed ? "none" : undefined }}> + <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}> + <div className="commandEntry-drop"> + <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon> + </div> + <select + className="collectionViewBaseChrome-viewPicker" + onPointerDown={stopPropagation} + onChange={this.viewChanged} + value={StrCast(this.props.CollectionView.props.Document._viewType)}> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="freeform">Freeform</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="schema">Schema</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="tree">Tree</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="stacking">Stacking</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="masonry">Masonry</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="multicolumn">MultiCol</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="multirow">MultiRow</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="time">Pivot/Time</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="carousel">Carousel</option> + </select> + </div> + </div> <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}> <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} > <FontAwesomeIcon icon="filter" size="2x" /> @@ -444,7 +475,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}> <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}> <div className="commandEntry-drop"> - <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon> + <FontAwesomeIcon icon="bullseye" size="2x" /> </div> <select className="collectionViewBaseChrome-cmdPicker" @@ -472,7 +503,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView @observable private suggestions: string[] = []; @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); } - @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); } + @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); } getKeySuggestions = async (value: string): Promise<string[]> => { value = value.toLowerCase(); @@ -510,26 +541,26 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView } setValue = (value: string) => { - this.props.CollectionView.props.Document.sectionFilter = value; + this.props.CollectionView.props.Document._pivotField = value; return true; } @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }; - @action resetValue = () => { this._currentKey = this.sectionFilter; }; + @action resetValue = () => { this._currentKey = this.pivotField; }; render() { return ( <div className="collectionStackingViewChrome-cont"> - <div className="collectionStackingViewChrome-sectionFilter-cont"> - <div className="collectionStackingViewChrome-sectionFilter-label"> - GROUP ITEMS BY: + <div className="collectionStackingViewChrome-pivotField-cont"> + <div className="collectionStackingViewChrome-pivotField-label"> + GROUP BY: </div> <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}> <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> </div> - <div className="collectionStackingViewChrome-sectionFilter"> + <div className="collectionStackingViewChrome-pivotField"> <EditableView - GetValue={() => this.sectionFilter} + GetValue={() => this.pivotField} autosuggestProps={ { resetValue: this.resetValue, @@ -551,7 +582,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView }} oneLine SetValue={this.setValue} - contents={this.sectionFilter ? this.sectionFilter : "N/A"} + contents={this.pivotField ? this.pivotField : "N/A"} /> </div> </div> diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 43ba5c614..afc1c6754 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import './ParentDocumentSelector.scss'; import { Doc } from "../../../new_fields/Doc"; import { observer } from "mobx-react"; -import { observable, action, runInAction } from "mobx"; +import { observable, action, runInAction, trace, computed } from "mobx"; import { Id } from "../../../new_fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; @@ -14,6 +14,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons"; import { library } from "@fortawesome/fontawesome-svg-core"; import { DocumentView } from "../nodes/DocumentView"; +import { SelectionManager } from "../../util/SelectionManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,7 +23,6 @@ library.add(faCog); type SelectorProps = { Document: Doc, - Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, location: string): void }; @@ -54,7 +54,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> { getOnClick({ col, target }: { col: Doc, target: Doc }) { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + if (col._viewType === CollectionViewType.Freeform) { const newPanX = NumCast(target.x) + NumCast(target._width) / 2; const newPanY = NumCast(target.y) + NumCast(target._height) / 2; col._panX = newPanX; @@ -93,9 +93,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> { } @observer -export class DockingViewButtonSelector extends React.Component<{ Document: Doc, Stack: any }> { - @observable hover = false; - +export class DockingViewButtonSelector extends React.Component<{ views: DocumentView[], Stack: any }> { customStylesheet(styles: any) { return { ...styles, @@ -105,16 +103,19 @@ export class DockingViewButtonSelector extends React.Component<{ Document: Doc, }, }; } + _ref = React.createRef<HTMLDivElement>(); - render() { - const view = DocumentManager.Instance.getDocumentView(this.props.Document); - const flyout = ( - <div className="ParentDocumentSelector-flyout" title=" "> - <DocumentButtonBar views={[view]} stack={this.props.Stack} /> + @computed get flyout() { + return ( + <div className="ParentDocumentSelector-flyout" title=" " ref={this._ref}> + <DocumentButtonBar views={this.props.views} stack={this.props.Stack} /> </div> ); - return <span title="Tap for menu, drag tab as document" onPointerDown={e => !this.props.Stack && e.stopPropagation()} className="buttonSelector"> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} stylesheet={this.customStylesheet}> + } + + render() { + return <span title="Tap for menu, drag tab as document" onPointerDown={e => { if (getComputedStyle(this._ref.current!).width !== "100%") {e.stopPropagation();e.preventDefault();} this.props.views[0].select(false); }} className="buttonSelector"> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> <FontAwesomeIcon icon={"cog"} size={"sm"} /> </Flyout> </span>; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 637c81fe2..bd4db89ec 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -147,6 +147,7 @@ export function computePivotLayout( const expander = 1.05; const gap = .15; + const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols); let x = 0; const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort(); sortedPivotKeys.forEach(key => { @@ -189,7 +190,6 @@ export function computePivotLayout( x += pivotAxisWidth * (numCols * expander + gap); }); - const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols); const dividers = sortedPivotKeys.map((key, i) => ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); groupNames.push(...dividers); @@ -348,7 +348,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { y: gname.y * scale, color: gname.color, width: gname.width === undefined ? undefined : gname.width * scale, - height: gname.height === -1 ? 1 : Math.max(fontHeight, (gname.height || 0) * scale), + height: gname.height === -1 ? 1 : gname.type === "text" ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale, fontSize: gname.fontSize, payload: gname.payload }))); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 1038347d4..09fc5148e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -26,8 +26,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo action(() => { setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => this._opacity = 0.05), 750); // this will unhighlight the link line. - const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; - const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; + const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!); const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!); const a = adiv.getBoundingClientRect(); @@ -43,11 +43,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1"; const bfield = afield === "anchor1" ? "anchor2" : "anchor1"; - // really hacky stuff to make the DocuLinkBox display where we want it to: + // really hacky stuff to make the LinkAnchorBox display where we want it to: // if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right // otherwise, we just use the computed nearest point on the document boundary to the target Document - const targetAhyperlink = window.document.getElementById((this.props.LinkDocs[0][afield] as Doc)[Id]); - const targetBhyperlink = window.document.getElementById((this.props.LinkDocs[0][bfield] as Doc)[Id]); + const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]); + const targetBhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][bfield] as Doc)[Id]); if (!targetBhyperlink) { this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100; this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100; @@ -81,8 +81,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo } render() { - const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; - const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; + const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect(); const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect(); const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 044d35eca..d12f93f15 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, IReactionDisposer } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; @@ -7,69 +7,12 @@ import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); -import { Utils } from "../../../../Utils"; +import { Utils, emptyFunction } from "../../../../Utils"; import { SelectionManager } from "../../../util/SelectionManager"; import { DocumentType } from "../../../documents/DocumentTypes"; @observer export class CollectionFreeFormLinksView extends React.Component { - - _brushReactionDisposer?: IReactionDisposer; - componentDidMount() { - // this._brushReactionDisposer = reaction( - // () => { - // let doclist = DocListCast(this.props.Document[this.props.fieldKey]); - // return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) }; - // }, - // () => { - // let doclist = DocListCast(this.props.Document[this.props.fieldKey]); - // let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : []; - // views.forEach((dstDoc, i) => { - // views.forEach((srcDoc, j) => { - // let dstTarg = dstDoc; - // let srcTarg = srcDoc; - // let x1 = NumCast(srcDoc.x); - // let x2 = NumCast(dstDoc.x); - // let x1w = NumCast(srcDoc.width, -1); - // let x2w = NumCast(dstDoc.width, -1); - // if (x1w < 0 || x2w < 0 || i === j) { } - // else { - // let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => { - // let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined; - // return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false; - // }); - // let brushAction = (field: (Doc | Promise<Doc>)[]) => { - // let found = findBrush(field); - // if (found !== -1) { - // field.splice(found, 1); - // } - // }; - // if (Math.abs(x1 + x1w - x2) < 20) { - // let linkDoc: Doc = new Doc(); - // linkDoc.title = "Histogram Brush"; - // linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); - // linkDoc.brushingDocs = new List([dstTarg, srcTarg]); - - // brushAction = (field: (Doc | Promise<Doc>)[]) => { - // if (findBrush(field) === -1) { - // field.push(linkDoc); - // } - // }; - // } - // if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>(); - // if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>(); - // let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []); - // let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []); - // brushAction(dstBrushDocs); - // brushAction(srcBrushDocs); - // } - // }); - // }); - // }); - } - componentWillUnmount() { - this._brushReactionDisposer && this._brushReactionDisposer(); - } @computed get uniqueConnections() { const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { @@ -86,8 +29,10 @@ export class CollectionFreeFormLinksView extends React.Component { } return drawnPairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); - return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) // get rid of the filter to show links to documents in addition to document anchors - .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); + return connections.filter(c => + c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK && + c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed + ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); } render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 0b5e44ccb..730392ab5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -36,6 +36,7 @@ height: 100%; display: flex; align-items: center; + .collectionfreeformview-placeholderSpan { font-size: 32; display: flex; @@ -99,4 +100,10 @@ #prevCursor { animation: blink 1s infinite; +} + +.pullpane-indicator { + z-index: 99999; + background-color: rgba($color: #000000, $alpha: .4); + position: absolute; }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ca8d5e18b..146ec9f7d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,24 +1,26 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../../new_fields/Doc"; +import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkTool } from "../../../../new_fields/InkField"; +import { InkData, InkField, InkTool } from "../../../../new_fields/InkField"; +import { List } from "../../../../new_fields/List"; +import { RichTextField } from "../../../../new_fields/RichTextField"; import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { Cast, NumCast, ScriptCast, BoolCast, StrCast } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types"; import { TraceMobx } from "../../../../new_fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils"; +import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager } from "../../../util/DragManager"; +import { DragManager, dropActionType } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; import { InteractionUtils } from "../../../util/InteractionUtils"; import { SelectionManager } from "../../../util/SelectionManager"; @@ -33,6 +35,7 @@ import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; +import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; @@ -63,16 +66,24 @@ export const panZoomSchema = createSchema({ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); +export type collectionFreeformViewProps = { + forceScaling?:boolean; // whether to force scaling of content (needed by ImageBox) +}; @observer -export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { +export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, undefined as any as collectionFreeformViewProps) { private _lastX: number = 0; private _lastY: number = 0; + private _inkToTextStartX: number | undefined; + private _inkToTextStartY: number | undefined; + private _wordPalette: Map<string, string> = new Map<string, string>(); private _clusterDistance: number = 75; private _hitCluster = false; private _layoutComputeReaction: IReactionDisposer | undefined; - private _layoutPoolData = observable.map<string, any>(); + private _layoutPoolData = new ObservableMap<string, any>(); private _cachedPool: Map<string, any> = new Map(); + @observable private _pullCoords: number[] = [0, 0]; + @observable private _pullDirection: string = ""; public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @@ -80,9 +91,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; } @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; } - @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); } - @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); } - @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); } + @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } + @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); } + @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); } private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } private easing = () => this.props.Document.panTransformType === "Ease"; @@ -91,6 +102,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ? Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) : this.Document.scale || 1) + private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); @@ -322,31 +334,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._lastX = e.pageX; this._lastY = e.pageY; } - // eraser or scrubber plus anything else mode + // eraser plus anything else mode else { e.stopPropagation(); e.preventDefault(); } } - // if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { - // document.removeEventListener("pointermove", this.onPointerMove); - // document.removeEventListener("pointerup", this.onPointerUp); - // document.addEventListener("pointermove", this.onPointerMove); - // document.addEventListener("pointerup", this.onPointerUp); - // if (InkingControl.Instance.selectedTool === InkTool.None) { - // this._lastX = e.pageX; - // this._lastY = e.pageY; - // } - // else { - // e.stopPropagation(); - // e.preventDefault(); - - // if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - // let point = this.getTransform().transformPoint(e.pageX, e.pageY); - // this._points.push({ x: point[0], y: point[1] }); - // } - // } - // } } @action @@ -411,8 +404,91 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); sel.forEach(d => this.props.removeDocument(d)); + e.stopPropagation(); break; - + case GestureUtils.Gestures.StartBracket: + const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); + this._inkToTextStartX = start[0]; + this._inkToTextStartY = start[1]; + console.log("start"); + break; + case GestureUtils.Gestures.EndBracket: + console.log("end"); + if (this._inkToTextStartX && this._inkToTextStartY) { + const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); + const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color); + const sets = setDocs.map((sd) => { + return Cast(sd.data, RichTextField)?.Text as string; + }); + if (sets.length && sets[0]) { + this._wordPalette.clear(); + const colors = setDocs.map(sd => FieldValue(sd.color) as string); + sets.forEach((st: string, i: number) => { + const words = st.split(","); + words.forEach(word => { + this._wordPalette.set(word, colors[i]); + }); + }); + } + const inks = this.getActiveDocuments().filter(doc => { + if (doc.type === "ink") { + const l = NumCast(doc.x); + const r = l + doc[WidthSym](); + const t = NumCast(doc.y); + const b = t + doc[HeightSym](); + const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t); + return pass; + } + return false; + }); + // const inkFields = inks.map(i => Cast(i.data, InkField)); + const strokes: InkData[] = []; + inks.forEach(i => { + const d = Cast(i.data, InkField); + const x = NumCast(i.x); + const y = NumCast(i.y); + const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); + const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + if (d) { + strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); + } + }); + + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { + console.log(results); + const wordResults = results.filter((r: any) => r.category === "inkWord"); + for (const word of wordResults) { + const indices: number[] = word.strokeIds; + indices.forEach(i => { + const otherInks: Doc[] = []; + indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2])); + inks[i].relatedInks = new List<Doc>(otherInks); + const uniqueColors: string[] = []; + Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c)); + inks[i].alternativeColors = new List<string>(uniqueColors); + if (this._wordPalette.has(word.recognizedText.toLowerCase())) { + inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase()); + } + else if (word.alternates) { + for (const alt of word.alternates) { + if (this._wordPalette.has(alt.recognizedString.toLowerCase())) { + inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase()); + break; + } + } + } + }); + } + }); + this._inkToTextStartX = end[0]; + } + break; + case GestureUtils.Gestures.Text: + if (ge.text) { + const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y); + this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); + e.stopPropagation(); + } } } @@ -428,37 +504,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { - // I think it makes sense for the marquee menu to go away when panned. -syip2 - MarqueeOptionsMenu.Instance.fadeOut(true); + // bcz: theres should be a better way of doing these than referencing these static instances directly + MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2 + PDFMenu.Instance.fadeOut(true); - let x = this.Document._panX || 0; - let y = this.Document._panY || 0; - const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) { - PDFMenu.Instance.fadeOut(true); - const minx = this.childDataProvider(docs[0]).x; - const miny = this.childDataProvider(docs[0]).y; - const maxx = this.childDataProvider(docs[0]).width + minx; - const maxy = this.childDataProvider(docs[0]).height + miny; - const ranges = docs.filter(doc => doc && this.childDataProvider(doc)).reduce((range, doc) => { - const x = this.childDataProvider(doc).x; - const y = this.childDataProvider(doc).y; - const xe = this.childDataProvider(doc).width + x; - const ye = this.childDataProvider(doc).height + y; - return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], - [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; - }, [[minx, maxx], [miny, maxy]]); - - const cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; - const panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale, - this.props.PanelHeight() / this.zoomScaling() * cscale); - if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; - if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; - if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; - if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; + let x = (this.Document._panX || 0) - dx; + let y = (this.Document._panY || 0) - dy; + if (!this.isAnnotationOverlay) { + // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds + const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); + const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc)); + if (measuredDocs.length) { + const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content + ({ + xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) }, + yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) } + }) + , { + xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, + yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE } + }); + + const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()]; + if (ranges.xrange.min > (this.panX() + panelDim[0] / 2)) x = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds + if (ranges.xrange.max < (this.panX() - panelDim[0] / 2)) x = ranges.xrange.min - panelDim[0] / 2; + if (ranges.yrange.min > (this.panY() + panelDim[1] / 2)) y = ranges.yrange.max + panelDim[1] / 2; + if (ranges.yrange.max < (this.panY() - panelDim[1] / 2)) y = ranges.yrange.min - panelDim[1] / 2; + } } - this.setPan(x - dx, y - dy); + this.setPan(x, y); this._lastX = e.clientX; this._lastY = e.clientY; } @@ -547,7 +622,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // use the centerx and centery as the "new mouse position" const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - this.pan({ clientX: centerX, clientY: centerY }); + // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY); + + if (!this._pullDirection) { // if we are not bezel movement + this.pan({ clientX: centerX, clientY: centerY }); + } else { + this._pullCoords = [centerX, centerY]; + } + this._lastX = centerX; this._lastY = centerY; } @@ -572,6 +654,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; this._lastX = centerX; this._lastY = centerY; + const screenBox = this._mainCont?.getBoundingClientRect(); + + + // determine if we are using a bezel movement + if (screenBox) { + if ((screenBox.right - centerX) < 100) { + this._pullCoords = [centerX, centerY]; + this._pullDirection = "right"; + } else if (centerX - screenBox.left < 100) { + this._pullCoords = [centerX, centerY]; + this._pullDirection = "left"; + } else if (screenBox.bottom - centerY < 100) { + this._pullCoords = [centerX, centerY]; + this._pullDirection = "bottom"; + } else if (centerY - screenBox.top < 100) { + this._pullCoords = [centerX, centerY]; + this._pullDirection = "top"; + } + } + + this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -582,12 +685,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } cleanUpInteractions = () => { + switch (this._pullDirection) { + case "left": + case "right": + case "top": + case "bottom": + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection); + } + + this._pullDirection = ""; + this._pullCoords = [0, 0]; + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); } + @action zoom = (pointX: number, pointY: number, deltaY: number): void => { let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1; @@ -670,6 +785,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const offset = annotOn && (contextHgt / 2 * 96 / 72); this.props.Document.scrollY = NumCast(doc.y) - offset; } + + afterFocus && setTimeout(afterFocus, 1000); } else { const layoutdoc = Doc.Layout(doc); const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2; @@ -680,14 +797,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType }; - if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { + if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + } Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); willZoom && this.setScaleToZoom(layoutdoc, scale); Doc.linkFollowHighlight(doc); afterFocus && setTimeout(() => { - if (afterFocus && afterFocus()) { + if (afterFocus?.()) { this.Document._panX = savedState.px; this.Document._panY = savedState.py; this.Document.scale = savedState.s; @@ -702,12 +821,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height)); } - zoomToScale = (scale: number) => { - this.Document.scale = scale; - } - - getScale = () => this.Document.scale || 1; - @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } backgroundHalo = () => BoolCast(this.Document.useClusters); @@ -715,10 +828,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, + NativeHeight: returnZero, + NativeWidth: returnZero, + fitToBox: false, DataDoc: childData, Document: childLayout, LibraryPath: this.libraryPath, + FreezeDimensions: this.props.freezeChildDimensions, layoutKey: undefined, + rootSelected: this.rootSelected, + dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them onClick: this.onChildClickHandler, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, @@ -733,11 +852,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { backgroundHalo: this.backgroundHalo, parentActive: this.props.active, bringToFront: this.bringToFront, - zoomToScale: this.zoomToScale, - getScale: this.getScale + addDocTab: this.addDocTab, }; } + addDocTab = (doc: Doc, where: string) => { + if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); + return true; + } + return this.props.addDocTab(doc, where); + } getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { const result = this.Document.arrangeScript?.script.run(params, console.log); if (result?.success) { @@ -789,12 +914,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }.bind(this)); doTimelineLayout(poolData: Map<string, PoolData>) { - return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs, + return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.childDocs, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } doPivotLayout(poolData: Map<string, PoolData>) { - return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs, + return computePivotLayout(poolData, this.props.Document, this.childDocs, this.childDocs, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } @@ -820,42 +945,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } - @computed get filterDocs() { - const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []); - const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - for (let i = 0; i < docFilters.length; i += 3) { - const [key, value, modifiers] = docFilters.slice(i, i + 3); - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - } - const filteredDocs = docFilters.length ? this.childDocs.filter(d => { - for (const facetKey of Object.keys(filterFacets)) { - const facet = filterFacets[facetKey]; - const satisfiesFacet = Object.keys(facet).some(value => - (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value)); - if (!satisfiesFacet) { - return false; - } - } - return true; - }) : this.childDocs; - const rangeFilteredDocs = filteredDocs.filter(d => { - for (let i = 0; i < docRangeFilters.length; i += 3) { - const key = docRangeFilters[i]; - const min = Number(docRangeFilters[i + 1]); - const max = Number(docRangeFilters[i + 2]); - const val = Cast(d[key], "number", null); - if (val !== undefined && (val < min || val > max)) { - return false; - } - } - return true; - }); - return rangeFilteredDocs; - } childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; @@ -872,11 +961,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const elements: ViewDefResult[] = computedElementData.slice(); this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => elements.push({ - ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)} + ele: <CollectionFreeFormDocumentView + key={pair.layout[Id]} + {...this.getChildDocumentViewProps(pair.layout, pair.data)} dataProvider={this.childDataProvider} LayoutDoc={this.childLayoutDocFunc} jitterRotation={NumCast(this.props.Document.jitterRotation)} - fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />, + fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} + FreezeDimensions={BoolCast(this.props.freezeChildDimensions)} + />, bounds: this.childDataProvider(pair.layout) })); @@ -995,7 +1088,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (doc instanceof Doc) { const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); doc.x = xx, doc.y = yy; - this.props.addDocument && this.props.addDocument(doc); + this.props.addDocument?.(doc); } } } @@ -1035,14 +1128,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { </CollectionFreeFormViewPannableContents> </MarqueeView>; } + @computed get contentScaling() { - if (this.props.annotationsKey) return 0; - const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; - const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; + if (this.props.annotationsKey && !this.props.forceScaling) return 0; + const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth()); + const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight()); + const hscale = nh ? this.props.PanelHeight() / nh : 1; + const wscale = nw ? this.props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } render() { TraceMobx(); + const clientRect = this._mainCont?.getBoundingClientRect(); // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) // this.Document.fitX = this.contentBounds && this.contentBounds.x; // this.Document.fitY = this.contentBounds && this.contentBounds.y; @@ -1064,7 +1161,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? this.placeholder : this.marqueeView} <CollectionFreeFormOverlayView elements={this.elementFunc} /> - </div>; + + <div className={"pullpane-indicator"} + style={{ + display: this._pullDirection ? "block" : "none", + top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto", + // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto", + left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto", + width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0, + height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0, + + }}> + </div> + + </div >; } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 71f265484..db4b674b5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; @@ -43,6 +44,13 @@ export default class MarqueeOptionsMenu extends AntimodeMenu { onPointerDown={this.delete}> <FontAwesomeIcon icon="trash-alt" size="lg" /> </button>, + <button + className="antimodeMenu-button" + title="Change to Text" + key="inkToText" + onPointerDown={this.inkToText}> + <FontAwesomeIcon icon="font" size="lg" /> + </button>, ]; return this.getElement(buttons); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index d4fa22a7e..503df10c2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,11 +1,10 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../new_fields/Doc"; -import { InkField } from "../../../../new_fields/InkField"; +import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc"; +import { InkField, InkData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { Cast, NumCast } from "../../../../new_fields/Types"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; +import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; import { Docs, DocUtils } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; @@ -17,6 +16,8 @@ import { SubCollectionViewProps } from "../CollectionSubView"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); +import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; +import { RichTextField } from "../../../../new_fields/RichTextField"; import { CollectionView } from "../CollectionView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; @@ -105,13 +106,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); } else if (!e.ctrlKey) { FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : ""; - this.props.addLiveTextDocument( - Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" })); - } else if (e.keyCode > 48 && e.keyCode <= 57) { - const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }); - text.layout = notes[(e.keyCode - 49) % notes.length]; - this.props.addLiveTextDocument(text); + const tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }); + const template = FormattedTextBox.DefaultLayout; + if (template instanceof Doc) { + tbox._width = NumCast(template._width); + tbox.layoutKey = "layout_" + StrCast(template.title); + tbox[StrCast(tbox.layoutKey)] = template; + } + this.props.addLiveTextDocument(tbox); } e.stopPropagation(); } @@ -209,6 +211,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight; MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); @@ -336,7 +339,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.displayTimecode = undefined; + d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection return d; }); } @@ -348,6 +351,85 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } @action + syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => { + const selected = this.marqueeSelect(false); + if (e instanceof KeyboardEvent ? e.key === "i" : true) { + const inks = selected.filter(s => s.proto?.type === "ink"); + const setDocs = selected.filter(s => s.proto?.type === "text" && s.color); + const sets = setDocs.map((sd) => { + return Cast(sd.data, RichTextField)?.Text as string; + }); + const colors = setDocs.map(sd => FieldValue(sd.color) as string); + const wordToColor = new Map<string, string>(); + sets.forEach((st: string, i: number) => { + const words = st.split(","); + words.forEach(word => { + wordToColor.set(word, colors[i]); + }); + }); + const strokes: InkData[] = []; + inks.forEach(i => { + const d = Cast(i.data, InkField); + const x = NumCast(i.x); + const y = NumCast(i.y); + const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); + const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + if (d) { + strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); + } + }); + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { + // const wordResults = results.filter((r: any) => r.category === "inkWord"); + // console.log(wordResults); + // console.log(results); + // for (const word of wordResults) { + // const indices: number[] = word.strokeIds; + // indices.forEach(i => { + // if (wordToColor.has(word.recognizedText.toLowerCase())) { + // inks[i].color = wordToColor.get(word.recognizedText.toLowerCase()); + // } + // else { + // for (const alt of word.alternates) { + // if (wordToColor.has(alt.recognizedString.toLowerCase())) { + // inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase()); + // break; + // } + // } + // } + // }) + // } + // const wordResults = results.filter((r: any) => r.category === "inkWord"); + // for (const word of wordResults) { + // const indices: number[] = word.strokeIds; + // indices.forEach(i => { + // const otherInks: Doc[] = []; + // indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2])); + // inks[i].relatedInks = new List<Doc>(otherInks); + // const uniqueColors: string[] = []; + // Array.from(wordToColor.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c)); + // inks[i].alternativeColors = new List<string>(uniqueColors); + // if (wordToColor.has(word.recognizedText.toLowerCase())) { + // inks[i].color = wordToColor.get(word.recognizedText.toLowerCase()); + // } + // else if (word.alternates) { + // for (const alt of word.alternates) { + // if (wordToColor.has(alt.recognizedString.toLowerCase())) { + // inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase()); + // break; + // } + // } + // } + // }); + // } + const lines = results.filter((r: any) => r.category === "line"); + console.log(lines); + const text = lines.map((l: any) => l.recognizedText).join("\r\n"); + this.props.addDocument(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); + }); + } + } + + @action summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { const bounds = this.Bounds; const selected = this.marqueeSelect(false); @@ -358,13 +440,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque d.page = -1; return d; }); - const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "-summary-" }); + const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); const portal = Doc.MakeAlias(summary); - Doc.GetProto(summary)["data-annotations"] = new List<Doc>(selected); - Doc.GetProto(summary).layout_portal = CollectionView.LayoutString("data-annotations"); + Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected); + Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations"); summary._backgroundColor = "#e2ad32"; portal.layoutKey = "layout_portal"; - DocUtils.MakeLink({ doc: summary, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, "portal link", "portal link"); + portal.title = "document collection"; + DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing"); this.props.addLiveTextDocument(summary); MarqueeOptionsMenu.Instance.fadeOut(true); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 82175c0b5..7e511ae34 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -14,6 +14,7 @@ import "./collectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; import { List } from '../../../../new_fields/List'; +import { returnZero } from '../../../../Utils'; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; const MulticolumnDocument = makeInterface(documentSchema); @@ -208,6 +209,10 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu {...this.props} Document={layout} DataDocument={layout.resolvedDataDoc as Doc} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)} backgroundColor={this.props.backgroundColor} CollectionDoc={this.props.Document} PanelWidth={width} @@ -223,17 +228,13 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu */ @computed private get contents(): JSX.Element[] | null { - // bcz: feels like a hack ... trying to show something useful when there's no list document in the data field of a templated object - const expanded = Cast(this.props.Document.expandedTemplate, Doc, null); - let { childLayoutPairs } = this.dataDoc[this.props.fieldKey] instanceof List || !expanded ? this : { childLayoutPairs: [] } as { childLayoutPairs: { layout: Doc, data: Doc }[] }; - const replaced = !childLayoutPairs.length && !Cast(expanded?.layout, Doc, null) && expanded; - childLayoutPairs = childLayoutPairs.length || !replaced ? childLayoutPairs : [{ layout: replaced, data: replaced }]; + const { childLayoutPairs } = this; const { Document, PanelHeight } = this.props; const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); - const width = () => expanded ? this.props.PanelWidth() : this.lookupPixels(layout); + const width = () => this.lookupPixels(layout); const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( <div className={"document-wrapper"} @@ -248,6 +249,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu <ResizeBar width={resizerWidth} key={"resizer" + i} + select={this.props.select} columnUnitLength={this.getColumnUnitLength} toLeft={layout} toRight={childLayoutPairs[i + 1]?.layout} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 5e59f8237..daf1fda6c 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import { Doc } from '../../../../new_fields/Doc'; import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; -import { Utils } from '../../../../Utils'; +import { Utils, returnZero } from '../../../../Utils'; import "./collectionMultirowView.scss"; import { computed, trace, observable, action } from 'mobx'; import { Transform } from '../../../util/Transform'; @@ -208,6 +208,10 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) {...this.props} Document={layout} DataDocument={layout.resolvedDataDoc as Doc} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)} backgroundColor={this.props.backgroundColor} CollectionDoc={this.props.Document} PanelWidth={width} diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 2cbeb3526..e1e604686 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -11,6 +11,7 @@ interface ResizerProps { columnUnitLength(): number | undefined; toLeft?: Doc; toRight?: Doc; + select: (isCtrlPressed: boolean) => void; } const resizerOpacity = 1; @@ -23,6 +24,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { @action private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => { + this.props.select(false); e.stopPropagation(); e.preventDefault(); window.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss index 019f931f9..9d3d2e592 100644 --- a/src/client/views/globalCssVariables.scss +++ b/src/client/views/globalCssVariables.scss @@ -21,7 +21,7 @@ serif; // misc values $border-radius: 0.3em; // -$search-thumnail-size: 175; +$search-thumnail-size: 130; // dragged items $contextMenu-zindex: 100000; // context menu shows up over everything diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index ac4f8a3cf..b7f3dd995 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -253,8 +253,8 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> { render() { const groupType = StrCast(this.props.groupDoc.linkRelationship); // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") { - let buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>; - let addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>; + const buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>; + const addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>; return ( <div className="linkEditor-group"> diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 88f837a03..928413a11 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { document.removeEventListener("pointerup", this.onLinkButtonUp); 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); + DragManager.StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); } @@ -70,6 +70,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType} addDocTab={this.props.addDocTab} + docView={this.props.docView} linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 5fd6e4630..d091e06ef 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -12,12 +12,14 @@ import './LinkMenuItem.scss'; import React = require("react"); import { DocumentManager } from '../../util/DocumentManager'; import { setupMoveUpEvents, emptyFunction } from '../../../Utils'; +import { DocumentView } from '../nodes/DocumentView'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); interface LinkMenuItemProps { groupType: string; linkDoc: Doc; + docView: DocumentView; sourceDoc: Doc; destinationDoc: Doc; showEditor: (linkDoc: Doc) => void; @@ -81,7 +83,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { document.removeEventListener("pointerup", this.onLinkButtonUp); 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]); + DragManager.StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]); } e.stopPropagation(); } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 3b19a6dba..53b54d7e4 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -5,11 +5,15 @@ display:flex; pointer-events: all; cursor:default; + .audiobox-buttons { + display: flex; + width: 100%; + align-items: center; + } .audiobox-handle { width:20px; height:100%; display:inline-block; - background: gray; } .audiobox-control, .audiobox-control-interactive { top:0; @@ -25,11 +29,14 @@ pointer-events: all; width:100%; height:100%; - position: absolute; + position: relative; pointer-events: none; } .audiobox-record-interactive { pointer-events: all; + width:100%; + height:100%; + position: relative; } .audiobox-controls { width:100%; @@ -37,7 +44,6 @@ position: relative; display: flex; padding-left: 2px; - border: gray solid 3px; .audiobox-player { margin-top:auto; margin-bottom:auto; @@ -46,13 +52,18 @@ position: relative; padding-right: 5px; display: flex; - .audiobox-playhead { + .audiobox-playhead, .audiobox-dictation { position: relative; margin-top: auto; margin-bottom: auto; width: 25px; padding: 2px; } + .audiobox-dictation { + align-items: center; + display: inherit; + background: dimgray; + } .audiobox-timeline { position:relative; height:100%; @@ -74,9 +85,10 @@ margin-left:-2.55px; background:gray; border-radius: 100%; + opacity:0.9; background-color: transparent; box-shadow: black 2px 2px 1px; - .docuLinkBox-cont { + .linkAnchorBox-cont { position: relative !important; height: 100% !important; width: 100% !important; @@ -91,7 +103,7 @@ box-shadow: black 1px 1px 1px; margin-left: -1; margin-top: -2; - .docuLinkBox-cont { + .linkAnchorBox-cont { position: relative !important; height: 100% !important; width: 100% !important; @@ -100,7 +112,7 @@ } } .audiobox-linker:hover, .audiobox-linker-mini:hover { - transform:scale(1.5); + opacity:1; } .audiobox-marker-container, .audiobox-marker-minicontainer { position:absolute; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 62a479b2a..ff9630273 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -7,7 +7,7 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField"; import { DocExtendableComponent } from "../DocComponent"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; @@ -17,6 +17,11 @@ import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentView } from "./DocumentView"; +import { Docs } from "../../documents/Documents"; +import { ComputedField } from "../../../new_fields/ScriptField"; +import { Networking } from "../../Network"; + +// testing testing interface Window { MediaRecorder: MediaRecorder; @@ -44,45 +49,54 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume _ele: HTMLAudioElement | null = null; _recorder: any; _recordStart = 0; + _stream: MediaStream | undefined; @observable private static _scrubTime = 0; - @observable private _audioState: "unrecorded" | "recording" | "recorded" = "unrecorded"; - @observable private _playing = false; - public static SetScrubTime = action((timeInMillisFrom1970: number) => AudioBox._scrubTime = timeInMillisFrom1970); + @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); } + set audioState(value) { this.dataDoc.audioState = value; } + public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); }; public static ActiveRecordings: Doc[] = []; + @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } + async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); } + + componentWillUnmount() { + this._reactionDisposer?.(); + this._linkPlayDisposer?.(); + this._scrubbingDisposer?.(); + } componentDidMount() { - runInAction(() => this._audioState = this.path ? "recorded" : "unrecorded"); + runInAction(() => this.audioState = this.path ? "paused" : undefined); this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { - scrollLinkId && DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => { - const la1 = l.anchor1 as Doc; - const linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode); - setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250); - }); - scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); + if (scrollLinkId) { + DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => { + const linkTime = Doc.AreProtosEqual(l.anchor1 as Doc, this.dataDoc) ? NumCast((l.anchor1 as Doc).timecode) : NumCast((l.anchor2 as Doc).timecode); + setTimeout(() => { this.playFromTime(linkTime); Doc.linkFollowHighlight(l); }, 250); + }); + Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); + } }, { fireImmediately: true }); this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(), selected => { const sel = selected.length ? selected[0].props.Document : undefined; - this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime()); + this.Document.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime()); + this.Document.playOnSelect && this.recordingStart && !sel && this.pause(); }); - this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => { - const start = DateCast(this.dataDoc[this.props.fieldKey + "-recordingStart"]); - start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000); - }); + this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.Document.playOnSelect && this.playFromTime(AudioBox._scrubTime)); } timecodeChanged = () => { const htmlEle = this._ele; - if (this._audioState === "recorded" && htmlEle) { + if (this.audioState !== "recording" && htmlEle) { htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { let la1 = l.anchor1 as Doc; - let linkTime = NumCast(l.anchor2Timecode); + const la2 = l.anchor2 as Doc; + let linkTime = NumCast(la2.timecode); if (Doc.AreProtosEqual(la1, this.dataDoc)) { + linkTime = NumCast(la1.timecode); la1 = l.anchor2 as Doc; - linkTime = NumCast(l.anchor1Timecode); } if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) { Doc.linkFollowHighlight(la1); @@ -94,68 +108,52 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume pause = action(() => { this._ele!.pause(); - this._playing = false; + this.audioState = "paused"; }); + playFromTime = (absoluteTime: number) => { + this.recordingStart && this.playFrom((absoluteTime - this.recordingStart) / 1000); + } playFrom = (seekTimeInSeconds: number) => { if (this._ele && AudioBox.Enabled) { if (seekTimeInSeconds < 0) { - this.pause(); + if (seekTimeInSeconds > -1) { + setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); + } else { + this.pause(); + } } else if (seekTimeInSeconds <= this._ele.duration) { this._ele.currentTime = seekTimeInSeconds; this._ele.play(); - runInAction(() => this._playing = true); + runInAction(() => this.audioState = "playing"); } else { this.pause(); } } } - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - this._linkPlayDisposer && this._linkPlayDisposer(); - this._scrubbingDisposer && this._scrubbingDisposer(); - } - updateRecordTime = () => { - if (this._audioState === "recording") { + if (this.audioState === "recording") { setTimeout(this.updateRecordTime, 30); this.Document.currentTimecode = (new Date().getTime() - this._recordStart) / 1000; } } - recordAudioAnnotation = () => { - let gumStream: any; - const self = this; - navigator.mediaDevices.getUserMedia({ - audio: true - }).then(function (stream) { - gumStream = stream; - self._recorder = new MediaRecorder(stream); - self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date()); - AudioBox.ActiveRecordings.push(self.props.Document); - self._recorder.ondataavailable = async function (e: any) { - const formData = new FormData(); - formData.append("file", e.data); - const res = await fetch(Utils.prepend("/uploadFormData"), { - method: 'POST', - body: formData - }); - const files = await res.json(); - const url = Utils.prepend(files[0].path); - // upload to server with known URL - self.props.Document[self.props.fieldKey] = new AudioField(url); - }; - runInAction(() => self._audioState = "recording"); - self._recordStart = new Date().getTime(); - setTimeout(self.updateRecordTime, 0); - self._recorder.start(); - setTimeout(() => { - self.stopRecording(); - gumStream.getAudioTracks()[0].stop(); - }, 60 * 60 * 1000); // stop after an hour? - }); + recordAudioAnnotation = async () => { + this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + this._recorder = new MediaRecorder(this._stream); + this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date()); + AudioBox.ActiveRecordings.push(this.props.Document); + this._recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(e.data); + this.props.Document[this.props.fieldKey] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client)); + }; + this._recordStart = new Date().getTime(); + runInAction(() => this.audioState = "recording"); + setTimeout(this.updateRecordTime, 0); + this._recorder.start(); + setTimeout(() => this._recorder && this.stopRecording(), 60 * 1000); // stop after an hour } specificContextMenu = (e: React.MouseEvent): void => { @@ -167,8 +165,10 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume stopRecording = action(() => { this._recorder.stop(); + this._recorder = undefined; this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000; - this._audioState = "recorded"; + this.audioState = "paused"; + this._stream?.getAudioTracks()[0].stop(); const ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1)); }); @@ -185,14 +185,25 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume e.stopPropagation(); } onStop = (e: any) => { - this.pause(); - this._ele!.currentTime = 0; + this.Document.playOnSelect = !this.Document.playOnSelect; + e.stopPropagation(); + } + onFile = (e: any) => { + const newDoc = Docs.Create.TextDocument("", { + title: "", _chromeStatus: "disabled", + x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, + _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height) + }); + Doc.GetProto(newDoc).recordingSource = this.dataDoc; + Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`this.recordingSource["${this.props.fieldKey}-recordingStart"]`); + Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("this.recordingSource.audioState"); + this.props.addDocument?.(newDoc); e.stopPropagation(); } setRef = (e: HTMLAudioElement | null) => { - e && e.addEventListener("timeupdate", this.timecodeChanged); - e && e.addEventListener("ended", this.pause); + e?.addEventListener("timeupdate", this.timecodeChanged); + e?.addEventListener("ended", this.pause); this._ele = e; } @@ -212,45 +223,55 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume render() { const interactive = this.active() ? "-interactive" : ""; - return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} - onClick={!this.path ? this.recordClick : undefined}> - <div className="audiobox-handle"></div> + return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} onClick={!this.path ? this.recordClick : undefined}> {!this.path ? - <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}> - {this._audioState === "recording" ? "STOP" : "RECORD"} - </button> : + <div className="audiobox-buttons"> + <div className="audiobox-dictation" onClick={this.onFile}> + <FontAwesomeIcon style={{ width: "30px", background: this.Document.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> + </div> + <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}> + {this.audioState === "recording" ? "STOP" : "RECORD"} + </button> + </div> : <div className="audiobox-controls"> <div className="audiobox-player" onClick={this.onPlay}> - <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> - <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> + <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this.audioState === "paused" ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> + <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.Document.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> <div className="audiobox-timeline" onClick={e => e.stopPropagation()} onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { const rect = (e.target as any).getBoundingClientRect(); + const wasPaused = this.audioState === "paused"; this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); - this.pause(); + wasPaused && this.pause(); e.stopPropagation(); } }} > {DocListCast(this.dataDoc.links).map((l, i) => { let la1 = l.anchor1 as Doc; let la2 = l.anchor2 as Doc; - let linkTime = NumCast(l.anchor2Timecode); + let linkTime = NumCast(la2.timecode); if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.anchor2 as Doc; la2 = l.anchor1 as Doc; - linkTime = NumCast(l.anchor1Timecode); + linkTime = NumCast(la1.timecode); } return !linkTime ? (null) : <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}> <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}> - <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)} - parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + <DocumentView {...this.props} + Document={l} + NativeHeight={returnZero} + NativeWidth={returnZero} + rootSelected={returnFalse} + layoutKey={Doc.LinkEndpoint(l, la2)} + ContainingCollectionDoc={this.props.Document} + parentActive={returnTrue} + bringToFront={emptyFunction} backgroundColor={returnTransparent} /> </div> <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)} - onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} - onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { const wasPaused = this.audioState === "paused"; this.playFrom(linkTime); wasPaused && this.pause(); e.stopPropagation(); } }} /> </div>; })} <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} /> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index eaab4086c..f9f5f449c 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -10,7 +10,6 @@ import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); import { PositionDocument } from "../../../new_fields/documentSchemas"; import { TraceMobx } from "../../../new_fields/util"; -import { returnFalse } from "../../../Utils"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -41,9 +40,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt; } + @computed get freezeDimensions() { return this.props.FreezeDimensions; } @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; } - @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); } - @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); } + @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } + @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); } @computed get renderScriptDim() { if (this.Document.renderScript) { @@ -60,14 +60,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF return undefined; } - contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox ? this.width / this.nativeWidth : 1; - panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth()); - panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight()); + contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1; + panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.()); + panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight?.()); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()) focusDoc = (doc: Doc) => this.props.focus(doc, false); + NativeWidth = () => this.nativeWidth; + NativeHeight = () => this.nativeHeight; render() { TraceMobx(); return <div className="collectionFreeFormDocumentView-container" @@ -89,21 +91,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF pointerEvents: this.props.Document.isBackground ? "none" : undefined }} > - {!this.props.fitToBox ? <DocumentView {...this.props} - dragDivName={"collectionFreeFormDocumentView-container"} - ContentScaling={this.contentScaling} - ScreenToLocalTransform={this.getTransform} - backgroundColor={this.props.backgroundColor} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} - /> : <ContentFittingDocumentView {...this.props} - CollectionDoc={this.props.ContainingCollectionDoc} - DataDocument={this.props.DataDoc} - getTransform={this.getTransform} - active={returnFalse} - focus={this.focusDoc} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} + {!this.props.fitToBox ? + <DocumentView {...this.props} + dragDivName={"collectionFreeFormDocumentView-container"} + ContentScaling={this.contentScaling} + ScreenToLocalTransform={this.getTransform} + backgroundColor={this.props.backgroundColor} + NativeHeight={this.NativeHeight} + NativeWidth={this.NativeWidth} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} /> + : <ContentFittingDocumentView {...this.props} + CollectionDoc={this.props.ContainingCollectionDoc} + DataDocument={this.props.DataDoc} + getTransform={this.getTransform} + NativeHeight={this.NativeHeight} + NativeWidth={this.NativeWidth} + active={this.props.parentActive} + focus={this.focusDoc} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} />} </div>; } diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 40674b034..d34d63d01 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -1,16 +1,15 @@ import React = require("react"); import { observer } from "mobx-react"; import { SketchPicker } from 'react-color'; -import { FieldView, FieldViewProps } from './FieldView'; -import "./ColorBox.scss"; -import { InkingControl } from "../InkingControl"; -import { DocExtendableComponent } from "../DocComponent"; +import { documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; -import { reaction, observable, action, IReactionDisposer } from "mobx"; -import { SelectionManager } from "../../util/SelectionManager"; import { StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { documentSchema } from "../../../new_fields/documentSchemas"; +import { SelectionManager } from "../../util/SelectionManager"; +import { DocExtendableComponent } from "../DocComponent"; +import { InkingControl } from "../InkingControl"; +import "./ColorBox.scss"; +import { FieldView, FieldViewProps } from './FieldView'; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @@ -19,29 +18,15 @@ const ColorDocument = makeInterface(documentSchema); export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocument>(ColorDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } - _selectedDisposer: IReactionDisposer | undefined; - _penDisposer: IReactionDisposer | undefined; - @observable _startupColor = "black"; - - componentDidMount() { - this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(), - action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"), - { fireImmediately: true }); - this._penDisposer = reaction(() => CurrentUserUtils.ActivePen, - action(() => this._startupColor = CurrentUserUtils.ActivePen ? StrCast(CurrentUserUtils.ActivePen.backgroundColor, "black") : "black"), - { fireImmediately: true }); - } - componentWillUnmount() { - this._penDisposer && this._penDisposer(); - this._selectedDisposer && this._selectedDisposer(); - } - render() { + const selDoc = SelectionManager.SelectedDocuments()?.[0]?.Document; return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > - <SketchPicker color={this._startupColor} onChange={InkingControl.Instance.switchColor} /> + <SketchPicker onChange={InkingControl.Instance.switchColor} + color={StrCast(CurrentUserUtils.ActivePen ? CurrentUserUtils.ActivePen.backgroundColor : undefined, + StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} /> </div>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 36233a7e6..641797cac 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; -import { Doc, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; @@ -12,15 +12,20 @@ import { CollectionView } from "../collections/CollectionView"; import '../DocumentDecorations.scss'; import { DocumentView } from "../nodes/DocumentView"; import "./ContentFittingDocumentView.scss"; +import { dropActionType } from "../../util/DragManager"; interface ContentFittingDocumentViewProps { - Document?: Doc; + Document: Doc; DataDocument?: Doc; LayoutDoc?: () => Opt<Doc>; + NativeWidth?: () => number; + NativeHeight?: () => number; + FreezeDimensions?: boolean; LibraryPath: Doc[]; - childDocs?: Doc[]; renderDepth: number; fitToBox?: boolean; + layoutKey?: string; + dropAction?: dropActionType; PanelWidth: () => number; PanelHeight: () => number; focus?: (doc: Doc) => void; @@ -37,18 +42,20 @@ interface ContentFittingDocumentViewProps { addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; dontRegisterView?: boolean; + rootSelected: (outsideReaction?: boolean) => boolean; } @observer export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive - private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); } - private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); } - private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); } + private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); } + @computed get freezeDimensions() { return this.props.FreezeDimensions; } + nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); + nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); @computed get scaling() { - const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1); - if (wscale * this.nativeHeight > this.props.PanelHeight()) { - return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1; + const wscale = this.props.PanelWidth() / this.nativeWidth(); + if (wscale * this.nativeHeight() > this.props.PanelHeight()) { + return (this.props.PanelHeight() / this.nativeHeight()) || 1; } return wscale || 1; } @@ -57,12 +64,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo private PanelWidth = () => this.panelWidth; private PanelHeight = () => this.panelHeight; - @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); } - @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); } + @computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); } + @computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); } private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); - private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } - private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; } + private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; } + private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; } @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); } @@ -77,7 +84,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo style={{ transform: `translate(${this.centeringOffset}px, 0px)`, borderRadius: this.borderRounding, - height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(), + height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight() / this.nativeWidth() * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(), width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth() }}> <DocumentView {...this.props} @@ -85,7 +92,14 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo DataDoc={this.props.DataDocument} LayoutDoc={this.props.LayoutDoc} LibraryPath={this.props.LibraryPath} + NativeWidth={this.nativeWidth} + NativeHeight={this.nativeHeight} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + ContentScaling={this.contentScaling} fitToBox={this.props.fitToBox} + layoutKey={this.props.layoutKey} + dropAction={this.props.dropAction} onClick={this.props.onClick} backgroundColor={this.props.backgroundColor} addDocument={this.props.addDocument} @@ -99,14 +113,9 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo parentActive={this.props.active} ScreenToLocalTransform={this.getTransform} renderDepth={this.props.renderDepth} - ContentScaling={this.contentScaling} - PanelWidth={this.PanelWidth} - PanelHeight={this.PanelHeight} focus={this.props.focus || emptyFunction} bringToFront={emptyFunction} dontRegisterView={this.props.dontRegisterView} - zoomToScale={emptyFunction} - getScale={returnOne} /> </div>)} </div>); diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss index b7d06b364..ce21391ce 100644 --- a/src/client/views/nodes/DocumentBox.scss +++ b/src/client/views/nodes/DocumentBox.scss @@ -3,13 +3,12 @@ height: 100%; pointer-events: all; background: gray; - border: #00000021 solid 15px; - border-top: #0000005e inset 15px; - border-bottom: #0000005e outset 15px; .documentBox-lock { margin: auto; color: white; - margin-top: -15px; + position: absolute; + } + .contentFittingDocumentView { position: absolute; } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx index db7be334f..4f2b3b656 100644 --- a/src/client/views/nodes/DocumentBox.tsx +++ b/src/client/views/nodes/DocumentBox.tsx @@ -1,33 +1,35 @@ -import { IReactionDisposer, reaction } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IReactionDisposer, reaction, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, Field } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; -import { List } from "../../../new_fields/List"; import { makeInterface } from "../../../new_fields/Schema"; import { ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath } from "../../../Utils"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyPath } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { DocComponent, DocAnnotatableComponent } from "../DocComponent"; +import { DocAnnotatableComponent } from "../DocComponent"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import "./DocumentBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); +import { TraceMobx } from "../../../new_fields/util"; +import { DocumentView } from "./DocumentView"; +import { Docs } from "../../documents/Documents"; -type DocBoxSchema = makeInterface<[typeof documentSchema]>; -const DocBoxDocument = makeInterface(documentSchema); +type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>; +const DocHolderBoxDocument = makeInterface(documentSchema); @observer -export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); } +export class DocHolderBox extends DocAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); } _prevSelectionDisposer: IReactionDisposer | undefined; _selections: Doc[] = []; _curSelection = -1; componentDidMount() { - this._prevSelectionDisposer = reaction(() => Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, (data) => { - if (data && !this.isSelectionLocked()) { + this._prevSelectionDisposer = reaction(() => this.contentDoc[this.props.fieldKey], (data) => { + if (data instanceof Doc && !this.isSelectionLocked()) { this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1); this._selections.push(data); this._curSelection = this._selections.length - 1; @@ -35,24 +37,28 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS }); } componentWillUnmount() { - this._prevSelectionDisposer && this._prevSelectionDisposer(); + this._prevSelectionDisposer?.(); } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" }); + funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => Doc.GetProto(this.props.Document).excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" }); funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" }); } + @computed get contentDoc() { + return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document)); + } lockSelection = () => { - Doc.GetProto(this.props.Document)[this.props.fieldKey] = this.props.Document[this.props.fieldKey]; + this.contentDoc[this.props.fieldKey] = this.props.Document[this.props.fieldKey]; } showSelection = () => { - Doc.GetProto(this.props.Document)[this.props.fieldKey] = ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"); + this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(this,this.excludeCollections,[_last_])?.[0]`); } isSelectionLocked = () => { - const kvpstring = Field.toKeyValueString(this.props.Document, this.props.fieldKey); - return !(kvpstring.startsWith("=") || kvpstring.startsWith(":=")); + const kvpstring = Field.toKeyValueString(this.contentDoc, this.props.fieldKey); + return !kvpstring || kvpstring.includes("DOC"); } toggleLockSelection = () => { !this.isSelectionLocked() ? this.lockSelection() : this.showSelection(); @@ -61,28 +67,35 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS prevSelection = () => { this.lockSelection(); if (this._curSelection > 0) { - Doc.GetProto(this.props.Document)[this.props.fieldKey] = this._selections[--this._curSelection]; + this.contentDoc[this.props.fieldKey] = this._selections[--this._curSelection]; return true; } } nextSelection = () => { if (this._curSelection < this._selections.length - 1 && this._selections.length) { - Doc.GetProto(this.props.Document)[this.props.fieldKey] = this._selections[++this._curSelection]; + this.contentDoc[this.props.fieldKey] = this._selections[++this._curSelection]; return true; } } onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0 && !e.ctrlKey) { + if (this.active() && e.button === 0 && !e.ctrlKey) { e.stopPropagation(); } } + onLockClick = (e: React.MouseEvent) => { + this.toggleLockSelection(); + (e.nativeEvent as any).formattedHandled = true; + e.stopPropagation(); + } + get xPad() { return NumCast(this.props.Document._xPadding); } + get yPad() { return NumCast(this.props.Document._yPadding); } onClick = (e: React.MouseEvent) => { let hitWidget: boolean | undefined = false; - if (this._contRef.current!.getBoundingClientRect().top + 15 > e.clientY) hitWidget = this.toggleLockSelection(); - else if (this._contRef.current!.getBoundingClientRect().bottom - 15 < e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); + if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); + else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); else { - if (this._contRef.current!.getBoundingClientRect().left + 15 > e.clientX) hitWidget = this.prevSelection(); - if (this._contRef.current!.getBoundingClientRect().right - 15 < e.clientX) hitWidget = this.nextSelection(); + if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection(); + if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection(); } if (hitWidget) { (e.nativeEvent as any).formattedHandled = true; @@ -90,36 +103,58 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS } } _contRef = React.createRef<HTMLDivElement>(); - pwidth = () => this.props.PanelWidth() - 30; - pheight = () => this.props.PanelHeight() - 30; - getTransform = () => this.props.ScreenToLocalTransform().translate(-15, -15); + pwidth = () => this.props.PanelWidth() - 2 * this.xPad; + pheight = () => this.props.PanelHeight() - 2 * this.yPad; + getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); + get renderContents() { + const containedDoc = Cast(this.contentDoc[this.props.fieldKey], Doc, null); + const childTemplateName = StrCast(this.props.Document.childTemplateName); + if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) { + setTimeout(() => { + DocumentView.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName); + Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined); + }, 0); + } + const contents = !(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView + Document={containedDoc} + DataDocument={undefined} + LibraryPath={emptyPath} + CollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected + fitToBox={this.props.fitToBox} + layoutKey={"layout_" + childTemplateName} + rootSelected={this.props.isSelected} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + getTransform={this.getTransform} + renderDepth={this.props.renderDepth + 1} + PanelWidth={this.pwidth} + PanelHeight={this.pheight} + focus={this.props.focus} + active={this.props.active} + dontRegisterView={!this.isSelectionLocked()} + whenActiveChanged={this.props.whenActiveChanged} + />; + return contents; + } render() { - const containedDoc = this.dataDoc[this.props.fieldKey] as Doc; + TraceMobx(); return <div className="documentBox-container" ref={this._contRef} onContextMenu={this.specificContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - style={{ background: StrCast(this.props.Document.backgroundColor) }}> - <div className="documentBox-lock"> + style={{ + background: StrCast(this.props.Document.backgroundColor), + border: `#00000021 solid ${this.xPad}px`, + borderTop: `#0000005e solid ${this.yPad}px`, + borderBottom: `#0000005e solid ${this.yPad}px`, + }}> + {this.renderContents} + <div className="documentBox-lock" onClick={this.onLockClick} + style={{ marginTop: - this.yPad }}> <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" /> </div> - {!(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView - Document={containedDoc} - DataDocument={undefined} - LibraryPath={emptyPath} - fitToBox={this.props.fitToBox} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - getTransform={this.getTransform} - renderDepth={this.props.renderDepth + 1} // bcz: need a forceActive prop here ... not the same as renderDepth = 0 - PanelWidth={this.pwidth} - PanelHeight={this.pheight} - focus={this.props.focus} - active={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} - />} - </div>; + </div >; } } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 3d2e8c81b..7522af3a3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,9 +1,8 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; import { Cast, StrCast } from "../../../new_fields/Types"; import { OmitKeys, Without } from "../../../Utils"; -import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; @@ -11,10 +10,11 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView } from "../collections/CollectionView"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; -import { ButtonBox } from "./ButtonBox"; +import { LabelBox } from "./LabelBox"; import { SliderBox } from "./SliderBox"; import { LinkBox } from "./LinkBox"; -import { DocumentBox } from "./DocumentBox"; +import { ScriptingBox } from "./ScriptingBox"; +import { DocHolderBox } from "./DocumentBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; @@ -26,14 +26,15 @@ import { PDFBox } from "./PDFBox"; import { PresBox } from "./PresBox"; import { QueryBox } from "./QueryBox"; import { ColorBox } from "./ColorBox"; -import { ScriptBox } from "../ScriptBox" -import { DocuLinkBox } from "./DocuLinkBox"; +import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; +import { LinkAnchorBox } from "./LinkAnchorBox"; import { PresElementBox } from "../presentationview/PresElementBox"; +import { ScreenshotBox } from "./ScreenshotBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import { InkingStroke } from "../InkingStroke"; import React = require("react"); -import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; +import { RecommendationsBox } from "../RecommendationsBox"; import { TraceMobx } from "../../../new_fields/util"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -57,38 +58,37 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, layoutKey: string, + forceLayout?: string, + forceFieldKey?: string, + hideOnLeave?: boolean, + makeLink?: () => Opt<Doc>, // function to call when a link is made }> { @computed get layout(): string { TraceMobx(); if (!this.layoutDoc) return "<p>awaiting layout</p>"; const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string"); - if (layout === undefined) { - return this.props.Document.data ? - "<FieldView {...props} fieldKey='data' />" : - KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : ""); - } else if (typeof layout === "string") { - return layout; - } else { - return "<p>Loading layout</p>"; - } + if (this.props.layoutKey === "layout_keyValue") { + return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data")); + } else + if (layout === undefined) { + return this.props.Document.data ? + "<FieldView {...props} fieldKey='data' />" : + KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : ""); + } else if (typeof layout === "string") { + return layout; + } else { + return "<p>Loading layout</p>"; + } } get dataDoc() { - if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { - // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), - // then we render the layout document as a template and use this document as the data context for the template layout. - const proto = Doc.GetProto(this.props.Document); - return proto instanceof Promise ? undefined : proto; - } - return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc; + const proto = this.props.DataDoc || Doc.GetProto(this.props.Document); + return proto instanceof Promise ? undefined : proto; } get layoutDoc() { - if (this.props.LayoutDoc || (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string")) { - // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), - // then we render the layout document as a template and use this document as the data context for the template layout. - return Doc.expandTemplateLayout(this.props.LayoutDoc ?.() || Doc.Layout(this.props.Document), this.props.Document); - } - return Doc.Layout(this.props.Document); + const params = StrCast(this.props.Document.PARAMS); + const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); + return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey); } CreateBindings(): JsxBindings { @@ -102,20 +102,24 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { render() { TraceMobx(); - return (this.props.renderDepth > 7 || !this.layout || !this.layoutDoc) ? (null) : - <ObserverJsxParser - blacklistedAttrs={[]} - components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView, - CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, - PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox, - ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox, ScriptBox - }} - bindings={this.CreateBindings()} - jsx={this.layout} - showWarnings={true} + return (this.props.renderDepth > 12 || !this.layout || !this.layoutDoc) ? (null) : + this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ? + <FormattedTextBox {...this.CreateBindings().props} fieldKey={this.props.forceFieldKey} /> + : + <ObserverJsxParser + blacklistedAttrs={[]} + components={{ + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView, + CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, + PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox, + ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, + RecommendationsBox, ScreenshotBox + }} + bindings={this.CreateBindings()} + jsx={this.layout} + showWarnings={true} - onError={(test: any) => { console.log(test); }} - />; + onError={(test: any) => { console.log(test); }} + />; } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 56e3eb220..fc9ee1201 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -5,6 +5,8 @@ position: inherit; top: 0; left: 0; + width: 100%; + height: 100%; border-radius: inherit; transition: outline .3s linear; cursor: grab; @@ -32,13 +34,18 @@ overflow-y: scroll; height: calc(100% - 20px); } - - .documentView-docuLinkWrapper { + .documentView-linkAnchorBoxAnchor { + display:flex; + overflow: hidden; + } + .documentView-linkAnchorBoxWrapper { pointer-events: none; position: absolute; transform-origin: top left; width: 100%; height: 100%; + top:0; + left:0; z-index: 1; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fb97c18c3..867405d54 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,9 +1,9 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, observable, runInAction } from "mobx"; +import { action, computed, runInAction, trace, observable } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; -import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Document, PositionDocument } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; import { InkTool } from '../../../new_fields/InkField'; @@ -14,8 +14,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField'; import { TraceMobx } from '../../../new_fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils } from "../../../Utils"; +import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys, returnZero } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; @@ -41,24 +40,35 @@ import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; -import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - +import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField'; +import { ClientRecommender } from '../../ClientRecommender'; +import { SearchUtil } from '../../util/SearchUtil'; +import { RadialMenu } from './RadialMenu'; +import { KeyphraseQueryView } from '../KeyphraseQueryView'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone); +export type DocFocusFunc = () => boolean; export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; + FreezeDimensions?: boolean; + NativeWidth: () => number; + NativeHeight: () => number; Document: Doc; DataDoc?: Doc; LayoutDoc?: () => Opt<Doc>; LibraryPath: Doc[]; fitToBox?: boolean; + rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected onClick?: ScriptField; + onPointerDown?: ScriptField; + onPointerUp?: ScriptField; + dropAction?: dropActionType; dragDivName?: string; addDocument?: (doc: Doc) => boolean; removeDocument?: (doc: Doc) => boolean; @@ -68,16 +78,14 @@ export interface DocumentViewProps { ContentScaling: () => number; PanelWidth: () => number; PanelHeight: () => number; - focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void; + focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void; parentActive: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; pinToPres: (document: Doc) => void; - zoomToScale: (scale: number) => void; backgroundHalo?: () => boolean; backgroundColor?: (doc: Doc) => string | undefined; - getScale: () => number; ChromeHeight?: () => number; dontRegisterView?: boolean; layoutKey?: string; @@ -92,103 +100,132 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _doubleTap = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; + private _showKPQuery: boolean = false; + private _queries: string = ""; private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _titleRef = React.createRef<EditableView>(); protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdDisposer?: InteractionUtils.MultiTouchEventDisposer; public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } - @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } + get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @computed get topMost() { return this.props.renderDepth === 0; } - @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; } - @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; } + @computed get freezeDimensions() { return this.props.FreezeDimensions; } + @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } + @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); } @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; } + @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; } + @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; } + NativeWidth = () => this.nativeWidth; + NativeHeight = () => this.nativeHeight; + + private _firstX: number = -1; + private _firstY: number = -1; + + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { + this.removeMoveListeners(); + this.removeEndListeners(); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + console.log(SelectionManager.SelectedDocuments()); + console.log("START"); + if (RadialMenu.Instance._display === false) { + this.addHoldMoveListeners(); + this.addHoldEndListeners(); + this.onRadialMenu(e, me); + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + this._firstX = pt.pageX; + this._firstY = pt.pageY; + } + + } - private _firstX: number = 0; - private _firstY: number = 0; - - - // handle1PointerHoldStart = (e: React.TouchEvent): any => { - // this.onRadialMenu(e); - // const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; - // this._firstX = pt.pageX; - // this._firstY = pt.pageY; - // e.stopPropagation(); - // e.preventDefault(); - - // document.removeEventListener("touchmove", this.onTouch); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.addEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // document.addEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handle1PointerHoldMove = (e: TouchEvent): void => { - // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - // if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { - // this.handleRelease(); - // } - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.addEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // document.addEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handleRelease() { - // RadialMenu.Instance.closeMenu(); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handle1PointerHoldEnd = (e: TouchEvent): void => { - // RadialMenu.Instance.closeMenu(); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // @action - // onRadialMenu = (e: React.TouchEvent): void => { - // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - - // RadialMenu.Instance.openMenu(); - - // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"), icon: "layer-group", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "folder", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); - - // RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); - // if (!SelectionManager.IsSelected(this, true)) { - // SelectionManager.SelectDoc(this, false); - // } - // e.stopPropagation(); - // } + handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { + + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + + if (this._firstX === -1 || this._firstY === -1) { + return; + } + if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { + this.handle1PointerHoldEnd(e, me); + } + } + + handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { + this.removeHoldMoveListeners(); + this.removeHoldEndListeners(); + RadialMenu.Instance.closeMenu(); + this._firstX = -1; + this._firstY = -1; + SelectionManager.DeselectAll(); + me.touchEvent.stopPropagation(); + me.touchEvent.preventDefault(); + e.stopPropagation(); + if (RadialMenu.Instance.used) { + this.onContextMenu(me.touches[0]); + } + } + + @action + onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => { + // console.log(InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)); + // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); + + RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Delete this document", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "layer-group", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 }); + + // if (SelectionManager.IsSelected(this, true)) { + // SelectionManager.SelectDoc(this, false); + // } + SelectionManager.DeselectAll(); + + + } @action componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); + // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); - !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); + if (!this.props.dontRegisterView) { + DocumentManager.Instance.DocumentViews.push(this); + } } @action componentDidUpdate() { - this._dropDisposer && this._dropDisposer(); - this._gestureEventDisposer && this._gestureEventDisposer(); - this.multiTouchDisposer && this.multiTouchDisposer(); - this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); - this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); - this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); + this._dropDisposer?.(); + this._gestureEventDisposer?.(); + this.multiTouchDisposer?.(); + this.holdDisposer?.(); + if (this._mainCont.current) { + this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)); + this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)); + this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)); + this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)); + } } @action componentWillUnmount() { - this._dropDisposer && this._dropDisposer(); + this._dropDisposer?.(); + this._gestureEventDisposer?.(); + this.multiTouchDisposer?.(); + this.holdDisposer?.(); Doc.UnBrushDoc(this.props.Document); - !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + if (!this.props.dontRegisterView) { + const index = DocumentManager.Instance.DocumentViews.indexOf(this); + index !== -1 && DocumentManager.Instance.DocumentViews.splice(index, 1); + } } startDragging(x: number, y: number, dropAction: dropActionType) { @@ -199,7 +236,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dragData.dropAction = dropAction; dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument; dragData.dragDivName = this.props.dragDivName; - this.props.Document.anchor1Context = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart }); } } @@ -245,10 +281,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } onClick = (e: React.MouseEvent | React.PointerEvent) => { - if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && + if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { - e.stopPropagation(); + let stopPropagate = true; let preventDefault = true; + this.props.bringToFront(this.props.Document); if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click const fullScreenAlias = Doc.MakeAlias(this.props.Document); if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) { @@ -257,36 +294,59 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); - } else if (this.onClickHandler && this.onClickHandler.script) { + } else if (this.onClickHandler?.script) { SelectionManager.DeselectAll(); - UndoManager.RunInBatch(() => this.onClickHandler!.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log), "on click"); - } else if (this.Document.type === DocumentType.BUTTON) { - UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click"); - } else if (this.Document.isButton) { - SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. - UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow"); + const func = () => this.onClickHandler!.script.run({ + this: this.props.Document, + self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document, + thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey + }, console.log); + if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) { + UndoManager.RunInBatch(func, "on click"); + } else func(); + } else if (this.Document.editScriptOnClick) { + UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, StrCast(this.Document.editScriptOnClick), e.clientX, e.clientY), "on button click"); + } else if (this.Document.isLinkButton) { + DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey); } else { - SelectionManager.SelectDoc(this, e.ctrlKey); + if ((this.props.Document.onDragStart || this.props.Document.isTemplateForField) && !(e.ctrlKey || e.button > 0)) { + stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template + } else { + SelectionManager.SelectDoc(this, e.ctrlKey); + } preventDefault = false; } + stopPropagate && e.stopPropagation(); preventDefault && e.preventDefault(); } } - buttonClick = async (altKey: boolean, ctrlKey: boolean) => { - const linkDocs = DocListCast(this.props.Document.links); - if (linkDocs.length) { - DocumentManager.Instance.FollowLink(undefined, this.props.Document, - // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards - (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, maxLocation)), - ctrlKey, altKey, this.props.ContainingCollectionDoc); - } + // follows a link - if the target is on screen, it highlights/pans to it. + // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place + // depending on the followLinkLocation property of the source (or the link itself as a fallback); + followLinkClick = async (altKey: boolean, ctrlKey: boolean, shiftKey: boolean) => { + const batch = UndoManager.StartBatch("follow link click"); + // open up target if it's not already in view ... + const createViewFunc = (doc: Doc, followLoc: string, finished: Opt<() => void>) => { + const targetFocusAfterDocFocus = () => { + const where = StrCast(this.Document.followLinkLocation) || followLoc; + const hackToCallFinishAfterFocus = () => { + finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout. + return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false + }; + this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); // add the target and focus on it. + return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target) + }; + // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target + this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus); + }; + await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined); } handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => { + SelectionManager.DeselectAll(); if (this.Document.onPointerDown) return; - const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - console.log("down"); + const touch = me.touchEvent.changedTouches.item(0); if (touch) { this._downX = touch.clientX; this._downY = touch.clientY; @@ -307,8 +367,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.removeMoveListeners(); } else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { - const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) { + + const touch = me.touchEvent.changedTouches.item(0); + if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) { this.cleanUpInteractions(); this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined); @@ -404,6 +465,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); + // TODO: check here for panning/inking } return; } @@ -438,7 +500,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined); + this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -447,6 +509,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } onPointerUp = (e: PointerEvent): void => { + this.cleanUpInteractions(); + + if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { + this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + document.removeEventListener("pointerup", this.onPointerUp); + return; + } + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); @@ -465,57 +535,70 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); } - static makeNativeViewClicked = (doc: Doc) => { - undoBatch(() => Doc.setNativeView(doc))(); - } - - static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, name: string = "custom", docLayoutTemplate?: Doc) => { - const batch = UndoManager.StartBatch("CustomViewClicked"); - const customName = "layout_" + name; - if (!StrCast(doc.title).endsWith(name)) doc.title = doc.title + "_" + name; - if (doc[customName] === undefined) { - const _width = NumCast(doc._width); - const _height = NumCast(doc._height); - const options = { title: "data", _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; - - const field = doc.data; - let fieldTemplate: Opt<Doc>; - if (field instanceof RichTextField || typeof (field) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); - } else if (field instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - } else if (field instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - } else if (field instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - } else if (field instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - } - - if (fieldTemplate) { - fieldTemplate.backgroundColor = doc.backgroundColor; - fieldTemplate.heading = 1; - fieldTemplate._autoHeight = true; + // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) + static makeCustomViewClicked = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => { + const batch = UndoManager.StartBatch("makeCustomViewClicked"); + runInAction(() => { + doc.layoutKey = "layout_" + templateSignature; + if (doc[doc.layoutKey] === undefined) { + DocumentView.createCustomView(doc, creator, templateSignature, docLayoutTemplate); } + }); + batch.end(); + } + static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => { + const iconViews = DocListCast(Cast(Doc.UserDoc().iconViews, Doc, null)?.data); + const templBtns = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data); + const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data); + const allTemplates = iconViews.concat(templBtns).concat(noteTypes).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc); + const templateName = templateSignature.replace(/\(.*\)/, ""); + // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized + // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName> + !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === doc.type + "_" + templateName && (docLayoutTemplate = tempDoc)); + !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc)); + + const customName = "layout_" + templateSignature; + const _width = NumCast(doc._width); + const _height = NumCast(doc._height); + const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; + + let fieldTemplate: Opt<Doc>; + if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { + fieldTemplate = Docs.Create.TextDocument("", options); + } else if (doc.data instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + } else if (doc.data instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + } + const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); + Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + } - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); - Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + @undoBatch + toggleLinkButtonBehavior = (): void => { + if (this.Document.isLinkButton || this.Document.onClick || this.Document.ignoreClick) { + this.Document.isLinkButton = false; + this.Document.ignoreClick = false; + this.Document.onClick = undefined; } else { - doc.layoutKey = customName; + this.Document.isLinkButton = true; + this.Document.followLinkLocation = undefined; } - batch.end(); } @undoBatch - toggleButtonBehavior = (): void => { - if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) { - this.Document.isButton = false; - this.Document.ignoreClick = false; - this.Document.onClick = undefined; + toggleFollowInPlace = (): void => { + if (this.Document.isLinkButton) { + this.Document.isLinkButton = false; } else { - this.Document.isButton = true; + this.Document.isLinkButton = true; + this.Document.followLinkLocation = "inPlace"; } } @@ -527,8 +610,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e.stopPropagation(); de.complete.annoDragData.linkedToDoc = true; - DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, - `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`); + DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); } if (de.complete.linkDragData) { e.stopPropagation(); @@ -536,7 +618,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); 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 }, `link from ${de.complete.linkDragData.linkSourceDocument.title} to ${this.props.Document.title}`)); // TODODO this is where in text links get passed + { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed } } @@ -562,34 +644,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document); if (!portalLink) { const portal = Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: StrCast(this.props.Document.title) + ".portal" }); - DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, "portal link", "portal link"); + DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to"); } - this.Document.isButton = true; + this.Document.isLinkButton = true; } @undoBatch @action - setCustomView = - (custom: boolean, layout: string): void => { - // if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { - // Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); - // } else - if (custom) { - DocumentView.makeNativeViewClicked(this.props.Document); - - let foundLayout: Opt<Doc>; - DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data)?.concat([Cast(Doc.UserDoc().iconView, Doc, null)]). - map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => { - if (StrCast(tempDoc.title) === layout) { - foundLayout = tempDoc; - } - }); - DocumentView. - makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout); - } else { - DocumentView.makeNativeViewClicked(this.props.Document); - } + setCustomView = (custom: boolean, layout: string): void => { + Doc.setNativeView(this.props.Document); + if (custom) { + DocumentView.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } + } @undoBatch @action @@ -611,20 +678,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action - onContextMenu = async (e: React.MouseEvent): Promise<void> => { + onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 - if (e.button === 0 && !e.ctrlKey) { - e.preventDefault(); - return; - } - e.persist(); - e.stopPropagation(); - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || - e.isDefaultPrevented()) { + if (!(e instanceof Touch)) { + if (e.button === 0 && !e.ctrlKey) { + e.preventDefault(); + return; + } + e.persist(); + e?.stopPropagation(); + + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || + e.isDefaultPrevented()) { + e.preventDefault(); + return; + } e.preventDefault(); - return; } - e.preventDefault(); const cm = ContextMenu.Instance; const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); @@ -661,7 +731,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); - onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" }); + onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" }); + onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" }); onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); @@ -700,6 +771,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); }); + const recommender_subitems: ContextMenuProps[] = []; + + recommender_subitems.push({ + description: "Internal recommendations", + event: () => this.recommender(), + icon: "brain" + }); + + const ext_recommender_subitems: ContextMenuProps[] = []; + + ext_recommender_subitems.push({ + description: "arXiv", + event: () => this.externalRecommendation("arxiv"), + icon: "brain" + }); + ext_recommender_subitems.push({ + description: "Bing", + event: () => this.externalRecommendation("bing"), + icon: "brain" + }); + + recommender_subitems.push({ + description: "External recommendations", + subitems: ext_recommender_subitems, + icon: "brain" + }); + + cm.addItem({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" }); + moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" }); moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); @@ -736,7 +836,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu icon: "external-link-alt" }); - if (!this.topMost) { + if (!this.topMost && !(e instanceof Touch)) { // DocumentViews should stop propagation of this event e.stopPropagation(); } @@ -754,6 +854,104 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } + recommender = async () => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + const documents: Doc[] = []; + const allDocs = await SearchUtil.GetAllDocs(); + // allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + //ClientRecommender.Instance.arxivrequest("electrons"); + await Promise.all(allDocs.map((doc: Doc) => { + let isMainDoc: boolean = false; + const dataDoc = Doc.GetProto(doc); + if (doc.type === DocumentType.RTF) { + if (dataDoc === Doc.GetProto(this.props.Document)) { + isMainDoc = true; + } + if (!documents.includes(dataDoc)) { + documents.push(dataDoc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc); + } + } + if (doc.type === DocumentType.IMG) { + if (dataDoc === Doc.GetProto(this.props.Document)) { + isMainDoc = true; + } + if (!documents.includes(dataDoc)) { + documents.push(dataDoc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true); + } + } + })); + const doclist = ClientRecommender.Instance.computeSimilarities("cosine"); + const recDocs: { preview: Doc, score: number }[] = []; + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < doclist.length; i++) { + recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); + } + + const data = recDocs.map(unit => { + unit.preview.score = unit.score; + return unit.preview; + }); + + console.log(recDocs.map(doc => doc.score)); + + const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`; + const recommendations = Docs.Create.RecommendationsDocument(data, { title }); + recommendations.documentIconHeight = 150; + recommendations.sourceDoc = this.props.Document; + recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document; + CollectionDockingView.AddRightSplit(recommendations, undefined); + + // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); + } + + @action + externalRecommendation = async (api: string) => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + ClientRecommender.Instance.reset_docs(); + const doc = Doc.GetDataDoc(this.props.Document); + const extdoc = doc.data_ext as Doc; + const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api); + let recs: any; + let kps: any; + if (recs_and_kps) { + recs = recs_and_kps.recs; + kps = recs_and_kps.keyterms; + } + else { + console.log("recommender system failed :("); + return; + } + console.log("ibm keyterms: ", kps.toString()); + const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; + const bodies: Doc[] = []; + const titles = recs.title_vals; + const urls = recs.url_vals; + for (let i = 0; i < 5; i++) { + const body = Docs.Create.FreeformDocument([], { title: titles[i] }); + body.href = urls[i]; + bodies.push(body); + } + CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); + this._showKPQuery = true; + this._queries = kps.toString(); + } + + onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; + onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; + + // does Document set a layout prop + // does Document set a layout prop + setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; + // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. + getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); + getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); + isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; @@ -770,33 +968,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const fallback = Cast(this.props.Document.layoutKey, "string"); return typeof fallback === "string" ? fallback : "layout"; } + rootSelected = (outsideReaction?: boolean) => { + return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false); + } childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); + panelWidth = () => this.props.PanelWidth(); + panelHeight = () => this.props.PanelHeight(); + screenToLocalTransform = () => this.props.ScreenToLocalTransform(); @computed get contents() { TraceMobx(); return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView} ContainingCollectionDoc={this.props.ContainingCollectionDoc} + NativeWidth={this.NativeWidth} + NativeHeight={this.NativeHeight} Document={this.props.Document} DataDoc={this.props.DataDoc} LayoutDoc={this.props.LayoutDoc} + makeLink={this.makeLink} + rootSelected={this.rootSelected} + dontRegisterView={this.props.dontRegisterView} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} + ScreenToLocalTransform={this.screenToLocalTransform} renderDepth={this.props.renderDepth} - ContentScaling={this.childScaling} - PanelWidth={this.props.PanelWidth} - PanelHeight={this.props.PanelHeight} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} focus={this.props.focus} parentActive={this.props.parentActive} whenActiveChanged={this.props.whenActiveChanged} bringToFront={this.props.bringToFront} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} - zoomToScale={this.props.zoomToScale} backgroundColor={this.props.backgroundColor} - getScale={this.props.getScale} + ContentScaling={this.childScaling} ChromeHeight={this.chromeHeight} isSelected={this.isSelected} select={this.select} @@ -805,51 +1012,61 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); - // used to decide whether a link document should be created or not. + // used to decide whether a link anchor view should be created or not. // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. // would be good to generalize this some way. isNonTemporalLink = (linkDoc: Doc) => { const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; - const ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; - return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; + return anchor.type === DocumentType.AUDIO ? false : true; } - // bcz: ARGH! these two are the same as in DocumentContentsView (without the _). They should be reconciled to be the same functions... - get _dataDoc() { - if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { - // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), - // then we render the layout document as a template and use this document as the data context for the template layout. - const proto = Doc.GetProto(this.props.Document); - return proto instanceof Promise ? undefined : proto; - } - return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc; - } - get _layoutDoc() { - if (this.props.LayoutDoc || (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string")) { - // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), - // then we render the layout document as a template and use this document as the data context for the template layout. - return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document); - } - return Doc.Layout(this.props.Document); + @observable _link: Opt<Doc>; + makeLink = () => { + return this._link; } + hideLinkAnchor = (doc: Doc) => undoBatch(doc => doc.hidden = true)(); + anchorPanelWidth = () => this.props.PanelWidth() || 1; + anchorPanelHeight = () => this.props.PanelHeight() || 1; + @computed get anchors() { + TraceMobx(); + return DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}> + <DocumentView {...this.props} + Document={d} + ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox + PanelWidth={this.anchorPanelWidth} + PanelHeight={this.anchorPanelHeight} + layoutKey={this.linkEndpoint(d)} + ContentScaling={returnOne} + backgroundColor={returnTransparent} + removeDocument={this.hideLinkAnchor} /> + </div>); + } @computed get innards() { TraceMobx(); + if (!this.props.PanelWidth()) { // this happens when the document is a tree view label + return <div className="documentView-linkAnchorBoxAnchor" > + {StrCast(this.props.Document.title)} + {this.anchors} + </div>; + } const showTitle = StrCast(this.layoutDoc._showTitle); const showTitleHover = StrCast(this.layoutDoc._showTitleHover); const showCaption = StrCast(this.layoutDoc._showCaption); const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; - const searchHighlight = (!this.Document.searchFields ? (null) : - <div className="documentView-searchHighlight"> - {this.Document.searchFields} - </div>); const captionView = (!showCaption ? (null) : <div className="documentView-captionWrapper"> - <FormattedTextBox {...this.props} onClick={this.onClickHandler} - DataDoc={this._dataDoc} active={returnTrue} Document={this._layoutDoc || this.props.Document} - isSelected={this.isSelected} focus={emptyFunction} select={this.select} - hideOnLeave={true} fieldKey={showCaption} - /> + <DocumentContentsView {...OmitKeys(this.props, ['children']).omit} + hideOnLeave={true} + forceLayout={"FormattedTextBox"} + forceFieldKey={showCaption} + ContentScaling={this.childScaling} + ChromeHeight={this.chromeHeight} + isSelected={this.isSelected} + select={this.select} + onClick={this.onClickHandler} + layoutKey={this.finalLayoutKey} /> </div>); const titleView = (!showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{ @@ -864,26 +1081,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu /> </div>); return <> - {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) => - <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}> - <DocumentView {...this.props} ContentScaling={returnOne} ContainingCollectionDoc={this.props.Document} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} /> - </div>)} + {this.anchors} {!showTitle && !showCaption ? - this.Document.searchFields ? - (<div className="documentView-searchWrapper"> - {this.contents} - {searchHighlight} - </div>) - : - this.contents - : + this.contents : <div className="documentView-styleWrapper" > <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}> {this.contents} </div> {titleView} {captionView} - {searchHighlight} </div> } </>; @@ -920,7 +1126,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way - return <div id={this.props.Document[Id]} className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown} + return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} style={{ @@ -933,8 +1139,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined, background: finalColor, - width: "100%", - height: "100%", opacity: this.Document.opacity }}> {this.Document.isBackground ? <div className="documentView-lock"> <FontAwesomeIcon icon="unlock" size="lg" /> </div> : (null)} @@ -944,11 +1148,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu </> : this.innards} </div>; + { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; } } } -Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string) { +Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: string = "layout") { const dv = DocumentManager.Instance.getDocumentView(doc); - if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(false, ""); + if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", "")); else dv?.switchViews(true, layoutKey.replace("layout_", "")); });
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 38fcbd211..a3790d38b 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -10,6 +10,7 @@ import { Transform } from "../../util/Transform"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; import { VideoBox } from "./VideoBox"; +import { dropActionType } from "../../util/DragManager"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. @@ -25,8 +26,10 @@ export interface FieldViewProps { DataDoc?: Doc; LibraryPath: Doc[]; onClick?: ScriptField; + dropAction: dropActionType; isSelected: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; + rootSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; addDocument?: (document: Doc) => boolean; addDocTab: (document: Doc, where: string) => boolean; @@ -38,9 +41,12 @@ export interface FieldViewProps { bringToFront: (doc: Doc, sendToBack?: boolean) => void; active: (outsideReaction?: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; + dontRegisterView?: boolean; focus: (doc: Doc) => void; PanelWidth: () => number; PanelHeight: () => number; + NativeHeight: () => number; + NativeWidth: () => number; setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index db2bb751f..7d40b3149 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -29,6 +29,7 @@ max-height: 100%; display: flex; flex-direction: row; + transition: opacity 1s; .formattedTextBox-dictation { height: 12px; @@ -38,11 +39,6 @@ position: absolute; } } - -.collectionfreeformview-container { - position: relative; -} - .formattedTextBox-outer { position: relative; overflow: auto; @@ -74,6 +70,10 @@ position: absolute; right: 0; + .collectionfreeformview-container { + position: relative; + } + >.formattedTextBox-sidebar-handle { right: unset; left: -5; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ad804209b..836d95830 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -20,9 +20,9 @@ import { InkTool } from '../../../new_fields/InkField'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types"; import { TraceMobx } from '../../../new_fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -47,12 +47,14 @@ import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); import { PrefetchProxy } from '../../../new_fields/Proxy'; +import { makeTemplate } from '../../util/DropConverter'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); export interface FormattedTextBoxProps { hideOnLeave?: boolean; + makeLink?: () => Opt<Doc>; } const richTextSchema = createSchema({ @@ -82,6 +84,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _lastY = 0; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; + private _recordReactionDisposer: Opt<IReactionDisposer>; private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>; private _reactionDisposer: Opt<IReactionDisposer>; private _heightReactionDisposer: Opt<IReactionDisposer>; @@ -89,9 +92,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _pullReactionDisposer: Opt<IReactionDisposer>; private _pushReactionDisposer: Opt<IReactionDisposer>; private _buttonBarReactionDisposer: Opt<IReactionDisposer>; + private _linkMakerDisposer: Opt<IReactionDisposer>; private _scrollDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; + @computed get _recording() { return this.dataDoc.audioState === "recording"; } + set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + @observable private _entered = false; public static FocusedBox: FormattedTextBox | undefined; @@ -150,7 +157,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id); + else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); }); }); }); @@ -186,7 +193,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); const tsel = this._editorView.state.selection.$from; - tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); + tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); const curText = state.doc.textBetween(0, state.doc.content.size, "\n\n"); const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); if (!this._applyingChange) { @@ -207,7 +214,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } updateTitle = () => { - if ((this.props.Document.isTemplateForField === "data" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { const str = this._editorView.state.doc.textContent; const titlestr = str.substr(0, Math.min(40, str.length)); @@ -245,7 +252,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } protected createDropTarget = (ele: HTMLDivElement) => { this.ProseRef = ele; - this.dropDisposer && this.dropDisposer(); + this.dropDisposer?.(); ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } @@ -255,7 +262,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & 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.altKey) { + if (draggedDoc && draggedDoc.type === DocumentType.RTF && !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(); @@ -277,8 +284,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & e.stopPropagation(); // } } // otherwise, fall through to outer collection to handle drop + } else if (de.complete.linkDragData) { + de.complete.linkDragData.linkDropCallback = this.linkDrop; } } + linkDrop = (data: DragManager.LinkDragData) => { + const linkDoc = data.linkDocument!; + const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; + const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); + } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { let offset = 0; @@ -382,12 +397,17 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document.proto as Doc), icon: "eye" }); + this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" }); + !this.props.Document.rootDocument && funcs.push({ + description: "Make Template", event: () => { + this.props.Document.isTemplateDoc = makeTemplate(this.props.Document, true); + Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); + }, icon: "eye" + }); funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); - funcs.push({ description: "Record Bullet", event: () => this.recordBullet(), icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Audio", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ @@ -405,12 +425,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" }); } - @observable _recording = false; - recordDictation = () => { - //this._editorView!.focus(); - if (this._recording) return; - runInAction(() => this._recording = true); DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, continuous: { indefinite: false }, @@ -418,17 +433,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (results && [DictationManager.Controls.Infringed].includes(results)) { DictationManager.Controls.stop(); } - this._editorView!.focus(); + //this._editorView!.focus(); }); } - stopDictation = (abort: boolean) => { - runInAction(() => this._recording = false); - DictationManager.Controls.stop(!abort); - } + stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; @action toggleMenubar = () => { - this.props.Document._chromeStatus = this.props.Document._chromeStatus == "disabled" ? "enabled" : "disabled"; + this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; } recordBullet = async () => { @@ -448,13 +460,25 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & setCurrentBulletContent = (value: string) => { if (this._editorView) { - let state = this._editorView.state; + const state = this._editorView.state; + const now = Date.now(); + let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); + if (!this._break && state.selection.to !== state.selection.from) { + for (let i = state.selection.from; i <= state.selection.to; i++) { + const pos = state.doc.resolve(i); + const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); + if (um) { + mark = um; + break; + } + } + } + const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); + this._break = false; + value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; const from = state.selection.from; - const to = state.selection.to; - this._editorView.dispatch(state.tr.insertText(value, from, to)); - state = this._editorView.state; - const updated = TextSelection.create(state.doc, from, from + value.length); - this._editorView.dispatch(state.tr.setSelection(updated)); + const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); + this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); } } @@ -499,6 +523,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & }; } + makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) { + if (this._editorView) { + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); + this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link). + addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link)); + } + } componentDidMount() { this._buttonBarReactionDisposer = reaction( () => DocumentButtonBar.Instance, @@ -509,6 +540,17 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } ); + this._linkMakerDisposer = reaction( + () => this.props.makeLink?.(), + (linkDoc: Opt<Doc>) => { + if (linkDoc) { + const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; + const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); + } + }, + { fireImmediately: true } + ); this._reactionDisposer = reaction( () => { @@ -558,6 +600,17 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), { fireImmediately: true }); + this._recordReactionDisposer = reaction(() => this._recording, + () => { + if (this._recording) { + setTimeout(action(() => { + this.stopDictation(true); + setTimeout(() => this.recordDictation(), 500); + }), 500); + } else setTimeout(() => this.stopDictation(true), 0); + } + ); + this._scrollToRegionReactionDisposer = reaction( () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { @@ -723,7 +776,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & targetAnnotations?.push(pdfRegion); }); - const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); + const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); const linkId = link[Id]; @@ -797,8 +850,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } - const selectOnLoad = (Cast(this.props.Document.expandedTemplate, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad; - if (selectOnLoad) { + const selectOnLoad = (Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad && !this.props.dontRegisterView) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar)); @@ -807,7 +860,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })]; + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; } getFont(font: string) { switch (font) { @@ -831,14 +884,28 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._pullReactionDisposer?.(); this._heightReactionDisposer?.(); this._searchReactionDisposer?.(); + this._recordReactionDisposer?.(); this._buttonBarReactionDisposer?.(); + this._linkMakerDisposer?.(); this._editorView?.destroy(); } static _downEvent: any; _downX = 0; _downY = 0; + _break = false; onPointerDown = (e: React.PointerEvent): void => { + if (this._recording && !e.ctrlKey && e.button === 0) { + this.stopDictation(true); + this._break = true; + const state = this._editorView!.state; + const to = state.selection.to; + const updated = TextSelection.create(state.doc, to, to); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to)); + e.preventDefault(); + e.stopPropagation(); + if (this._recording) setTimeout(() => this.recordDictation(), 500); + } this._downX = e.clientX; this._downY = e.clientY; this.doLinkOnDeselect(); @@ -869,6 +936,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } + this._downX = this._downY = Number.NaN; } @action @@ -882,12 +950,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & prosediv && (prosediv.keeplocation = undefined); 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)))); + const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos); // jump rich text menu to this textbox - const { current } = this._ref; - if (current && this.props.Document._chromeStatus !== "disabled") { - const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); - const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50; + const bounds = this._ref.current?.getBoundingClientRect(); + if (bounds && this.props.Document._chromeStatus !== "disabled") { + const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); + let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); + if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { + y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); + } RichTextMenu.Instance.jumpTo(x, y); } } @@ -955,7 +1027,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.props.select(e.ctrlKey); this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); } - if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. @@ -1016,7 +1087,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & richTextMenuPlugin() { return new Plugin({ view(newView) { - RichTextMenu.Instance.changeView(newView); + RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); return RichTextMenu.Instance; } }); @@ -1062,17 +1133,13 @@ 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.floor(Date.now() / 1000) }); this._lastTimedMark = mark; this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } - if (this._recording) { - this.stopDictation(true); - setTimeout(() => this.recordDictation(), 250); - } } onscrolled = (ev: React.UIEvent) => { @@ -1139,7 +1206,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ padding: `${NumCast(this.Document._xMargin, 0)}px ${NumCast(this.Document._yMargin, 0)}px`, - pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined + pointerEvents: ((this.Document.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} /> </div> {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? @@ -1149,6 +1216,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.sidebarWidth} + NativeHeight={returnZero} + NativeWidth={returnZero} annotationsKey={this.annotationKey} isAnnotationOverlay={false} focus={this.props.focus} @@ -1169,8 +1238,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & </div>} {!this.props.Document._showAudio ? (null) : <div className="formattedTextBox-dictation" - onClick={e => { - this._recording ? this.stopDictation(true) : this.recordDictation(); + onPointerDown={e => { + runInAction(() => this._recording = !this._recording); setTimeout(() => this._editorView!.focus(), 500); e.stopPropagation(); }} > diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index a3096f60b..1e48c76e7 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -2,7 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model"; import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocCastAsync } from "../../../new_fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -83,8 +83,8 @@ export class FormattedTextBoxComment { const keep = e.target && (e.target as any).type === "checkbox" ? true : false; const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.dataDoc, - (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight")); + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); } 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 }), "onRight"); } @@ -100,6 +100,7 @@ export class FormattedTextBoxComment { public static Hide() { FormattedTextBoxComment.textBox = undefined; FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); } public static SetState(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; @@ -167,20 +168,25 @@ export class FormattedTextBoxComment { 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 => { + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { } + docTarget && DocServer.GetRefField(docTarget).then(async 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.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { } + const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); + const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + if (anchor !== target && anchor && target) { + target.scrollY = NumCast(anchor?.y); + } if (target) { ReactDOM.render(<ContentFittingDocumentView Document={target} LibraryPath={emptyPath} fitToBox={true} moveDocument={returnFalse} + rootSelected={returnFalse} getTransform={Transform.Identity} active={returnFalse} addDocument={returnFalse} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c46191270..325d759ad 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,41 +1,40 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEye } from '@fortawesome/free-regular-svg-icons'; -import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons'; +import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, trace } from 'mobx'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc'; +import { documentSchema } from '../../../new_fields/documentSchemas'; +import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; +import { ObjectField } from '../../../new_fields/ObjectField'; import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; import { ComputedField } from '../../../new_fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; import { AudioField, ImageField } from '../../../new_fields/URLField'; -import { Utils, returnOne, emptyFunction } from '../../../Utils'; +import { TraceMobx } from '../../../new_fields/util'; +import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; +import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from '../DocComponent'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id, Copy } from '../../../new_fields/FieldSymbols'; -import { TraceMobx } from '../../../new_fields/util'; -import { SelectionManager } from '../../util/SelectionManager'; -import { cache } from 'sharp'; -import { ObjectField } from '../../../new_fields/ObjectField'; -import { Networking } from '../../Network'; const requestImageSize = require('../../util/request-image-size'); const path = require('path'); const { Howl } = require('howler'); -library.add(faImage, faEye as any, faPaintBrush); +library.add(faImage, faEye as any, faPaintBrush, faBrain); library.add(faFileAudio, faAsterisk); @@ -80,23 +79,27 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } + get fieldKey() { + return this.props.fieldKey.startsWith("@") ? StrCast(this.props.Document[this.props.fieldKey]) : this.props.fieldKey; + } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { if (de.metaKey) { de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => { - Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-alternates", drop); + Doc.AddDocToList(this.dataDoc, this.fieldKey + "-alternates", drop); e.stopPropagation(); })); - } else if (de.altKey || !this.dataDoc[this.props.fieldKey]) { + } else if (de.altKey || !this.dataDoc[this.fieldKey]) { const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; const targetField = Doc.LayoutFieldKey(layoutDoc); const targetDoc = layoutDoc[DataSym]; if (targetDoc[targetField] instanceof ImageField) { - this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(targetDoc[targetField + "-nativeWidth"]); - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(targetDoc[targetField + "-nativeHeight"]); + this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); + this.dataDoc[this.fieldKey + "-nativeWidth"] = NumCast(targetDoc[targetField + "-nativeWidth"]); + this.dataDoc[this.fieldKey + "-nativeHeight"] = NumCast(targetDoc[targetField + "-nativeHeight"]); e.stopPropagation(); } } @@ -124,9 +127,9 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum // upload to server with known URL const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 }); audioDoc.treeViewExpandedView = "layout"; - const audioAnnos = Cast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"], listSpec(Doc)); + const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc)); if (audioAnnos === undefined) { - this.dataDoc[this.props.fieldKey + "-audioAnnotations"] = new List([audioDoc]); + this.dataDoc[this.fieldKey + "-audioAnnotations"] = new List([audioDoc]); } else { audioAnnos.push(audioDoc); } @@ -143,33 +146,33 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @undoBatch rotate = action(() => { - const nw = NumCast(this.Document[this.props.fieldKey + "-nativeWidth"]); - const nh = NumCast(this.Document[this.props.fieldKey + "-nativeHeight"]); + const nw = NumCast(this.Document[this.fieldKey + "-nativeWidth"]); + const nh = NumCast(this.Document[this.fieldKey + "-nativeHeight"]); const w = this.Document._width; const h = this.Document._height; - this.dataDoc[this.props.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) + 90) % 360; - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = nh; - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = nw; + this.dataDoc[this.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.fieldKey + "-rotation"]) + 90) % 360; + this.dataDoc[this.fieldKey + "-nativeWidth"] = nh; + this.dataDoc[this.fieldKey + "-nativeHeight"] = nw; this.Document._width = h; this.Document._height = w; }); specificContextMenu = (e: React.MouseEvent): void => { - const field = Cast(this.Document[this.props.fieldKey], ImageField); + const field = Cast(this.Document[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); funcs.push({ - description: "Reset Native Dimensions", event: action(() => { - const curNW = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]); - const curNH = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]); + description: "Reset Native Dimensions", event: action(async () => { + const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); + this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; + this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); } else { - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; + this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); + this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; } }), icon: "expand-arrows-alt" }); @@ -178,6 +181,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); @@ -190,7 +194,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>()); return faceDocs; }; - this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-faces"], this.url, Service.Face, converter); + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { @@ -202,16 +206,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const sanitized = tag.name.replace(" ", "_"); tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); }); - this.dataDoc[this.props.fieldKey + "-generatedTags"] = tagsList; + this.dataDoc[this.fieldKey + "-generatedTags"] = tagsList; tagDoc.title = "Generated Tags Doc"; tagDoc.confidence = threshold; return tagDoc; }; - this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter); + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter); } @computed private get url() { - const data = Cast(this.dataDoc[this.props.fieldKey], ImageField); + const data = Cast(this.dataDoc[this.fieldKey], ImageField); return data ? data.url.href : undefined; } @@ -221,7 +225,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum return url.href; } else if (url.href.indexOf(window.location.origin) === -1) { return Utils.CorsProxy(url.href); - } else if (!/\.(png|jpg|jpeg|gif)$/.test(lower)) { + } else if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) { return url.href;//Why is this here } const ext = path.extname(url.href); @@ -244,33 +248,36 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } const original = StrCast(this.dataDoc.originalUrl); if (error.type === "error" && original) { - this.dataDoc[this.props.fieldKey] = new ImageField(original); + this.dataDoc[this.fieldKey] = new ImageField(original); } } _curSuffix = "_m"; resize = (imgPath: string) => { const cachedNativeSize = { - width: NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]), - height: NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]) + width: NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]), + height: NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) }; - const cachedImgPath = this.dataDoc[this.props.fieldKey + "-imgPath"]; - if (!cachedNativeSize.width || !cachedNativeSize.height || imgPath !== cachedImgPath) { - (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) && requestImageSize(imgPath).then((inquiredSize: any) => { - const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180; - const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize; - const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width; - const docAspect = this.Document[HeightSym]() / this.Document[WidthSym](); - setTimeout(action(() => { - if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { - this.Document._height = this.Document[WidthSym]() * rotatedAspect; - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width; - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height; - } - this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath; - }), 0); - }) - .catch((err: any) => console.log(err)); + const docAspect = this.Document[HeightSym]() / this.Document[WidthSym](); + const cachedAspect = cachedNativeSize.height / cachedNativeSize.width; + if (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(NumCast(this.layoutDoc._width) / NumCast(this.layoutDoc._height) - cachedNativeSize.width / cachedNativeSize.height) > 0.05) { + if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) { + requestImageSize(imgPath).then((inquiredSize: any) => { + const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180; + const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize; + const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width; + setTimeout(action(() => { + if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { + this.Document._height = this.Document[WidthSym]() * rotatedAspect; + this.dataDoc[this.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width; + this.dataDoc[this.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height; + } + }), 0); + }).catch((err: any) => console.log(err)); + } else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) { + this.Document._width = this.Document[WidthSym]() || cachedNativeSize.width; + this.Document._height = this.Document[WidthSym]() * cachedAspect; + } } else if (this.Document._nativeWidth !== cachedNativeSize.width || this.Document._nativeHeight !== cachedNativeSize.height) { !(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc) && setTimeout(() => { if (!(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc)) { @@ -284,7 +291,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @action onPointerEnter = () => { const self = this; - const audioAnnos = DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]); + const audioAnnos = DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]); if (audioAnnos && audioAnnos.length && this._audioState === 0) { const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; anno.data instanceof AudioField && new Howl({ @@ -306,6 +313,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum considerGooglePhotosLink = () => { const remoteUrl = this.Document.googlePhotosUrl; return !remoteUrl ? (null) : (<img + style={{ transform: `scale(${this.props.ContentScaling()})`, transformOrigin: "bottom right" }} id={"google-photos"} src={"/assets/google_photos.png"} onClick={() => window.open(remoteUrl)} @@ -319,7 +327,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @computed private get considerDownloadIcon() { - const data = this.dataDoc[this.props.fieldKey]; + const data = this.dataDoc[this.fieldKey]; if (!(data instanceof ImageField)) { return (null); } @@ -330,6 +338,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum return ( <img id={"upload-icon"} + style={{ transform: `scale(${1 / this.props.ContentScaling()})`, transformOrigin: "bottom right" }} src={`/assets/${this.uploadIcon}`} onClick={async () => { const { dataDoc } = this; @@ -348,7 +357,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum setTimeout(action(() => { this.uploadIcon = idle; if (data) { - dataDoc[this.props.fieldKey] = data; + dataDoc[this.fieldKey] = data; } }), 2000); }} @@ -358,8 +367,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @computed get nativeSize() { const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; - const nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], pw); - const nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], 1); + const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], pw); + const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1); return { nativeWidth, nativeHeight }; } @@ -367,9 +376,9 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; // this._curSuffix = ""; // if (w > 20) { - const alts = DocListCast(this.dataDoc[this.props.fieldKey + "-alternates"]); + const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url)); - const field = this.dataDoc[this.props.fieldKey]; + const field = this.dataDoc[this.fieldKey]; // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s"; // else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m"; // else if (this._largeRetryCount < 10) this._curSuffix = "_l"; @@ -384,9 +393,11 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const srcpath = this.paths[NumCast(this.props.Document.curPage, 0)]; const fadepath = this.paths[Math.min(1, this.paths.length - 1)]; const { nativeWidth, nativeHeight } = this.nativeSize; - const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]); - const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; - const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; + const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]); + const aspect = (rotation % 180) ? nativeHeight / nativeWidth : 1; + const pwidth = this.props.PanelWidth(); + const pheight = this.props.PanelHeight(); + const shift = (rotation % 180) ? (pheight - pwidth) / aspect / 2 + (pheight - pwidth) / 2 : 0; this.resize(srcpath); @@ -414,8 +425,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }} > <FontAwesomeIcon className="imageBox-audioFont" - style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} - icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" /> + style={{ color: [DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} + icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" /> </div>} {this.considerDownloadIcon} {this.considerGooglePhotosLink()} @@ -426,17 +437,24 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum contentFunc = () => [this.content]; render() { TraceMobx(); + const { nativeWidth, nativeHeight } = this.nativeSize; + const aspect = nativeWidth / nativeHeight; + const pwidth = this.props.PanelWidth() > this.props.PanelHeight() / aspect ? this.props.PanelHeight() / aspect : this.props.PanelWidth(); const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging"; return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu} style={{ - transform: `scale(${this.props.ContentScaling()})`, - width: `${100 / this.props.ContentScaling()}%`, - height: `${100 / this.props.ContentScaling()}%`, - pointerEvents: this.props.Document.isBackground ? "none" : undefined + transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`, + width: this.props.PanelWidth() ? `${pwidth}px` : `${100 / this.props.ContentScaling()}%`, + height: this.props.PanelWidth() ? `${pwidth / aspect}px` : `${100 / this.props.ContentScaling()}%`, + pointerEvents: this.props.Document.isBackground ? "none" : undefined, + borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px` }} > <CollectionFreeFormView {...this.props} + forceScaling={true} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} + NativeHeight={returnZero} + NativeWidth={returnZero} annotationsKey={this.annotationKey} isAnnotationOverlay={true} focus={this.props.focus} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 93bda6d02..6dc4ae578 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -59,13 +59,18 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { ContainingCollectionView: undefined, ContainingCollectionDoc: undefined, fieldKey: this.props.keyName, + rootSelected: returnFalse, isSelected: returnFalse, select: emptyFunction, + dropAction: "alias", + bringToFront: emptyFunction, renderDepth: 1, active: returnFalse, whenActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyFunction, + NativeHeight: returnZero, + NativeWidth: returnZero, PanelWidth: this.props.PanelWidth, PanelHeight: this.props.PanelHeight, addDocTab: returnFalse, diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/LabelBox.scss index 7c3825978..ab5b2c6b3 100644 --- a/src/client/views/nodes/ButtonBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -1,4 +1,4 @@ -.buttonBox-outerDiv { +.labelBox-outerDiv { width: 100%; height: 100%; pointer-events: all; @@ -7,30 +7,32 @@ flex-direction: column; } -.buttonBox-mainButton { +.labelBox-mainButton { width: 100%; height: 100%; border-radius: inherit; - text-align: center; - display: table; - overflow: hidden; - text-overflow: ellipsis; letter-spacing: 2px; text-transform: uppercase; + overflow: hidden; + display:flex; } -.buttonBox-mainButtonCenter { - height: 100%; - display: table-cell; - vertical-align: middle; +.labelBox-mainButtonCenter { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline; + align-items: center; + margin: auto; } -.buttonBox-params { +.labelBox-params { display: flex; flex-direction: row; } -.buttonBox-missingParam { +.labelBox-missingParam { width: 100%; background: lightgray; + border: dimGray solid 1px; }
\ No newline at end of file diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/LabelBox.tsx index de0b509eb..0ec6af93a 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -11,7 +11,7 @@ import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; -import './ButtonBox.scss'; +import './LabelBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; @@ -20,18 +20,18 @@ import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit as any); -const ButtonSchema = createSchema({ +const LabelSchema = createSchema({ onClick: ScriptField, buttonParams: listSpec("string"), text: "string" }); -type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>; -const ButtonDocument = makeInterface(ButtonSchema, documentSchema); +type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>; +const LabelDocument = makeInterface(LabelSchema, documentSchema); @observer -export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); } +export class LabelBox extends DocComponent<FieldViewProps, LabelDocument>(LabelDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); } private dropDisposer?: DragManager.DragDropDisposer; @computed get dataDoc() { @@ -42,9 +42,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt protected createDropTarget = (ele: HTMLDivElement) => { - if (this.dropDisposer) { - this.dropDisposer(); - } + this.dropDisposer?.(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } @@ -55,7 +53,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt funcs.push({ description: "Clear Script Params", event: () => { const params = FieldValue(this.Document.buttonParams); - params && params.map(p => this.props.Document[p] = undefined); + params?.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -66,7 +64,9 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt @action drop = (e: Event, de: DragManager.DropEvent) => { const docDragData = de.complete.docDragData; - if (docDragData && e.target) { + const params = this.Document.buttonParams; + const missingParams = params?.filter(p => this.props.Document[p] === undefined); + if (docDragData && missingParams?.includes((e.target as any).textContent)) { this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) => d.onDragStart ? docDragData.draggedDocuments[i] : d)); e.stopPropagation(); @@ -75,21 +75,21 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = this.Document.buttonParams; - const missingParams = params && params.filter(p => this.props.Document[p] === undefined); - params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... + const missingParams = params?.filter(p => this.props.Document[p] === undefined); + params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return ( - <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} + <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}> - <div className="buttonBox-mainButton" style={{ + <div className="labelBox-mainButton" style={{ background: this.Document.backgroundColor, color: this.Document.color || "inherit", - fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: this.Document.textTransform || "" + fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || "" }} > - <div className="buttonBox-mainButtonCenter"> + <div className="labelBox-mainButtonCenter"> {(this.Document.text || this.Document.title)} </div> </div> - <div className="buttonBox-params" > - {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="buttonBox-missingParam">{m}</div>)} + <div className="labelBox-params" > + {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)} </div> </div> ); diff --git a/src/client/views/nodes/DocuLinkBox.scss b/src/client/views/nodes/LinkAnchorBox.scss index 286033475..7b6093ebd 100644 --- a/src/client/views/nodes/DocuLinkBox.scss +++ b/src/client/views/nodes/LinkAnchorBox.scss @@ -1,4 +1,4 @@ -.docuLinkBox-cont { +.linkAnchorBox-cont, .linkAnchorBox-cont-small { cursor: default; position: absolute; width: 15; @@ -7,7 +7,7 @@ pointer-events: all; user-select: none; - .docuLinkBox-linkCloser { + .linkAnchorBox-linkCloser { position: absolute; width: 18; height: 18; @@ -21,4 +21,9 @@ padding-left: 2px; padding-top: 1px; } +} + +.linkAnchorBox-cont-small { + width:5px; + height:5px; }
\ No newline at end of file diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 882e57006..6f6533771 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -4,11 +4,11 @@ import { Doc, DocListCast } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils } from '../../../Utils'; +import { Utils, setupMoveUpEvents } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; -import "./DocuLinkBox.scss"; +import "./LinkAnchorBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); import { ContextMenuProps } from "../ContextMenuItem"; @@ -16,21 +16,20 @@ import { ContextMenu } from "../ContextMenu"; import { LinkEditor } from "../linking/LinkEditor"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SelectionManager } from "../../util/SelectionManager"; +import { TraceMobx } from "../../../new_fields/util"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -type DocLinkSchema = makeInterface<[typeof documentSchema]>; -const DocLinkDocument = makeInterface(documentSchema); +type LinkAnchorSchema = makeInterface<[typeof documentSchema]>; +const LinkAnchorDocument = makeInterface(documentSchema); @observer -export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(DocLinkDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey); } +export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); } _doubleTap = false; _lastTap: number = 0; _ref = React.createRef<HTMLDivElement>(); - _downX = 0; - _downY = 0; _isOpen = false; _timeout: NodeJS.Timeout | undefined; @observable _x = 0; @@ -40,54 +39,42 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc @observable _forceOpen = false; onPointerDown = (e: React.PointerEvent) => { - this._downX = e.clientX; - this._downY = e.clientY; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); + setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick); } - onPointerMove = action((e: PointerEvent) => { + onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { const cdiv = this._ref && this._ref.current && this._ref.current.parentElement; - if (!this._isOpen && cdiv && (Math.abs(e.clientX - this._downX) > 5 || Math.abs(e.clientY - this._downY) > 5)) { + if (!this._isOpen && cdiv) { const bounds = cdiv.getBoundingClientRect(); const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); 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)); + const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1])); if (separation > 100) { - //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. const dragData = new DragManager.DocumentDragData([this.props.Document]); dragData.dropAction = "alias"; - DragManager.StartDocumentDrag([this._ref.current!], dragData, this._downX, this._downY); - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"]; + DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]); + return true; } else if (dragdist > separation) { this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; } } + return false; }); - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) { + @action + onClick = (e: PointerEvent) => { + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0); + this._lastTap = Date.now(); + if ((e.button === 2 || e.ctrlKey || !this.props.Document.isLinkButton)) { this.props.select(false); } - this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); - this._lastTap = Date.now(); - } - - @action - onClick = (e: React.MouseEvent) => { if (!this._doubleTap) { + const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack! need a better prop for passing the anchor's container this._editing = true; - this.props.ContainingCollectionDoc && this.props.bringToFront(this.props.ContainingCollectionDoc, false); - if (!this.props.Document.onClick && !this._isOpen) { + anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false); + if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) { this._timeout = setTimeout(action(() => { - if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { - DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false); - } + DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false); this._editing = false; }), 300 - (Date.now() - this._lastTap)); } @@ -95,7 +82,6 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc this._timeout && clearTimeout(this._timeout); this._timeout = undefined; } - e.stopPropagation(); } openLinkDocOnRight = (e: React.MouseEvent) => { @@ -103,7 +89,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc } openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null)); - alias.isButton = undefined; + alias.isLinkButton = undefined; alias.isBackground = undefined; alias.layoutKey = "layout"; this.props.addDocTab(alias, "onRight"); @@ -112,7 +98,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc openLinkEditor = action((e: React.MouseEvent) => { SelectionManager.DeselectAll(); this._editing = this._forceOpen = true; - }) + }); specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; @@ -124,8 +110,9 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc } render() { - const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100); - const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100); + TraceMobx(); + const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0; + const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0; const c = StrCast(this.props.Document.backgroundColor, "lightblue"); const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1"; const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15; @@ -133,16 +120,19 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc const timecode = this.props.Document[anchor + "Timecode"]; const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : ""); const flyout = ( - <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}> - <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)!} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} /> - {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}> + <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}> + <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} /> + {!this._forceOpen ? (null) : <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}> <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} /> </div>} </div> ); - return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu} + const small = this.props.PanelWidth() <= 1; + return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu} ref={this._ref} style={{ - background: c, left: `calc(${x}% - 7.5px)`, top: `calc(${y}% - 7.5px)`, + background: c, + left: !small ? `calc(${x}% - 7.5px)` : undefined, + top: !small ? `calc(${y}% - 7.5px)` : undefined, transform: `scale(${anchorScale / this.props.ContentScaling()})` }} > {!this._editing && !this._forceOpen ? (null) : diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 0e327e130..542c86049 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -23,6 +23,8 @@ export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument <CollectionTreeView {...this.props} ChromeHeight={returnZero} overrideDocuments={[this.dataDoc]} + NativeHeight={returnZero} + NativeWidth={returnZero} ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)} annotationsKey={""} CollectionView={undefined} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 593f40f10..f2a3e1484 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -9,7 +9,6 @@ import { ScriptField } from '../../../new_fields/ScriptField'; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { PdfField, URLField } from "../../../new_fields/URLField"; import { Utils } from '../../../Utils'; -import { KeyCodes } from '../../northstar/utils/KeyCodes'; import { undoBatch } from '../../util/UndoManager'; import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; @@ -18,10 +17,10 @@ import { DocAnnotatableComponent } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; +import { KeyCodes } from '../../util/KeyCodes'; import "./PDFBox.scss"; import React = require("react"); import { documentSchema } from '../../../new_fields/documentSchemas'; -import { url } from 'inspector'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -56,7 +55,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> const backup = "oldPath"; const { Document } = this.props; - const { url: { href } } = Cast(Document[this.props.fieldKey], PdfField)!; + const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!; const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g; const matches = pathCorrectionTest.exec(href); console.log("\nHere's the { url } being fed into the outer regex:"); @@ -78,9 +77,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> } } - componentWillUnmount() { - this._selectReactionDisposer && this._selectReactionDisposer(); - } + componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { this._selectReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -96,11 +93,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); } - public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } - public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); } - public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); } - public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); } - public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); } + public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }; + public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }; + public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }; + public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }; + public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }; public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; @undoBatch @@ -233,7 +230,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> isChildActive = (outsideReaction?: boolean) => this._isChildActive; @computed get renderPdfView() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}> + return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded} setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView} renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} @@ -243,7 +240,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} isChildActive={this.isChildActive} - fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 || this.props.Document._scrollTop ? true : false} /> {this.settingsPanel()} </div>; } @@ -251,8 +248,8 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> _pdfjsRequested = false; render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); - if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; - if (pdfUrl && (this._everActive || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) { + if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true; + if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) { if (pdfUrl instanceof PdfField && this._pdf) { return this.renderPdfView; } diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 6a20751cc..ba8389fda 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -25,6 +25,13 @@ width: 20%; border-radius: 5px; } + .collectionViewBaseChrome-viewPicker { + min-width: 50; + width: 5%; + height: 25; + position: relative; + display: inline-block; + } } .presBox-backward, .presBox-forward { width: 25px; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 4180ee255..e7434feaa 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,21 +1,17 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faHandPointLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; -import { listSpec } from "../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { returnFalse } from "../../../Utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; @@ -24,6 +20,7 @@ library.add(faArrowLeft); library.add(faArrowRight); library.add(faPlay); library.add(faStop); +library.add(faHandPointLeft); library.add(faPlus); library.add(faTimes); library.add(faMinus); @@ -44,63 +41,39 @@ export class PresBox extends React.Component<FieldViewProps> { } @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); } + @computed get currentIndex() { return NumCast(this.props.Document._itemIndex); } - next = async () => { - runInAction(() => Doc.UserDoc().curPresentation = this.props.Document); - const current = NumCast(this.props.Document._itemIndex); - //asking to get document at current index - const docAtCurrentNext = await this.getDocAtIndex(current + 1); - if (docAtCurrentNext !== undefined) { - const presDocs = DocListCast(this.props.Document[this.props.fieldKey]); - let nextSelected = current + 1; - - for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!presDocs[nextSelected + 1].groupButton) { + updateCurrentPresentation = action(() => Doc.UserDoc().curPresentation = this.props.Document); + + next = () => { + this.updateCurrentPresentation(); + if (this.childDocs[this.currentIndex + 1] !== undefined) { + let nextSelected = this.currentIndex + 1; + this.gotoDocument(nextSelected, this.currentIndex); + + for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) { + if (!this.childDocs[nextSelected].groupButton) { break; + } else { + this.gotoDocument(nextSelected, this.currentIndex); } } - - this.gotoDocument(nextSelected, current); } } - back = async () => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); - const current = NumCast(this.props.Document._itemIndex); - //requesting for the doc at current index - const docAtCurrent = await this.getDocAtIndex(current); - if (docAtCurrent !== undefined) { - - //asking for its presentation id. - let prevSelected = current; - let zoomOut: boolean = false; - - const presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - const currentsArray: Doc[] = []; - for (; presDocs && prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) { - currentsArray.push(presDocs[prevSelected]); + back = () => { + this.updateCurrentPresentation(); + const docAtCurrent = this.childDocs[this.currentIndex]; + if (docAtCurrent) { + //check if any of the group members had used zooming in including the current document + //If so making sure to zoom out, which goes back to state before zooming action + let prevSelected = this.currentIndex; + let didZoom = docAtCurrent.zoomButton; + for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) { + didZoom = this.childDocs[prevSelected].zoomButton; } prevSelected = Math.max(0, prevSelected - 1); - //checking if any of the group members had used zooming in - currentsArray.forEach((doc: Doc) => { - if (doc.showButton) { - zoomOut = true; - return; - } - }); - - // if a group set that flag to zero or a single element - //If so making sure to zoom out, which goes back to state before zooming action - if (current > 0) { - if (zoomOut || docAtCurrent.showButton) { - const prevScale = NumCast(this.childDocs[prevSelected].viewScale, null); - const curScale = DocumentManager.Instance.getScaleOfDocView(this.childDocs[current]); - if (prevScale !== undefined && prevScale !== curScale) { - DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); - } - } - } - this.gotoDocument(prevSelected, current); + this.gotoDocument(prevSelected, this.currentIndex); } } @@ -114,7 +87,7 @@ export class PresBox extends React.Component<FieldViewProps> { * Hide Until Presented, Hide After Presented, Fade After Presented */ showAfterPresented = (index: number) => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + this.updateCurrentPresentation(); this.childDocs.forEach((doc, ind) => { //the order of cases is aligned based on priority if (doc.hideTillShownButton && ind <= index) { @@ -135,7 +108,7 @@ export class PresBox extends React.Component<FieldViewProps> { * Hide Until Presented, Hide After Presented, Fade After Presented */ hideIfNotPresented = (index: number) => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + this.updateCurrentPresentation(); this.childDocs.forEach((key, ind) => { //the order of cases is aligned based on priority @@ -157,7 +130,7 @@ export class PresBox extends React.Component<FieldViewProps> { * te option open, navigates to that element. */ navigateToElement = async (curDoc: Doc, fromDocIndex: number) => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + this.updateCurrentPresentation(); const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc; let docToJump = curDoc; let willZoom = false; @@ -177,7 +150,7 @@ export class PresBox extends React.Component<FieldViewProps> { docToJump = doc; willZoom = false; } - if (doc.showButton) { + if (doc.zoomButton) { docToJump = doc; willZoom = true; } @@ -185,74 +158,36 @@ export class PresBox extends React.Component<FieldViewProps> { //docToJump stayed same meaning, it was not in the group or was the last element in the group const aliasOf = await Cast(docToJump.aliasOf, Doc); - const srcContext = aliasOf && await Cast(aliasOf.anchor1Context, Doc); + const srcContext = aliasOf && await Cast(aliasOf.context, Doc); if (docToJump === curDoc) { //checking if curDoc has navigation open const target = await Cast(curDoc.presentationTargetDoc, Doc); if (curDoc.navButton && target) { DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext); - } else if (curDoc.showButton && target) { - const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); + } else if (curDoc.zoomButton && target) { //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext); - curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target); - - //saving the scale user was on before zooming in - if (curScale !== 1) { - fromDoc.viewScale = curScale; - } - } return; } - const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); //awaiting jump so that new scale can be found, since jumping is async const presTargetDoc = await docToJump.presentationTargetDoc as Doc; await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext); - const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc); - curDoc.viewScale = newScale; - //saving the scale that user was on - if (curScale !== 1) { - fromDoc.viewScale = curScale; - } - - } - - /** - * Async function that supposedly return the doc that is located at given index. - */ - getDocAtIndex = async (index: number) => { - const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); - if (list && index >= 0 && index < list.length) { - this.props.Document._itemIndex = index; - //awaiting async call to finish to get Doc instance - return list[index]; - } - return undefined; } @undoBatch public removeDocument = (doc: Doc) => { - const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); - if (value) { - const indexOfDoc = value.indexOf(doc); - if (indexOfDoc !== - 1) { - value.splice(indexOfDoc, 1)[0]; - return true; - } - } - return false; + return Doc.RemoveDocFromList(this.props.Document, this.props.fieldKey, doc); } //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. - public gotoDocument = async (index: number, fromDoc: number) => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + public gotoDocument = (index: number, fromDoc: number) => { + this.updateCurrentPresentation(); Doc.UnBrushAllDocs(); - const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); - if (list && index >= 0 && index < list.length) { + if (index >= 0 && index < this.childDocs.length) { this.props.Document._itemIndex = index; if (!this.props.Document.presStatus) { @@ -260,24 +195,21 @@ export class PresBox extends React.Component<FieldViewProps> { this.startPresentation(index); } - const doc = await list[index]; - if (this.props.Document.presStatus) { - this.navigateToElement(doc, fromDoc); - this.hideIfNotPresented(index); - this.showAfterPresented(index); - } + this.navigateToElement(this.childDocs[index], fromDoc); + this.hideIfNotPresented(index); + this.showAfterPresented(index); } } //The function that starts or resets presentaton functionally, depending on status flag. startOrResetPres = () => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + this.updateCurrentPresentation(); if (this.props.Document.presStatus) { this.resetPresentation(); } else { this.props.Document.presStatus = true; this.startPresentation(0); - this.gotoDocument(0, NumCast(this.props.Document._itemIndex)); + this.gotoDocument(0, this.currentIndex); } } @@ -291,79 +223,54 @@ export class PresBox extends React.Component<FieldViewProps> { //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); - this.childDocs.forEach((doc: Doc) => { - doc.opacity = 1; - doc.viewScale = 1; - }); + this.updateCurrentPresentation(); + this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1); this.props.Document._itemIndex = 0; this.props.Document.presStatus = false; - if (this.childDocs.length !== 0) { - DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1); - } } //The function that starts the presentation, also checking if actions should be applied //directly at start. startPresentation = (startIndex: number) => { - action(() => Doc.UserDoc().curPresentation = this.props.Document); + this.updateCurrentPresentation(); this.childDocs.map(doc => { if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { - doc.opacity = 0; + (doc.presentationTargetDoc as Doc).opacity = 0; } if (doc.hideAfterButton && this.childDocs.indexOf(doc) < startIndex) { - doc.opacity = 0; + (doc.presentationTargetDoc as Doc).opacity = 0; } if (doc.fadeButton && this.childDocs.indexOf(doc) < startIndex) { - doc.opacity = 0.5; + (doc.presentationTargetDoc as Doc).opacity = 0.5; } }); } - updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: number) => { - const toggle = BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid); - if (toggle) { + updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => { + if (BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid)) { if (this.props.Document.inOverlay) { - Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); + Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document); CollectionDockingView.AddRightSplit(this.props.Document); this.props.Document.inOverlay = false; } else { this.props.Document.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25; this.props.Document.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25; this.props.addDocTab?.(this.props.Document, "close"); - Doc.AddDocToList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); + Doc.AddDocToList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document); } } })); - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._viewType = CollectionViewType.Carousel), icon: "asterisk" }); - funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._viewType = CollectionViewType.Time), icon: "asterisk" }); - funcs.push({ description: "Show as List", event: action(() => this.props.Document._viewType = CollectionViewType.Invalid), icon: "asterisk" }); - ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" }); - } - - /** - * Initially every document starts with a viewScale 1, which means - * that they will be displayed in a canvas with scale 1. - */ - initializeScaleViews = (docList: Doc[], viewtype: number) => { + initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => { const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; - docList.forEach((doc: Doc) => { - doc.presBox = this.props.Document; - doc.presBoxKey = this.props.fieldKey; - doc.collapsedHeight = hgt; - const curScale = NumCast(doc.viewScale, null); - if (curScale === undefined) { - doc.viewScale = 1; - } + docList.forEach(doc => { + doc.presBox = this.props.Document; // give contained documents a reference to the presentation + doc.collapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din }); } selectElement = (doc: Doc) => { - const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc); - index !== -1 && this.gotoDocument(index, NumCast(this.props.Document._itemIndex)); + this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.props.Document._itemIndex)); } getTransform = () => { @@ -376,18 +283,18 @@ export class PresBox extends React.Component<FieldViewProps> { @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore - this.props.Document._viewType = Number(e.target.selectedOptions[0].value); + this.props.Document._viewType = e.target.selectedOptions[0].value; + this.props.Document._viewType === CollectionViewType.Stacking && (this.props.Document._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here this.updateMinimize(e, Number(this.props.Document._viewType)); }); childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined; render() { - const mode = NumCast(this.props.Document._viewType, CollectionViewType.Invalid); - this.initializeScaleViews(this.childDocs, mode); - return <div className="presBox-cont" onContextMenu={this.specificContextMenu} style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} > + const mode = StrCast(this.props.Document._viewType) as CollectionViewType; + this.initializeViewAliases(this.childDocs, mode); + return <div className="presBox-cont" style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} > <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}> - <select style={{ minWidth: 50, width: "5%", height: "25", position: "relative", display: "inline-block" }} - className="collectionViewBaseChrome-viewPicker" + <select className="collectionViewBaseChrome-viewPicker" onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> diff --git a/src/client/views/nodes/QueryBox.scss b/src/client/views/nodes/QueryBox.scss index e69de29bb..82f64054c 100644 --- a/src/client/views/nodes/QueryBox.scss +++ b/src/client/views/nodes/QueryBox.scss @@ -0,0 +1,6 @@ +.queryBox, .queryBox-dragging { + width: 100%; + height: 100%; + position: absolute; + pointer-events: all; +}
\ No newline at end of file diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index 99b5810fc..7016b4f04 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -1,35 +1,34 @@ import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; import { IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { FilterBox } from "../search/FilterBox"; +import { documentSchema } from "../../../new_fields/documentSchemas"; +import { Id } from '../../../new_fields/FieldSymbols'; +import { makeInterface } from "../../../new_fields/Schema"; +import { StrCast } from "../../../new_fields/Types"; +import { SelectionManager } from "../../util/SelectionManager"; +import { DocAnnotatableComponent } from '../DocComponent'; +import { SearchBox } from "../search/SearchBox"; import { FieldView, FieldViewProps } from './FieldView'; -import "./PresBox.scss"; +import "./QueryBox.scss"; -library.add(faArrowLeft); -library.add(faArrowRight); -library.add(faPlay); -library.add(faStop); -library.add(faPlus); -library.add(faTimes); -library.add(faMinus); -library.add(faEdit); +type QueryDocument = makeInterface<[typeof documentSchema]>; +const QueryDocument = makeInterface(documentSchema); @observer -export class QueryBox extends React.Component<FieldViewProps> { +export class QueryBox extends DocAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { } componentWillUnmount() { - this._docListChangedReaction && this._docListChangedReaction(); + this._docListChangedReaction?.(); } render() { - return <div style={{ width: "100%", height: "100%", position: "absolute", pointerEvents: "all" }}> - <FilterBox></FilterBox> - </div>; + const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging"; + return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > + <SearchBox id={this.props.Document[Id]} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQquery={StrCast(this.dataDoc.filterQuery)} /> + </div >; } }
\ No newline at end of file diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index 9314a3899..0ffed78de 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -5,6 +5,8 @@ import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Measure from "react-measure"; import "./RadialMenu.scss"; +import MobileInkOverlay from "../../../mobile/MobileInkOverlay"; +import MobileInterface from "../../../mobile/MobileInterface"; @observer export class RadialMenu extends React.Component { @@ -23,13 +25,23 @@ export class RadialMenu extends React.Component { @observable private _mouseDown: boolean = false; private _reactionDisposer?: IReactionDisposer; + public used: boolean = false; + + + catchTouch = (te: React.TouchEvent) => { + console.log("caught"); + te.stopPropagation(); + te.preventDefault(); + } @action onPointerDown = (e: PointerEvent) => { this._mouseDown = true; this._mouseX = e.clientX; this._mouseY = e.clientY; + this.used = false; document.addEventListener("pointermove", this.onPointerMove); + } @observable @@ -42,7 +54,6 @@ export class RadialMenu extends React.Component { const deltX = this._mouseX - curX; const deltY = this._mouseY - curY; const scale = Math.hypot(deltY, deltX); - if (scale < 150 && scale > 50) { const rad = Math.atan2(deltY, deltX) + Math.PI; let closest = 0; @@ -62,6 +73,7 @@ export class RadialMenu extends React.Component { } @action onPointerUp = (e: PointerEvent) => { + this.used = true; this._mouseDown = false; const curX = e.clientX; const curY = e.clientY; @@ -83,6 +95,7 @@ export class RadialMenu extends React.Component { @action componentDidMount = () => { + console.log(this._pageX); document.addEventListener("pointerdown", this.onPointerDown); document.addEventListener("pointerup", this.onPointerUp); this.previewcircle(); @@ -98,7 +111,7 @@ export class RadialMenu extends React.Component { @observable private _pageX: number = 0; @observable private _pageY: number = 0; - @observable private _display: boolean = false; + @observable _display: boolean = false; @observable private _yRelativeToTop: boolean = true; @@ -124,35 +137,34 @@ export class RadialMenu extends React.Component { displayMenu = (x: number, y: number) => { //maxX and maxY will change if the UI/font size changes, but will work for any amount //of items added to the menu - - this._pageX = x; - this._pageY = y; + this._mouseX = x; + this._mouseY = y; this._shouldDisplay = true; } - - get pageX() { - const x = this._pageX; - if (x < 0) { - return 0; - } - const width = this._width; - if (x + width > window.innerWidth - RadialMenu.buffer) { - return window.innerWidth - RadialMenu.buffer - width; - } - return x; - } - - get pageY() { - const y = this._pageY; - if (y < 0) { - return 0; - } - const height = this._height; - if (y + height > window.innerHeight - RadialMenu.buffer) { - return window.innerHeight - RadialMenu.buffer - height; - } - return y; - } + // @computed + // get pageX() { + // const x = this._pageX; + // if (x < 0) { + // return 0; + // } + // const width = this._width; + // if (x + width > window.innerWidth - RadialMenu.buffer) { + // return window.innerWidth - RadialMenu.buffer - width; + // } + // return x; + // } + // @computed + // get pageY() { + // const y = this._pageY; + // if (y < 0) { + // return 0; + // } + // const height = this._height; + // if (y + height > window.innerHeight - RadialMenu.buffer) { + // return window.innerHeight - RadialMenu.buffer - height; + // } + // return y; + // } @computed get menuItems() { return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />); @@ -166,7 +178,10 @@ export class RadialMenu extends React.Component { } @action - openMenu = () => { + openMenu = (x: number, y: number) => { + + this._pageX = x; + this._pageY = y; this._shouldDisplay; this._display = true; } @@ -204,15 +219,15 @@ export class RadialMenu extends React.Component { render() { - if (!this._display) { + if (!this._display || MobileInterface.Instance) { return null; } - const style = this._yRelativeToTop ? { left: this._mouseX - 150, top: this._mouseY - 150 } : - { left: this._mouseX - 150, top: this._mouseY - 150 }; + const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : + { left: this._pageX - 130, top: this._pageY - 130 }; return ( - <div className="radialMenu-cont" style={style}> + <div className="radialMenu-cont" onTouchStart={this.catchTouch} style={style}> <canvas id="newCanvas" style={{ position: "absolute" }} height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas> {this.menuItems} </div> diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx index fdc732d3f..bd5b3bff4 100644 --- a/src/client/views/nodes/RadialMenuItem.tsx +++ b/src/client/views/nodes/RadialMenuItem.tsx @@ -44,12 +44,12 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { setcircle() { let circlemin = 0; - let circlemax = 1 + let circlemax = 1; this.props.min ? circlemin = this.props.min : null; this.props.max ? circlemax = this.props.max : null; if (document.getElementById("myCanvas") !== null) { - var c: any = document.getElementById("myCanvas"); - let color = "white" + const c: any = document.getElementById("myCanvas"); + let color = "white"; switch (circlemin % 3) { case 1: color = "#c2c2c5"; @@ -70,38 +70,38 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { } if (c.getContext) { - var ctx = c.getContext("2d"); + const ctx = c.getContext("2d"); ctx.beginPath(); ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI); ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true); ctx.fillStyle = color; - ctx.fill() + ctx.fill(); } } } calculatorx() { let circlemin = 0; - let circlemax = 1 + let circlemax = 1; this.props.min ? circlemin = this.props.min : null; this.props.max ? circlemax = this.props.max : null; - let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; - let degrees = 360 * avg; - let x = 100 * Math.cos(degrees * Math.PI / 180); - let y = -125 * Math.sin(degrees * Math.PI / 180); + const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + const degrees = 360 * avg; + const x = 100 * Math.cos(degrees * Math.PI / 180); + const y = -125 * Math.sin(degrees * Math.PI / 180); return x; } calculatory() { let circlemin = 0; - let circlemax = 1 + let circlemax = 1; this.props.min ? circlemin = this.props.min : null; this.props.max ? circlemax = this.props.max : null; - let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; - let degrees = 360 * avg; - let x = 125 * Math.cos(degrees * Math.PI / 180); - let y = -100 * Math.sin(degrees * Math.PI / 180); + const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + const degrees = 360 * avg; + const x = 125 * Math.cos(degrees * Math.PI / 180); + const y = -100 * Math.sin(degrees * Math.PI / 180); return y; } diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss new file mode 100644 index 000000000..6cc184948 --- /dev/null +++ b/src/client/views/nodes/ScreenshotBox.scss @@ -0,0 +1,55 @@ +.screenshotBox { + pointer-events: all; + transform-origin: top left; + background: white; + color: black; + // .screenshotBox-viewer { + // opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger + // } + // .inkingCanvas-paths-markers { + // opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround + // } +} + +.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen { + width: 100%; + z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt + position: absolute; +} + +.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen { + height: Auto; +} + +.screenshotBox-content-interactive, .screenshotBox-content-fullScreen { + pointer-events: all; +} + +.screenshotBox-uiButtons { + background:dimgray; + border: orange solid 1px; + position: absolute; + right: 25; + top: 0; + width:50; + height: 25; + .screenshotBox-snapshot{ + color : white; + top :0px; + right : 5px; + position: absolute; + background-color:rgba(50, 50, 50, 0.2); + transform-origin: left top; + pointer-events:all; + } + + .screenshotBox-recorder{ + color : white; + top :0px; + left: 5px; + position: absolute; + background-color:rgba(50, 50, 50, 0.2); + transform-origin: left top; + pointer-events:all; + } +} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx new file mode 100644 index 000000000..58ff4971a --- /dev/null +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -0,0 +1,196 @@ +import React = require("react"); +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import * as rp from 'request-promise'; +import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; +import { makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../new_fields/Types"; +import { VideoField } from "../../../new_fields/URLField"; +import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { DocAnnotatableComponent } from "../DocComponent"; +import { InkingControl } from "../InkingControl"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./ScreenshotBox.scss"; +const path = require('path'); + +type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +const ScreenshotDocument = makeInterface(documentSchema, positionSchema); + +library.add(faVideo); + +@observer +export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { + private _reactionDisposer?: IReactionDisposer; + private _videoRef: HTMLVideoElement | null = null; + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } + + public get player(): HTMLVideoElement | null { + return this._videoRef; + } + + videoLoad = () => { + const aspect = this.player!.videoWidth / this.player!.videoHeight; + const nativeWidth = (this.Document._nativeWidth || 0); + const nativeHeight = (this.Document._nativeHeight || 0); + if (!nativeWidth || !nativeHeight) { + if (!this.Document._nativeWidth) this.Document._nativeWidth = 400; + this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect; + this.Document._height = (this.Document._width || 0) / aspect; + } + if (!this.Document.duration) this.Document.duration = this.player!.duration; + } + + @action public Snapshot() { + const width = this.Document._width || 0; + const height = this.Document._height || 0; + const canvas = document.createElement('canvas'); + canvas.width = 640; + canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1); + const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions + if (ctx) { + ctx.rect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "blue"; + ctx.fill(); + this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); + } + + if (this._videoRef) { + //convert to desired file format + const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + // if you want to preview the captured image, + const filename = path.basename(encodeURIComponent("screenshot" + Utils.GenerateGuid().replace(/\..*$/, "").replace(" ", "_"))); + ScreenshotBox.convertDataUri(dataUrl, filename).then(returnedFilename => { + setTimeout(() => { + if (returnedFilename) { + const imageSummary = Docs.Create.ImageDocument(Utils.prepend(returnedFilename), { + x: (this.Document.x || 0) + width, y: (this.Document.y || 0), + _width: 150, _height: height / width * 150, title: "--screenshot--" + }); + this.props.addDocument?.(imageSummary); + } + }, 500); + }); + } + } + + componentDidMount() { + } + + componentWillUnmount() { + this._reactionDisposer && this._reactionDisposer(); + } + + @action + setVideoRef = (vref: HTMLVideoElement | null) => { + this._videoRef = vref; + } + + public static async convertDataUri(imageUri: string, returnedFilename: string) { + try { + const posting = Utils.prepend("/uploadURI"); + const returnedUri = await rp.post(posting, { + body: { + uri: imageUri, + name: returnedFilename + }, + json: true, + }); + return returnedUri; + + } catch (e) { + console.log(e); + } + } + @observable _screenCapture = false; + specificContextMenu = (e: React.MouseEvent): void => { + const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); + if (field) { + const url = field.url.href; + const subitems: ContextMenuProps[] = []; + subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" }); + subitems.push({ + description: "Screen Capture", event: (async () => { + runInAction(() => this._screenCapture = !this._screenCapture); + this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); + }), icon: "expand-arrows-alt" + }); + ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" }); + } + } + + @computed get content() { + const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; + const style = "videoBox-content" + interactive; + return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} + style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }} + onCanPlay={this.videoLoad} + controls={true} + onClick={e => e.preventDefault()}> + <source type="video/mp4" /> + Not supported. + </video>; + } + + toggleRecording = action(async () => { + this._screenCapture = !this._screenCapture; + this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); + }); + + private get uIButtons() { + return (<div className="screenshotBox-uiButtons"> + <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} > + <FontAwesomeIcon icon="file" size="lg" /> + </div>, + <div className="screenshotBox-snapshot" key="snap" onPointerDown={this.onSnapshot} > + <FontAwesomeIcon icon="camera" size="lg" /> + </div> + </div>); + } + + onSnapshot = (e: React.PointerEvent) => { + this.Snapshot(); + e.stopPropagation(); + e.preventDefault(); + } + + + contentFunc = () => [this.content]; + render() { + return (<div className="videoBox" onContextMenu={this.specificContextMenu} + style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > + <div className="videoBox-viewer" > + <CollectionFreeFormView {...this.props} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.props.PanelWidth} + NativeHeight={returnZero} + NativeWidth={returnZero} + annotationsKey={this.annotationKey} + focus={this.props.focus} + isSelected={this.props.isSelected} + isAnnotationOverlay={true} + select={emptyFunction} + active={this.annotationsActive} + ContentScaling={returnOne} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={returnFalse} + CollectionView={undefined} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> + {this.contentFunc} + </CollectionFreeFormView> + </div> + {this.active() ? this.uIButtons : (null)} + </div >); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss new file mode 100644 index 000000000..f31c72bb2 --- /dev/null +++ b/src/client/views/nodes/ScriptingBox.scss @@ -0,0 +1,33 @@ +.scriptingBox-outerDiv { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + pointer-events: all; + background-color: rgb(241, 239, 235); + padding: 10px; + .scriptingBox-inputDiv { + display: flex; + flex-direction: column; + height: calc(100% - 30px); + .scriptingBox-errorMessage { + overflow: auto; + } + .scriptingBox-textArea { + width: 100%; + height: 100%; + box-sizing: border-box; + resize: none; + padding: 7px; + } + } + + .scriptingBox-toolbar { + width: 100%; + height: 30px; + .scriptingBox-button { + width: 50% + } + } +} + diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx new file mode 100644 index 000000000..74d9c2e94 --- /dev/null +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -0,0 +1,96 @@ +import { action, observable, computed } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { Opt } from "../../../new_fields/Doc"; +import { documentSchema } from "../../../new_fields/documentSchemas"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { StrCast, ScriptCast } from "../../../new_fields/Types"; +import { returnTrue } from "../../../Utils"; +import { InteractionUtils } from "../../util/InteractionUtils"; +import { CompileScript } from "../../util/Scripting"; +import { DocAnnotatableComponent } from "../DocComponent"; +import { EditableView } from "../EditableView"; +import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import "./ScriptingBox.scss"; + +const ScriptingSchema = createSchema({ +}); + +type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>; +const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema); + +@observer +export class ScriptingBox extends DocAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) { + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); } + + @observable private _errorMessage: string = ""; + @computed get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-raw"]); } + set rawScript(value) { this.dataDoc[this.props.fieldKey + "-raw"] = value; } + + @action + componentDidMount() { + this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript || this.rawScript; + } + + @action + onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + this.rawScript = e.target.value; + } + + @action + onError = (error: any) => { + this._errorMessage = (error as any).map((e: any) => e.messageText).join(" "); + } + + @action + onCompile = () => { + const result = CompileScript(this.rawScript, {}); + this._errorMessage = ""; + if (result.compiled) { + this._errorMessage = ""; + this.dataDoc[this.props.fieldKey] = new ScriptField(result); + } + else { + this.dataDoc[this.props.fieldKey] = undefined; + this.onError(result.errors); + } + } + + @action + onRun = () => { + this.onCompile(); + ScriptCast(this.dataDoc[this.props.fieldKey])?.script.run({}, + (err: any) => { + this._errorMessage = ""; + this.onError(err); + }); + } + + render() { + let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined; + const params = <EditableView + contents={""} + display={"block"} + maxHeight={72} + height={35} + fontSize={28} + GetValue={() => ""} + SetValue={returnTrue} + />; + return ( + <div className="scriptingBox-outerDiv" onPointerDown={(e) => this.props.isSelected() && e.stopPropagation()} onWheel={(e) => this.props.isSelected() && e.stopPropagation()}> + <div className="scriptingBox-inputDiv" > + <textarea className="scriptingBox-textarea" placeholder="write your script here" onChange={this.onChange} value={this.rawScript} onFocus={onFocus} onBlur={onBlur} /> + <div className="scriptingBox-errorMessage">{this._errorMessage}</div> + <div style={{ background: "beige" }} >{params}</div> + </div> + <div className="scriptingBox-toolbar"> + <button className="scriptingBox-button" onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button> + <button className="scriptingBox-button" onPointerDown={e => { this.onRun(); e.stopPropagation(); }}>Run</button> + </div> + </div> + ); + } +} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 69c6f2617..a4a330fe6 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -11,7 +11,7 @@ import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; -import { Utils, emptyFunction, returnOne } from "../../../Utils"; +import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; @@ -132,9 +132,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum x: (this.Document.x || 0) + width, y: (this.Document.y || 0), _width: 150, _height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" }); - imageSummary.isButton = true; + imageSummary.isLinkButton = true; this.props.addDocument && this.props.addDocument(imageSummary); - DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot"); + DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "video snapshot"); } }); } @@ -195,6 +195,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum console.log(e); } } + @observable _screenCapture = false; specificContextMenu = (e: React.MouseEvent): void => { const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); if (field) { @@ -203,6 +204,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" }); subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" }); subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" }); + subitems.push({ + description: "Screen Capture", event: (async () => { + runInAction(() => this._screenCapture = !this._screenCapture); + this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); + }), icon: "expand-arrows-alt" + }); ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" }); } } @@ -212,8 +219,14 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div>Loading</div> : - <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls} - onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}> + <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} + style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }} + onCanPlay={this.videoLoad} + controls={VideoBox._showControls} + onPlay={() => this.Play()} + onSeeked={this.updateTimecode} + onPause={() => this.Pause()} + onClick={e => e.preventDefault()}> <source src={field.url.href} type="video/mp4" /> Not supported. </video>; @@ -261,7 +274,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } private get uIButtons() { - const scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); const curTime = (this.Document.currentTimecode || 0); return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} > <span>{"" + Math.round(curTime)}</span> @@ -340,6 +352,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} + NativeHeight={returnZero} + NativeWidth={returnZero} annotationsKey={this.annotationKey} focus={this.props.focus} isSelected={this.props.isSelected} diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index fbe9bf063..b41687c11 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,13 +1,18 @@ @import "../globalCssVariables.scss"; + +.webBox-container, .webBox-container-dragging { + transform-origin: top left; +} .webBox-cont, -.webBox-cont-interactive { +.webBox-cont-dragging { padding: 0vw; position: absolute; top: 0; left: 0; width: 100%; height: 100%; + transform-origin: top left; overflow: auto; pointer-events: none; } @@ -90,4 +95,19 @@ width: 100%; margin-right: 10px; height: 100%; +} + +.touch-iframe-overlay { + width: 100%; + height: 100%; + position: absolute; + pointer-events: all; + + .indicator { + position: absolute; + + &.active { + background-color: rgba(0, 0, 0, 0.1); + } + } }
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 7e49d957d..ea5d601ec 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,26 +1,31 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, FieldResult } from "../../../new_fields/Doc"; +import { documentSchema } from "../../../new_fields/documentSchemas"; import { HtmlField } from "../../../new_fields/HtmlField"; import { InkTool } from "../../../new_fields/InkField"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast } from "../../../new_fields/Types"; import { WebField } from "../../../new_fields/URLField"; -import { emptyFunction, returnOne, Utils } from "../../../Utils"; +import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils"; import { Docs } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; +import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { SelectionManager } from "../../util/SelectionManager"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from "./KeyValueBox"; import "./WebBox.scss"; import React = require("react"); -import { DocAnnotatableComponent } from "../DocComponent"; -import { documentSchema } from "../../../new_fields/documentSchemas"; +import * as WebRequest from 'web-request'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +const htmlToText = require("html-to-text"); + library.add(faStickyNote); @@ -32,12 +37,23 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @observable private collapsed: boolean = true; - @observable private url: string = ""; + @observable private url: string = "hello"; - componentDidMount() { + private _longPressSecondsHack?: NodeJS.Timeout; + private _iframeRef = React.createRef<HTMLIFrameElement>(); + private _iframeIndicatorRef = React.createRef<HTMLDivElement>(); + private _iframeDragRef = React.createRef<HTMLDivElement>(); + @observable private _pressX: number = 0; + @observable private _pressY: number = 0; + async componentDidMount() { + + this.setURL(); + + document.addEventListener("pointerup", this.onLongPressUp); + document.addEventListener("pointermove", this.onLongPressMove); const field = Cast(this.props.Document[this.props.fieldKey], WebField); - if (field && field.url.href.indexOf("youtube") !== -1) { + if (field?.url.href.indexOf("youtube") !== -1) { const youtubeaspect = 400 / 315; const nativeWidth = NumCast(this.layoutDoc._nativeWidth); const nativeHeight = NumCast(this.layoutDoc._nativeHeight); @@ -46,9 +62,16 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect; this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; } + } else if (field?.url) { + const result = await WebRequest.get(Utils.CorsProxy(field.url.href)); + this.dataDoc.text = htmlToText.fromString(result.content); } - this.setURL(); + } + + componentWillUnmount() { + document.removeEventListener("pointerup", this.onLongPressUp); + document.removeEventListener("pointermove", this.onLongPressMove); } @action @@ -58,14 +81,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> @action submitURL = () => { - const script = KeyValueBox.CompileKVPScript(`new WebField("${this.url}")`); - if (!script) return; - KeyValueBox.ApplyKVPScript(this.props.Document, "data", script); + this.dataDoc[this.props.fieldKey] = new WebField(new URL(this.url)); } @action setURL() { - const urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField); + const urlField: FieldResult<WebField> = Cast(this.dataDoc[this.props.fieldKey], WebField); if (urlField) this.url = urlField.url.toString(); else this.url = ""; } @@ -164,6 +185,107 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> } } + onLongPressDown = (e: React.PointerEvent) => { + this._pressX = e.clientX; + this._pressY = e.clientY; + + // find the pressed element in the iframe (currently only works if its an img) + let pressedElement: HTMLElement | undefined; + let pressedBound: ClientRect | undefined; + let selectedText: string = ""; + let pressedImg: boolean = false; + if (this._iframeRef.current) { + const B = this._iframeRef.current.getBoundingClientRect(); + const iframeDoc = this._iframeRef.current.contentDocument; + if (B && iframeDoc) { + // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload + const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top); + if (element && element.nodeName === "IMG") { + pressedBound = element.getBoundingClientRect(); + pressedElement = element.cloneNode(true) as HTMLElement; + pressedImg = true; + } else { + // check if there is selected text + const text = iframeDoc.getSelection(); + if (text && text.toString().length > 0) { + selectedText = text.toString(); + + // get html of the selected text + const range = text.getRangeAt(0); + const contents = range.cloneContents(); + const div = document.createElement("div"); + div.appendChild(contents); + pressedElement = div; + + pressedBound = range.getBoundingClientRect(); + } + } + } + } + + // mark the pressed element + if (pressedElement && pressedBound) { + if (this._iframeIndicatorRef.current) { + this._iframeIndicatorRef.current.style.top = pressedBound.top + "px"; + this._iframeIndicatorRef.current.style.left = pressedBound.left + "px"; + this._iframeIndicatorRef.current.style.width = pressedBound.width + "px"; + this._iframeIndicatorRef.current.style.height = pressedBound.height + "px"; + this._iframeIndicatorRef.current.classList.add("active"); + } + } + + // start dragging the pressed element if long pressed + this._longPressSecondsHack = setTimeout(() => { + if (pressedImg && pressedElement && pressedBound) { + e.stopPropagation(); + e.preventDefault(); + if (pressedElement.nodeName === "IMG") { + const src = pressedElement.getAttribute("src"); // TODO: may not always work + if (src) { + const doc = Docs.Create.ImageDocument(src); + ImageUtils.ExtractExif(doc); + + // add clone to div so that dragging ghost is placed properly + if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement); + + const dragData = new DragManager.DocumentDragData([doc]); + DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX, this._pressY, { hideSource: true }); + } + } + } else if (selectedText && pressedBound && pressedElement) { + e.stopPropagation(); + e.preventDefault(); + // create doc with the selected text's html + const doc = Docs.Create.HtmlDocument(pressedElement.innerHTML); + + // create dragging ghost with the selected text + if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement); + + // start the drag + const dragData = new DragManager.DocumentDragData([doc]); + DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX - pressedBound.top, this._pressY - pressedBound.top, { hideSource: true }); + } + }, 1500); + } + + onLongPressMove = (e: PointerEvent) => { + // this._pressX = e.clientX; + // this._pressY = e.clientY; + } + + onLongPressUp = (e: PointerEvent) => { + if (this._longPressSecondsHack) { + clearTimeout(this._longPressSecondsHack); + } + if (this._iframeIndicatorRef.current) { + this._iframeIndicatorRef.current.classList.remove("active"); + } + if (this._iframeDragRef.current) { + while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild); + } + } + + @computed get content() { const field = this.dataDoc[this.props.fieldKey]; @@ -171,9 +293,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> if (field instanceof HtmlField) { view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { - view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />; + view = <iframe ref={this._iframeRef} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />; } else { - view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />; + view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />; } const content = <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> @@ -181,23 +303,40 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> {view} </div>; - const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + const decInteracting = DocumentDecorations.Instance && DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const frozen = !this.props.isSelected() || decInteracting; + + const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : ""); return ( <> <div className={classname} > {content} </div> - {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />} + {!frozen ? (null) : + <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}> + <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} > + <div className="indicator" ref={this._iframeIndicatorRef}></div> + <div className="dragger" ref={this._iframeDragRef}></div> + </div> + </div>} </>); } render() { - return (<div className={"webBox-container"} > + const dragging = "";//</div>!SelectionManager.GetIsDragging() ? "" : "-dragging"; + return (<div className={`webBox-container${dragging}`} + style={{ + transform: `scale(${this.props.ContentScaling()})`, + width: `${100 / this.props.ContentScaling()}%`, + height: `${100 / this.props.ContentScaling()}%`, + pointerEvents: this.props.Document.isBackground ? "none" : undefined + }} > <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} annotationsKey={this.annotationKey} + NativeHeight={returnZero} + NativeWidth={returnZero} focus={this.props.focus} isSelected={this.props.isSelected} isAnnotationOverlay={true} diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index d23c81065..71b19f3a6 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -97,9 +97,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { else if (e.button === 0) { const annoGroup = await Cast(this.props.document.group, Doc); if (annoGroup) { - DocumentManager.Instance.FollowLink(undefined, annoGroup, - (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"), - false, false, undefined); + DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined); e.stopPropagation(); } } diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 05c70b74a..2a6eff7ff 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -61,11 +61,10 @@ export default class PDFMenu extends AntimodeMenu { e.preventDefault(); } - @action - togglePin = (e: React.MouseEvent) => { + togglePin = action((e: React.MouseEvent) => { this.Pinned = !this.Pinned; !this.Pinned && (this.Highlighting = false); - } + }); @action highlightClicked = (e: React.MouseEvent) => { diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 4f81c6f70..5cd2c4fe4 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,6 +1,5 @@ -.pdfViewer, .pdfViewer-zoomed { - pointer-events: all; +.pdfViewer, .pdfViewer-interactive { width: 100%; height: 100%; position: absolute; @@ -91,7 +90,8 @@ z-index: 10; } } -.pdfViewer-zoomed { - overflow-x: scroll; + +.pdfViewer-interactive { + pointer-events: all; }
\ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 198aeb856..f93c1fa97 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -4,12 +4,12 @@ import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import { Dictionary } from "typescript-collections"; import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Id, Copy } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils"; +import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompiledScript, CompileScript } from "../../util/Scripting"; @@ -31,6 +31,7 @@ import { InkingControl } from "../InkingControl"; import { InkTool } from "../../../new_fields/InkField"; import { TraceMobx } from "../../../new_fields/util"; import { PdfField } from "../../../new_fields/URLField"; +import { DocumentView } from "../nodes/DocumentView"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -58,6 +59,7 @@ interface IViewerProps { PanelHeight: () => number; ContentScaling: () => number; select: (isCtrlPressed: boolean) => void; + rootSelected: (outsideReaction?: boolean) => boolean; startupLive: boolean; renderDepth: number; focus: (doc: Doc) => void; @@ -101,6 +103,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument private _reactionDisposer?: IReactionDisposer; private _selectionReactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; + private _scrollTopReactionDisposer?: IReactionDisposer; private _filterReactionDisposer?: IReactionDisposer; private _searchReactionDisposer?: IReactionDisposer; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); @@ -126,7 +129,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument !this.props.Document.lockedTransform && (this.props.Document.lockedTransform = true); // change the address to be the file address of the PNG version of each page // file address of the pdf - const { url: { href } } = Cast(this.props.Document[this.props.fieldKey], PdfField)!; + const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!; const addr = Utils.prepend(`/thumbnail${this.props.url.substring("files/pdfs/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.png`); this._coverPath = href.startsWith(window.location.origin) ? JSON.parse(await rp.get(addr)) : { width: 100, height: 100, path: "" }; runInAction(() => this._showWaiting = this._showCover = true); @@ -161,11 +164,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument } componentWillUnmount = () => { - this._reactionDisposer && this._reactionDisposer(); - this._annotationReactionDisposer && this._annotationReactionDisposer(); - this._filterReactionDisposer && this._filterReactionDisposer(); - this._selectionReactionDisposer && this._selectionReactionDisposer(); - this._searchReactionDisposer && this._searchReactionDisposer(); + this._reactionDisposer?.(); + this._scrollTopReactionDisposer?.(); + this._annotationReactionDisposer?.(); + this._filterReactionDisposer?.(); + this._selectionReactionDisposer?.(); + this._searchReactionDisposer?.(); document.removeEventListener("copy", this.copy); } @@ -206,6 +210,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument this.props.setPdfViewer(this); await this.initialLoad(); + this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null), + (stop) => (stop !== undefined) && this._mainCont.current && smoothScroll(500, this._mainCont.current, stop), { fireImmediately: true }); this._annotationReactionDisposer = reaction( () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), annotations => annotations?.length && (this._annotations = annotations), @@ -227,6 +233,12 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument this.createPdfViewer(); } + pagesinit = action(() => { + this._pdfViewer.currentScaleValue = this._zoomed = 1; + this.gotoPage(this.Document.curPage || 1); + document.removeEventListener("pagesinit", this.pagesinit); + }); + createPdfViewer() { if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed if (this._retries < 5) { @@ -237,10 +249,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument } document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); - document.addEventListener("pagesinit", action(() => { - this._pdfViewer.currentScaleValue = this._zoomed = 1; - this.gotoPage(this.Document.curPage || 1); - })); + document.addEventListener("pagesinit", this.pagesinit); document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); const pdfLinkService = new PDFJSViewer.PDFLinkService(); const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService }); @@ -267,13 +276,13 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument let minY = Number.MAX_VALUE; if ((this._savedAnnotations.values()[0][0] as any).marqueeing) { const anno = this._savedAnnotations.values()[0][0]; - const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + this.Document.title }); + const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, _LODdisable: true, title: "Annotation on " + this.Document.title }); if (anno.style.left) annoDoc.x = parseInt(anno.style.left); if (anno.style.top) annoDoc.y = parseInt(anno.style.top); if (anno.style.height) annoDoc._height = parseInt(anno.style.height); if (anno.style.width) annoDoc._width = parseInt(anno.style.width); annoDoc.group = mainAnnoDoc; - annoDoc.isButton = true; + annoDoc.isLinkButton = true; annoDocs.push(annoDoc); anno.remove(); mainAnnoDoc = annoDoc; @@ -320,7 +329,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument @action gotoPage = (p: number) => { - this._pdfViewer && this._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); + this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); } @action @@ -554,32 +563,35 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument * start a drag event and create or put the necessary info into the drag event. */ @action - startDrag = (e: PointerEvent, ele: HTMLElement): void => { + startDrag = async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); + + const clipDoc = Doc.MakeAlias(this.dataDoc); + clipDoc._fitWidth = true; + clipDoc._width = this.marqueeWidth(); + clipDoc._height = this.marqueeHeight(); + clipDoc._scrollTop = this.marqueeY(); const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title }); + Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]); + DocumentView.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined); + // const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title }); + // Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy](); + // const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() }); + // Doc.GetProto(snipLayout).layout = PDFBox.LayoutString("snipped"); const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection if (annotationDoc) { 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") + dragComplete: e => { + if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) { + const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); + if (link) link.followLinkLocation = "onRight"; + } + } }); } } - createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { - const view = Doc.MakeAlias(this.props.Document); - const data = Doc.MakeDelegate(Doc.GetProto(this.props.Document)); - data.title = StrCast(data.title) + "_snippet"; - view.proto = data; - view._nativeHeight = marquee.height; - view._height = (this.Document[WidthSym]() / (this.Document._nativeWidth || 1)) * marquee.height; - view._nativeWidth = this.Document._nativeWidth; - view.startY = marquee.top; - view._width = this.Document[WidthSym](); - DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0); - } - scrollXf = () => { return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform(); } @@ -636,6 +648,9 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument setPreviewCursor={this.setPreviewCursor} PanelHeight={this.panelWidth} PanelWidth={this.panelHeight} + NativeHeight={returnZero} + NativeWidth={returnZero} + dropAction={"alias"} VisibleHeight={this.visibleHeight} focus={this.props.focus} isSelected={this.props.isSelected} @@ -643,6 +658,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument select={emptyFunction} active={this.annotationsActive} ContentScaling={this.contentZoom} + bringToFront={emptyFunction} whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} @@ -673,9 +689,10 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument contentZoom = () => this._zoomed; render() { TraceMobx(); - return <div className={"pdfViewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont} + return <div className={"pdfViewer" + (this.active() ? "-interactive" : "")} ref={this._mainCont} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ + overflowX: this._zoomed !== 1 ? "scroll" : undefined, width: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`, height: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`, transform: `scale(${this.props.ContentScaling()})` diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 8d62c34c5..289d3a9a1 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -9,7 +9,7 @@ import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; import { createSchema, makeInterface } from '../../../new_fields/Schema'; import { Cast, NumCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; import { DocExtendableComponent } from '../DocComponent'; @@ -28,14 +28,13 @@ library.add(faArrowDown); export const presSchema = createSchema({ presentationTargetDoc: Doc, presBox: Doc, - presBoxKey: "string", - showButton: "boolean", + zoomButton: "boolean", navButton: "boolean", hideTillShownButton: "boolean", fadeButton: "boolean", hideAfterButton: "boolean", groupButton: "boolean", - embedOpen: "boolean" + expandInlineButton: "boolean" }); type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>; @@ -51,13 +50,13 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD _heightDisposer: IReactionDisposer | undefined; @computed get indexInPres() { return NumCast(this.presElementDoc?.presentationIndex); } @computed get presBoxDoc() { return Cast(this.presElementDoc?.presBox, Doc) as Doc; } - @computed get presElementDoc() { return this.props.Document.expandedTemplate as Doc; } + @computed get presElementDoc() { return this.props.Document.rootDocument as Doc; } @computed get presLayoutDoc() { return this.props.Document; } @computed get targetDoc() { return this.presElementDoc?.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); } componentDidMount() { - this._heightDisposer = reaction(() => [this.presElementDoc.embedOpen, this.presElementDoc.collapsedHeight], + this._heightDisposer = reaction(() => [this.presElementDoc.expandInlineButton, this.presElementDoc.collapsedHeight], params => this.presLayoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { @@ -133,7 +132,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD e.stopPropagation(); this.presElementDoc.navButton = !this.presElementDoc.navButton; if (this.presElementDoc.navButton) { - this.presElementDoc.showButton = false; + this.presElementDoc.zoomButton = false; if (this.currentIndex === this.indexInPres) { this.props.focus(this.presElementDoc); } @@ -147,8 +146,8 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD onZoomDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.presElementDoc.showButton = !this.presElementDoc.showButton; - if (!this.presElementDoc.showButton) { + this.presElementDoc.zoomButton = !this.presElementDoc.zoomButton; + if (!this.presElementDoc.zoomButton) { this.presElementDoc.viewScale = 1; } else { this.presElementDoc.navButton = false; @@ -169,13 +168,14 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD * presentation element. */ renderEmbeddedInline = () => { - return !this.presElementDoc.embedOpen || !this.targetDoc ? (null) : + return !this.presElementDoc.expandInlineButton || !this.targetDoc ? (null) : <div className="presElementBox-embedded" style={{ height: this.embedHeight() }}> <ContentFittingDocumentView Document={this.targetDoc} DataDocument={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]} LibraryPath={emptyPath} fitToBox={true} + rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} @@ -197,7 +197,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree; const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : ""); const pbi = "presElementBox-interaction"; - return !this.presElementDoc ? (null) : ( + return !(this.presElementDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : ( <div className={className} key={this.props.Document[Id] + this.indexInPres} style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }} onClick={e => { this.props.focus(this.presElementDoc); e.stopPropagation(); }}> @@ -208,13 +208,13 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument && this.props.removeDocument(this.presElementDoc)}>X</button> <br /> </>} - <button title="Zoom" className={pbi + (this.presElementDoc.showButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button> + <button title="Zoom" className={pbi + (this.presElementDoc.zoomButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button> <button title="Navigate" className={pbi + (this.presElementDoc.navButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button> <button title="Hide Before" className={pbi + (this.presElementDoc.hideTillShownButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button> <button title="Fade After" className={pbi + (this.presElementDoc.fadeButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> <button title="Hide After" className={pbi + (this.presElementDoc.hideAfterButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button> <button title="Group With Up" className={pbi + (this.presElementDoc.groupButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.groupButton = !this.presElementDoc.groupButton; }}><FontAwesomeIcon icon={"arrow-up"} /></button> - <button title="Expand Inline" className={pbi + (this.presElementDoc.embedOpen ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.embedOpen = !this.presElementDoc.embedOpen; }}><FontAwesomeIcon icon={"arrow-down"} /></button> + <button title="Expand Inline" className={pbi + (this.presElementDoc.expandInlineButton ? "-selected" : "")} onPointerDown={e => e.stopPropagation()} onClick={e => { e.stopPropagation(); this.presElementDoc.expandInlineButton = !this.presElementDoc.expandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} /></button> <br style={{ lineHeight: 0.1 }} /> {this.renderEmbeddedInline()} diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx index a9d48219a..8c97d5dbc 100644 --- a/src/client/views/search/CheckBox.tsx +++ b/src/client/views/search/CheckBox.tsx @@ -17,8 +17,8 @@ interface CheckBoxProps { export class CheckBox extends React.Component<CheckBoxProps>{ // true = checked, false = unchecked @observable private _status: boolean; - @observable private uncheckTimeline: anime.AnimeTimelineInstance; - @observable private checkTimeline: anime.AnimeTimelineInstance; + // @observable private uncheckTimeline: anime.AnimeTimelineInstance; + // @observable private checkTimeline: anime.AnimeTimelineInstance; @observable private checkRef: any; @observable private _resetReaction?: IReactionDisposer; @@ -28,87 +28,87 @@ export class CheckBox extends React.Component<CheckBoxProps>{ this._status = this.props.originalStatus; this.checkRef = React.createRef(); - this.checkTimeline = anime.timeline({ - loop: false, - autoplay: false, - direction: "normal", - }); this.uncheckTimeline = anime.timeline({ - loop: false, - autoplay: false, - direction: "normal", - }); + // this.checkTimeline = anime.timeline({ + // loop: false, + // autoplay: false, + // direction: "normal", + // }); this.uncheckTimeline = anime.timeline({ + // loop: false, + // autoplay: false, + // direction: "normal", + // }); } - componentDidMount = () => { - this.uncheckTimeline.add({ - targets: this.checkRef.current, - easing: "easeInOutQuad", - duration: 500, - opacity: 0, - }); - this.checkTimeline.add({ - targets: this.checkRef.current, - easing: "easeInOutQuad", - duration: 500, - strokeDashoffset: [anime.setDashoffset, 0], - opacity: 1 - }); + // componentDidMount = () => { + // this.uncheckTimeline.add({ + // targets: this.checkRef.current, + // easing: "easeInOutQuad", + // duration: 500, + // opacity: 0, + // }); + // this.checkTimeline.add({ + // targets: this.checkRef.current, + // easing: "easeInOutQuad", + // duration: 500, + // strokeDashoffset: [anime.setDashoffset, 0], + // opacity: 1 + // }); - if (this.props.originalStatus) { - this.checkTimeline.play(); - this.checkTimeline.restart(); - } + // if (this.props.originalStatus) { + // this.checkTimeline.play(); + // this.checkTimeline.restart(); + // } - this._resetReaction = reaction( - () => this.props.parent._resetBoolean, - () => { - if (this.props.parent._resetBoolean) { - runInAction(() => { - this.reset(); - this.props.parent._resetCounter++; - if (this.props.parent._resetCounter === this.props.numCount) { - this.props.parent._resetCounter = 0; - this.props.parent._resetBoolean = false; - } - }); - } - }, - ); - } + // this._resetReaction = reaction( + // () => this.props.parent._resetBoolean, + // () => { + // if (this.props.parent._resetBoolean) { + // runInAction(() => { + // this.reset(); + // this.props.parent._resetCounter++; + // if (this.props.parent._resetCounter === this.props.numCount) { + // this.props.parent._resetCounter = 0; + // this.props.parent._resetBoolean = false; + // } + // }); + // } + // }, + // ); + // } - @action.bound - reset() { - if (this.props.default) { - if (!this._status) { - this._status = true; - this.checkTimeline.play(); - this.checkTimeline.restart(); - } - } - else { - if (this._status) { - this._status = false; - this.uncheckTimeline.play(); - this.uncheckTimeline.restart(); - } - } + // @action.bound + // reset() { + // if (this.props.default) { + // if (!this._status) { + // this._status = true; + // this.checkTimeline.play(); + // this.checkTimeline.restart(); + // } + // } + // else { + // if (this._status) { + // this._status = false; + // this.uncheckTimeline.play(); + // this.uncheckTimeline.restart(); + // } + // } - this.props.updateStatus(this.props.default); - } + // this.props.updateStatus(this.props.default); + // } @action.bound onClick = () => { - if (this._status) { - this.uncheckTimeline.play(); - this.uncheckTimeline.restart(); - } - else { - this.checkTimeline.play(); - this.checkTimeline.restart(); + // if (this._status) { + // this.uncheckTimeline.play(); + // this.uncheckTimeline.restart(); + // } + // else { + // this.checkTimeline.play(); + // this.checkTimeline.restart(); - } - this._status = !this._status; - this.props.updateStatus(this._status); + // } + // this._status = !this._status; + // this.props.updateStatus(this._status); } diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss index ebb39460d..094ea9cc5 100644 --- a/src/client/views/search/FilterBox.scss +++ b/src/client/views/search/FilterBox.scss @@ -4,7 +4,6 @@ .filter-form { padding: 25px; width: 440px; - background: whitesmoke; position: relative; right: 1px; color: grey; @@ -12,9 +11,7 @@ display: inline-block; transform-origin: top; overflow: auto; - border-radius: 15px; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw; - border: solid #BBBBBBBB 1px; + border-bottom: solid black 3px; .top-filter-header { @@ -124,7 +121,7 @@ max-width: 40px; flex: initial; - &.icon{ + &.icon { width: 40px; text-align: center; margin-bottom: 5px; @@ -150,7 +147,7 @@ transition: all 0.2s ease-in-out; } - &.icon:hover + .description { + &.icon:hover+.description { opacity: 1; } } diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index d4c9e67fb..662b37d77 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -33,7 +33,7 @@ export enum Keys { export class FilterBox extends React.Component { static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB]; + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied @@ -387,7 +387,7 @@ export class FilterBox extends React.Component { {/* {this.getActiveFilters()} */} </div> {this._filterOpen ? ( - <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}> + <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex", background: "black" } : { display: "none" }}> <div className="top-filter-header" style={{ display: "flex", width: "100%" }}> <div id="header">Filter Search Results</div> <div style={{ marginLeft: "auto" }}></div> diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss index 2555ad271..013dcd57e 100644 --- a/src/client/views/search/IconBar.scss +++ b/src/client/views/search/IconBar.scss @@ -2,10 +2,9 @@ .icon-bar { display: flex; + flex-wrap: wrap; justify-content: space-evenly; - align-items: center; - height: 35px; + height: auto; width: 100%; - flex-wrap: wrap; - margin-bottom: 10px; + flex-direction: row-reverse; }
\ No newline at end of file diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx index cff397407..ec942bf7c 100644 --- a/src/client/views/search/IconBar.tsx +++ b/src/client/views/search/IconBar.tsx @@ -9,7 +9,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { library } from '@fortawesome/fontawesome-svg-core'; import * as _ from "lodash"; import { IconButton } from './IconButton'; -import { FilterBox } from './FilterBox'; +import { DocumentType } from "../../documents/DocumentTypes"; + library.add(faSearch); library.add(faObjectGroup); @@ -25,6 +26,9 @@ library.add(faBan); @observer export class IconBar extends React.Component { + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; + + @observable private _icons: string[] = this._allIcons; static Instance: IconBar; @@ -33,16 +37,22 @@ export class IconBar extends React.Component { @observable public _reset: number = 0; @observable public _select: number = 0; + @action.bound + updateIcon(newArray: string[]) { this._icons = newArray; } + + @action.bound + getIcons(): string[] { return this._icons; } + constructor(props: any) { super(props); IconBar.Instance = this; } @action.bound - getList(): string[] { return FilterBox.Instance.getIcons(); } + getList(): string[] { return this.getIcons(); } @action.bound - updateList(newList: string[]) { FilterBox.Instance.updateIcon(newList); } + updateList(newList: string[]) { this.updateIcon(newList); } @action.bound resetSelf = () => { @@ -53,13 +63,13 @@ export class IconBar extends React.Component { @action.bound selectAll = () => { this._selectAllClicked = true; - this.updateList(FilterBox.Instance._allIcons); + this.updateList(this._allIcons); } render() { return ( <div className="icon-bar"> - {FilterBox.Instance._allIcons.map((type: string) => + {this._allIcons.map((type: string) => <IconButton key={type.toString()} type={type} /> )} </div> diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss index 4a3107676..4ec03c7c9 100644 --- a/src/client/views/search/IconButton.scss +++ b/src/client/views/search/IconButton.scss @@ -5,7 +5,6 @@ flex-direction: column; align-items: center; width: 30px; - height: 60px; .type-icon { height: 30px; diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx index f01508141..52641c543 100644 --- a/src/client/views/search/IconButton.tsx +++ b/src/client/views/search/IconButton.tsx @@ -11,7 +11,6 @@ import '../globalCssVariables.scss'; import * as _ from "lodash"; import { IconBar } from './IconBar'; import { props } from 'bluebird'; -import { FilterBox } from './FilterBox'; import { Search } from '../../../server/Search'; import { gravity } from 'sharp'; @@ -34,7 +33,7 @@ interface IconButtonProps { @observer export class IconButton extends React.Component<IconButtonProps>{ - @observable private _isSelected: boolean = FilterBox.Instance.getIcons().indexOf(this.props.type) !== -1; + @observable private _isSelected: boolean = IconBar.Instance.getIcons().indexOf(this.props.type) !== -1; @observable private _hover = false; private _resetReaction?: IReactionDisposer; private _selectAllReaction?: IReactionDisposer; @@ -87,15 +86,13 @@ export class IconButton extends React.Component<IconButtonProps>{ return faMusic; case (DocumentType.COL): return faObjectGroup; - case (DocumentType.HIST): - return faChartBar; case (DocumentType.IMG): return faImage; case (DocumentType.LINK): return faLink; case (DocumentType.PDF): return faFilePdf; - case (DocumentType.TEXT): + case (DocumentType.RTF): return faStickyNote; case (DocumentType.VID): return faVideo; @@ -108,7 +105,7 @@ export class IconButton extends React.Component<IconButtonProps>{ @action.bound onClick = () => { - const newList: string[] = FilterBox.Instance.getIcons(); + const newList: string[] = IconBar.Instance.getIcons(); if (!this._isSelected) { this._isSelected = true; @@ -119,21 +116,24 @@ export class IconButton extends React.Component<IconButtonProps>{ _.pull(newList, this.props.type); } - FilterBox.Instance.updateIcon(newList); + IconBar.Instance.updateIcon(newList); } selected = { opacity: 1, - backgroundColor: "rgb(128, 128, 128)" + backgroundColor: "#121721", + //backgroundColor: "rgb(128, 128, 128)" }; notSelected = { opacity: 0.2, + backgroundColor: "#121721", }; hoverStyle = { opacity: 1, - backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent + backgroundColor: "rgb(128, 128, 128)" + //backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent }; @action.bound @@ -156,15 +156,13 @@ export class IconButton extends React.Component<IconButtonProps>{ return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />); case (DocumentType.COL): return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />); - case (DocumentType.HIST): - return (<FontAwesomeIcon className="fontawesome-icon" icon={faChartBar} />); case (DocumentType.IMG): return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />); case (DocumentType.LINK): return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />); case (DocumentType.PDF): return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />); - case (DocumentType.TEXT): + case (DocumentType.RTF): return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />); case (DocumentType.VID): return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />); @@ -186,7 +184,7 @@ export class IconButton extends React.Component<IconButtonProps>{ > {this.getFA()} </div> - <div className="filter-description">{this.props.type}</div> + {/* <div className="filter-description">{this.props.type}</div> */} </div> ); } diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index f492ea773..f0223ca76 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -4,20 +4,21 @@ .searchBox-container { display: flex; flex-direction: column; - width:100%; - height:100%; + width: 100%; + height: 100%; position: absolute; font-size: 10px; line-height: 1; - overflow: hidden; + overflow: auto; + background: lightgrey, } + .searchBox-bar { height: 32px; display: flex; justify-content: flex-end; align-items: center; padding-left: 2px; - padding-right: 2px; .searchBox-barChild { @@ -33,8 +34,7 @@ -webkit-transition: width 0.4s; transition: width 0.4s; align-self: stretch; - margin-left: 2px; - margin-right: 2px + } .searchBox-input:focus { @@ -44,6 +44,10 @@ &.searchBox-filter { align-self: stretch; + button:hover{ + transform:scale(1.0); + background:"#121721"; + } } &.searchBox-submit { @@ -65,7 +69,7 @@ } .searchBox-results { - display:flex; + display: flex; flex-direction: column; top: 300px; display: flex; @@ -83,6 +87,249 @@ text-transform: uppercase; text-align: left; font-weight: bold; - margin-left: 28px; + } +} + +.filter-form { + position: relative; + background: #121721; + flex-direction: column; + transform-origin: top; + transition: height 0.3s ease, display 0.6s ease; + height:0px; + overflow:hidden; + + + .filter-header { + display: flex; + position: relative; + flex-wrap:wrap; + right: 1px; + color: grey; + flex-direction: row-reverse; + transform-origin: top; + justify-content: space-evenly; + margin-bottom: 5px; + overflow:hidden; + transition:height 0.3s ease-out; + + + + .filter-item { + position: relative; + border:1px solid grey; + border-radius: 16px; + + } + } + + .filter-body { + position: relative; + right: 1px; + color: grey; + transform-origin: top; + border-top: 0px; + //padding-top: 5px; + margin-left: 10px; + margin-right: 10px; + overflow:hidden; + transition:height 0.3s ease-out; + height:0px; + + } + .filter-key { + position: relative; + right: 1px; + color: grey; + transform-origin: top; + border-top: 0px; + //padding-top: 5px; + margin-left: 10px; + margin-right: 10px; + overflow:hidden; + transition:height 0.3s ease-out; + height:0px; + .filter-keybar { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + height: auto; + width: 100%; + flex-direction: row-reverse; + margin-top:5px; + + .filter-item { + position: relative; + border:1px solid grey; + border-radius: 16px; + + } + } + + + } +} + +// .top-filter-header { + +// #header { +// text-transform: uppercase; +// letter-spacing: 2px; +// font-size: 13; +// width: 80%; +// } + +// .close-icon { +// width: 20%; +// opacity: .6; +// position: relative; +// display: block; + +// .line { +// display: block; +// background: $alt-accent; +// width: 20; +// height: 3; +// position: absolute; +// right: 0; +// border-radius: ($height-line / 2); + +// &.line-1 { +// transform: rotate(45deg); +// top: 45%; +// } + +// &.line-2 { +// transform: rotate(-45deg); +// top: 45%; +// } +// } +// } + +// .close-icon:hover { +// opacity: 1; +// } + +// } + +// .filter-options { + +// .filter-div { +// margin-top: 10px; +// margin-bottom: 10px; +// display: inline-block; +// width: 100%; +// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent +// border-top-style: solid; + +// .filter-header { +// display: flex; +// align-items: center; +// margin-bottom: 10px; +// letter-spacing: 2px; + +// .filter-title { +// font-size: 13; +// text-transform: uppercase; +// margin-top: 10px; +// margin-bottom: 10px; +// -webkit-transition: all 0.2s ease-in-out; +// -moz-transition: all 0.2s ease-in-out; +// -o-transition: all 0.2s ease-in-out; +// transition: all 0.2s ease-in-out; +// } +// } + +// .filter-header:hover .filter-title { +// transform: scale(1.05); +// } + +// .filter-panel { +// max-height: 0px; +// width: 100%; +// overflow: hidden; +// opacity: 0; +// transform-origin: top; +// -webkit-transition: all 0.2s ease-in-out; +// -moz-transition: all 0.2s ease-in-out; +// -o-transition: all 0.2s ease-in-out; +// transition: all 0.2s ease-in-out; +// text-align: center; +// } +// } +// } + +// .filter-buttons { +// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent +// border-top-style: solid; +// padding-top: 10px; +// } + + +.active-filters { + display: flex; + flex-direction: row-reverse; + justify-content: flex-end; + width: 100%; + margin-right: 30px; + position: relative; + + .active-icon { + max-width: 40px; + flex: initial; + + &.icon { + width: 40px; + text-align: center; + margin-bottom: 5px; + position: absolute; + } + + &.container { + display: flex; + flex-direction: column; + width: 40px; + } + + &.description { + text-align: center; + top: 40px; + position: absolute; + width: 40px; + font-size: 9px; + opacity: 0; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + } + + &.icon:hover+.description { + opacity: 1; + } + } + + .col-icon { + height: 35px; + margin-left: 5px; + width: 35px; + background-color: black; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + + .save-filter, + .reset-filter, + .all-filter { + background-color: gray; + } + + .save-filter:hover, + .reset-filter:hover, + .all-filter:hover { + background-color: $darker-alt-accent; + } } }
\ No newline at end of file diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 9bd42b516..19a4d558e 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,37 +1,51 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { Cast, NumCast } from '../../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; import { Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { SearchUtil } from '../../util/SearchUtil'; -import { FilterBox } from './FilterBox'; -import "./FilterBox.scss"; import "./SearchBox.scss"; import { SearchItem } from './SearchItem'; import { IconBar } from './IconBar'; +import { FieldView } from '../nodes/FieldView'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentView } from '../nodes/DocumentView'; +import { SelectionManager } from '../../util/SelectionManager'; library.add(faTimes); +export interface SearchProps { + id: string; + searchQuery?: string; + filterQquery?: string; +} + +export enum Keys { + TITLE = "title", + AUTHOR = "author", + DATA = "data" +} + @observer -export class SearchBox extends React.Component { +export class SearchBox extends React.Component<SearchProps> { @observable private _searchString: string = ""; @observable private _resultsOpen: boolean = false; @observable private _searchbarOpen: boolean = false; @observable private _results: [Doc, string[], string[]][] = []; - private _resultsSet = new Map<Doc, number>(); @observable private _openNoResults: boolean = false; @observable private _visibleElements: JSX.Element[] = []; - private resultsRef = React.createRef<HTMLDivElement>(); + private _resultsSet = new Map<Doc, number>(); + private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); private _isSearch: ("search" | "placeholder" | undefined)[] = []; @@ -42,10 +56,18 @@ export class SearchBox extends React.Component { private _maxSearchIndex: number = 0; private _curRequest?: Promise<any> = undefined; + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } + + + //if true, any keywords can be used. if false, all keywords are required. + //this also serves as an indicator if the word status filter is applied + @observable private _basicWordStatus: boolean = false; + @observable private _nodeStatus: boolean = false; + @observable private _keyStatus: boolean = false; + constructor(props: any) { super(props); - SearchBox.Instance = this; this.resultsScrolled = this.resultsScrolled.bind(this); } @@ -53,21 +75,21 @@ export class SearchBox extends React.Component { componentDidMount = () => { if (this.inputRef.current) { this.inputRef.current.focus(); + runInAction(() => this._searchbarOpen = true); + } + if (this.props.searchQuery && this.props.filterQquery) { + console.log(this.props.searchQuery); + const sq = this.props.searchQuery; runInAction(() => { - this._searchbarOpen = true; + this._searchString = sq; + this.submitSearch(); }); } } + @action - getViews = async (doc: Doc) => { - const results = await SearchUtil.GetViewsOfDocument(doc); - let toReturn: Doc[] = []; - await runInAction(() => { - toReturn = results; - }); - return toReturn; - } + getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) @action.bound onChange(e: React.ChangeEvent<HTMLInputElement>) { @@ -106,33 +128,186 @@ export class SearchBox extends React.Component { } } + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; + //if true, any keywords can be used. if false, all keywords are required. + //this also serves as an indicator if the word status filter is applied + @observable private _filterOpen: boolean = false; + //if icons = all icons, then no icon filter is applied + @observable private _icons: string[] = this._allIcons; + //if all of these are true, no key filter is applied + @observable private _titleFieldStatus: boolean = true; + @observable private _authorFieldStatus: boolean = true; + //this also serves as an indicator if the collection status filter is applied + @observable public _deletedDocsStatus: boolean = false; + @observable private _collectionStatus = false; + + + getFinalQuery(query: string): string { + //alters the query so it looks in the correct fields + //if this is true, then not all of the field boxes are checked + //TODO: data + if (this.fieldFiltersApplied) { + query = this.applyBasicFieldFilters(query); + query = query.replace(/\s+/g, ' ').trim(); + } + + //alters the query based on if all words or any words are required + //if this._wordstatus is false, all words are required and a + is added before each + if (!this._basicWordStatus) { + query = this.basicRequireWords(query); + query = query.replace(/\s+/g, ' ').trim(); + } + + //if should be searched in a specific collection + if (this._collectionStatus) { + query = this.addCollectionFilter(query); + query = query.replace(/\s+/g, ' ').trim(); + } + return query; + } + + basicRequireWords(query: string): string { + const oldWords = query.split(" "); + const newWords: string[] = []; + oldWords.forEach(word => { + const newWrd = "+" + word; + newWords.push(newWrd); + }); + query = newWords.join(" "); + + return query; + } + + @action + filterDocsByType(docs: Doc[]) { + if (this._icons.length === this._allIcons.length) { + return docs; + } + const finalDocs: Doc[] = []; + docs.forEach(doc => { + const layoutresult = Cast(doc.type, "string"); + if (layoutresult && this._icons.includes(layoutresult)) { + finalDocs.push(doc); + } + }); + return finalDocs; + } + + addCollectionFilter(query: string): string { + const collections: Doc[] = this.getCurCollections(); + const oldWords = query.split(" "); + + const collectionString: string[] = []; + collections.forEach(doc => { + const proto = doc.proto; + const protoId = (proto || doc)[Id]; + const colString: string = "{!join from=data_l to=id}id:" + protoId + " "; + collectionString.push(colString); + }); + + let finalColString = collectionString.join(" "); + finalColString = finalColString.trim(); + return "+(" + finalColString + ")" + query; + } + + get filterTypes() { + return this._icons.length === this._allIcons.length ? undefined : this._icons; + } + + @action.bound + updateIcon(newArray: string[]) { this._icons = newArray; } + + @action.bound + getIcons(): string[] { return this._icons; } + + //TODO: basically all of this + //gets all of the collections of all the docviews that are selected + //if a collection is the only thing selected, search only in that collection (not its container) + getCurCollections(): Doc[] { + const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); + const collections: Doc[] = []; + + selectedDocs.forEach(async element => { + const layout: string = StrCast(element.props.Document.layout); + //checks if selected view (element) is a collection. if it is, adds to list to search through + if (layout.indexOf("Collection") > -1) { + //makes sure collections aren't added more than once + if (!collections.includes(element.props.Document)) { + collections.push(element.props.Document); + } + } + //makes sure collections aren't added more than once + if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { + collections.push(element.props.ContainingCollectionDoc); + } + }); + + return collections; + } + + + applyBasicFieldFilters(query: string) { + let finalQuery = ""; + + if (this._titleFieldStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); + } + if (this._authorFieldStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); + } + if (this._deletedDocsStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); + } + return finalQuery; + } + + basicFieldFilters(query: string, type: string): string { + const oldWords = query.split(" "); + let mod = ""; + + if (type === Keys.AUTHOR) { + mod = " author_t:"; + } if (type === Keys.DATA) { + //TODO + } if (type === Keys.TITLE) { + mod = " title_t:"; + } + + const newWords: string[] = []; + oldWords.forEach(word => { + const newWrd = mod + word; + newWords.push(newWrd); + }); + + query = newWords.join(" "); + + return query; + } + + get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } + + @action submitSearch = async () => { - let query = this._searchString; - query = FilterBox.Instance.getFinalQuery(query); + const query = this._searchString; + this.getFinalQuery(query); this._results = []; this._resultsSet.clear(); this._isSearch = []; this._visibleElements = []; - FilterBox.Instance.closeFilter(); - - //if there is no query there should be no result - if (query === "") { - return; - } - else { + if (query !== "") { this._endIndex = 12; this._maxSearchIndex = 0; this._numTotalResults = -1; await this.getResults(query); - } - runInAction(() => { - this._resultsOpen = true; - this._searchbarOpen = true; - this._openNoResults = true; - this.resultsScrolled(); - }); + runInAction(() => { + this._resultsOpen = true; + this._searchbarOpen = true; + this._openNoResults = true; + this.resultsScrolled(); + }); + } } getAllResults = async (query: string) => { @@ -140,11 +315,14 @@ export class SearchBox extends React.Component { } private get filterQuery() { - const types = FilterBox.Instance.filterTypes; - const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : ""); + const types = this.filterTypes; + const includeDeleted = this.getDataStatus() ? "" : " AND NOT deleted_b:true"; + const includeIcons = this.getDataStatus() ? "" : " AND NOT type_t:fonticonbox"; + return "NOT baseProto_b:true" + includeDeleted + includeIcons + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } + getDataStatus() { return this._deletedDocsStatus; } + private NumResults = 25; private lockPromise?: Promise<void>; @@ -155,7 +333,6 @@ export class SearchBox extends React.Component { this.lockPromise = new Promise(async res => { while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { - // happens at the beginning if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { this._numTotalResults = res.numFound; @@ -168,9 +345,9 @@ export class SearchBox extends React.Component { const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); const highlights: typeof res.highlighting = {}; docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); - const filteredDocs = FilterBox.Instance.filterDocsByType(docs); + const filteredDocs = this.filterDocsByType(docs); runInAction(() => { - // this._results.push(...filteredDocs); + //this._results.push(...filteredDocs); filteredDocs.forEach(doc => { const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; @@ -200,9 +377,8 @@ export class SearchBox extends React.Component { collectionRef = React.createRef<HTMLSpanElement>(); startDragCollection = async () => { - const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString)); - const filtered = FilterBox.Instance.filterDocsByType(res.docs); - // console.log(this._results) + const res = await this.getAllResults(this.getFinalQuery(this._searchString)); + const filtered = this.filterDocsByType(res.docs); const docs = filtered.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); if (isProto) { @@ -234,22 +410,19 @@ export class SearchBox extends React.Component { y += 300; } } - return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, title: `Search Docs: "${this._searchString}"` }); + return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString }); } @action.bound openSearch(e: React.SyntheticEvent) { e.stopPropagation(); this._openNoResults = false; - FilterBox.Instance.closeFilter(); this._resultsOpen = true; this._searchbarOpen = true; - FilterBox.Instance._pointerTime = e.timeStamp; } @action.bound closeSearch = () => { - FilterBox.Instance.closeFilter(); this.closeResults(); this._searchbarOpen = false; } @@ -267,11 +440,11 @@ export class SearchBox extends React.Component { @action resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { - if (!this.resultsRef.current) return; - const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0; + if (!this._resultsRef.current) return; + const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0; const itemHght = 53; const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght))); + const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); this._endIndex = endIndex === -1 ? 12 : endIndex; @@ -337,9 +510,131 @@ export class SearchBox extends React.Component { @computed get resultHeight() { return this._numTotalResults * 70; } + //if true, any keywords can be used. if false, all keywords are required. + @action.bound + handleWordQueryChange = () => { + this._basicWordStatus = !this._basicWordStatus; + } + + @action.bound + handleNodeChange = () => { + this._nodeStatus = !this._nodeStatus; + if (this._nodeStatus) { + this.expandSection(`node${this.props.id}`); + } + else { + this.collapseSection(`node${this.props.id}`); + } + } + + @action.bound + handleKeyChange = () => { + this._keyStatus = !this._keyStatus; + if (this._keyStatus) { + this.expandSection(`key${this.props.id}`); + } + else { + this.collapseSection(`key${this.props.id}`); + } + } + + @action.bound + handleFilterChange = () => { + this._filterOpen = !this._filterOpen; + if (this._filterOpen) { + this.expandSection(`filterhead${this.props.id}`); + document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5"; + } + else { + this.collapseSection(`filterhead${this.props.id}`); + + + } + } + + @computed + get menuHeight() { + return document.getElementById("hi")?.clientHeight; + } + + + collapseSection(thing: string) { + const id = this.props.id; + const element = document.getElementById(thing)!; + // get the height of the element's inner content, regardless of its actual size + const sectionHeight = element.scrollHeight; + + // temporarily disable all css transitions + const elementTransition = element.style.transition; + element.style.transition = ''; + + // on the next frame (as soon as the previous style change has taken effect), + // explicitly set the element's height to its current pixel height, so we + // aren't transitioning out of 'auto' + requestAnimationFrame(function () { + element.style.height = sectionHeight + 'px'; + element.style.transition = elementTransition; + + // on the next frame (as soon as the previous style change has taken effect), + // have the element transition to height: 0 + requestAnimationFrame(function () { + element.style.height = 0 + 'px'; + thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null; + }); + }); + + // mark the section as "currently collapsed" + element.setAttribute('data-collapsed', 'true'); + } + + expandSection(thing: string) { + console.log("expand"); + const element = document.getElementById(thing)!; + // get the height of the element's inner content, regardless of its actual size + const sectionHeight = element.scrollHeight; + + // have the element transition to the height of its inner content + element.style.height = sectionHeight + 'px'; + + // when the next css transition finishes (which should be the one we just triggered) + element.addEventListener('transitionend', function handler(e) { + // remove this event listener so it only gets triggered once + console.log("autoset"); + element.removeEventListener('transitionend', handler); + + // remove "height" from the element's inline styles, so it can return to its initial value + element.style.height = "auto"; + //element.style.height = undefined; + }); + + // mark the section as "currently not collapsed" + element.setAttribute('data-collapsed', 'false'); + + } + + autoset(thing: string) { + const element = document.getElementById(thing)!; + console.log("autoset"); + element.removeEventListener('transitionend', function (e) { }); + + // remove "height" from the element's inline styles, so it can return to its initial value + element.style.height = "auto"; + //element.style.height = undefined; + } + + @action.bound + updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; } + + @action.bound + updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; } + + @action.bound + updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; } + render() { + return ( - <div className="searchBox-container" onPointerDown={e => { e.stopPropagation(); e.preventDefault(); }}> + <div className="searchBox-container"> <div className="searchBox-bar"> <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => this._searchString ? this.startDragCollection() : undefined)} ref={this.collectionRef} title="Drag Results as Collection"> <FontAwesomeIcon icon="object-group" size="lg" /> @@ -347,16 +642,31 @@ export class SearchBox extends React.Component { <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} style={{ width: this._searchbarOpen ? "500px" : "100px" }} /> - <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> + <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> </div> - <div className="searchBox-quickFilter" onPointerDown={this.openSearch}> - <div className="filter-panel"><IconBar /></div> + + <div id={`filterhead${this.props.id}`} className="filter-form" > + <div id={`filterhead2${this.props.id}`} className="filter-header" style={this._filterOpen ? {} : {}}> + <button className="filter-item" style={this._basicWordStatus ? { background: "#aaaaa3", } : {}} onClick={this.handleWordQueryChange}>Keywords</button> + <button className="filter-item" style={this._keyStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleKeyChange}>Keys</button> + <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button> + </div> + <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> + <IconBar /> + </div> + <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> + <div className="filter-keybar"> + <button className="filter-item" style={this._titleFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateTitleStatus}>Title</button> + <button className="filter-item" style={this._deletedDocsStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateDataStatus}>Deleted Docs</button> + <button className="filter-item" style={this._authorFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateAuthorStatus}>Author</button> + </div> + </div> </div> <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ display: this._resultsOpen ? "flex" : "none", height: this.resFull ? "auto" : this.resultHeight, overflow: "visibile" // this.resFull ? "auto" : "visible" - }} ref={this.resultsRef}> + }} ref={this._resultsRef}> {this._visibleElements} </div> </div> diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 2cbb24da7..fe2000700 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; @@ -68,7 +68,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { getOnClick({ col, target }: { col: Doc, target: Doc }) { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + if (col._viewType === CollectionViewType.Freeform) { const newPanX = NumCast(target.x) + NumCast(target._width) / 2; const newPanY = NumCast(target.y) + NumCast(target._height) / 2; col._panX = newPanX; @@ -158,6 +158,7 @@ export class SearchItem extends React.Component<SearchItemProps> { <ContentFittingDocumentView Document={this.props.doc} LibraryPath={emptyPath} + rootSelected={returnFalse} fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1} addDocument={returnFalse} removeDocument={returnFalse} @@ -177,14 +178,13 @@ export class SearchItem extends React.Component<SearchItemProps> { } const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : - layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : + layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote : layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm : layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup : layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic : layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink : - layoutresult.indexOf(DocumentType.HIST) !== -1 ? faChartBar : - layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia : - faCaretUp; + layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia : + faCaretUp; return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} > <FontAwesomeIcon icon={button} size="2x" /> </div>; @@ -272,7 +272,7 @@ export class SearchItem extends React.Component<SearchItemProps> { @computed get contextButton() { - return <ParentDocSelector Views={DocumentManager.Instance.DocumentViews} Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />; + return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />; } render() { diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 9c339e986..1d52ba38f 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -36,7 +36,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV */ private onEnterKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === 13) { - let submittedTitle = this.roomText!.value; + const submittedTitle = this.roomText!.value; this.roomText!.value = ""; this.roomText!.blur(); initialize(submittedTitle, this.changeUILook); @@ -56,7 +56,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV } render() { - let content = + const content = <div className="webcam-cont" style={{ width: "100%", height: "100%" }}> <div className="webcam-header">DashWebRTC</div> <input id="roomName" type="text" placeholder="Enter room name" ref={(e) => this.roomText = e!} onKeyDown={this.onEnterKeyDown} /> @@ -72,8 +72,8 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV </div> </div >; - let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + const classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( |
