aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/DocumentDecorations.tsx1049
-rw-r--r--src/client/views/OverlayView.tsx2
-rw-r--r--src/client/views/collections/TabDocView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx3
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx85
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx483
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss324
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx137
8 files changed, 1004 insertions, 1081 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 29e088143..b666b5977 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -33,548 +33,549 @@ import React = require("react");
@observer
export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
- static Instance: DocumentDecorations;
- private _resizeHdlId = "";
- private _keyinput = React.createRef<HTMLInputElement>();
- private _resizeBorderWidth = 16;
- private _linkBoxHeight = 20 + 3; // link button height + margin
- private _titleHeight = 20;
- private _resizeUndo?: UndoManager.Batch;
- private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
- private _snapX = 0; _snapY = 0; // last snapped location of resize border
- private _dragHeights = new Map<Doc, { start: number, lowest: number }>();
- private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = [];
-
- @observable private _accumulatedTitle = "";
- @observable private _titleControlString: string = "#title";
- @observable private _edtingTitle = false;
- @observable private _hidden = false;
- @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
- @observable public Interacting = false;
- @observable public pushIcon: IconProp = "arrow-alt-circle-up";
- @observable public pullIcon: IconProp = "arrow-alt-circle-down";
- @observable public pullColor: string = "white";
-
- constructor(props: any) {
- super(props);
- DocumentDecorations.Instance = this;
- reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false));
- }
-
- @computed
- get Bounds() {
- const views = SelectionManager.Views();
- return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) =>
- !rect ? bounds :
- {
- x: Math.min(rect.left, bounds.x),
- y: Math.min(rect.top, bounds.y),
- r: Math.max(rect.right, bounds.r),
- b: Math.max(rect.bottom, bounds.b),
- c: views.length === 1 ? rect.center : undefined
- },
- { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
- }
-
- @action
- titleBlur = () => {
- this._edtingTitle = false;
- if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
- this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith("#")) {
- const titleFieldKey = this._titleControlString.substring(1);
- UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
- if (titleFieldKey === "title") {
- d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-");
- if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
- Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
- }
- if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) {
- Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
- }
- }
- //@ts-ignore
- const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
- Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
-
- if (d.rootDoc.syncLayoutFieldWithTitle) {
- const title = titleField.toString();
- const curKey = Doc.LayoutFieldKey(d.rootDoc);
- if (curKey !== title && d.dataDoc[title] === undefined) {
- d.rootDoc.layout = FormattedTextBox.LayoutString(title);
- setTimeout(() => {
- const val = d.dataDoc[curKey];
- d.dataDoc[curKey] = undefined;
- d.dataDoc[title] = val;
- });
- }
- }
- }), "title blur");
- }
- }
-
- titleEntered = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
- e.stopPropagation();
- (e.target as any).blur();
- }
- }
-
- @action onTitleDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
- !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
- this._edtingTitle = true;
- this._keyinput.current && setTimeout(this._keyinput.current.focus);
- }));
- }
-
- onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
-
- @action
- onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
- const dragDocView = SelectionManager.Views()[0];
- const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
- const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
- dragData.moveDocument = dragDocView.props.moveDocument;
- dragData.isDocDecorationMove = true;
- dragData.canEmbed = dragTitle;
- this._hidden = this.Interacting = true;
- DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
- dragComplete: action(e => {
- dragData.canEmbed && SelectionManager.DeselectAll();
- this._hidden = this.Interacting = false;
- }),
- hideSource: true
- });
- return true;
- }
-
- _deleteAfterIconify = false;
- _iconifyBatch: UndoManager.Batch | undefined;
- onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
- if (this.canDelete) {
+ static Instance: DocumentDecorations;
+ private _resizeHdlId = "";
+ private _keyinput = React.createRef<HTMLInputElement>();
+ private _resizeBorderWidth = 16;
+ private _linkBoxHeight = 20 + 3; // link button height + margin
+ private _titleHeight = 20;
+ private _resizeUndo?: UndoManager.Batch;
+ private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
+ private _snapX = 0; _snapY = 0; // last snapped location of resize border
+ private _dragHeights = new Map<Doc, { start: number, lowest: number }>();
+ private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = [];
+
+ @observable private _accumulatedTitle = "";
+ @observable private _titleControlString: string = "#title";
+ @observable private _edtingTitle = false;
+ @observable private _hidden = false;
+ @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
+ @observable public Interacting = false;
+ @observable public pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable public pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable public pullColor: string = "white";
+
+ constructor(props: any) {
+ super(props);
+ DocumentDecorations.Instance = this;
+ reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false));
+ }
+
+ @computed
+ get Bounds() {
+ const views = SelectionManager.Views();
+ return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) =>
+ !rect ? bounds :
+ {
+ x: Math.min(rect.left, bounds.x),
+ y: Math.min(rect.top, bounds.y),
+ r: Math.max(rect.right, bounds.r),
+ b: Math.max(rect.bottom, bounds.b),
+ c: views.length === 1 ? rect.center : undefined
+ },
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
+ }
+
+ @action
+ titleBlur = () => {
+ this._edtingTitle = false;
+ if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
+ this._titleControlString = this._accumulatedTitle;
+ } else if (this._titleControlString.startsWith("#")) {
+ const titleFieldKey = this._titleControlString.substring(1);
+ UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
+ if (titleFieldKey === "title") {
+ d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-");
+ if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
+ Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+ }
+ if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) {
+ Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+ }
+ }
+ //@ts-ignore
+ const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
+ Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+
+ if (d.rootDoc.syncLayoutFieldWithTitle) {
+ const title = titleField.toString();
+ const curKey = Doc.LayoutFieldKey(d.rootDoc);
+ if (curKey !== title && d.dataDoc[title] === undefined) {
+ d.rootDoc.layout = FormattedTextBox.LayoutString(title);
+ setTimeout(() => {
+ const val = d.dataDoc[curKey];
+ d.dataDoc[curKey] = undefined;
+ d.dataDoc[title] = val;
+ });
+ }
+ }
+ }), "title blur");
+ }
+ }
+
+ titleEntered = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.stopPropagation();
+ (e.target as any).blur();
+ }
+ }
+
+ @action onTitleDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
+ !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
+ this._edtingTitle = true;
+ this._keyinput.current && setTimeout(this._keyinput.current.focus);
+ }));
+ }
+
+ onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
+
+ @action
+ onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
+ const dragDocView = SelectionManager.Views()[0];
+ const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
+ const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
+ dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+ dragData.moveDocument = dragDocView.props.moveDocument;
+ dragData.isDocDecorationMove = true;
+ dragData.canEmbed = dragTitle;
+ this._hidden = this.Interacting = true;
+ DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
+ dragComplete: action(e => {
+ dragData.canEmbed && SelectionManager.DeselectAll();
+ this._hidden = this.Interacting = false;
+ }),
+ hideSource: true
+ });
+ return true;
+ }
+
+ _deleteAfterIconify = false;
+ _iconifyBatch: UndoManager.Batch | undefined;
+ onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
+
const views = SelectionManager.Views().slice().filter(v => v);
if (forceDeleteOrIconify === false && this._iconifyBatch) return;
this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
if (!this._iconifyBatch) {
- this._iconifyBatch = UndoManager.StartBatch("iconifying");
+ this._iconifyBatch = UndoManager.StartBatch("iconifying");
} else {
- forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
+ forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
}
var iconifyingCount = views.length;
const finished = action((force?: boolean) => {
- if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
- if (this._deleteAfterIconify) {
- views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
- SelectionManager.DeselectAll();
- }
- this._iconifyBatch?.end();
- this._iconifyBatch = undefined;
- }
+ if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
+ if (this._deleteAfterIconify) {
+ views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
+ SelectionManager.DeselectAll();
+ }
+ this._iconifyBatch?.end();
+ this._iconifyBatch = undefined;
+ }
});
if (forceDeleteOrIconify) finished(forceDeleteOrIconify);
else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
- }
- }
- onMaximizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, () => {
- DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
- return true;
- }, emptyFunction, this.onMaximizeClick, false, false);
- }
-
- onMaximizeClick = (e: any): void => {
- const selectedDocs = SelectionManager.Views();
- if (selectedDocs.length) {
- if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
- const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
- } else if (e.shiftKey) { // open centered in a new workspace with Shift Key
- const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
- alias.context = undefined;
- alias.x = -alias[WidthSym]() / 2;
- alias.y = -alias[HeightSym]() / 2;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
- } else if (e.altKey) { // open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
- } else {
- var openDoc = selectedDocs[0].props.Document;
- if (openDoc.layoutKey === "layout_icon") {
- openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
- Doc.deiconifyView(openDoc);
- }
- LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document));
- }
- }
- SelectionManager.DeselectAll();
- }
-
- onIconifyClick = (): void => {
- SelectionManager.Views().forEach(dv => dv?.iconify());
- SelectionManager.DeselectAll();
- }
-
- onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
-
- onRadiusDown = (e: React.PointerEvent): void => {
- this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
- setupMoveUpEvents(this, e, (e, down) => {
- const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
- SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
- map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`);
- return false;
- }, (e) => this._resizeUndo?.end(), (e) => { });
- }
-
- @action
- onRotateDown = (e: React.PointerEvent): void => {
- const rotateUndo = UndoManager.StartBatch("rotatedown");
- const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
- const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
- setupMoveUpEvents(this, e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- const previousPoint = { X: e.clientX, Y: e.clientY };
- const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
- const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint);
- if (selectedInk.length) {
- angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
- } else {
- SelectionManager.Views().forEach(dv => dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - angle * 180 / Math.PI);
- }
- return false;
- },
- () => {
- rotateUndo?.end();
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- },
- emptyFunction);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent): void => {
- DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
- this._inkDragDocs = DragManager.docsBeingDragged
- .filter(doc => doc.type === DocumentType.INK)
- .map(doc => {
- if (InkStrokeProperties.Instance._lock) {
- Doc.SetNativeHeight(doc, NumCast(doc._height));
- Doc.SetNativeWidth(doc, NumCast(doc._width));
- }
- return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
- });
- setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
- this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
- this._resizeHdlId = e.currentTarget.className;
- const bounds = e.currentTarget.getBoundingClientRect();
- this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
- this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
- this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
- this._snapX = e.pageX;
- this._snapY = e.pageY;
- const ffviewSet = new Set<CollectionFreeFormView>();
- SelectionManager.Views().forEach(docView => {
- const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && ffviewSet.add(ffview);
- this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
- });
- Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
- }
-
- onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
- const first = SelectionManager.Views()[0];
- let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
- var fixedAspect = Doc.NativeAspect(first.layoutDoc);
- InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
- .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
-
- const resizeHdl = this._resizeHdlId.split(" ")[0];
- if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
- const project = (p: number[], a: number[], b: number[]) => {
- const atob = [b[0] - a[0], b[1] - a[1]];
- const atop = [p[0] - a[0], p[1] - a[1]];
- const len = atob[0] * atob[0] + atob[1] * atob[1];
- let dot = atop[0] * atob[0] + atop[1] * atob[1];
- const t = dot / len;
- dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
- return [a[0] + atob[0] * t, a[1] + atob[1] * t];
- };
- const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
- thisPt = DragManager.snapDragAspect(drag, fixedAspect);
- } else {
- thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
- }
-
- move[0] = thisPt.x - this._snapX;
- move[1] = thisPt.y - this._snapY;
- this._snapX = thisPt.x;
- this._snapY = thisPt.y;
- let dragBottom = false, dragRight = false, dragBotRight = false;
- let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizeHdlId.split(" ")[0]) {
- case "": break;
- case "documentDecorations-topLeftResizer":
- dX = -1;
- dY = -1;
- dW = -move[0];
- dH = -move[1];
- break;
- case "documentDecorations-topRightResizer":
- dW = move[0];
- dY = -1;
- dH = -move[1];
- break;
- case "documentDecorations-topResizer":
- dY = -1;
- dH = -move[1];
- dragBottom = true;
- break;
- case "documentDecorations-bottomLeftResizer":
- dX = -1;
- dW = -move[0];
- dH = move[1];
- break;
- case "documentDecorations-bottomRightResizer":
- dW = move[0];
- dH = move[1];
- dragBotRight = true;
- break;
- case "documentDecorations-bottomResizer":
- dH = move[1];
- dragBottom = true;
- break;
- case "documentDecorations-leftResizer":
- dX = -1;
- dW = -move[0];
- break;
- case "documentDecorations-rightResizer":
- dW = move[0];
- dragRight = true;
- break;
- }
-
- SelectionManager.Views().forEach(action((docView: DocumentView) => {
- if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(docView.rootDoc);
- const nwidth = docView.nativeWidth;
- const nheight = docView.nativeHeight;
- const docheight = doc._height || 0;
- const docwidth = doc._width || 0;
- const width = docwidth;
- let height = (docheight || (nheight / nwidth * width));
- height = !height || isNaN(height) ? 20 : height;
- const scale = docView.props.ScreenToLocalTransform().Scale;
- const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
- if (nwidth && nheight) {
- if (nwidth / nheight !== width / height && !dragBottom) {
- height = nheight / nwidth * width;
- }
- if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
- if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
- else dW = dH * nwidth / nheight;
- }
- }
- let actualdW = Math.max(width + (dW * scale), 20);
- let actualdH = Math.max(height + (dH * scale), 20);
- const fixedAspect = (nwidth && nheight && !doc._fitWidth);
- if (fixedAspect) {
- if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
- if (dragRight && modifyNativeDim) {
- doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
- } else {
- if (!doc._fitWidth) {
- actualdH = nheight / nwidth * actualdW;
- doc._height = actualdH;
- }
- else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ }
+ onMaximizeDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, () => {
+ DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
+ return true;
+ }, emptyFunction, this.onMaximizeClick, false, false);
+ }
+
+ onMaximizeClick = (e: any): void => {
+ const selectedDocs = SelectionManager.Views();
+ if (selectedDocs.length) {
+ if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
+ const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
+ } else if (e.shiftKey) { // open centered in a new workspace with Shift Key
+ const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+ alias.context = undefined;
+ alias.x = -alias[WidthSym]() / 2;
+ alias.y = -alias[HeightSym]() / 2;
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
+ } else if (e.altKey) { // open same document in new tab
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+ } else {
+ var openDoc = selectedDocs[0].props.Document;
+ if (openDoc.layoutKey === "layout_icon") {
+ openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
+ Doc.deiconifyView(openDoc);
}
- doc._width = actualdW;
- }
- else {
- if (dragBottom && (modifyNativeDim ||
- (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
- doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
- doc._autoHeight = false;
+ LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document));
+ }
+ }
+ SelectionManager.DeselectAll();
+ }
+
+ onIconifyClick = (): void => {
+ SelectionManager.Views().forEach(dv => dv?.iconify());
+ SelectionManager.DeselectAll();
+ }
+
+ onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+
+ onRadiusDown = (e: React.PointerEvent): void => {
+ this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
+ setupMoveUpEvents(this, e, (e, down) => {
+ const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
+ SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
+ map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`);
+ return false;
+ }, (e) => this._resizeUndo?.end(), (e) => { });
+ }
+
+ @action
+ onRotateDown = (e: React.PointerEvent): void => {
+ const rotateUndo = UndoManager.StartBatch("rotatedown");
+ const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
+ const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
+ setupMoveUpEvents(this, e,
+ (e: PointerEvent, down: number[], delta: number[]) => {
+ const previousPoint = { X: e.clientX, Y: e.clientY };
+ const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
+ const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint);
+ if (selectedInk.length) {
+ angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
} else {
- if (!doc._fitWidth) {
- actualdW = nwidth / nheight * actualdH;
- doc._width = actualdW;
- }
- else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+ SelectionManager.Views().forEach(dv => dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - angle * 180 / Math.PI);
}
- if (!modifyNativeDim) {
- actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
- doc._height = actualdH;
+ return false;
+ },
+ () => {
+ rotateUndo?.end();
+ UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+ },
+ emptyFunction);
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent): void => {
+ DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
+ this._inkDragDocs = DragManager.docsBeingDragged
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => {
+ if (InkStrokeProperties.Instance._lock) {
+ Doc.SetNativeHeight(doc, NumCast(doc._height));
+ Doc.SetNativeWidth(doc, NumCast(doc._width));
}
- else doc._height = actualdH;
- }
- } else {
- dH && (doc._height = actualdH);
- dW && (doc._width = actualdW);
- dH && (doc._autoHeight = false);
- }
- doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
- doc.y = (doc.y || 0) + dY * (actualdH - docheight);
- doc._lastModified = new DateField();
- }
- const val = this._dragHeights.get(docView.layoutDoc);
- if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
- }));
- return false;
- }
-
- @action
- onPointerUp = (e: PointerEvent): void => {
- this._resizeHdlId = "";
- this.Interacting = false;
- this._resizeUndo?.end();
- SnappingManager.clearSnapLines();
-
- // detect autoHeight gesture and apply
- SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
- .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
- .forEach(pair => pair.doc._autoHeight = true);
- //need to change points for resize, or else rotation/control points will fail.
- this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
- .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
- Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
- ({
- X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
- Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
- })));
- Doc.SetNativeWidth(doc, undefined);
- Doc.SetNativeHeight(doc, undefined);
+ return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
+ });
+
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+ this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+ this._resizeHdlId = e.currentTarget.className;
+ const bounds = e.currentTarget.getBoundingClientRect();
+ this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
+ this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
+ this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
+ this._snapX = e.pageX;
+ this._snapY = e.pageY;
+ const ffviewSet = new Set<CollectionFreeFormView>();
+ SelectionManager.Views().forEach(docView => {
+ const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && ffviewSet.add(ffview);
+ this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
});
- }
-
- @computed
- get selectionTitle(): string {
- if (SelectionManager.Views().length === 1) {
- const selected = SelectionManager.Views()[0];
- if (selected.ComponentView?.getTitle?.()) {
- return selected.ComponentView.getTitle();
+ Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
+ }
+
+ onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
+ const first = SelectionManager.Views()[0];
+ let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
+ var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+ InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
+ .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
+
+ const resizeHdl = this._resizeHdlId.split(" ")[0];
+ if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+ const project = (p: number[], a: number[], b: number[]) => {
+ const atob = [b[0] - a[0], b[1] - a[1]];
+ const atop = [p[0] - a[0], p[1] - a[1]];
+ const len = atob[0] * atob[0] + atob[1] * atob[1];
+ let dot = atop[0] * atob[0] + atop[1] * atob[1];
+ const t = dot / len;
+ dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
+ return [a[0] + atob[0] * t, a[1] + atob[1] * t];
+ };
+ const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
+ thisPt = DragManager.snapDragAspect(drag, fixedAspect);
+ } else {
+ thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
}
- if (this._titleControlString.startsWith("=")) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
+
+ move[0] = thisPt.x - this._snapX;
+ move[1] = thisPt.y - this._snapY;
+ this._snapX = thisPt.x;
+ this._snapY = thisPt.y;
+ let dragBottom = false, dragRight = false, dragBotRight = false;
+ let dX = 0, dY = 0, dW = 0, dH = 0;
+ switch (this._resizeHdlId.split(" ")[0]) {
+ case "": break;
+ case "documentDecorations-topLeftResizer":
+ dX = -1;
+ dY = -1;
+ dW = -move[0];
+ dH = -move[1];
+ break;
+ case "documentDecorations-topRightResizer":
+ dW = move[0];
+ dY = -1;
+ dH = -move[1];
+ break;
+ case "documentDecorations-topResizer":
+ dY = -1;
+ dH = -move[1];
+ dragBottom = true;
+ break;
+ case "documentDecorations-bottomLeftResizer":
+ dX = -1;
+ dW = -move[0];
+ dH = move[1];
+ break;
+ case "documentDecorations-bottomRightResizer":
+ dW = move[0];
+ dH = move[1];
+ dragBotRight = true;
+ break;
+ case "documentDecorations-bottomResizer":
+ dH = move[1];
+ dragBottom = true;
+ break;
+ case "documentDecorations-leftResizer":
+ dX = -1;
+ dW = -move[0];
+ break;
+ case "documentDecorations-rightResizer":
+ dW = move[0];
+ dragRight = true;
+ break;
+ }
+
+ SelectionManager.Views().forEach(action((docView: DocumentView) => {
+ if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = Document(docView.rootDoc);
+ const nwidth = docView.nativeWidth;
+ const nheight = docView.nativeHeight;
+ const docheight = doc._height || 0;
+ const docwidth = doc._width || 0;
+ const width = docwidth;
+ let height = (docheight || (nheight / nwidth * width));
+ height = !height || isNaN(height) ? 20 : height;
+ const scale = docView.props.ScreenToLocalTransform().Scale;
+ const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
+ if (nwidth && nheight) {
+ if (nwidth / nheight !== width / height && !dragBottom) {
+ height = nheight / nwidth * width;
+ }
+ if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+ if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
+ else dW = dH * nwidth / nheight;
+ }
+ }
+ let actualdW = Math.max(width + (dW * scale), 20);
+ let actualdH = Math.max(height + (dH * scale), 20);
+ const fixedAspect = (nwidth && nheight && !doc._fitWidth);
+ if (fixedAspect) {
+ if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
+ if (dragRight && modifyNativeDim) {
+ doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
+ } else {
+ if (!doc._fitWidth) {
+ actualdH = nheight / nwidth * actualdW;
+ doc._height = actualdH;
+ }
+ else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ }
+ doc._width = actualdW;
+ }
+ else {
+ if (dragBottom && (modifyNativeDim ||
+ (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
+ doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
+ doc._autoHeight = false;
+ } else {
+ if (!doc._fitWidth) {
+ actualdW = nwidth / nheight * actualdH;
+ doc._width = actualdW;
+ }
+ else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+ }
+ if (!modifyNativeDim) {
+ actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+ doc._height = actualdH;
+ }
+ else doc._height = actualdH;
+ }
+ } else {
+ dH && (doc._height = actualdH);
+ dW && (doc._width = actualdW);
+ dH && (doc._autoHeight = false);
+ }
+ doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
+ doc.y = (doc.y || 0) + dY * (actualdH - docheight);
+ doc._lastModified = new DateField();
+ }
+ const val = this._dragHeights.get(docView.layoutDoc);
+ if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
+ }));
+ return false;
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ this._resizeHdlId = "";
+ this.Interacting = false;
+ this._resizeUndo?.end();
+ SnappingManager.clearSnapLines();
+
+ // detect autoHeight gesture and apply
+ SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
+ .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
+ .forEach(pair => pair.doc._autoHeight = true);
+ //need to change points for resize, or else rotation/control points will fail.
+ this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
+ .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
+ Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
+ ({
+ X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+ Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+ })));
+ Doc.SetNativeWidth(doc, undefined);
+ Doc.SetNativeHeight(doc, undefined);
+ });
+ }
+
+ @computed
+ get selectionTitle(): string {
+ if (SelectionManager.Views().length === 1) {
+ const selected = SelectionManager.Views()[0];
+ if (selected.ComponentView?.getTitle?.()) {
+ return selected.ComponentView.getTitle();
+ }
+ if (this._titleControlString.startsWith("=")) {
+ return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
+ }
+ if (this._titleControlString.startsWith("#")) {
+ return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
+ }
+ return this._accumulatedTitle;
}
- if (this._titleControlString.startsWith("#")) {
- return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
+ return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
+ }
+
+ @computed get hasIcons() {
+ return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon");
+ }
+
+ render() {
+ const bounds = this.Bounds;
+ const seldoc = SelectionManager.Views().slice(-1)[0];
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+ return (null);
}
- return this._accumulatedTitle;
- }
- return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
- }
-
- @computed get canDelete() {
- return SelectionManager.Views().some(docView => {
- if (docView.rootDoc.stayInCollection) return false;
- const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
- //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
- return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
- });
- }
- @computed get hasIcons() {
- return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon");
- }
-
- render() {
- const bounds = this.Bounds;
- const seldoc = SelectionManager.Views().slice(-1)[0];
- if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- return (null);
- }
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
- const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
- const canDelete = this.canDelete;
- const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
- <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
- <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
- onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
- <FontAwesomeIcon icon={icon as any} />
- </div>
- </Tooltip>);
-
- const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} key="title" /> :
- this._edtingTitle ?
- <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
- type="text" name="dynbox" autoComplete="on"
- value={this._accumulatedTitle}
- onBlur={e => this.titleBlur()}
- onChange={action(e => this._accumulatedTitle = e.target.value)}
- onKeyDown={this.titleEntered} /> :
- <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
- <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
- </div>;
-
-
- const leftBounds = this.props.boundsLeft;
- const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
- bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
- bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
- const borderRadiusDraggerWidth = 15;
- bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
- bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
-
- const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
- const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
-
- const rotation = NumCast(seldoc.rootDoc._jitterRotation);
-
- return (<div className={`documentDecorations${colorScheme}`}>
- <div className="documentDecorations-background" style={{
- transform: `rotate(${rotation}deg)`,
- transformOrigin: "top left",
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2,
- pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? "none" : "all",
- display: SelectionManager.Views().length <= 1 ? "none" : undefined
- }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} />
- {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
- <div className="documentDecorations-container" key="container" style={{
- transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
- transformOrigin: `8px 26px`,
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
- }}>
- {!canDelete ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), "Close")}
- {titleArea}
- {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
- {hideResizers ? (null) :
- <>
- <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
- <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
- <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
- onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown}
- onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div>
- </>
- }
- {seldoc?.Document.type === DocumentType.FONTICON ? (null) :
- <div className="link-button-container" key="links"
- style={{
- transform: ` translate(${- this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
- }}>
- <DocumentButtonBar views={SelectionManager.Views} />
- </div>}
- </div >
- </>}
- </div >
- );
- }
+ // hide the decorations if the parent chooses to hide it or if the document itself hides it
+ const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
+ const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
+ const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar;
+ // if multiple documents have been opened at the same time, then don't show open button
+ const hideOpenButton = seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton ||
+ SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton);
+ const hideDeleteButton = seldoc.props.hideDeleteButton || seldoc.rootDoc.hideDeleteButton ||
+ SelectionManager.Views().some(docView => {
+ const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+ return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+ });
+ const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
+ <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
+ <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
+ onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
+ <FontAwesomeIcon icon={icon as any} />
+ </div>
+ </Tooltip>);
+
+ const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
+ const titleArea = hideTitle ? (null) :
+ this._edtingTitle ?
+ <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
+ type="text" name="dynbox" autoComplete="on"
+ value={this._accumulatedTitle}
+ onBlur={e => this.titleBlur()}
+ onChange={action(e => this._accumulatedTitle = e.target.value)}
+ onKeyDown={this.titleEntered} /> :
+ <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
+ <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
+ </div>;
+
+
+ const leftBounds = this.props.boundsLeft;
+ const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
+ bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
+ bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
+ const borderRadiusDraggerWidth = 15;
+ bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
+ bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
+
+ const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
+ const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
+
+ const rotation = NumCast(seldoc.rootDoc._jitterRotation);
+
+ return (<div className={`documentDecorations${colorScheme}`}>
+ <div className="documentDecorations-background" style={{
+ transform: `rotate(${rotation}deg)`,
+ transformOrigin: "top left",
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2,
+ pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? "none" : "all",
+ display: SelectionManager.Views().length <= 1 ? "none" : undefined
+ }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} />
+ {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
+ <div className="documentDecorations-container" key="container" style={{
+ transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transformOrigin: `8px 26px`,
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
+ }}>
+ {hideDeleteButton ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), "Close")}
+ {titleArea}
+ {hideOpenButton ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
+ {hideResizers ? (null) :
+ <>
+ <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+ <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+ topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
+ <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
+ onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown}
+ onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div>
+ </>
+ }
+
+ {hideDocumentButtonBar ? (null) :
+ <div className="link-button-container" key="links"
+ style={{
+ transform: ` translate(${- this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ }}>
+ <DocumentButtonBar views={SelectionManager.Views} />
+ </div>}
+ </div >
+ </>}
+ </div >
+ );
+ }
} \ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 18abcda72..ab5dc74c9 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,3 +1,4 @@
+import { docs } from "googleapis/build/src/apis/docs";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
@@ -188,7 +189,6 @@ export class OverlayView extends React.Component {
offsetx = NumCast(d.x) - e.clientX;
offsety = NumCast(d.y) - e.clientY;
};
-
return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ top: d.type === 'presentation' ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
<DocumentView
Document={d}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 34fe572e3..11d5df197 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -546,4 +546,4 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
</div>
</div>;
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 59a402e01..41a64d6a9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -163,6 +163,9 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDocumentButtonBar?: boolean;
+ hideOpenButton?: boolean;
+ hideDeleteButton?: boolean;
treeViewDoc?: Doc;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 159271223..eac1c63f9 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -16,47 +16,46 @@ import { Id } from "../../../../fields/FieldSymbols";
@observer
export class RecordingBox extends ViewBoxBaseComponent() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); }
-
- private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-
- constructor(props: any) {
- super(props);
- }
-
- componentDidMount() {
- console.log("set native width and height")
- Doc.SetNativeWidth(this.dataDoc, 1280);
- Doc.SetNativeHeight(this.dataDoc, 720);
- }
-
- @observable result: Upload.FileInformation | undefined = undefined
- @observable videoDuration: number | undefined = undefined
-
- @action
- setVideoDuration = (duration: number) => {
- this.videoDuration = duration
- }
-
- @action
- setResult = (info: Upload.FileInformation, trackScreen: boolean) => {
- this.result = info
- this.dataDoc.type = DocumentType.VID;
- this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration;
-
- this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
- this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client);
- this.dataDoc[this.fieldKey + "-recorded"] = true;
- // stringify the presenation and store it
- if (trackScreen) {
- this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(RecordingApi.Instance.clear());
- }
- }
-
- render() {
- // console.log("Proto[Is]: ", this.rootDoc.proto?.[Id])
- return <div className="recordingBox" ref={this._ref}>
- {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id]} />}
- </div>;
- }
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); }
+
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+
+ constructor(props: any) {
+ super(props);
+ }
+
+ componentDidMount() {
+ console.log("set native width and height")
+ Doc.SetNativeWidth(this.dataDoc, 1280);
+ Doc.SetNativeHeight(this.dataDoc, 720);
+ }
+
+ @observable result: Upload.FileInformation | undefined = undefined
+ @observable videoDuration: number | undefined = undefined
+
+ @action
+ setVideoDuration = (duration: number) => {
+ this.videoDuration = duration
+ }
+
+ @action
+ setResult = (info: Upload.FileInformation, trackScreen: boolean) => {
+ this.result = info
+ this.dataDoc.type = DocumentType.VID;
+ this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration;
+
+ this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
+ this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client);
+ this.dataDoc[this.fieldKey + "-recorded"] = true;
+ // stringify the presenation and store it
+ if (trackScreen) {
+ this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(RecordingApi.Instance.clear());
+ }
+ }
+
+ render() {
+ return <div className="recordingBox" ref={this._ref}>
+ {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id]} />}
+ </div>;
+ }
}
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 87716e9cc..b95335792 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -9,269 +9,274 @@ import { Networking } from '../../../Network';
import { Upload } from '../../../../server/SharedMediaTypes';
import { RecordingApi } from '../../../util/RecordingApi';
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
interface MediaSegment {
- videoChunks: any[],
- endTime: number
+ videoChunks: any[],
+ endTime: number
}
interface IRecordingViewProps {
- setResult: (info: Upload.FileInformation, trackScreen: boolean) => void
- setDuration: (seconds: number) => void
- id: string
+ setResult: (info: Upload.FileInformation, trackScreen: boolean) => void
+ setDuration: (seconds: number) => void
+ id: string
}
const MAXTIME = 100000;
export function RecordingView(props: IRecordingViewProps) {
- const [recording, setRecording] = useState(false);
- const recordingTimerRef = useRef<number>(0);
- const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
- const [playing, setPlaying] = useState(false);
- const [progress, setProgress] = useState(0);
-
- const [videos, setVideos] = useState<MediaSegment[]>([]);
- const videoRecorder = useRef<MediaRecorder | null>(null);
- const videoElementRef = useRef<HTMLVideoElement | null>(null);
-
- const [finished, setFinished] = useState<boolean>(false)
- const [trackScreen, setTrackScreen] = useState<boolean>(true)
-
-
-
- const DEFAULT_MEDIA_CONSTRAINTS = {
- video: {
- width: 1280,
- height: 720,
- },
- audio: {
- echoCancellation: true,
- noiseSuppression: true,
- sampleRate: 44100
- }
- }
-
- useEffect(() => {
-
- if (finished) {
- props.setDuration(recordingTimer * 100)
- let allVideoChunks: any = []
- videos.forEach((vid) => {
- console.log(vid.videoChunks)
- allVideoChunks = allVideoChunks.concat(vid.videoChunks)
- })
-
- const videoFile = new File(allVideoChunks, "video.mkv", { type: allVideoChunks[0].type, lastModified: Date.now() });
-
- Networking.UploadFilesToServer(videoFile)
- .then((data) => {
- const result = data[0].result
- if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox
- props.setResult(result, trackScreen)
- } else {
- alert("video conversion failed");
- }
- })
-
- }
-
-
- }, [finished])
-
- useEffect(() => {
- // check if the browser supports media devices on first load
- if (!navigator.mediaDevices) {
- console.log('This browser does not support getUserMedia.')
- }
- console.log('This device has the correct media devices.')
- }, [])
-
- useEffect(() => {
- // get access to the video element on every render
- videoElementRef.current = document.getElementById(`video-${props.id}`) as HTMLVideoElement;
- })
-
- useEffect(() => {
- let interval: any = null;
- if (recording) {
- interval = setInterval(() => {
- setRecordingTimer(unit => unit + 1);
- }, 10);
- } else if (!recording && recordingTimer !== 0) {
- clearInterval(interval);
- }
- return () => clearInterval(interval);
- }, [recording])
-
- useEffect(() => {
- setVideoProgressHelper(recordingTimer)
- recordingTimerRef.current = recordingTimer;
- }, [recordingTimer])
-
- const setVideoProgressHelper = (progress: number) => {
- const newProgress = (progress / MAXTIME) * 100;
- setProgress(newProgress)
- }
- const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
- const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints)
-
- videoElementRef.current!.src = ""
- videoElementRef.current!.srcObject = stream
- videoElementRef.current!.muted = true
-
- return stream
- }
-
- const record = async () => {
- const stream = await startShowingStream();
- videoRecorder.current = new MediaRecorder(stream)
-
- // temporary chunks of video
- let videoChunks: any = []
-
- videoRecorder.current.ondataavailable = (event: any) => {
- if (event.data.size > 0) {
- videoChunks.push(event.data)
- }
- }
-
- videoRecorder.current.onstart = (event: any) => {
- setRecording(true);
- trackScreen && RecordingApi.Instance.start();
- }
-
- videoRecorder.current.onstop = () => {
- // if we have a last portion
- if (videoChunks.length > 1) {
- // append the current portion to the video pieces
- setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current }])
- }
-
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
- setFinished(true);
- trackScreen && RecordingApi.Instance.pause();
+ const [recording, setRecording] = useState(false);
+ const recordingTimerRef = useRef<number>(0);
+ const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
+ const [playing, setPlaying] = useState(false);
+ const [progress, setProgress] = useState(0);
+
+ const [videos, setVideos] = useState<MediaSegment[]>([]);
+ const videoRecorder = useRef<MediaRecorder | null>(null);
+ const videoElementRef = useRef<HTMLVideoElement | null>(null);
+
+ const [finished, setFinished] = useState<boolean>(false)
+ const [trackScreen, setTrackScreen] = useState<boolean>(true)
+
+
+
+ const DEFAULT_MEDIA_CONSTRAINTS = {
+ video: {
+ width: 1280,
+ height: 720,
+ },
+ audio: {
+ echoCancellation: true,
+ noiseSuppression: true,
+ sampleRate: 44100
+ }
+ }
+
+ useEffect(() => {
+
+ if (finished) {
+ props.setDuration(recordingTimer * 100)
+ let allVideoChunks: any = []
+ videos.forEach((vid) => {
+ console.log(vid.videoChunks)
+ allVideoChunks = allVideoChunks.concat(vid.videoChunks)
+ })
+
+ const videoFile = new File(allVideoChunks, "video.mkv", { type: allVideoChunks[0].type, lastModified: Date.now() });
+
+ Networking.UploadFilesToServer(videoFile)
+ .then((data) => {
+ const result = data[0].result
+ if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox
+ props.setResult(result, trackScreen)
+ } else {
+ alert("video conversion failed");
+ }
+ })
+
+ }
+
+
+ }, [finished])
+
+ useEffect(() => {
+ // check if the browser supports media devices on first load
+ if (!navigator.mediaDevices) {
+ console.log('This browser does not support getUserMedia.')
+ }
+ console.log('This device has the correct media devices.')
+ }, [])
+
+ useEffect(() => {
+ // get access to the video element on every render
+ videoElementRef.current = document.getElementById(`video-${props.id}`) as HTMLVideoElement;
+ })
+
+ useEffect(() => {
+ let interval: any = null;
+ if (recording) {
+ interval = setInterval(() => {
+ setRecordingTimer(unit => unit + 1);
+ }, 10);
+ } else if (!recording && recordingTimer !== 0) {
+ clearInterval(interval);
+ }
+ return () => clearInterval(interval);
+ }, [recording])
+
+ useEffect(() => {
+ setVideoProgressHelper(recordingTimer)
+ recordingTimerRef.current = recordingTimer;
+ }, [recordingTimer])
+
+ const setVideoProgressHelper = (progress: number) => {
+ const newProgress = (progress / MAXTIME) * 100;
+ setProgress(newProgress)
+ }
+ const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
+ const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints)
+
+ videoElementRef.current!.src = ""
+ videoElementRef.current!.srcObject = stream
+ videoElementRef.current!.muted = true
+
+ return stream
+ }
+
+ const record = async () => {
+ const stream = await startShowingStream();
+ videoRecorder.current = new MediaRecorder(stream)
+
+ // temporary chunks of video
+ let videoChunks: any = []
+
+ videoRecorder.current.ondataavailable = (event: any) => {
+ if (event.data.size > 0) {
+ videoChunks.push(event.data)
}
-
- // recording paused
- videoRecorder.current.onpause = (event: any) => {
- // append the current portion to the video pieces
- setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current }])
-
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
- trackScreen && RecordingApi.Instance.pause();
- }
-
- videoRecorder.current.onresume = async (event: any) => {
- await startShowingStream();
- setRecording(true);
- trackScreen && RecordingApi.Instance.resume();
+ }
+
+ videoRecorder.current.onstart = (event: any) => {
+ setRecording(true);
+ trackScreen && RecordingApi.Instance.start();
+ }
+
+ videoRecorder.current.onstop = () => {
+ // if we have a last portion
+ if (videoChunks.length > 1) {
+ // append the current portion to the video pieces
+ setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current }])
}
- videoRecorder.current.start(200)
- }
-
-
- const stop = () => {
- if (videoRecorder.current) {
- if (videoRecorder.current.state !== "inactive") {
- videoRecorder.current.stop();
- // recorder.current.stream.getTracks().forEach((track: any) => track.stop())
- }
+ // reset the temporary chunks
+ videoChunks = []
+ setRecording(false);
+ setFinished(true);
+ trackScreen && RecordingApi.Instance.pause();
+ }
+
+ // recording paused
+ videoRecorder.current.onpause = (event: any) => {
+ // append the current portion to the video pieces
+ setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current }])
+
+ // reset the temporary chunks
+ videoChunks = []
+ setRecording(false);
+ trackScreen && RecordingApi.Instance.pause();
+ }
+
+ videoRecorder.current.onresume = async (event: any) => {
+ await startShowingStream();
+ setRecording(true);
+ trackScreen && RecordingApi.Instance.resume();
+ }
+
+ videoRecorder.current.start(200)
+ }
+
+
+ const stop = () => {
+ if (videoRecorder.current) {
+ if (videoRecorder.current.state !== "inactive") {
+ videoRecorder.current.stop();
+ // recorder.current.stream.getTracks().forEach((track: any) => track.stop())
}
- }
+ }
+ }
- const pause = () => {
- if (videoRecorder.current) {
- if (videoRecorder.current.state === "recording") {
- videoRecorder.current.pause();
- }
+ const pause = () => {
+ if (videoRecorder.current) {
+ if (videoRecorder.current.state === "recording") {
+ videoRecorder.current.pause();
}
- }
+ }
+ }
- const startOrResume = () => {
+ const startOrResume = (e: React.PointerEvent) => {
+ // the code to start or resume does not get triggered if we start dragging the button
+ setupMoveUpEvents({}, e, returnTrue, returnFalse, e => {
if (!videoRecorder.current || videoRecorder.current.state === "inactive") {
- record();
+ record();
} else if (videoRecorder.current.state === "paused") {
- videoRecorder.current.resume();
+ videoRecorder.current.resume();
}
- }
-
- const clearPrevious = () => {
- const numVideos = videos.length
- setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
- setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
- setVideos(videos.filter((_, idx) => idx !== numVideos - 1));
- }
-
- const handleOnTimeUpdate = () => {
- if (playing) {
- setVideoProgressHelper(videoElementRef.current!.currentTime)
- }
- };
-
- const millisecondToMinuteSecond = (milliseconds: number) => {
- const toTwoDigit = (digit: number) => {
- return String(digit).length == 1 ? "0" + digit : digit
- }
- const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
- const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
- return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
- }
-
- return (
- <div className="recording-container">
- <div className="video-wrapper">
- <video id={`video-${props.id}`}
- autoPlay
- muted
- onTimeUpdate={handleOnTimeUpdate}
- />
- <div className="recording-sign">
- <span className="dot" />
- <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
+ return true; // cancels propagation to documentView to avoid selecting it.
+ }, false, false);
+ }
+
+ const clearPrevious = () => {
+ const numVideos = videos.length
+ setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
+ setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
+ setVideos(videos.filter((_, idx) => idx !== numVideos - 1));
+ }
+
+ const handleOnTimeUpdate = () => {
+ if (playing) {
+ setVideoProgressHelper(videoElementRef.current!.currentTime)
+ }
+ };
+
+ const millisecondToMinuteSecond = (milliseconds: number) => {
+ const toTwoDigit = (digit: number) => {
+ return String(digit).length == 1 ? "0" + digit : digit
+ }
+ const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
+ return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
+ }
+
+ return (
+ <div className="recording-container">
+ <div className="video-wrapper">
+ <video id={`video-${props.id}`}
+ autoPlay
+ muted
+ onTimeUpdate={handleOnTimeUpdate}
+ />
+ <div className="recording-sign">
+ <span className="dot" />
+ <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
+ </div>
+ <div className="controls">
+
+ <div className="controls-inner-container">
+ <div className="record-button-wrapper">
+ {recording ?
+ <button className="stop-button" onPointerDown={pause} /> :
+ <button className="record-button" onPointerDown={startOrResume} />
+ }
</div>
- <div className="controls">
-
- <div className="controls-inner-container">
- <div className="record-button-wrapper">
- {recording ?
- <button className="stop-button" onClick={pause} /> :
- <button className="record-button" onClick={startOrResume} />
- }
- </div>
- {!recording && (videos.length > 0 ?
+ {!recording && (videos.length > 0 ?
- <div className="options-wrapper video-edit-wrapper">
- {/* <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}>
+ <div className="options-wrapper video-edit-wrapper">
+ {/* <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}>
<MdBackspace onClick={clearPrevious} />
</IconContext.Provider> */}
- <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
- <FaCheckCircle onClick={stop} />
- </IconContext.Provider>
- </div>
-
- : <div className="options-wrapper track-screen-wrapper">
- <label className="track-screen">
- <input type="checkbox" checked={trackScreen} onChange={(e) => { setTrackScreen(e.target.checked) }} />
- <span className="checkmark"></span>
- Track Screen
- </label>
- </div>)}
-
- </div>
-
- <ProgressBar
- progress={progress}
- marks={videos.map((elt) => elt.endTime / MAXTIME * 100)}
- // playSegment={playSegment}
- />
- </div>
- </div>
- </div>)
+ <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
+ <FaCheckCircle onClick={stop} />
+ </IconContext.Provider>
+ </div>
+
+ : <div className="options-wrapper track-screen-wrapper">
+ <label className="track-screen">
+ <input type="checkbox" checked={trackScreen} onChange={(e) => { setTrackScreen(e.target.checked) }} />
+ <span className="checkmark"></span>
+ Track Screen
+ </label>
+ </div>)}
+
+ </div>
+
+ <ProgressBar
+ progress={progress}
+ marks={videos.map((elt) => elt.endTime / MAXTIME * 100)}
+ // playSegment={playSegment}
+ />
+ </div>
+ </div>
+ </div>)
} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 19644dd78..969f034a8 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -5,155 +5,157 @@ $slide-background: #d5dce2;
$slide-active: #5B9FDD;
.presItem-container {
- cursor: grab;
- display: grid;
- grid-template-columns: 20px auto;
- font-family: Roboto;
- letter-spacing: normal;
- position: relative;
- pointer-events: all;
- width: 100%;
- height: 100%;
- font-weight: 400;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- align-items: center;
+ cursor: grab;
+ display: flex;
+ grid-template-columns: 20px auto;
+ font-family: Roboto;
+ letter-spacing: normal;
+ position: relative;
+ pointer-events: all;
+ width: 100%;
+ height: 100%;
+ font-weight: 400;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ align-items: center;
- .presItem-number {
- margin-top: 3.5px;
- font-size: 12px;
- font-weight: 700;
- text-align: center;
- justify-self: center;
- align-self: flex-start;
- position: relative;
- display: inline-block;
- overflow: hidden;
- }
+ // .presItem-number {
+ // margin-top: 3.5px;
+ // font-size: 12px;
+ // font-weight: 700;
+ // text-align: center;
+ // justify-self: center;
+ // align-self: flex-start;
+ // position: relative;
+ // display: inline-block;
+ // overflow: hidden;
+ // }
}
.presItem-slide {
- position: relative;
- background-color: #d5dce2;
- border-radius: 5px;
- height: calc(100% - 7px);
- width: 100%;
- display: grid;
- grid-template-rows: 16px 10px auto;
- grid-template-columns: max-content max-content max-content max-content auto;
-
- .presItem-name {
- min-width: 20px;
- z-index: 300;
- top: 2px;
- align-self: center;
- font-size: 11px;
- font-family: Roboto;
- font-weight: 500;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- }
+ position: relative;
+ height: 100%;
+ width: 100%;
+ border-bottom: .5px solid grey;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
- .presItem-docName {
- min-width: 20px;
- z-index: 300;
- align-self: center;
- font-size: 9px;
- font-family: Roboto;
- font-weight: 300;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- grid-row: 2;
- grid-column: 1/6;
- }
+ .presItem-name {
+ display: flex;
+ min-width: 20px;
+ z-index: 300;
+ top: 2px;
+ align-self: center;
+ font-size: 11px;
+ font-family: Roboto;
+ font-weight: 500;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ }
- .presItem-time {
- align-self: center;
- position: relative;
- padding-right: 10px;
- top: 1px;
- font-size: 10;
- font-weight: 300;
- font-family: Roboto;
- z-index: 300;
- letter-spacing: normal;
- }
+ .presItem-docName {
+ min-width: 20px;
+ z-index: 300;
+ align-self: center;
+ font-size: 9px;
+ font-family: Roboto;
+ font-weight: 300;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ grid-row: 2;
+ grid-column: 1/6;
+ }
- .presItem-embedded {
- overflow: hidden;
- grid-row: 3;
- grid-column: 1/8;
- position: relative;
- display: flex;
- width: auto;
- justify-content: center;
- margin: auto;
- margin-bottom: 2px;
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- }
+ .presItem-time {
+ align-self: center;
+ position: relative;
+ padding-right: 10px;
+ top: 1px;
+ font-size: 10;
+ font-weight: 300;
+ font-family: Roboto;
+ z-index: 300;
+ letter-spacing: normal;
+ }
- .presItem-embeddedMask {
- width: 100%;
- height: 100%;
- position: absolute;
- border-radius: 3px;
- top: 0;
- left: 0;
- z-index: 1;
- overflow: hidden;
- }
+ .presItem-embedded {
+ overflow: hidden;
+ grid-row: 3;
+ grid-column: 1/8;
+ position: relative;
+ display: flex;
+ width: auto;
+ justify-content: center;
+ margin: auto;
+ margin-bottom: 2px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ }
+ .presItem-embeddedMask {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ border-radius: 3px;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ overflow: hidden;
+ }
- .presItem-slideButtons {
- display: flex;
- grid-column: 7;
- grid-row: 1/3;
- width: max-content;
- justify-self: right;
- justify-content: flex-end;
- .slideButton {
- cursor: pointer;
- position: relative;
- border-radius: 100px;
- z-index: 300;
- width: 18px;
- height: 18px;
+ .presItem-slideButtons {
display: flex;
- font-size: 12px;
- justify-self: center;
- align-self: center;
- background-color: rgba(0, 0, 0, 0.5);
- color: white;
- justify-content: center;
- align-items: center;
- transition: 0.2s;
- margin-right: 3px;
- }
+ grid-column: 7;
+ grid-row: 1/3;
+ width: max-content;
+ justify-self: right;
+ justify-content: flex-end;
- .slideButton:hover {
- background-color: rgba(0, 0, 0, 1);
- transform: scale(1.2);
- }
- }
+ .slideButton {
+ cursor: pointer;
+ position: relative;
+ border-radius: 100px;
+ z-index: 300;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+ }
+
+ .slideButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+ }
+ }
}
// .presItem-slide:hover {
@@ -237,38 +239,38 @@ $slide-active: #5B9FDD;
}
.presItem-multiDrag {
- font-family: Roboto;
- font-weight: 600;
- color: white;
- text-align: center;
- justify-content: center;
- align-content: center;
- width: 100px;
- height: 30px;
- position: absolute;
- background-color: $dark-blue;
- z-index: 4000;
- border-radius: 10px;
- box-shadow: black 0.4vw 0.4vw 0.8vw;
- line-height: 30px;
+ font-family: Roboto;
+ font-weight: 600;
+ color: white;
+ text-align: center;
+ justify-content: center;
+ align-content: center;
+ width: 100px;
+ height: 30px;
+ position: absolute;
+ background-color: $dark-blue;
+ z-index: 4000;
+ border-radius: 10px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+ line-height: 30px;
}
.presItem-miniSlide {
- font-weight: 700;
- font-size: 12;
- grid-column: 1/8;
- align-self: center;
- justify-self: center;
- background-color: #d5dce2;
- width: 26px;
- text-align: center;
- height: 26px;
- line-height: 28px;
- border-radius: 100%;
+ font-weight: 700;
+ font-size: 12;
+ grid-column: 1/8;
+ align-self: center;
+ justify-self: center;
+ background-color: #d5dce2;
+ width: 26px;
+ text-align: center;
+ height: 26px;
+ line-height: 28px;
+ border-radius: 100%;
}
.presItem-miniSlide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ box-shadow: 0 0 0px 1.5px $dark-blue;
}
.expandButton {
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 8e53abdba..f56ca5471 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -219,11 +219,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
}
- /**
- * Returns a local transformed coordinate array for given coordinates.
- */
- ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
-
onPointerOver = (e: any) => {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
@@ -312,100 +307,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
- @computed
- get toolbarWidth(): number {
- const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
- let width: number = NumCast(this.presBox._width);
- if (presBoxDocView) width = presBoxDocView.props.PanelWidth();
- if (width === 0) width = 300;
- return width;
- }
-
- onPointerOver = (e: any) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerMove = (e: PointerEvent) => {
- const slide = this._itemRef.current!;
- let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
- for (const doc of DragManager.docsBeingDragged) {
- if (!doc.presentationTargetDoc) dragIsPresItem = false;
- }
- if (slide && dragIsPresItem) {
- const rect = slide.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
- const height = slide.clientHeight;
- const halfLine = height / 2;
- if (y <= halfLine) {
- slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
- slide.style.borderBottom = "0px";
- } else if (y > halfLine) {
- slide.style.borderTop = "0px";
- slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
- }
- }
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerLeave = (e: any) => {
- this._itemRef.current!.style.borderTop = "0px";
- this._itemRef.current!.style.borderBottom = "0px";
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- @action
- toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth < 5) {
- action(() => (CurrentUserUtils.propertiesWidth = 250));
- }
- }
-
- @undoBatch
- removeItem = action((e: React.MouseEvent) => {
- this.props.removeDocument?.(this.rootDoc);
- if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.delete(this.rootDoc);
- }
- this.hideRecording();
- e.stopPropagation();
- });
-
- // set the value/title of the individual pres element
- @undoBatch
- @action
- onSetValue = (value: string) => {
- this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
- return true;
- }
-
- /**
- * Method called for updating the view of the currently selected document
- *
- * @param targetDoc
- * @param activeItem
- */
- @undoBatch
- @action
- updateView = (targetDoc: Doc, activeItem: Doc) => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
- }
- }
-
@computed get recordingIsInOverlay() {
let isInOverlay = false
DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
@@ -419,7 +320,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
- hideRecording = () => {
+ hideRecording = (e: React.PointerEvent) => {
+ e.stopPropagation()
DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
if (doc.slides === this.rootDoc) {
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
@@ -459,16 +361,23 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else {
// if we dont have any recording
- const recording = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", cloneFieldFilter: new List<string>(["system"]) });
+ const recording = Docs.Create.WebCamDocument("", {
+ _width: 400, _height: 200,
+ hideDocumentButtonBar: true,
+ hideDecorationTitle: true,
+ hideOpenButton: true,
+ // hideDeleteButton: true,
+ cloneFieldFilter:
+ new List<string>(["system"])
+ });
// attach the recording to the slide, and attach the slide to the recording
recording.slides = activeItem
activeItem.recording = recording
- // TODO: Figure out exactly where we want the video to appear
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- recording.x = pt[0];
- recording.y = pt[1];
+ // make recording box appear in the bottom right corner of the screen
+ recording.x = window.innerWidth - recording._width - 20;
+ recording.y = window.innerHeight - recording._height - 20;
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording);
}
}
@@ -515,7 +424,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerDown={this.headerDown}
onPointerUp={this.headerUp}
>
- {miniView ?
+ {/* {miniView ?
// when width is LESS than 110 px
<div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
{`${this.indexInPres + 1}.`}
@@ -524,13 +433,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// when width is MORE than 110 px
<div className="presItem-number">
{`${this.indexInPres + 1}.`}
- </div>}
+ </div>} */}
+ {/* <div className="presItem-number">
+ {`${this.indexInPres + 1}.`}
+ </div> */}
{miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
style={{
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
}}>
<div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <div>{`${this.indexInPres + 1}. `}</div>
<EditableView
ref={this._titleRef}
editing={!isSelected ? false : undefined}
@@ -540,8 +453,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
SetValue={this.onSetValue}
/>
</div>
- <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip>
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
<div className={"presItem-slideButtons"}>
<Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}>
<div className="slideButton"
@@ -567,7 +480,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
- {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
+ {/* {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
<div className="slideButton"
onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
style={{
@@ -581,7 +494,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
</div>
</div>
- </Tooltip>}
+ </Tooltip>} */}
{/* <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}>
<FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} />
</div></Tooltip> */}
@@ -591,7 +504,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
</div></Tooltip>
</div>
- <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div>
+ {/* <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> */}
{this.renderEmbeddedInline}
</div>}
</div >);