From 0402105238f24785a1229dbbb37f2e4dba958f88 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 29 Jan 2019 10:47:28 -0500 Subject: playing with a docking view. --- src/views/collections/CollectionFreeFormView.tsx | 207 +++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/views/collections/CollectionFreeFormView.tsx (limited to 'src/views/collections/CollectionFreeFormView.tsx') diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx new file mode 100644 index 000000000..479d883d6 --- /dev/null +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -0,0 +1,207 @@ +import { observer } from "mobx-react"; +import { Key, KeyStore } from "../../fields/Key"; +import React = require("react"); +import { action, observable, computed } from "mobx"; +import { Document } from "../../fields/Document"; +import { DocumentView, CollectionViewProps } from "../nodes/DocumentView"; +import { ListField } from "../../fields/ListField"; +import { NumberField } from "../../fields/NumberField"; +import { SSL_OP_SINGLE_DH_USE } from "constants"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Documents } from "../../documents/Documents"; +import { ContextMenu } from "../ContextMenu"; +import { DragManager } from "../../util/DragManager"; +import "./CollectionFreeFormView.scss"; +import { Utils } from "../../Utils"; + +@observer +export class CollectionFreeFormView extends React.Component { + private _containerRef = React.createRef(); + private _canvasRef = React.createRef(); + + constructor(props: CollectionViewProps) { + super(props); + } + + public static BORDER_WIDTH = 2; + + @computed + public get active(): boolean { + var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); + var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this); + var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined; + return isSelected || childSelected || topMost; + } + + drop = (e: Event, de: DragManager.DropEvent) => { + const ele = this._canvasRef.current; + if (!ele) { + return; + } + const doc = de.data[ "document" ]; + const xOffset = de.data[ "xOffset" ] as number || 0; + const yOffset = de.data[ "yOffset" ] as number || 0; + if (doc instanceof DocumentView) { + if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { + doc.props.ContainingCollectionView.removeDocument(doc.props.Document); + this.addDocument(doc.props.Document); + } + const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); + const screenX = de.x - xOffset; + const screenY = de.y - yOffset; + const docX = (screenX - translateX) / scale; + const docY = (screenY - translateY) / scale; + doc.x = docX; + doc.y = docY; + } + e.stopPropagation(); + } + + componentDidMount() { + if (this._containerRef.current) { + DragManager.MakeDropTarget(this._containerRef.current, { + handlers: { + drop: this.drop + } + }); + } + } + + _lastX: number = 0; + _lastY: number = 0; + @action + onPointerDown = (e: React.PointerEvent): void => { + if (e.button === 2 && this.active) { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + this._lastX = e.pageX; + this._lastY = e.pageY; + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + } + + @action + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble) { + e.preventDefault(); + e.stopPropagation(); + let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; + let x = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + let y = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + this.props.Document.SetFieldValue(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); + this.props.Document.SetFieldValue(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); + this._lastX = e.pageX; + this._lastY = e.pageY; + } + } + + @action + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + + let { LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); + + var deltaScale = (1 - (e.deltaY / 1000)) * Ss; + + var newContainerX = LocalX * deltaScale + W / 2 - W / 2 * deltaScale + Panxx + Xx; + var newContainerY = LocalY * deltaScale + Panyy + Yy; + + let dx = ContainerX - newContainerX; + let dy = ContainerY - newContainerY; + + this.props.Document.SetField(KeyStore.Scale, new NumberField(deltaScale)); + this.props.Document.SetFieldValue(KeyStore.PanX, Panxx + dx, NumberField); + this.props.Document.SetFieldValue(KeyStore.PanY, Panyy + dy, NumberField); + } + + onDrop = (e: React.DragEvent): void => { + e.stopPropagation() + e.preventDefault() + let fReader = new FileReader() + let file = e.dataTransfer.items[ 0 ].getAsFile(); + let that = this; + const panx: number = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + const pany: number = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + let x = e.pageX - panx + let y = e.pageY - pany + + fReader.addEventListener("load", action("drop", (event) => { + if (fReader.result) { + let url = "" + fReader.result; + let doc = Documents.ImageDocument(url, { + x: x, y: y + }) + let docs = that.props.Document.GetFieldT(KeyStore.Data, ListField); + if (!docs) { + docs = new ListField(); + that.props.Document.SetField(KeyStore.Data, docs) + } + docs.Data.push(doc); + } + }), false) + + if (file) { + fReader.readAsDataURL(file) + } + } + + @action + addDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + value.push(doc); + } + + @action + removeDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + if (value.indexOf(doc) !== -1) { + value.splice(value.indexOf(doc), 1) + + SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + } + } + + onDragOver = (e: React.DragEvent): void => { + } + render() { + const { fieldKey, Document: Document } = this.props; + + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + const pany: number = Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + const currScale: number = Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); + return ( +
+
e.preventDefault()} style={{ + width: "100%", + height: `calc(100% - 2*${CollectionFreeFormView.BORDER_WIDTH}px)`, + }} onDrop={this.onDrop} onDragOver={this.onDragOver} ref={this._containerRef}> +
+ +
+ {value.map(doc => { + return (); + })} +
+
+
+
+ ); + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0a1264837da6de1bd73637307cc9c52678efa20f Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 29 Jan 2019 13:07:33 -0500 Subject: cleaned up zooming --- src/views/collections/CollectionFreeFormView.scss | 2 +- src/views/collections/CollectionFreeFormView.tsx | 4 +- src/views/nodes/DocumentView.tsx | 45 ++++++++--------------- 3 files changed, 19 insertions(+), 32 deletions(-) (limited to 'src/views/collections/CollectionFreeFormView.tsx') diff --git a/src/views/collections/CollectionFreeFormView.scss b/src/views/collections/CollectionFreeFormView.scss index 1563712fb..870e48556 100644 --- a/src/views/collections/CollectionFreeFormView.scss +++ b/src/views/collections/CollectionFreeFormView.scss @@ -4,7 +4,7 @@ left: 0; overflow: hidden; .collectionfreeformview { - position: relative; + position: absolute; top: 0; left: 0; } diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx index 479d883d6..a1224f4da 100644 --- a/src/views/collections/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -109,11 +109,11 @@ export class CollectionFreeFormView extends React.Component onPointerWheel = (e: React.WheelEvent): void => { e.stopPropagation(); - let { LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); + let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); var deltaScale = (1 - (e.deltaY / 1000)) * Ss; - var newContainerX = LocalX * deltaScale + W / 2 - W / 2 * deltaScale + Panxx + Xx; + var newContainerX = LocalX * deltaScale + Panxx + Xx; var newContainerY = LocalY * deltaScale + Panyy + Yy; let dx = ContainerX - newContainerX; diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 3b9e6cc04..0df97d62a 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -167,7 +167,7 @@ export class DocumentView extends React.Component { // Converts a coordinate in the screen space of the app into a local document coordinate. // public TransformToLocalPoint(screenX: number, screenY: number) { - let ContainerX = screenX; + let ContainerX = screenX - CollectionFreeFormView.BORDER_WIDTH; let ContainerY = screenY - CollectionFreeFormView.BORDER_WIDTH; // if this collection view is nested within another collection view, then @@ -178,16 +178,15 @@ export class DocumentView extends React.Component { ContainerY = LocalY - CollectionFreeFormView.BORDER_WIDTH; } - let W = this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); let Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); let Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); let Ss = this.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); let Panxx = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); let Panyy = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); - let LocalX = (ContainerX - (Xx + Panxx) - W / 2) / Ss + W / 2; + let LocalX = (ContainerX - (Xx + Panxx)) / Ss; let LocalY = (ContainerY - (Yy + Panyy)) / Ss; - return { LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; + return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; } // @@ -197,11 +196,11 @@ export class DocumentView extends React.Component { if (this.props.ContainingCollectionView != undefined && !(this.props.ContainingCollectionView instanceof CollectionFreeFormView)) { return { ScreenX: undefined, ScreenY: undefined }; } - let W = this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); + let W = CollectionFreeFormView.BORDER_WIDTH; // this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); let H = CollectionFreeFormView.BORDER_WIDTH; let Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); let Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); - let parentX: Opt = (localX - W / 2) * Ss + (Xx + Panxx) + W / 2; + let parentX: Opt = (localX - W) * Ss + (Xx + Panxx) + W; let parentY: Opt = (localY - H) * Ss + (Yy + Panyy) + H; // if this collection view is nested within another collection view, then @@ -299,28 +298,16 @@ export class DocumentView extends React.Component { render() { var freestyling = this.props.ContainingCollectionView === undefined || this.props.ContainingCollectionView instanceof CollectionFreeFormView; - if (freestyling) - return ( -
- -
- ); - else - return ( -
- -
- ); + return ( +
+ +
+ ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d8ff5b1effe0db563defc2a6e1391b9b0d160e67 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 29 Jan 2019 14:43:42 -0500 Subject: intermediate state -- things don't resize properly yet. --- src/DocumentDecorations.tsx | 2 -- src/views/collections/CollectionDockingView.tsx | 6 +++--- src/views/collections/CollectionFreeFormView.tsx | 18 +++++------------- src/views/nodes/DocumentView.tsx | 6 +++--- 4 files changed, 11 insertions(+), 21 deletions(-) (limited to 'src/views/collections/CollectionFreeFormView.tsx') diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx index d5a682b82..d71cda539 100644 --- a/src/DocumentDecorations.tsx +++ b/src/DocumentDecorations.tsx @@ -1,10 +1,8 @@ import { observable, computed } from "mobx"; import React = require("react"); -import { DocumentView } from "./views/nodes/DocumentView"; import { SelectionManager } from "./util/SelectionManager"; import { observer } from "mobx-react"; import './DocumentDecorations.scss' -import { CollectionFreeFormView } from "./views/collections/CollectionFreeFormView"; @observer export class DocumentDecorations extends React.Component { diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx index b6fff6ba0..a59a40b33 100644 --- a/src/views/collections/CollectionDockingView.tsx +++ b/src/views/collections/CollectionDockingView.tsx @@ -132,13 +132,13 @@ export class CollectionDockingView extends React.Component console.log("Gettting " + component); const { fieldKey, Document: Document } = this.props; const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); - if (component === "doc1") { + if (component === "doc1" && value.length > 0) { return (); } - if (component === "doc2") { + if (component === "doc2" && value.length > 1) { return (); } - if (component === "doc3") { + if (component === "doc3" && value.length > 2) { return (); } if (component === "text") { diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx index a1224f4da..c84b8e3e5 100644 --- a/src/views/collections/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -34,25 +34,17 @@ export class CollectionFreeFormView extends React.Component } drop = (e: Event, de: DragManager.DropEvent) => { - const ele = this._canvasRef.current; - if (!ele) { - return; - } const doc = de.data[ "document" ]; - const xOffset = de.data[ "xOffset" ] as number || 0; - const yOffset = de.data[ "yOffset" ] as number || 0; if (doc instanceof DocumentView) { if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { doc.props.ContainingCollectionView.removeDocument(doc.props.Document); this.addDocument(doc.props.Document); } - const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); - const screenX = de.x - xOffset; - const screenY = de.y - yOffset; - const docX = (screenX - translateX) / scale; - const docY = (screenY - translateY) / scale; - doc.x = docX; - doc.y = docY; + const xOffset = de.data[ "xOffset" ] as number || 0; + const yOffset = de.data[ "yOffset" ] as number || 0; + let { LocalX, LocalY } = this.props.ContainingDocumentView!.TransformToLocalPoint(de.x - xOffset, de.y - yOffset); + doc.x = LocalX; + doc.y = LocalY; } e.stopPropagation(); } diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 0df97d62a..5ce64b347 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -193,9 +193,9 @@ export class DocumentView extends React.Component { // Converts a point in the coordinate space of a document to a screen space coordinate. // public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: Opt, ScreenY: Opt } { - if (this.props.ContainingCollectionView != undefined && !(this.props.ContainingCollectionView instanceof CollectionFreeFormView)) { - return { ScreenX: undefined, ScreenY: undefined }; - } + // if (this.props.ContainingCollectionView != undefined && !(this.props.ContainingCollectionView instanceof CollectionFreeFormView)) { + // return { ScreenX: undefined, ScreenY: undefined }; + // } let W = CollectionFreeFormView.BORDER_WIDTH; // this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); let H = CollectionFreeFormView.BORDER_WIDTH; let Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); -- cgit v1.2.3-70-g09d2 From 13518146c955f012a6f6cd2b802f80aeeffcd58d Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 30 Jan 2019 09:45:15 -0500 Subject: docking for top-level only --- src/DocumentDecorations.tsx | 10 ++++++---- src/Main.tsx | 8 ++++---- src/views/collections/CollectionFreeFormView.tsx | 5 ++++- src/views/nodes/DocumentView.tsx | 10 ++++------ 4 files changed, 18 insertions(+), 15 deletions(-) (limited to 'src/views/collections/CollectionFreeFormView.tsx') diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx index d71cda539..1cf875ea5 100644 --- a/src/DocumentDecorations.tsx +++ b/src/DocumentDecorations.tsx @@ -3,6 +3,7 @@ import React = require("react"); import { SelectionManager } from "./util/SelectionManager"; import { observer } from "mobx-react"; import './DocumentDecorations.scss' +import { CollectionFreeFormView } from "./views/collections/CollectionFreeFormView"; @observer export class DocumentDecorations extends React.Component { @@ -20,12 +21,13 @@ export class DocumentDecorations extends React.Component { @computed get Bounds(): { x: number, y: number, b: number, r: number } { return SelectionManager.SelectedDocuments().reduce((bounds, element) => { + if (element.props.ContainingCollectionView != undefined && + !(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) { + return bounds; + } var spt = element.TransformToScreenPoint(0, 0); var bpt = element.TransformToScreenPoint(element.width, element.height); - if (spt.ScreenX == undefined || spt.ScreenY == undefined || - bpt.ScreenX == undefined || bpt.ScreenY == undefined) - return { x: bounds.x, y: bounds.y, r: bounds.r, b: bounds.b }; - else return { + return { x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y), r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b) } diff --git a/src/Main.tsx b/src/Main.tsx index 118e745cd..33e38004f 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -22,7 +22,7 @@ configure({ }); const mainNodeCollection = new Array(); -let mainContainer = Documents.CollectionDocument(mainNodeCollection, { +let mainContainer = Documents.DockDocument(mainNodeCollection, { x: 0, y: 0, width: window.screen.width, height: window.screen.height }) @@ -73,10 +73,10 @@ runInAction(() => { } // mainNodes.Data.push(doc1); // mainNodes.Data.push(doc2); - //mainNodes.Data.push(doc4); + mainNodes.Data.push(doc4); // mainNodes.Data.push(doc3); - //mainNodes.Data.push(doc5); + mainNodes.Data.push(doc5); // mainNodes.Data.push(doc1); // mainNodes.Data.push(doc2); - mainNodes.Data.push(doc6); + //mainNodes.Data.push(doc6); }); \ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx index c84b8e3e5..736bcb786 100644 --- a/src/views/collections/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -13,6 +13,7 @@ import { ContextMenu } from "../ContextMenu"; import { DragManager } from "../../util/DragManager"; import "./CollectionFreeFormView.scss"; import { Utils } from "../../Utils"; +import { CollectionDockingView } from "./CollectionDockingView"; @observer export class CollectionFreeFormView extends React.Component { @@ -29,7 +30,9 @@ export class CollectionFreeFormView extends React.Component public get active(): boolean { var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this); - var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined; + var topMost = this.props.ContainingDocumentView != undefined && ( + this.props.ContainingDocumentView.props.ContainingCollectionView == undefined || + this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView); return isSelected || childSelected || topMost; } diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 38e695ed2..86d5ed305 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -196,17 +196,15 @@ export class DocumentView extends React.Component { // // Converts a point in the coordinate space of a document to a screen space coordinate. // - public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: Opt, ScreenY: Opt } { - // if (this.props.ContainingCollectionView != undefined && !(this.props.ContainingCollectionView instanceof CollectionFreeFormView)) { - // return { ScreenX: undefined, ScreenY: undefined }; - // } + public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { + let dockingViewChromeHack = this.props.ContainingCollectionView instanceof CollectionDockingView; let W = CollectionFreeFormView.BORDER_WIDTH; // this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); let H = CollectionFreeFormView.BORDER_WIDTH; let Xx = dockingViewChromeHack ? 0 : this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); let Yy = dockingViewChromeHack ? CollectionDockingView.TAB_HEADER_HEIGHT : this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); - let parentX: Opt = (localX - W) * Ss + (Xx + Panxx) + W; - let parentY: Opt = (localY - H) * Ss + (Yy + Panyy) + H; + let parentX = (localX - W) * Ss + (Xx + Panxx) + W; + let parentY = (localY - H) * Ss + (Yy + Panyy) + H; // if this collection view is nested within another collection view, then // first transform the local point into the parent collection's coordinate space. -- cgit v1.2.3-70-g09d2