aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/CollectionMulticolumnView.tsx221
1 files changed, 116 insertions, 105 deletions
diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 653ff5b84..61ce8d99c 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -16,27 +16,31 @@ import { computed } from 'mobx';
type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
const MulticolumnDocument = makeInterface(documentSchema);
-interface UnitBase {
+interface Unresolved {
config: Doc;
target: Doc;
+ magnitude: number;
+ unit: string;
}
-type WidthSpecifier = { pixels: number } | { ratio: number };
-interface LayoutUnit extends UnitBase {
- specifier: WidthSpecifier;
-}
-
-interface Fixed extends UnitBase {
- specifier: { pixels: number };
+interface Resolved {
+ config: Doc;
+ target: Doc;
+ pixels: number;
}
-interface Ratio extends UnitBase {
- specifier: { ratio: number };
-}
+const resolvedUnits = ["*", "px"];
@observer
export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
private _dropDisposer?: DragManager.DragDropDisposer;
+
+ /**
+ * Returns the list of so-called configuration documents.
+ * Each one is a wrapper around what we typically think of as
+ * the child document, just also encoding the magnitude and unit
+ * of the specified width.
+ */
private get configuration() {
const { Document } = this.props;
if (!Document.multicolumnData) {
@@ -45,126 +49,132 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
return DocListCast(this.Document.multicolumnData);
}
- protected createDropTarget = (ele: HTMLDivElement) => {
- this._dropDisposer && this._dropDisposer();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
- }
- }
-
- getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
- if (!ele.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
- return new Transform(-translateX, -translateY, 1 / scale);
- }
-
- public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
-
@computed
- private get layoutInformation() {
- const values: LayoutUnit[] = [];
- let ratioSum = 0;
- let fixedCount = 0;
- let ratioCount = 0;
+ private get resolvedLayoutInformation() {
+ const unresolved: Unresolved[] = [];
+ let ratioSum = 0, numFixed = 0, numRatio = 0;
for (const config of this.configuration) {
- const { columnWidth, target } = config;
- if (!(target instanceof Doc)) {
- // we're still waiting on promises, so it's not worth rendering anything yet
- return (null);
+ const { target, widthMagnitude, widthUnit } = config;
+ if (target instanceof Doc) {
+ const unit = StrCast(widthUnit);
+ const magnitude = NumCast(widthMagnitude);
+ if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+ switch (unit) {
+ case "*":
+ ratioSum += magnitude;
+ numRatio++;
+ break;
+ case "px":
+ numFixed++;
+ break;
+ }
+ unresolved.push({ config, target, magnitude, unit });
+ }
+ // otherwise, the particular configuration entry is ignored and the remaining
+ // space is allocated as if the document were absent from the configuration list
}
- const widthSpecifier = Cast(columnWidth, "number");
- let matches: RegExpExecArray | null;
- let specifier: WidthSpecifier | null = null;
- if (widthSpecifier !== undefined) {
- // we've gotten a number, referring to a pixel value
- specifier = { pixels: widthSpecifier };
- fixedCount++;
- } else if ((matches = /^(\d+(\.\d+)?)\*/.exec(StrCast(columnWidth))) !== null) {
- // we've gotten a proportional measure, like 1.8*
- const ratio = Number(matches[1]);
- ratioSum += ratio;
- specifier = { ratio };
- ratioCount++;
- }
- if (specifier !== null) {
- values.push({ config, target, specifier });
- }
- // otherwise, the particular configuration entry is ignored and the remaining
- // space is allocated as if the document were absent from the configuration list
}
- return { values, ratioCount, fixedCount, ratioSum };
+ return { unresolved, numRatio, numFixed, ratioSum };
}
- @computed private get totalFixedPool() {
- const fixed: Fixed[] = [];
- const layout = this.layoutInformation;
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with higher priority) requested a fixed pixel width.
+ *
+ * If the underlying resolvedLayoutInformation returns null
+ * because we're waiting on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalFixedAllocation(): number | undefined {
+ const layout = this.resolvedLayoutInformation;
if (!layout) {
return undefined;
}
- layout.values.forEach(unit => {
- ("pixels" in unit.specifier) && fixed.push(unit as Fixed);
- });
- return fixed.reduce((sum, unit) => sum + unit.specifier.pixels, 0);
+ let sum = 0;
+ for (const { magnitude, unit } of layout.unresolved) {
+ if (unit === "px") {
+ sum += magnitude;
+ }
+ }
+ return sum;
}
- @computed private get totalProportionalPool() {
- const { totalFixedPool } = this;
- return totalFixedPool !== undefined ? this.props.PanelWidth() - totalFixedPool : undefined;
+ /**
+ * This returns the total quantity, in pixels, that this
+ * view needs to reserve for child documents that have
+ * (with lower priority) requested a certain relative proportion of the
+ * remaining pixel width not allocated for fixed widths.
+ *
+ * If the underlying totalFixedAllocation returns undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get totalRatioAllocation(): number | undefined {
+ const { totalFixedAllocation } = this;
+ return totalFixedAllocation !== undefined ? this.props.PanelWidth() - totalFixedAllocation : undefined;
}
- @computed private get columnUnitLength() {
- const layout = this.layoutInformation;
- const { totalProportionalPool } = this;
- if (layout !== null && totalProportionalPool !== undefined) {
- const { ratioSum, values } = layout;
- return (totalProportionalPool - 2 * (values.length - 1)) / ratioSum;
+ /**
+ * This returns the total quantity, in pixels, that
+ * 1* (relative / star unit) is worth. For example,
+ * if the configuration had three documents, with, respectively,
+ * widths of 2*, 2* and 1*, and the panel width was 1000px,
+ * this value would return 1000 / (2 + 2 + 1), or 200px.
+ * This is then multiplied by each relative-width
+ * document's * factor to compute its actual width (400px, 400px and 200px).
+ *
+ * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+ * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+ */
+ @computed
+ private get columnUnitLength(): number | undefined {
+ const layout = this.resolvedLayoutInformation;
+ const { totalRatioAllocation } = this;
+ if (layout !== null && totalRatioAllocation !== undefined) {
+ const { ratioSum, unresolved } = layout;
+ return (totalRatioAllocation - 2 * (unresolved.length - 1)) / ratioSum;
}
return undefined;
}
@computed
private get contents(): JSX.Element[] | null {
- const layout = this.layoutInformation;
- if (layout === null) {
- return (null);
- }
- const { values } = layout;
- const { columnUnitLength } = this;
- if (columnUnitLength === undefined) {
- return (null);
+ const layout = this.resolvedLayoutInformation;
+ const columnUnitLength = this.columnUnitLength;
+ if (layout === null || columnUnitLength === undefined) {
+ return (null); // we're still waiting on promises to resolve
}
- const { GenerateGuid } = Utils;
- const toView = ({ target, specifier: { pixels } }: Fixed) =>
- <ContentFittingDocumentView
+ const resolved: Resolved[] = [];
+ layout.unresolved.forEach(value => {
+ const { unit, magnitude, ...remaining } = value;
+ let width = magnitude;
+ if (unit === "*") {
+ width = magnitude * columnUnitLength;
+ }
+ resolved.push({ pixels: width, ...remaining, });
+ });
+ const collector: JSX.Element[] = [];
+ for (let i = 0; i < resolved.length; i++) {
+ const { target, pixels, config } = resolved[i];
+ collector.push(<ContentFittingDocumentView
{...this.props}
- key={GenerateGuid()}
+ key={Utils.GenerateGuid()}
Document={target}
DataDocument={undefined}
PanelWidth={() => pixels}
getTransform={this.props.ScreenToLocalTransform}
- />;
- const resolved: Fixed[] = [];
- values.forEach(value => {
- const { specifier, ...remaining } = value;
- if ("ratio" in specifier) {
- resolved.push({ ...remaining, specifier: { pixels: specifier.ratio * columnUnitLength } });
- } else {
- resolved.push(value as Fixed);
- }
- });
- const collector: JSX.Element[] = [];
- for (let i = 0; i < resolved.length; i++) {
- collector.push(toView(resolved[i]));
+ />);
collector.push(
<MulticolumnSpacer
- key={GenerateGuid()}
- columnBaseUnit={columnUnitLength}
- toLeft={resolved[i].config}
+ key={Utils.GenerateGuid()}
+ columnUnitLength={columnUnitLength}
+ toLeft={config}
toRight={resolved[i + 1]?.config}
/>
);
}
- collector.pop();
+ collector.pop(); // not the cleanest, but simply removes the final extraneous spacer
return collector;
}
@@ -179,7 +189,7 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
}
interface SpacerProps {
- columnBaseUnit: number;
+ columnUnitLength: number;
toLeft?: Doc;
toRight?: Doc;
}
@@ -196,12 +206,13 @@ class MulticolumnSpacer extends React.Component<SpacerProps> {
}
private onPointerMove = ({ movementX }: PointerEvent) => {
- const { toLeft, toRight, columnBaseUnit } = this.props;
+ const { toLeft, toRight, columnUnitLength } = this.props;
const target = movementX > 0 ? toRight : toLeft;
if (target) {
- let widthSpecifier = Number(StrCast(target.columnWidth).replace("*", ""));
- widthSpecifier -= Math.abs(movementX) / columnBaseUnit;
- target.columnWidth = `${widthSpecifier}*`;
+ const { widthUnit, widthMagnitude } = target;
+ if (widthUnit === "*") {
+ target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / columnUnitLength;
+ }
}
}