Skip to content

Commit 377ebea

Browse files
authored
Merge pull request #14 from palantir/support-multiple-mosaics
Support multiple mosaics
2 parents de34772 + 90d2d74 commit 377ebea

20 files changed

+131
-87
lines changed

.npmignore

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22
.editorconfig
33
.gitignore
44
.npmignore
5-
.npmrc
6-
.tsdrc
75
circle.yml
8-
extra-typings.d.ts
96
tsconfig*.json
10-
tsd.json
117
dev/
128
docs/
13-
scripts/
14-
typings/
159
webpack.config*.js
1610
screencast.gif
1711
yarn.lock

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ This uses the excellent [Blueprint](http://blueprintjs.com/) React UI Toolkit to
3030
It is recommended to at least start developing with this theme.
3131
To use it install Blueprint `yarn add @blueprintjs/core` and add its CSS to your page.
3232

33-
See [BlueprintTheme.less](./src/BlueprintTheme.less) for an example of creating a theme.
33+
See [blueprint-theme.less](./styles/blueprint-theme.less) for an example of creating a theme.
3434

3535
### Examples
3636

dev/ExampleApp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import {
3232
MosaicZeroStateFactory,
3333
updateTree,
3434
} from '../src/index';
35-
import '../src/index.less';
3635
import { MosaicDirection } from '../src/types';
36+
import '../styles/index.less';
3737
import './example.less';
3838

3939
const { div, h1, a, button, span } = React.DOM;

dev/example.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ html, body, #app, .react-mosaic-example-app {
3838
color: @white;
3939
}
4040

41-
.mosaic-root {
41+
> .mosaic-root {
4242
height: ~"calc(100% - 50px)";
4343
}
4444
}

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"docs": "rm -rf docs/ && webpack",
1818
"test": "tslint --project tsconfig.json && mocha --opts test/mocha.opts 'test/*.ts'",
1919
"lib.compile.ts": "tsc -p tsconfig-build.json",
20-
"lib.compile.less": " lessc --source-map src/index.less react-mosaic-component.css",
20+
"lib.compile.less": " lessc --source-map styles/index.less react-mosaic-component.css",
2121
"lib.remove.type.references": "replace '^/// <reference.+?\\n' '' lib -r --include='*.d.ts'"
2222
},
2323
"devDependencies": {
@@ -30,10 +30,11 @@
3030
"@types/lodash": "^4.14.43",
3131
"@types/mocha": "^2.2.33",
3232
"@types/pure-render-decorator": "^0.2.27",
33-
"@types/react": "^0.14.55",
33+
"@types/react": "^15.0.23",
3434
"@types/react-dnd": "^2.0.31",
3535
"@types/react-dnd-html5-backend": "^2.1.6",
36-
"@types/react-dom": "^0.14.19",
36+
"@types/react-dom": "^15.5.0",
37+
"@types/uuid": "^2.0.29",
3738
"chai": "^3.5.0",
3839
"css-loader": "^0.26.1",
3940
"file-loader": "^0.9.0",
@@ -66,7 +67,8 @@
6667
"lodash": "^4.17.2",
6768
"pure-render-decorator": "^1.2.1",
6869
"react-dnd": "^2.1.4",
69-
"react-dnd-html5-backend": "^2.1.2"
70+
"react-dnd-html5-backend": "^2.1.2",
71+
"uuid": "^3.0.1"
7072
},
7173
"peerDependencies": {
7274
"react": "^15.0.0 || ^0.14.0"

src/Mosaic.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import * as PureRenderDecorator from 'pure-render-decorator';
2020
import * as React from 'react';
2121
import { DragDropContext } from 'react-dnd';
2222
import HTML5 from 'react-dnd-html5-backend';
23-
import { MosaicActionsPropType, MosaicContext, MosaicRootActions } from './contextTypes';
23+
import { v4 as uuid } from 'uuid';
24+
import { MosaicContext, MosaicRootActions } from './contextTypes';
25+
import { MosaicDropTargetPosition } from './internalTypes';
2426
import { MosaicWindowDropTarget } from './MosaicDropTarget';
2527
import { MosaicTile } from './MosaicTile';
2628
import { createExpandUpdate, createHideUpdate, createRemoveUpdate, updateTree } from './mosaicUpdates';
2729
import { MosaicZeroStateFactory } from './MosaicZeroState';
28-
import { MosaicDropTargetPosition, MosaicNode, MosaicPath, MosaicUpdate, TileRenderer } from './types';
30+
import { MosaicNode, MosaicPath, MosaicUpdate, TileRenderer } from './types';
2931

3032
const { div } = React.DOM;
3133
const DEFAULT_EXPAND_PERCENTAGE = 70;
@@ -77,31 +79,32 @@ function isUncontrolled<T>(props: MosaicProps<T>): props is MosaicUncontrolledPr
7779
return (props as MosaicUncontrolledProps<T>).initialValue != null;
7880
}
7981

80-
interface State<T> {
82+
export interface MosaicState<T> {
8183
currentNode: MosaicNode<T> | null;
84+
mosaicId: string;
8285
}
8386

8487
@(DragDropContext(HTML5) as ClassDecorator)
8588
@PureRenderDecorator
86-
class MosaicComponentClass<T> extends React.Component<MosaicProps<T>, State<T>> {
89+
export class Mosaic<T> extends React.Component<MosaicProps<T>, MosaicState<T>> {
8790
static defaultProps = {
8891
onChange: () => void 0,
8992
resizeable: true,
9093
zeroStateView: MosaicZeroStateFactory(),
9194
className: 'mosaic-blueprint-theme',
9295
} as any;
9396

94-
static childContextTypes = {
95-
mosaicActions: MosaicActionsPropType,
96-
};
97+
static childContextTypes = MosaicContext;
9798

98-
state: State<T> = {
99+
state: MosaicState<T> = {
99100
currentNode: null,
101+
mosaicId: uuid(),
100102
};
101103

102104
getChildContext(): MosaicContext<T> {
103105
return {
104106
mosaicActions: this.actions,
107+
mosaicId: this.state.mosaicId,
105108
};
106109
}
107110

@@ -120,7 +123,7 @@ class MosaicComponentClass<T> extends React.Component<MosaicProps<T>, State<T>>
120123
getPath: this.getPath,
121124
}),
122125
div({ className: 'drop-target-container' },
123-
_.values<string>(MosaicDropTargetPosition).map((position) =>
126+
_.values<MosaicDropTargetPosition>(MosaicDropTargetPosition).map((position) =>
124127
MosaicWindowDropTarget({
125128
position,
126129
path: [],
@@ -134,7 +137,6 @@ class MosaicComponentClass<T> extends React.Component<MosaicProps<T>, State<T>>
134137
componentWillReceiveProps(nextProps: MosaicProps<T>) {
135138
if (isUncontrolled(nextProps) &&
136139
nextProps.initialValue !== (this.props as MosaicUncontrolledProps<T>).initialValue) {
137-
138140
this.setState({ currentNode: nextProps.initialValue });
139141
}
140142
}
@@ -194,11 +196,10 @@ class MosaicComponentClass<T> extends React.Component<MosaicProps<T>, State<T>>
194196
}]),
195197
};
196198
}
197-
export const Mosaic: React.ComponentClass<MosaicProps<any>> = MosaicComponentClass;
198199

199200
// Factory that works with generics
200201
export function MosaicFactory<T>(props: MosaicProps<T> & React.Attributes, ...children: React.ReactNode[]) {
201202
const element: React.ReactElement<MosaicProps<T>> =
202-
React.createElement(MosaicComponentClass as React.ComponentClass<MosaicProps<T>>, props, ...children);
203+
React.createElement(Mosaic as React.ComponentClass<MosaicProps<T>>, props, ...children);
203204
return element;
204205
}

src/MosaicDropTarget.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import * as classNames from 'classnames';
1818
import * as PureRenderDecorator from 'pure-render-decorator';
1919
import * as React from 'react';
2020
import { ConnectDropTarget, DropTarget } from 'react-dnd';
21-
import { MosaicDragType, MosaicDropData, MosaicDropTargetPosition, MosaicPath } from './types';
21+
import { MosaicContext } from './contextTypes';
22+
import { MosaicDragItem, MosaicDropData, MosaicDropTargetPosition } from './internalTypes';
23+
import { MosaicDragType, MosaicPath } from './types';
2224
import DropTargetMonitor = __ReactDnd.DropTargetMonitor;
2325

2426
const { div } = React.DOM;
@@ -31,28 +33,39 @@ export interface MosaicWindowDropTargetProps {
3133
interface DropTargetProps {
3234
connectDropTarget: ConnectDropTarget;
3335
isOver: boolean;
36+
draggedMosaicId: string | undefined;
3437
}
3538

3639
type Props = MosaicWindowDropTargetProps & DropTargetProps;
3740

3841
const dropTarget = {
39-
drop: (props: Props, _monitor: DropTargetMonitor): MosaicDropData => ({
40-
path: props.path,
41-
position: props.position,
42-
}),
42+
drop: (props: Props, monitor: DropTargetMonitor, component: MosaicWindowDropTargetClass): MosaicDropData => {
43+
if (component.context.mosaicId === ((monitor.getItem() || {}) as MosaicDragItem).mosaicId) {
44+
return {
45+
path: props.path,
46+
position: props.position,
47+
};
48+
} else {
49+
return {};
50+
}
51+
},
4352
};
4453

4554
@(DropTarget(MosaicDragType.WINDOW, dropTarget, (connect, monitor): DropTargetProps => ({
4655
connectDropTarget: connect.dropTarget(),
4756
isOver: monitor.isOver(),
57+
draggedMosaicId: ((monitor.getItem() || {}) as MosaicDragItem).mosaicId,
4858
})) as ClassDecorator)
4959
@PureRenderDecorator
5060
class MosaicWindowDropTargetClass extends React.Component<Props, void> {
61+
static contextTypes = MosaicContext;
62+
context: MosaicContext<any>;
63+
5164
render() {
52-
const { position, isOver, connectDropTarget } = this.props;
65+
const { position, isOver, connectDropTarget, draggedMosaicId } = this.props;
5366
return connectDropTarget(div({
5467
className: classNames('drop-target', position, {
55-
'drop-target-hover': isOver,
68+
'drop-target-hover': isOver && draggedMosaicId === this.context.mosaicId,
5669
}),
5770
}));
5871
}

src/MosaicZeroState.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface MosaicZeroStateProps<T> {
2727
}
2828

2929
@PureRenderDecorator
30-
class MosaicZeroStateComponentClass<T> extends React.Component<MosaicZeroStateProps<T>, void> {
30+
export class MosaicZeroState<T> extends React.Component<MosaicZeroStateProps<T>, void> {
3131
context: MosaicContext<T>;
3232

3333
static contextTypes = {
@@ -54,11 +54,10 @@ class MosaicZeroStateComponentClass<T> extends React.Component<MosaicZeroStatePr
5454
.then((node) => this.context.mosaicActions.replaceWith([], node))
5555
.catch(_.noop); // Swallow rejections (i.e. on user cancel)
5656
}
57-
export const MosaicZeroState: React.ComponentClass<MosaicZeroStateProps<any>> = MosaicZeroStateComponentClass;
5857

5958
// Factory that works with generics
6059
export function MosaicZeroStateFactory<T>(props?: MosaicZeroStateProps<T> & React.Attributes, ...children: React.ReactNode[]) {
6160
const element: React.ReactElement<MosaicZeroStateProps<T>> = React.createElement(
62-
MosaicZeroStateComponentClass as React.ComponentClass<MosaicZeroStateProps<T>>, props, ...children);
61+
MosaicZeroState as React.ComponentClass<MosaicZeroStateProps<T>>, props, ...children);
6362
return element;
6463
}

src/contextTypes.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { MosaicNode, MosaicPath, MosaicUpdate } from './types';
2727
*/
2828
export interface MosaicContext<T> {
2929
mosaicActions: MosaicRootActions<T>;
30+
mosaicId: string;
3031
}
3132

3233
/**
@@ -122,13 +123,17 @@ export const MosaicWindowActionsPropType = React.PropTypes.shape({
122123
* Bundled PropTypes for convenience
123124
*/
124125

125-
export const MosaicTileContext = {
126+
export const MosaicContext = {
126127
mosaicActions: MosaicActionsPropType,
128+
mosaicId: React.PropTypes.string.isRequired,
129+
};
130+
131+
export const MosaicTileContext = {
132+
...MosaicContext,
127133
getMosaicPath: MosaicPathGetterPropType,
128134
};
129135

130136
export const MosaicWindowContext = {
137+
...MosaicTileContext,
131138
mosaicWindowActions: MosaicWindowActionsPropType,
132-
mosaicActions: MosaicActionsPropType,
133-
getMosaicPath: MosaicPathGetterPropType,
134139
};

src/internalTypes.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright 2016 Palantir Technologies, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { MosaicPath } from './types';
18+
19+
export type MosaicDropTargetPosition = 'top' | 'bottom' | 'left' | 'right';
20+
export const MosaicDropTargetPosition = {
21+
TOP: 'top' as 'top',
22+
BOTTOM: 'bottom' as 'bottom',
23+
LEFT: 'left' as 'left',
24+
RIGHT: 'right' as 'right',
25+
};
26+
27+
export interface MosaicDropData {
28+
path?: MosaicPath;
29+
position?: MosaicDropTargetPosition;
30+
}
31+
32+
export interface MosaicDragItem {
33+
mosaicId: string;
34+
}

0 commit comments

Comments
 (0)