aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ComparisonBox.tsx
blob: 0ba53dee6140fbd4c5f50837b292c88ae11abf7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
import { createSchema, makeInterface } from '../../../fields/Schema';
import { NumCast, Cast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import "./ComparisonBox.scss";
import React = require("react");
import { ContentFittingDocumentView } from './ContentFittingDocumentView';
import { undoBatch } from '../../util/UndoManager';
import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { SnappingManager } from '../../util/SnappingManager';
import { DocumentViewProps } from './DocumentView';

export const comparisonSchema = createSchema({});

type ComparisonDocument = makeInterface<[typeof comparisonSchema, typeof documentSchema]>;
const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);

@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
    protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
    private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];

    @observable _animating = "";

    protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
        this._disposers[disposerId]?.();
        if (ele) {
            // create disposers identified by disposerId to remove drag & drop listeners
            this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
        }
    }

    @undoBatch
    private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
        if (dropEvent.complete.docDragData) {
            event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
            const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
            if (droppedDocs?.length) {
                this.dataDoc[fieldKey] = droppedDocs[0];
            }
        }
    }

    private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => {
            // on click, animate slider movement to the targetWidth
            this._animating = "all 200ms";
            this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth();
            setTimeout(action(() => this._animating = ""), 200);
        }), false);
    }

    @action
    private onPointerMove = ({ movementX }: PointerEvent) => {
        const width = movementX * this.props.ScreenToLocalTransform().Scale + NumCast(this.layoutDoc._clipWidth) / 100 * this.props.PanelWidth();
        if (width && width > 5 && width < this.props.PanelWidth()) {
            this.layoutDoc._clipWidth = width * 100 / this.props.PanelWidth();
        }
        return false;
    }

    @undoBatch
    clearDoc = (e: React.MouseEvent, fieldKey: string) => {
        e.stopPropagation; // prevent click event action (slider movement) in registerSliding
        delete this.dataDoc[fieldKey];
    }

    render() {
        const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
        const childProps: DocumentViewProps = { ...this.props, pointerEvents: "none", parentActive: this.props.active };
        const clearButton = (which: string) => {
            return <div className={`clear-button ${which}`}
                onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding 
                onClick={e => this.clearDoc(e, `compareBox-${which}`)}>
                <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
            </div>;
        };
        const displayDoc = (which: string) => {
            const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
            return whichDoc ? <>
                <ContentFittingDocumentView {...childProps} Document={whichDoc} />
                {clearButton(which)}
            </> :  // placeholder image if doc is missing
                <div className="placeholder">
                    <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
                </div>;
        };
        const displayBox = (which: string, index: number, cover: number) => {
            return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
                onPointerDown={e => this.registerSliding(e, cover)}
                ref={ele => this.createDropTarget(ele, `compareBox-${which}`, index)} >
                {displayDoc(which)}
            </div>;
        };

        return (
            <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
                {displayBox("after", 1, this.props.PanelWidth() - 3)}
                <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
                    {displayBox("before", 0, 0)}
                </div>

                <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)`, cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? "e-resize" : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }}
                    onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ >
                    <div className="slide-handle" />
                </div>
            </div >);
    }
}