diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/DocServer.ts | 15 | ||||
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 36 | ||||
| -rw-r--r-- | src/client/util/request-image-size.ts | 26 | ||||
| -rw-r--r-- | src/client/views/ScriptingRepl.tsx | 191 | ||||
| -rw-r--r-- | src/client/views/StyleProvider.tsx | 108 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionMenu.tsx | 73 | ||||
| -rw-r--r-- | src/client/views/collections/collectionSchema/CollectionSchemaView.tsx | 181 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.scss | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 53 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 14 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 56 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/nodes_rts.ts | 19 | ||||
| -rw-r--r-- | src/client/views/webcam/DashWebRTCVideo.scss | 82 | ||||
| -rw-r--r-- | src/client/views/webcam/DashWebRTCVideo.tsx | 76 | ||||
| -rw-r--r-- | src/client/views/webcam/WebCamLogic.js | 292 |
17 files changed, 458 insertions, 770 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index b833d3287..cf7a61d24 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -8,7 +8,7 @@ import { FieldLoader } from '../fields/FieldLoader'; import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols'; import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField'; import { RefField } from '../fields/RefField'; -import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message'; +import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message'; import { SerializationHelper } from './util/SerializationHelper'; /** @@ -63,7 +63,7 @@ export namespace DocServer { return foundDocId ? (_cache[foundDocId] as Doc) : undefined; } - export let _socket: Socket; + let _socket: Socket; // this client's distinct GUID created at initialization let USER_ID: string; // indicates whether or not a document is currently being udpated, and, if so, its id @@ -317,7 +317,7 @@ export namespace DocServer { // ii) which are already in the process of being fetched // iii) which already exist in the cache // eslint-disable-next-line no-restricted-syntax - for (const id of ids.filter(id => id)) { + for (const id of ids.filter(filterid => filterid)) { const cached = _cache[id]; if (cached === undefined) { defaultPromises.push({ @@ -362,6 +362,7 @@ export namespace DocServer { for (const field of serializedFields) { processed++; if (processed % 150 === 0) { + // eslint-disable-next-line no-loop-func runInAction(() => { FieldLoader.ServerLoadStatus.retrieved = processed; }); @@ -375,6 +376,7 @@ export namespace DocServer { // deserialize // adds to a list of promises that will be awaited asynchronously promises.push( + // eslint-disable-next-line no-loop-func (_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => { // overwrite or delete any promises (that we inserted as flags // to indicate that the field was in the process of being fetched). Now everything @@ -447,7 +449,10 @@ export namespace DocServer { } // WRITE A NEW DOCUMENT TO THE SERVER - export let CacheNeedsUpdate = false; + let _cacheNeedsUpdate = false; + export function CacheNeedsUpdate() { + return _cacheNeedsUpdate; + } /** * A wrapper around the function local variable _createField. @@ -456,7 +461,7 @@ export namespace DocServer { * @param field the [RefField] to be serialized and sent to the server to be stored in the database */ export function CreateField(field: RefField) { - CacheNeedsUpdate = true; + _cacheNeedsUpdate = true; _CreateField(field); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6dba8027d..acbd0c0b9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -946,7 +946,7 @@ pie title Minerals in my tap water // eslint-disable-next-line no-new new LinkManager(); - DocServer.CacheNeedsUpdate && setTimeout(UPDATE_SERVER_CACHE, 2500); + DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500); setInterval(UPDATE_SERVER_CACHE, 120000); return doc; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9a7786125..cca92816f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -178,11 +178,13 @@ export class DocumentManager { return containerDocContext; } + static _howl: Howl; static playAudioAnno(doc: Doc) { const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement(); if (anno) { + this._howl?.stop(); if (anno instanceof AudioField) { - new Howl({ + this._howl = new Howl({ src: [anno.url.href], format: ['mp3'], autoplay: true, @@ -200,13 +202,13 @@ export class DocumentManager { static _overlayViews = new ObservableSet<DocumentView>(); public static LinkCommonAncestor(linkDoc: Doc) { - const anchor = (which: number) => { + const getAnchor = (which: number) => { const anch = DocCast(linkDoc['link_anchor_' + which]); const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch; return DocumentManager.Instance.getDocumentView(anchor); }; - const anchor1 = anchor(1); - const anchor2 = anchor(2); + const anchor1 = getAnchor(1); + const anchor2 = getAnchor(2); return anchor1 ?.docViewPath() .reverse() @@ -275,9 +277,13 @@ export class DocumentManager { if (rootContextView) { const target = await this.focusViewsInPath(rootContextView, options, childViewIterator); - this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc); - finished?.(target.focused); - } else finished?.(false); + if (target) { + this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc); + finished?.(target.focused); + return; + } + } + finished?.(false); }; focusViewsInPath = async ( @@ -289,12 +295,15 @@ export class DocumentManager { let focused = false; let docView = docViewIn; const options = optionsIn; - while (true) { + const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop + for (let i = 0; i < maxFocusLength; i++) { if (docView.Document.layout_fieldKey === 'layout_icon') { - // eslint-disable-next-line no-await-in-loop - await new Promise<any>(res => { + // eslint-disable-next-line no-loop-func + const prom = new Promise<void>(res => { docView.iconify(res); }); + // eslint-disable-next-line no-await-in-loop + await prom; options.didMove = true; } const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container @@ -305,6 +314,7 @@ export class DocumentManager { contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView; docView = childDocView; } + return undefined; }; @action @@ -347,9 +357,9 @@ export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZ const showDoc = !Doc.IsSystem(container) && !cv ? container : doc; options.toggleTarget = undefined; DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => { - const cv = DocumentManager.Instance.getDocumentView(containingDoc); - const dv = DocumentManager.Instance.getDocumentView(doc, cv); - dv && Doc.linkFollowHighlight(dv.Document); + const cvFound = DocumentManager.Instance.getDocumentView(containingDoc); + const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound); + dvFound && Doc.linkFollowHighlight(dvFound.Document); }); } }; diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts index 57e8516ac..0f98a2710 100644 --- a/src/client/util/request-image-size.ts +++ b/src/client/util/request-image-size.ts @@ -14,19 +14,17 @@ const imageSize = require('image-size'); const HttpError = require('standard-http-error'); module.exports = function requestImageSize(options: any) { - let opts = { + let opts: any = { encoding: null, }; if (options && typeof options === 'object') { opts = Object.assign(options, opts); } else if (options && typeof options === 'string') { - opts = Object.assign( - { - uri: options, - }, - opts - ); + opts = { + uri: options, + ...opts, + }; } else { return Promise.reject(new Error('You should provide an URI string or a "request" options object.')); } @@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) { req.on('response', (res: any) => { if (res.statusCode >= 400) { - return reject(new HttpError(res.statusCode, res.statusMessage)); + reject(new HttpError(res.statusCode, res.statusMessage)); + return; } let buffer = Buffer.from([]); @@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) { size = imageSize(buffer); if (size) { resolve(size); - return req.abort(); + req.abort(); } - } catch (err) {} + } catch (err) { + /* empty */ + } }); res.on('error', reject); res.on('end', () => { if (!size) { - return reject(new Error('Image has no size')); + reject(new Error('Image has no size')); + return; } size.downloaded = buffer.length; - return resolve(size); + resolve(size); }); }); diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index acf0ecff4..ba2e22b3b 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -1,3 +1,6 @@ +/* eslint-disable react/no-array-index-key */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -12,6 +15,36 @@ import { OverlayView } from './OverlayView'; import './ScriptingRepl.scss'; import { DocumentIconContainer } from './nodes/DocumentIcon'; +interface replValueProps { + scrollToBottom: () => void; + value: any; + name?: string; +} +@observer +export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + + render() { + const val = this._props.name ? this._props.value[this._props.name] : this._props.value; + const title = (name: string) => ( + <> + {this._props.name ? <b>{this._props.name} : </b> : <> </>} + {name} + </> + ); + if (typeof val === 'object') { + // eslint-disable-next-line no-use-before-define + return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />; + } + if (typeof val === 'function') { + return <div className="scriptingObject-leaf">{title('[Function]')}</div>; + } + return <div className="scriptingObject-leaf">{title(String(val))}</div>; + } +} interface ReplProps { scrollToBottom: () => void; value: { [key: string]: any }; @@ -37,7 +70,7 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps> const name = (proto && proto.constructor && proto.constructor.name) || String(val); const title = ( <> - {this.props.name ? <b>{this._props.name} : </b> : <></>} + {this.props.name ? <b>{this._props.name} : </b> : null} {name} </> ); @@ -50,53 +83,23 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps> {title} (+{Object.keys(val).length}) </div> ); - } else { - return ( - <div className="scriptingObject-open"> - <div> - <span onClick={this.toggle} className="scriptingObject-icon"> - <FontAwesomeIcon icon="caret-down" size="sm" /> - </span> - {title} - </div> - <div className="scriptingObject-fields"> - {Object.keys(val).map(key => ( - <ScriptingValueDisplay {...this._props} name={key} /> - ))} - </div> - </div> - ); } - } -} - -interface replValueProps { - scrollToBottom: () => void; - value: any; - name?: string; -} -@observer -export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> { - constructor(props: any) { - super(props); - makeObservable(this); - } - - render() { - const val = this._props.name ? this._props.value[this._props.name] : this._props.value; - const title = (name: string) => ( - <> - {this._props.name ? <b>{this._props.name} : </b> : <> </>} - {name} - </> + return ( + <div className="scriptingObject-open"> + <div> + <span onClick={this.toggle} className="scriptingObject-icon"> + <FontAwesomeIcon icon="caret-down" size="sm" /> + </span> + {title} + </div> + <div className="scriptingObject-fields"> + {Object.keys(val).map(key => ( + // eslint-disable-next-line react/jsx-props-no-spreading + <ScriptingValueDisplay {...this._props} name={key} /> + ))} + </div> + </div> ); - if (typeof val === 'object') { - return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />; - } else if (typeof val === 'function') { - const name = '[Function]'; - return <div className="scriptingObject-leaf">{title('[Function]')}</div>; - } - return <div className="scriptingObject-leaf">{title(String(val))}</div>; } } @@ -119,47 +122,45 @@ export class ScriptingRepl extends ObservableReactComponent<{}> { private args: any = {}; - getTransformer = (): Transformer => { - return { - transformer: context => { - const knownVars: { [name: string]: number } = {}; - const usedDocuments: number[] = []; - ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1)); - return root => { - function visit(node: ts.Node) { - let skip = false; - if (ts.isIdentifier(node)) { - if (ts.isParameter(node.parent)) { - skip = true; - knownVars[node.text] = 1; - } + getTransformer = (): Transformer => ({ + transformer: context => { + const knownVars: { [name: string]: number } = {}; + const usedDocuments: number[] = []; + ScriptingGlobals.getGlobals().forEach((global: any) => { + knownVars[global] = 1; + }); + return root => { + function visit(nodeIn: ts.Node) { + if (ts.isIdentifier(nodeIn)) { + if (ts.isParameter(nodeIn.parent)) { + knownVars[nodeIn.text] = 1; } - node = ts.visitEachChild(node, visit, context); + } + const node = ts.visitEachChild(nodeIn, visit, context); - if (ts.isIdentifier(node)) { - const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; - const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; - if (ts.isParameter(node.parent)) { - // delete knownVars[node.text]; - } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) { - const match = node.text.match(/d([0-9]+)/); - if (match) { - const m = parseInt(match[1]); - usedDocuments.push(m); - } else { - return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node); - // ts.createPropertyAccess(ts.createIdentifier('args'), node); - } + if (ts.isIdentifier(node)) { + const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; + const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; + if (ts.isParameter(node.parent)) { + // delete knownVars[node.text]; + } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) { + const match = node.text.match(/d([0-9]+)/); + if (match) { + const m = parseInt(match[1]); + usedDocuments.push(m); + } else { + return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node); + // ts.createPropertyAccess(ts.createIdentifier('args'), node); } } - - return node; } - return ts.visitNode(root, visit); - }; - }, - }; - }; + + return node; + } + return ts.visitNode(root, visit); + }; + }, + }); @action onKeyDown = (e: React.KeyboardEvent) => { @@ -168,14 +169,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> { case 'Enter': { e.stopPropagation(); const docGlobals: { [name: string]: any } = {}; - DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document)); + DocumentManager.Instance.DocumentViews.forEach((dv, i) => { + docGlobals[`d${i}`] = dv.Document; + }); const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals); const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals }); if (!script.compiled) { this.commands.push({ command: this.commandString, result: script.errors }); return; } - const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)(); + const result = undoable(() => script.run({ args: this.args }, () => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)(); if (result.success) { this.commands.push({ command: this.commandString, result: result.result }); this.commandsHistory.push(this.commandString); @@ -260,18 +263,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> { return ( <div className="scriptingRepl-outerContainer"> <div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}> - {this.commands.map(({ command, result }, i) => { - return ( - <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}> - <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}> - {command || <br />} - </div> - <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}> - {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />} - </div> + {this.commands.map(({ command, result }, i) => ( + <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}> + <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}> + {command || <br />} + </div> + <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}> + <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} /> </div> - ); - })} + </div> + ))} </div> <input className="scriptingRepl-commandInput" diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 75f1a7d80..3697aa010 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -49,6 +49,7 @@ export enum StyleProp { TitleHeight = 'titleHeight', // Height of Title area ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) BorderPath = 'customBorder', // border path for document view + FontColor = 'fontColor', // color o tet FontSize = 'fontSize', // size of text font FontFamily = 'fontFamily', // font family of text FontWeight = 'fontWeight', // font weight of text @@ -109,13 +110,35 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & const layoutDoc = doc ? Doc.Layout(doc) : doc; const isOpen = property.includes(':open'); const boxBackground = property.includes(':box'); - const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : ''; - const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString; + const { + fieldKey: fieldKeyProp, + styleProvider, + pointerEvents, + isGroupActive, + isDocumentActive, + containerViewPath, + childFilters, + hideCaptions, + // eslint-disable-next-line camelcase + layout_showTitle, + childFiltersByRanges, + renderDepth, + docViewPath, + DocumentView, + LayoutTemplateString, + disableBrushing, + NativeDimScaling, + PanelWidth, + PanelHeight, + } = props || {}; // extract props that are not shared between fieldView and documentView props. + const fieldKey = fieldKeyProp ? fieldKeyProp + '_' : isCaption ? 'caption_' : ''; + const isInk = () => layoutDoc?._layout_isSvg && !LayoutTemplateString; const lockedPosition = () => doc && BoolCast(doc._lockedPosition); - const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight); - const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1)); - const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); - const layoutShowTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); + const titleHeight = () => styleProvider?.(doc, props, StyleProp.TitleHeight); + const backgroundCol = () => styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1)); + const color = () => styleProvider?.(doc, props, StyleProp.Color); + const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity); + const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle); // prettier-ignore switch (property.split(':')[0]) { case StyleProp.TreeViewIcon: { @@ -137,7 +160,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & } case StyleProp.Highlighting: if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined; - if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) { + if (doc && !doc.layout_disableBrushing && !disableBrushing) { const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length; const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0); const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; @@ -152,26 +175,27 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & } } return undefined; - case StyleProp.DocContents:return undefined; - case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; - case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); - case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize)); - case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily)); - case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight)); - case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent'))); - case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption); - case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30); + case StyleProp.DocContents: return undefined; + case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; + case StyleProp.Opacity: return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); + case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color())); + case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize)); + case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily)); + case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight)); + case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent'))); + case StyleProp.ShowCaption: return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption); + case StyleProp.TitleHeight: return Math.min(4,(DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30); case StyleProp.ShowTitle: return ( (doc && - !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) && - !props?.LayoutTemplateString && + !(DocumentView?.().ComponentView instanceof CollectionSchemaView) && + !LayoutTemplateString && !doc.presentation_targetDoc && - !props?.LayoutTemplateString?.includes(KeyValueBox.name) && - props?.layout_showTitle?.() !== '' && + !LayoutTemplateString?.includes(KeyValueBox.name) && + layout_showTitle?.() !== '' && StrCast( doc._layout_showTitle, - props?.layout_showTitle?.() || + layout_showTitle?.() || (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ? doc.author === ClientUtils.CurrentUserEmail() ? StrCast(Doc.UserDoc().layout_showTitle) @@ -195,15 +219,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & } case StyleProp.BorderPath: { const borderPath = Doc.IsComicStyle(doc) && - props?.renderDepth && - !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 }; + renderDepth && + !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 }; return !borderPath ? null : { clipPath: `path('${borderPath.path}')`, jsx: ( <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}> - <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}> + <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}> <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} /> </svg> </div> @@ -251,13 +275,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & ? undefined : doc?._type_collection === CollectionViewType.Stacking ? (Colors.DARK_GRAY) - : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY)); + : Cast((renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY)); break; // if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (Colors.WHITE); } - if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) { - return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1)); + if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && styleProvider) { + return styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1)); } return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor; } @@ -271,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & doc?.layout_boxShadow, doc?._type_collection === CollectionViewType.Pile ? '4px 4px 10px 2px' - : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString + : lockedPosition() || doc?.isGroup || LayoutTemplateString ? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide) : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}` ); @@ -282,10 +306,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & default: return doc.z ? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow - : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters - ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent + : containerViewPath?.().lastElement()?.Document._freeform_useClusters + ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : NumCast(doc.group, -1) !== -1 - ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent + ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : lockedPosition() ? undefined // if it's a background & has a cluster color, make the shadow spread really big : fieldKey.includes('_inline') // if doc is an inline document in a text box @@ -296,14 +320,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & } } case StyleProp.PointerEvents: - if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc - if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all'; + if (StrCast(doc?.pointerEvents) && !LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc + if (LayoutTemplateString?.includes(KeyValueBox.name)) return 'all'; if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all'; - if (props?.pointerEvents?.() === 'none') return 'none'; + if (pointerEvents?.() === 'none') return 'none'; if (opacity() === 0) return 'none'; - if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?. + if (isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?. isGroup )? undefined: 'all' - if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all'; + if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all'; return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active case StyleProp.Decorations: { const lock = () => doc?.pointerEvents !== 'none' ? null : ( @@ -312,7 +336,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & </div> ); const paint = () => !doc?.onPaint ? null : ( - <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}> + <div className={`styleProvider-paint${DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}> <FontAwesomeIcon icon='pen' size="lg" /> </div> ); @@ -321,7 +345,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & const showFilterIcon = StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length ? 'green' // #18c718bd' //'hasFilter' - : props?.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length + : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length ? 'orange' // 'inheritsFilter' : undefined; return !showFilterIcon ? null : ( @@ -353,7 +377,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & "this view inherits filters from one of its parents"} color={SettingsManager.userColor} background={showFilterIcon} - items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])] + items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])] .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length) .map(dv => ({ text: StrCast(dv?.Document.title), @@ -365,9 +389,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & ); }; const audio = () => { - const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped); - const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length; - if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null; + const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped); + const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length; + if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null; const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' }; return ( <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 5a509128d..6dba9e155 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,3 +1,9 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/control-has-associated-label */ +/* eslint-disable react/no-unused-class-component-methods */ +/* eslint-disable react/sort-comp */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; @@ -312,8 +318,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu } private get _buttonizableCommands() { switch (this.props.type) { - default: - return this._doc_commands; case CollectionViewType.Freeform: return this._freeform_commands; case CollectionViewType.Tree: @@ -332,6 +336,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu return this._freeform_commands; case CollectionViewType.Carousel3D: return this._freeform_commands; + default: + return this._doc_commands; } } private _commandRef = React.createRef<HTMLInputElement>(); @@ -345,11 +351,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu @undoBatch viewChanged = (e: React.ChangeEvent) => { const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto); - target._type_collection = e.target.selectedOptions[0].value; + target._type_collection = (e.target as any).selectedOptions[0].value; }; commandChanged = (e: React.ChangeEvent) => { - runInAction(() => (this._currentKey = e.target.selectedOptions[0].value)); + runInAction(() => { + this._currentKey = (e.target as any).selectedOptions[0].value; + }); }; @action closeViewSpecs = () => { @@ -367,7 +375,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { - const docDragData = de.complete.docDragData; + const { docDragData } = de.complete; if (docDragData?.draggedDocuments.length) { this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || [])); e.stopPropagation(); @@ -420,11 +428,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu <div className="collectionViewBaseChrome-template" ref={this.createDropTarget}> <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}> - <button className={'antimodeMenu-button'}> + <button type="button" className="antimodeMenu-button"> <FontAwesomeIcon icon="bullseye" size="lg" /> </button> <select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} /> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key="empty" value="" /> {this._buttonizableCommands?.map(cmd => ( <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}> {cmd.title} @@ -471,23 +479,19 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi if (docs instanceof Doc) { const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_')); return keys.filter(key => key.toLowerCase().indexOf(val) > -1); - } else { - const keys = new Set<string>(); - docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - const noviceKeys = Array.from(keys).filter( - key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_') - ); - return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')); + return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } if (docs instanceof Doc) { return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); - } else { - const keys = new Set<string>(); - docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); } + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); }; @action @@ -497,9 +501,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi getSuggestionValue = (suggestion: string) => suggestion; - renderSuggestion = (suggestion: string) => { - return <p>{suggestion}</p>; - }; + renderSuggestion = (suggestion: string) => <p>{suggestion}</p>; onSuggestionFetch = async ({ value }: { value: string }) => { const sugg = await this.getKeySuggestions(value); @@ -587,12 +589,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu } componentDidMount() { - runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700)); + runInAction(() => { + this.resize = this.props.docView.props.PanelWidth() < 700; + }); // listener to reduce text on chrome resize (panel resize) this.resizeListenerDisposer = reaction( () => this.panelWidth, - newValue => (this.resize = newValue < 700) + newValue => { + this.resize = newValue < 700; + } ); } @@ -608,7 +614,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu * Sets the value of `numCols` on the grid's Document to the value entered. */ onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => { - if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))(); + if (e.currentTarget.valueAsNumber > 0) + undoBatch(() => { + this.document.gridNumCols = e.currentTarget.valueAsNumber; + })(); }; /** @@ -637,7 +646,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu onIncrementButtonClick = () => { this.clicked = true; this.entered && (this.document.gridNumCols as number)--; - undoBatch(() => (this.document.gridNumCols = this.numCols + 1))(); + undoBatch(() => { + this.document.gridNumCols = this.numCols + 1; + })(); this.entered = false; }; @@ -648,7 +659,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu this.clicked = true; if (this.numCols > 1 && !this.decrementLimitReached) { this.entered && (this.document.gridNumCols as number)++; - undoBatch(() => (this.document.gridNumCols = this.numCols - 1))(); + undoBatch(() => { + this.document.gridNumCols = this.numCols - 1; + })(); if (this.numCols === 1) this.decrementLimitReached = true; } this.entered = false; @@ -741,7 +754,13 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu <label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label> </span> - <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button> + <button + type="button" + onClick={() => { + this.document.gridResetLayout = true; + }}> + {!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />} + </button> </div> ); } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 46bf56dc8..ee79812a1 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-syntax */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Popup, PopupTrigger, Type } from 'browndash-components'; import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx'; @@ -31,6 +32,7 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; + const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export const FInfotoColType: { [key: string]: ColumnType } = { @@ -90,12 +92,11 @@ export class CollectionSchemaView extends CollectionSubView() { @computed get _selectedDocs() { const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document)); if (!selected.length) { - for (const sel of SelectionManager.Docs) { - const contextPath = DocumentManager.GetContextPath(sel, true); - if (contextPath.includes(this.Document)) { - const parentInd = contextPath.indexOf(this.Document); - return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : []; - } + // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window) + const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document)); + if (childOfSchemaDoc) { + const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true); + return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected } } return selected; @@ -158,7 +159,9 @@ export class CollectionSchemaView extends CollectionSubView() { Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author')))))); break; - case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list + case 'update': // let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list + break; + default: } }, true @@ -187,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() { if (this._selectedDocs.includes(newDoc)) { SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); } else { - this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1); + this.addDocToSelection(newDoc, e.shiftKey); this._selectedCell && (this._selectedCell[0] = newDoc); this.scrollToDoc(newDoc, {}); } @@ -206,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() { const newDoc = this.sortedDocs.docs[firstIndex - 1]; if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); else { - this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1); + this.addDocToSelection(newDoc, e.shiftKey); this._selectedCell && (this._selectedCell[0] = newDoc); this.scrollToDoc(newDoc, {}); } @@ -235,7 +238,9 @@ export class CollectionSchemaView extends CollectionSubView() { } case 'Escape': { this.deselectCell(); + break; } + default: } } }; @@ -254,7 +259,7 @@ export class CollectionSchemaView extends CollectionSubView() { this.addNewKey(newKey, defaultVal); } - let currKeys = [...this.columnKeys]; + const currKeys = [...this.columnKeys]; currKeys[index] = newKey; this.layoutDoc.schema_columnKeys = new List<string>(currKeys); }; @@ -271,13 +276,16 @@ export class CollectionSchemaView extends CollectionSubView() { const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0); this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth))); - let currKeys = this.columnKeys.slice(); + const currKeys = this.columnKeys.slice(); currKeys.splice(0, 0, key); this.layoutDoc.schema_columnKeys = new List<string>(currKeys); }; @action - addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal)); + addNewKey = (key: string, defaultVal: any) => + this.childDocs.forEach(doc => { + doc[DocData][key] = defaultVal; + }); @undoBatch removeColumn = (index: number) => { @@ -287,7 +295,7 @@ export class CollectionSchemaView extends CollectionSubView() { const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0); this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth))); - let currKeys = this.columnKeys.slice(); + const currKeys = this.columnKeys.slice(); currKeys.splice(index, 1); this.layoutDoc.schema_columnKeys = new List<string>(currKeys); }; @@ -295,7 +303,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action startResize = (e: any, index: number) => { this._displayColumnWidths = this.storedColumnWidths; - setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction); + setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction); }; @action @@ -334,11 +342,11 @@ export class CollectionSchemaView extends CollectionSubView() { @undoBatch moveColumn = (fromIndex: number, toIndex: number) => { - let currKeys = this.columnKeys.slice(); + const currKeys = this.columnKeys.slice(); currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]); this.layoutDoc.schema_columnKeys = new List<string>(currKeys); - let currWidths = this.storedColumnWidths.slice(); + const currWidths = this.storedColumnWidths.slice(); currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]); this.layoutDoc.schema_columnWidths = new List<number>(currWidths); }; @@ -352,7 +360,7 @@ export class CollectionSchemaView extends CollectionSubView() { document.removeEventListener('pointermove', this.highlightDropColumn); document.addEventListener('pointermove', this.highlightDropColumn); - let stopHighlight = (e: PointerEvent) => { + const stopHighlight = () => { document.removeEventListener('pointermove', this.highlightDropColumn); document.removeEventListener('pointerup', stopHighlight); }; @@ -405,7 +413,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => { + addDocToSelection = (doc: Doc, extendSelection: boolean) => { const rowDocView = DocumentManager.Instance.getDocumentView(doc); if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection); }; @@ -420,19 +428,25 @@ export class CollectionSchemaView extends CollectionSubView() { const endRow = Math.max(lastSelectedRow, index); for (let i = startRow; i <= endRow; i++) { const currDoc = this.sortedDocs.docs[i]; - if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i); + if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true); } }; @action - selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]); + selectCell = (doc: Doc, index: number) => { + this._selectedCell = [doc, index]; + }; @action - deselectCell = () => (this._selectedCell = undefined); + deselectCell = () => { + this._selectedCell = undefined; + }; sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc)); - setDropIndex = (index: number) => (this._closestDropIndex = index); + setDropIndex = (index: number) => { + this._closestDropIndex = index; + }; onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.columnDragData) { @@ -476,7 +490,7 @@ export class CollectionSchemaView extends CollectionSubView() { onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction); @action - onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { + onDividerMove = (e: PointerEvent) => { const nativeWidth = this._previewRef!.getBoundingClientRect(); const minWidth = 40; const maxWidth = 1000; @@ -506,37 +520,76 @@ export class CollectionSchemaView extends CollectionSubView() { const rect = found.getBoundingClientRect(); const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height); if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) { - let focusSpeed = options.zoomTime ?? 50; + const focusSpeed = options.zoomTime ?? 50; smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc); return focusSpeed; } } + return undefined; }; @computed get fieldDefaultInput() { switch (this._newFieldType) { case ColumnType.Number: - return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />; + return ( + <input + type="number" + name="" + id="" + value={this._newFieldDefault ?? 0} + onPointerDown={e => e.stopPropagation()} + onChange={action(e => { + this._newFieldDefault = e.target.value; + })} + /> + ); case ColumnType.Boolean: return ( <> - <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} /> + <input + type="checkbox" + name="" + id="" + value={this._newFieldDefault} + onPointerDown={e => e.stopPropagation()} + onChange={action(e => { + this._newFieldDefault = e.target.checked; + })} + /> {this._newFieldDefault ? 'true' : 'false'} </> ); case ColumnType.String: - return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />; + return ( + <input + type="text" + name="" + id="" + value={this._newFieldDefault ?? ''} + onPointerDown={e => e.stopPropagation()} + onChange={action(e => { + this._newFieldDefault = e.target.value; + })} + /> + ); + default: + return undefined; } } onSearchKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'Enter': - this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))(); + this._menuKeys.length > 0 && this._menuValue.length > 0 + ? this.setKey(this._menuKeys[0]) + : action(() => { + this._makeNewField = true; + })(); break; case 'Escape': this.closeColumnMenu(); break; + default: } }; @@ -568,7 +621,9 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - closeColumnMenu = () => (this._columnMenuIndex = undefined); + closeColumnMenu = () => { + this._columnMenuIndex = undefined; + }; @action openFilterMenu = (index: number) => { @@ -577,7 +632,9 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - closeFilterMenu = () => (this._filterColumnIndex = undefined); + closeFilterMenu = () => { + this._filterColumnIndex = undefined; + }; openContextMenu = (x: number, y: number, index: number) => { this.closeColumnMenu(); @@ -607,7 +664,7 @@ export class CollectionSchemaView extends CollectionSubView() { this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); }; - getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field); + getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] === field); removeFieldFilters = (field: string) => { this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove')); @@ -619,11 +676,14 @@ export class CollectionSchemaView extends CollectionSubView() { case 'Escape': this.closeFilterMenu(); break; + default: } }; @action - updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value); + updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => { + this._filterSearchValue = e.target.value; + }; @computed get newFieldMenu() { return ( @@ -632,7 +692,7 @@ export class CollectionSchemaView extends CollectionSubView() { <input type="radio" name="newFieldType" - checked={this._newFieldType == ColumnType.Number} + checked={this._newFieldType === ColumnType.Number} onChange={action(() => { this._newFieldType = ColumnType.Number; this._newFieldDefault = 0; @@ -644,7 +704,7 @@ export class CollectionSchemaView extends CollectionSubView() { <input type="radio" name="newFieldType" - checked={this._newFieldType == ColumnType.Boolean} + checked={this._newFieldType === ColumnType.Boolean} onChange={action(() => { this._newFieldType = ColumnType.Boolean; this._newFieldDefault = false; @@ -656,7 +716,7 @@ export class CollectionSchemaView extends CollectionSubView() { <input type="radio" name="newFieldType" - checked={this._newFieldType == ColumnType.String} + checked={this._newFieldType === ColumnType.String} onChange={action(() => { this._newFieldType = ColumnType.String; this._newFieldDefault = ''; @@ -668,7 +728,7 @@ export class CollectionSchemaView extends CollectionSubView() { <div className="schema-key-warning">{this._newFieldWarning}</div> <div className="schema-column-menu-button" - onPointerDown={action(e => { + onPointerDown={action(() => { if (this.documentKeys.includes(this._menuValue)) { this._newFieldWarning = 'Field already exists'; } else if (this._menuValue.length === 0) { @@ -733,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get renderColumnMenu() { - const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); + const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); return ( <div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} /> @@ -817,7 +877,7 @@ export class CollectionSchemaView extends CollectionSubView() { : [...this.childDocs].sort((docA, docB) => { const aStr = Field.toString(docA[field] as FieldType); const bStr = Field.toString(docB[field] as FieldType); - var out = 0; + let out = 0; if (aStr < bStr) out = -1; if (aStr > bStr) out = 1; if (desc) out *= -1; @@ -835,7 +895,7 @@ export class CollectionSchemaView extends CollectionSubView() { render() { return ( <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}> - <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div> + <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} /> <div className="schema-table" style={{ width: `calc(100% - ${this.previewWidth}px)` }} @@ -851,7 +911,7 @@ export class CollectionSchemaView extends CollectionSubView() { placement="right" background={SettingsManager.userBackgroundColor} color={SettingsManager.userColor} - toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />} + toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />} trigger={PopupTrigger.CLICK} type={Type.TERT} isOpen={this._columnMenuIndex !== -1 ? false : undefined} @@ -860,6 +920,7 @@ export class CollectionSchemaView extends CollectionSubView() { </div> {this.columnKeys.map((key, index) => ( <SchemaColumnHeader + // eslint-disable-next-line react/no-array-index-key key={index} columnIndex={index} columnKeys={this.columnKeys} @@ -879,28 +940,42 @@ export class CollectionSchemaView extends CollectionSubView() { </div> {this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu} {this._filterColumnIndex !== undefined && this.renderFilterMenu} - <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} /> + { + // eslint-disable-next-line no-use-before-define + <CollectionSchemaViewDocs + schema={this} + childDocs={this.sortedDocsFunc} + rowHeight={this.rowHeightFunc} + setRef={(ref: HTMLDivElement | null) => { + this._tableContentRef = ref; + }} + /> + } {this.layoutDoc.chromeHidden ? null : ( <div className="schema-add"> <EditableView GetValue={returnEmptyString} SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')} placeholder={"Type text to create note or ':' to create specific type"} - contents={'+ New Node'} + contents="+ New Node" menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} /> </div> )} </div> - {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>} + {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />} {this.previewWidth > 0 && ( - <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}> + <div + style={{ width: `${this.previewWidth}px` }} + ref={ref => { + this._previewRef = ref; + }}> {Array.from(this._selectedDocs).lastElement() && ( <DocumentView Document={Array.from(this._selectedDocs).lastElement()} fitContentsToBox={returnTrue} - dontCenter={'y'} + dontCenter="y" onClickScriptDisable="always" focus={emptyFunction} defaultDoubleClick={returnIgnore} @@ -945,7 +1020,10 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}> {this.props.childDocs().docs.map((doc: Doc, index: number) => ( <div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}> - <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} /> + { + // eslint-disable-next-line no-use-before-define + <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} /> + } </div> ))} </div> @@ -977,6 +1055,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV return ( <DocumentView key={this._props.doc[Id]} + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props.schema._props} containerViewPath={this._props.schema.childContainerViewPath} LayoutTemplate={this._props.schema._props.childLayoutTemplate} @@ -996,14 +1075,14 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV searchFilterDocs={this._props.schema.searchFilterDocs} rootSelected={this._props.schema.rootSelected} ScreenToLocalTransform={this.screenToLocalXf} - dragWhenActive={true} + dragWhenActive isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive} isContentActive={emptyFunction} whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged} - hideDecorations={true} - hideTitle={true} - hideDocumentButtonBar={true} - hideLinkAnchors={true} + hideDecorations + hideTitle + hideDocumentButtonBar + hideLinkAnchors layout_fitWidth={returnTrue} /> ); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 832e18b68..cc4b5b67f 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -21,7 +21,6 @@ import { CollectionSchemaView } from '../collections/collectionSchema/Collection import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; import { PresElementBox } from './trails/PresElementBox'; import { SearchBox } from '../search/SearchBox'; -import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { AudioBox } from './AudioBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; @@ -248,7 +247,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte PresElementBox, SearchBox, FunctionPlotBox, - DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 3a1a72910..99b4a84fc 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -349,7 +349,7 @@ footnote::before { touch-action: none; span { font-family: inherit; - background-color: inherit; + // background-color: inherit; // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others display: inline; // needs to be inline for search highlighting to appear // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 99a2f4ab9..ba37c3265 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -65,6 +65,7 @@ import { FootnoteView } from './FootnoteView'; import './FormattedTextBox.scss'; import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer'; +// eslint-disable-next-line import/extensions import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; @@ -291,7 +292,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }); }; AnchorMenu.Instance.Highlight = undoable((color: string) => { - this._editorView?.state && RichTextMenu.Instance?.setHighlight(color); + this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'); return undefined; }, 'highlght text'); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); @@ -637,8 +638,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos; pos && view.dispatch(view.state.tr.insert(pos, node)); added = !!pos; // pos will be null if you don't drop onto an actual text location - } catch (e) { - console.log('Drop failed', e); + } catch (err) { + console.log('Drop failed', err); added = false; } finally { this._inDrop = false; @@ -778,7 +779,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this, e, this.sidebarMove, - (e, movement, isClick) => !isClick && batch.end(), + (moveEv, movement, isClick) => !isClick && batch.end(), () => { this.toggleSidebar(); batch.end(); @@ -1301,7 +1302,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._disposers.selected = reaction( () => this._props.rootSelected?.(), action(selected => { - // selected && setTimeout(() => this.prepareForTyping()); + this.prepareForTyping(); if (FormattedTextBox._globalHighlights.has('Bold Text')) { // eslint-disable-next-line operator-assignment this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed @@ -1498,8 +1499,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data - } else if (curText && !FormattedTextBox.DontSelectInitialText) { - selectAll(this._editorView.state, this._editorView?.dispatch); + } else if (!FormattedTextBox.DontSelectInitialText) { + const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); + selectAll(this._editorView.state, (tx: Transaction) => { + this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark)); + }); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } else { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; @@ -1526,17 +1530,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB // 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. prepareForTyping = () => { - if (!this._editorView) return; - const docDefaultMarks = [ - ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { fontFamily: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), - ...[schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })], - ]; - this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks)); + if (this._editorView) { + const { text, paragraph } = schema.nodes; + const selNode = this._editorView.state.selection.$anchor.node(); + if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { + const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })]; + this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); + } + } }; componentWillUnmount() { @@ -1601,10 +1602,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const state = this.EditorView?.state; if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) { if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); - let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> - for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement); - while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); + let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> + for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement); + while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement; + FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true'); } }; @action @@ -1724,9 +1725,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB if (!(this.EditorView?.state.selection instanceof NodeSelection)) { this.autoLink(); if (this._editorView?.state.tr) { - const tr = stordMarks?.reduce((tr, m) => { - tr.addStoredMark(m); - return tr; + const tr = stordMarks?.reduce((tr2, m) => { + tr2.addStoredMark(m); + return tr2; }, this._editorView.state.tr); tr && this._editorView.dispatch(tr); } @@ -2038,7 +2039,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }; _oldWheel: any; @computed get fontColor() { - return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); + return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor); } @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 073ed91c3..5d448d40e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -7,7 +7,7 @@ import { liftTarget } from 'prosemirror-transform'; import { EditorView } from 'prosemirror-view'; import { ClientUtils } from '../../../../ClientUtils'; import { Utils } from '../../../../Utils'; -import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols'; +import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols'; import { GetEffectiveAcl } from '../../../../fields/util'; import { Docs } from '../../../documents/Documents'; import { RTFMarkup } from '../../../util/RTFMarkup'; @@ -48,11 +48,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa } const canEdit = (state: any) => { - switch (GetEffectiveAcl(props.TemplateDataDocument)) { + const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]); + switch (permissions) { case AclAugment: { const prevNode = state.selection.$cursor.nodeBefore; - const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid; if (prevUser !== ClientUtils.CurrentUserEmail()) { return false; } @@ -278,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa dispatch(updateBullets(tx, schema)); if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) { // gets rid of an extra paragraph when joining two list items together. - joinBackward(view.state, (tx: Transaction) => view.dispatch(tx)); + joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2)); } }) ) { @@ -344,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa !splitBlockKeepMarks(state, (tx3: Transaction) => { const tonode = tx3.selection.$to.node(); if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { - const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); + const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []); dispatch(tx4); } @@ -365,7 +366,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa // Command to create a blank space bind('Space', () => { - if (props.TemplateDataDocument && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(props.TemplateDataDocument))) return true; + const editDoc = props.TemplateDataDocument ?? props.Document[DocData]; + if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true; return false; }); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 6108383c2..6c12b9991 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -10,7 +10,6 @@ import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; -import { numberRange } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -147,12 +146,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const { activeHighlights } = active; const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc(); const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey); + const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt)); this._activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection @@ -161,12 +161,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { if (mark) { - const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item); - const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item); - const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list); - const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst); - const fromRange = numberRange(state.selection.from).reverse(); - const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from; + const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from; const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined); if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) { const hasMark = node.marks.some(m => m.type === mark.type); @@ -174,18 +169,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey])); const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]); dispatch(updateBullets(markup, state.schema)); - } else { - const state = this.view?.state; - if (state) { - const { tr } = state; - if (dontToggle) { - tr.addMark(state.selection.from, state.selection.to, mark); - dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise - } else { - toggleMark(mark.type, mark.attrs)(state, dispatch); - } + } else if (state) { + const { tr } = state; + if (dontToggle) { + tr.addMark(state.selection.from, state.selection.to, mark); + dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise + } else { + toggleMark(mark.type, mark.attrs)(state, dispatch); } } + this.updateMenu(this.view, undefined, undefined, this.layoutDoc); } }; @@ -242,7 +235,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily); m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor); m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); - m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHigh)); + m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight)); }); } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) { SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize))); @@ -359,18 +352,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { if (this.view) { - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === value))) { + const { text, paragraph } = this.view.state.schema.nodes; + const selNode = this.view.state.selection.$anchor.node(); + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value; this.view.focus(); - } else { - const attrs: { [key: string]: string } = {}; - attrs[fontField] = value; - const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs); - this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); - this.view.focus(); } - } else Doc.UserDoc()[fontField] = value; - this.updateMenu(this.view, undefined, this.props, this.layoutDoc); + const attrs: { [key: string]: string } = {}; + attrs[fontField] = value; + const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs); + this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); + this.view.focus(); + } else { + Doc.UserDoc()[fontField] = value; + this.updateMenu(this.view, undefined, this.props, this.layoutDoc); + } }; // TODO: remove doesn't work diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 184487b7d..5bf942218 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -3,6 +3,7 @@ import { listItem, orderedList } from 'prosemirror-schema-list'; import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; import { DocServer } from '../../../DocServer'; import { Doc, Field, FieldType } from '../../../../fields/Doc'; +import { schema } from './schema_rts'; const blockquoteDOM: DOMOutputSpec = ['blockquote', 0]; const hrDOM: DOMOutputSpec = ['hr']; @@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, { style: 'list-style-type=disc', - getAttrs(dom: any) { + getAttrs() { return { mapStyle: 'bullet' }; }, }, @@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = { ], toDOM(node: Node) { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; - const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight); - const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize); - const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family); - const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color); + const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight); + const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize); + const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily); + const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor); const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ''; if (node.attrs.mapStyle === 'bullet') { return [ @@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = { }, ], toDOM(node: Node) { - const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight); - const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize); - const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family); - const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color); + const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight); + const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize); + const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily); + const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor); const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; return [ 'li', diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss deleted file mode 100644 index 5744ebbcd..000000000 --- a/src/client/views/webcam/DashWebRTCVideo.scss +++ /dev/null @@ -1,82 +0,0 @@ -@import '../global/globalCssVariables.module.scss'; - -.webcam-cont { - background: whitesmoke; - color: grey; - border-radius: 15px; - box-shadow: #9c9396 0.2vw 0.2vw 0.4vw; - border: solid #bbbbbbbb 5px; - pointer-events: all; - display: flex; - flex-direction: column; - overflow: hidden; - - .webcam-header { - height: 50px; - text-align: center; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 16px; - width: 100%; - margin-top: 20px; - } - - .videoContainer { - position: relative; - width: calc(100% - 20px); - height: 100%; - /* border: 10px solid red; */ - margin-left: 10px; - } - - .buttonContainer { - display: flex; - width: calc(100% - 20px); - height: 50px; - justify-content: center; - text-align: center; - /* border: 1px solid black; */ - margin-left: 10px; - margin-top: 0; - margin-bottom: 15px; - } - - #roomName { - outline: none; - border-radius: inherit; - border: 1px solid #bbbbbbbb; - margin: 10px; - padding: 10px; - } - - .side { - width: 25%; - height: 20%; - position: absolute; - /* top: 65%; */ - z-index: 2; - right: 0px; - bottom: 18px; - } - - .main { - position: absolute; - width: 100%; - height: 100%; - /* top: 20%; */ - align-self: center; - } - - .videoButtons { - border-radius: 50%; - height: 30px; - width: 30px; - display: flex; - justify-content: center; - align-items: center; - justify-self: center; - align-self: center; - margin: 5px; - border: 1px solid black; - } -} diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx deleted file mode 100644 index 4e984f3d6..000000000 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { IconLookup } from '@fortawesome/fontawesome-svg-core'; -import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc } from '../../../fields/Doc'; -import { InkTool } from '../../../fields/InkField'; -import { SnappingManager } from '../../util/SnappingManager'; -import '../../views/nodes/WebBox.scss'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import './DashWebRTCVideo.scss'; -import { hangup, initialize, refreshVideos } from './WebCamLogic'; - -/** - * This models the component that will be rendered, that can be used as a doc that will reflect the video cams. - */ -@observer -export class DashWebRTCVideo extends React.Component<FieldViewProps> { - private roomText: HTMLInputElement | undefined; - @observable remoteVideoAdded: boolean = false; - - @action - changeUILook = () => (this.remoteVideoAdded = true); - - /** - * Function that submits the title entered by user on enter press. - */ - private onEnterKeyDown = (e: React.KeyboardEvent) => { - if (e.keyCode === 13) { - const submittedTitle = this.roomText!.value; - this.roomText!.value = ''; - this.roomText!.blur(); - initialize(submittedTitle, this.changeUILook); - } - }; - - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DashWebRTCVideo, fieldKey); - } - - onClickRefresh = () => refreshVideos(); - - onClickHangUp = () => hangup(); - - render() { - 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} /> - <div className="videoContainer"> - <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video> - <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video> - </div> - <div className="buttonContainer"> - <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}> - <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" /> - </div> - <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}> - <FontAwesomeIcon icon={faSync as IconLookup} color="white" /> - </div> - </div> - </div> - ); - - const frozen = !this.props.isSelected() || SnappingManager.IsResizing; - const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : ''); - - return ( - <> - <div className={classname}>{content}</div> - {!frozen ? null : <div className="webBox-overlay" />} - </> - ); - } -} diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js deleted file mode 100644 index 5f6202bc8..000000000 --- a/src/client/views/webcam/WebCamLogic.js +++ /dev/null @@ -1,292 +0,0 @@ -'use strict'; -import io from "socket.io-client"; - -var socket; -var isChannelReady = false; -var isInitiator = false; -var isStarted = false; -var localStream; -var pc; -var remoteStream; -var turnReady; -var room; - -export function initialize(roomName, handlerUI) { - - var pcConfig = { - 'iceServers': [{ - 'urls': 'stun:stun.l.google.com:19302' - }] - }; - - // Set up audio and video regardless of what devices are present. - var sdpConstraints = { - offerToReceiveAudio: true, - offerToReceiveVideo: true - }; - - ///////////////////////////////////////////// - - room = roomName; - - socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`); - - if (room !== '') { - socket.emit('create or join', room); - console.log('Attempted to create or join room', room); - } - - socket.on('created', function (room) { - console.log('Created room ' + room); - isInitiator = true; - }); - - socket.on('full', function (room) { - console.log('Room ' + room + ' is full'); - }); - - socket.on('join', function (room) { - console.log('Another peer made a request to join room ' + room); - console.log('This peer is the initiator of room ' + room + '!'); - isChannelReady = true; - }); - - socket.on('joined', function (room) { - console.log('joined: ' + room); - isChannelReady = true; - }); - - socket.on('log', function (array) { - console.log.apply(console, array); - }); - - //////////////////////////////////////////////// - - - // This client receives a message - socket.on('message', function (message) { - console.log('Client received message:', message); - if (message === 'got user media') { - maybeStart(); - } else if (message.type === 'offer') { - if (!isInitiator && !isStarted) { - maybeStart(); - } - pc.setRemoteDescription(new RTCSessionDescription(message)); - doAnswer(); - } else if (message.type === 'answer' && isStarted) { - pc.setRemoteDescription(new RTCSessionDescription(message)); - } else if (message.type === 'candidate' && isStarted) { - var candidate = new RTCIceCandidate({ - sdpMLineIndex: message.label, - candidate: message.candidate - }); - pc.addIceCandidate(candidate); - } else if (message === 'bye' && isStarted) { - handleRemoteHangup(); - } - }); - - //////////////////////////////////////////////////// - - var localVideo = document.querySelector('#localVideo'); - var remoteVideo = document.querySelector('#remoteVideo'); - - const gotStream = (stream) => { - console.log('Adding local stream.'); - localStream = stream; - localVideo.srcObject = stream; - sendMessage('got user media'); - if (isInitiator) { - maybeStart(); - } - } - - - navigator.mediaDevices.getUserMedia({ - audio: true, - video: true - }) - .then(gotStream) - .catch(function (e) { - alert('getUserMedia() error: ' + e.name); - }); - - - - var constraints = { - video: true - }; - - console.log('Getting user media with constraints', constraints); - - const requestTurn = (turnURL) => { - var turnExists = false; - for (var i in pcConfig.iceServers) { - if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { - turnExists = true; - turnReady = true; - break; - } - } - if (!turnExists) { - console.log('Getting TURN server from ', turnURL); - // No TURN server. Get one from computeengineondemand.appspot.com: - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4 && xhr.status === 200) { - var turnServer = JSON.parse(xhr.responseText); - console.log('Got TURN server: ', turnServer); - pcConfig.iceServers.push({ - 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn, - 'credential': turnServer.password - }); - turnReady = true; - } - }; - xhr.open('GET', turnURL, true); - xhr.send(); - } - } - - - - - if (location.hostname !== 'localhost') { - requestTurn( - `${window.location.origin}/corsProxy/${encodeURIComponent("https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913")}` - ); - } - - const maybeStart = () => { - console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); - if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { - console.log('>>>>>> creating peer connection'); - createPeerConnection(); - pc.addStream(localStream); - isStarted = true; - console.log('isInitiator', isInitiator); - if (isInitiator) { - doCall(); - } - } - }; - - window.onbeforeunload = function () { - sendMessage('bye'); - }; - - ///////////////////////////////////////////////////////// - - const createPeerConnection = () => { - try { - pc = new RTCPeerConnection(null); - pc.onicecandidate = handleIceCandidate; - pc.onaddstream = handleRemoteStreamAdded; - pc.onremovestream = handleRemoteStreamRemoved; - console.log('Created RTCPeerConnnection'); - } catch (e) { - console.log('Failed to create PeerConnection, exception: ' + e.message); - alert('Cannot create RTCPeerConnection object.'); - return; - } - } - - const handleIceCandidate = (event) => { - console.log('icecandidate event: ', event); - if (event.candidate) { - sendMessage({ - type: 'candidate', - label: event.candidate.sdpMLineIndex, - id: event.candidate.sdpMid, - candidate: event.candidate.candidate - }); - } else { - console.log('End of candidates.'); - } - } - - const handleCreateOfferError = (event) => { - console.log('createOffer() error: ', event); - } - - const doCall = () => { - console.log('Sending offer to peer'); - pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); - } - - const doAnswer = () => { - console.log('Sending answer to peer.'); - pc.createAnswer().then( - setLocalAndSendMessage, - onCreateSessionDescriptionError - ); - } - - const setLocalAndSendMessage = (sessionDescription) => { - pc.setLocalDescription(sessionDescription); - console.log('setLocalAndSendMessage sending message', sessionDescription); - sendMessage(sessionDescription); - } - - const onCreateSessionDescriptionError = (error) => { - trace('Failed to create session description: ' + error.toString()); - } - - - - const handleRemoteStreamAdded = (event) => { - console.log('Remote stream added.'); - remoteStream = event.stream; - remoteVideo.srcObject = remoteStream; - handlerUI(); - - }; - - const handleRemoteStreamRemoved = (event) => { - console.log('Remote stream removed. Event: ', event); - } -} - -export function hangup() { - console.log('Hanging up.'); - stop(); - sendMessage('bye'); - if (localStream) { - localStream.getTracks().forEach(track => track.stop()); - } -} - -function stop() { - isStarted = false; - if (pc) { - pc.close(); - } - pc = null; -} - -function handleRemoteHangup() { - console.log('Session terminated.'); - stop(); - isInitiator = false; - if (localStream) { - localStream.getTracks().forEach(track => track.stop()); - } -} - -function sendMessage(message) { - console.log('Client sending message: ', message); - socket.emit('message', message, room); -}; - -export function refreshVideos() { - var localVideo = document.querySelector('#localVideo'); - var remoteVideo = document.querySelector('#remoteVideo'); - if (localVideo) { - localVideo.srcObject = localStream; - } - if (remoteVideo) { - remoteVideo.srcObject = remoteStream; - } - -}
\ No newline at end of file |
