Replies: 3 comments 11 replies
-
Hi! Usually if you're lucky you can use the tool Dukat which helps converting Typescript definitions to Kotlin external declarations. In my case with blueprintjs (and I think with all react component libraries in general) Dukat struggles a bit, and a lot of manual work is necessary to tweak the Kotlin declarations. In the end I ended up looking at the Typescript definitions myself and converting manually some of them, but the Kotlin/JS docs in this respect are pretty scarce. You can find the path to the Typescript declarations of a library in the package.json file of the lib in general (the "typings" field). And it gives you the root of the Typescript files. After converting these declarations manually, I also declared extensions on The best would be to create a library out of these declarations, but I didn't take the time to do so yet. Will do though :) Btw, no need to apologize, this is a toy project and any sort of question is welcome! |
Beta Was this translation helpful? Give feedback.
-
well, I intended (after very brief first look arounds) maybe to use blueprintjs together with https://github.com/STRML/react-grid-layout but integration of the latter just slips my abilities (I would understand if looking into this would not be a pleasure for you and you therefore just ignoring this :) always getting a:
kotlin wrapper try: @file:JsModule("react-grid-layout") // import GridLayout from "react-grid-layout";
@file:JsNonModule
package net.strml.reactgridlayout
import react.*
//@JsModule("prop-types")
@JsName("default")
//external val GridLayout: RClass<ReactGridLayoutProps>
external val reactGridLayout : RClass<ReactGridLayoutProps>
@JsName("default")
external val GridItem: RClass<GridItemProps>
external interface ReactGridLayoutProps : RProps {
var className: String
var width: Int
var autoSize: Boolean? // = true
var cols: Int? // = 12
var draggableCancel: String? // = ""
var draggableHandle: String? // = ""
var verticalCompact: Boolean? // = true
var compactType: String // = "vertical"
var layout: Array<Any>?
var margin: Array<Int>? // = arrayOf(10, 10)
var containerPadding: Array<Int>? // = margin
var rowHeight: Int? // = 150
var droppingItem: Any? // = object { var i: String; var w: Int; var h: Int }
var isDraggable: Boolean? // = true
var isResizable: Boolean? // = true
var isBounded: Boolean? // = false
var useCSSTransforms: Boolean? // = true
var transformScale: Int? // = 1
var preventCollision: Boolean? // = false
var isDroppable: Boolean? // = false
var resizeHandles: Array<String>? // = arrayOf("se")
// resizeHandle?: ReactElement<any> | ((resizeHandleAxis: ResizeHandleAxis) => ReactElement<any>)
var resizeHandle: ReactElement
// onLayoutChange: (layout: Layout) => void,
var onLayoutChange: (layouts: Array<Any>) -> Unit
//
// // Calls when drag starts.
// onDragStart: ItemCallback,
// // Calls on each drag movement.
// onDrag: ItemCallback,
// // Calls when drag is complete.
// onDragStop: ItemCallback,
// // Calls when resize starts.
// onResizeStart: ItemCallback,
// // Calls when resize movement happens.
// onResize: ItemCallback,
// // Calls when resize is complete.
// onResizeStop: ItemCallback,
// // Calls when an element has been dropped into the grid from outside.
// onDrop: (layout: Layout, item: ?LayoutItem, e: Event) // => void
//
// // Ref for getting a reference for the grid's wrapping div.
// // You can use this instead of a regular ref and the deprecated `ReactDOM.findDOMNode()`` function.
// innerRef: ?React.Ref<"div">
}
external interface GridItemProps : RProps {
var i: String
var children: ReactElement
// These are all in grid units, not pixels
var x: Int
var y: Int
var w: Int
var h: Int
var minW: Int?
var maxW: Int?
var minH: Int?
var maxH: Int?
// If true, equal to `isDraggable: false, isResizable: false`.
var static: Boolean?
// If false, will not be draggable. Overrides `static`.
var isDraggable: Boolean?
// If false, will not be resizable. Overrides `static`.
var isResizable: Boolean?
// By default, a handle is only shown on the bottom-right (southeast) corner.
// Note that resizing from the top or left is generally not intuitive.
var resizeHandles: Array<String>?
// If true and draggable, item will be moved only within grid.
var isBounded: Boolean?
} for // @flow
import React, {PropTypes} from 'react';
import {DraggableCore} from 'react-draggable';
import {Resizable} from 'react-resizable';
import {perc, setTopLeft, setTransform} from './utils';
import type {DragCallbackData, Position} from './utils';
type State = {
resizing: ?{width: number, height: number},
dragging: ?{top: number, left: number},
className: string
};
/**
* An individual item within a ReactGridLayout.
*/
export default class GridItem extends React.Component {
static propTypes = {
// Children must be only a single element
children: PropTypes.element,
// General grid attributes
cols: PropTypes.number.isRequired,
containerWidth: PropTypes.number.isRequired,
rowHeight: PropTypes.number.isRequired,
margin: PropTypes.array.isRequired,
maxRows: PropTypes.number.isRequired,
containerPadding: PropTypes.array.isRequired,
// These are all in grid units
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
w: PropTypes.number.isRequired,
h: PropTypes.number.isRequired,
// All optional
minW: function (props, propName, componentName) {
const value = props[propName];
if (typeof value !== 'number') return new Error('minWidth not Number');
if (value > props.w || value > props.maxW) return new Error('minWidth larger than item width/maxWidth');
},
maxW: function (props, propName, componentName) {
const value = props[propName];
if (typeof value !== 'number') return new Error('maxWidth not Number');
if (value < props.w || value < props.minW) return new Error('maxWidth smaller than item width/minWidth');
},
minH: function (props, propName, componentName) {
const value = props[propName];
if (typeof value !== 'number') return new Error('minHeight not Number');
if (value > props.h || value > props.maxH) return new Error('minHeight larger than item height/maxHeight');
},
maxH: function (props, propName, componentName) {
const value = props[propName];
if (typeof value !== 'number') return new Error('maxHeight not Number');
if (value < props.h || value < props.minH) return new Error('maxHeight smaller than item height/minHeight');
},
// ID is nice to have for callbacks
i: PropTypes.string.isRequired,
// Functions
onDragStop: PropTypes.func,
onDragStart: PropTypes.func,
onDrag: PropTypes.func,
onResizeStop: PropTypes.func,
onResizeStart: PropTypes.func,
onResize: PropTypes.func,
// Flags
isDraggable: PropTypes.bool.isRequired,
isResizable: PropTypes.bool.isRequired,
static: PropTypes.bool,
// Use CSS transforms instead of top/left
useCSSTransforms: PropTypes.bool.isRequired,
// Others
className: PropTypes.string,
// Selector for draggable handle
handle: PropTypes.string,
// Selector for draggable cancel (see react-draggable)
cancel: PropTypes.string
};
static defaultProps = {
className: '',
cancel: '',
minH: 1,
minW: 1,
maxH: Infinity,
maxW: Infinity
};
state: State = {
resizing: null,
dragging: null,
className: ''
};
// Helper for generating column width
calcColWidth(): number {
const {margin, containerPadding, containerWidth, cols} = this.props;
return (containerWidth - (margin[0] * (cols - 1)) - (containerPadding[0] * 2)) / cols;
}
/**
* Return position on the page given an x, y, w, h.
* left, top, width, height are all in pixels.
* @param {Number} x X coordinate in grid units.
* @param {Number} y Y coordinate in grid units.
* @param {Number} w W coordinate in grid units.
* @param {Number} h H coordinate in grid units.
* @return {Object} Object containing coords.
*/
calcPosition(x: number, y: number, w: number, h: number, state: ?Object): Position {
const {margin, containerPadding, rowHeight} = this.props;
const colWidth = this.calcColWidth();
const out = {
left: Math.round((colWidth + margin[0]) * x + containerPadding[0]),
top: Math.round((rowHeight + margin[1]) * y + containerPadding[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * margin[0]),
height: h === Infinity ? h : Math.round(rowHeight * h + Math.max(0, h - 1) * margin[1])
};
if (state && state.resizing) {
out.width = Math.round(state.resizing.width);
out.height = Math.round(state.resizing.height);
}
if (state && state.dragging) {
out.top = Math.round(state.dragging.top);
out.left = Math.round(state.dragging.left);
}
return out;
}
/**
* Translate x and y coordinates from pixels to grid units.
* @param {Number} top Top position (relative to parent) in pixels.
* @param {Number} left Left position (relative to parent) in pixels.
* @return {Object} x and y in grid units.
*/
calcXY(top: number, left: number): {x: number, y: number} {
const {margin, cols, rowHeight, w, h, maxRows} = this.props;
const colWidth = this.calcColWidth();
// left = colWidth * x + margin * (x + 1)
// l = cx + m(x+1)
// l = cx + mx + m
// l - m = cx + mx
// l - m = x(c + m)
// (l - m) / (c + m) = x
// x = (left - margin) / (coldWidth + margin)
let x = Math.round((left - margin[0]) / (colWidth + margin[0]));
let y = Math.round((top - margin[1]) / (rowHeight + margin[1]));
// Capping
x = Math.max(Math.min(x, cols - w), 0);
y = Math.max(Math.min(y, maxRows - h), 0);
return {x, y};
}
/**
* Given a height and width in pixel values, calculate grid units.
* @param {Number} height Height in pixels.
* @param {Number} width Width in pixels.
* @return {Object} w, h as grid units.
*/
calcWH({height, width}: {height: number, width: number}): {w: number, h: number} {
const {margin, maxRows, cols, rowHeight, x, y} = this.props;
const colWidth = this.calcColWidth();
// width = colWidth * w - (margin * (w - 1))
// ...
// w = (width + margin) / (colWidth + margin)
let w = Math.round((width + margin[0]) / (colWidth + margin[0]));
let h = Math.round((height + margin[1]) / (rowHeight + margin[1]));
// Capping
w = Math.max(Math.min(w, cols - x), 0);
h = Math.max(Math.min(h, maxRows - y), 0);
return {w, h};
}
/**
* This is where we set the grid item's absolute placement. It gets a little tricky because we want to do it
* well when server rendering, and the only way to do that properly is to use percentage width/left because
* we don't know exactly what the browser viewport is.
* Unfortunately, CSS Transforms, which are great for performance, break in this instance because a percentage
* left is relative to the item itself, not its container! So we cannot use them on the server rendering pass.
*
* @param {Object} pos Position object with width, height, left, top.
* @return {Object} Style object.
*/
createStyle(pos: Position): {[key: string]: ?string} {
const {usePercentages, containerWidth, useCSSTransforms} = this.props;
let style;
// CSS Transforms support (default)
if (useCSSTransforms) {
style = setTransform(pos);
}
// top,left (slow)
else {
style = setTopLeft(pos);
// This is used for server rendering.
if (usePercentages) {
style.left = perc(pos.left / containerWidth);
style.width = perc(pos.width / containerWidth);
}
}
return style;
}
/**
* Mix a Draggable instance into a child.
* @param {Element} child Child element.
* @return {Element} Child wrapped in Draggable.
*/
mixinDraggable(child: React.Element<any>): React.Element<any> {
return (
<DraggableCore
onStart={this.onDragHandler('onDragStart')}
onDrag={this.onDragHandler('onDrag')}
onStop={this.onDragHandler('onDragStop')}
handle={this.props.handle}
cancel={".react-resizable-handle" + (this.props.cancel ? "," + this.props.cancel : "")}>
{child}
</DraggableCore>
);
}
/**
* Mix a Resizable instance into a child.
* @param {Element} child Child element.
* @param {Object} position Position object (pixel values)
* @return {Element} Child wrapped in Resizable.
*/
mixinResizable(child: React.Element<any>, position: Position): React.Element<any> {
const {cols, x, minW, minH, maxW, maxH} = this.props;
// This is the max possible width - doesn't go to infinity because of the width of the window
const maxWidth = this.calcPosition(0, 0, cols - x, 0).width;
// Calculate min/max constraints using our min & maxes
const mins = this.calcPosition(0, 0, minW, minH);
const maxes = this.calcPosition(0, 0, maxW, maxH);
const minConstraints = [mins.width, mins.height];
const maxConstraints = [Math.min(maxes.width, maxWidth), Math.min(maxes.height, Infinity)];
return (
<Resizable
width={position.width}
height={position.height}
minConstraints={minConstraints}
maxConstraints={maxConstraints}
onResizeStop={this.onResizeHandler('onResizeStop')}
onResizeStart={this.onResizeHandler('onResizeStart')}
onResize={this.onResizeHandler('onResize')}>
{child}
</Resizable>
);
}
/**
* Wrapper around drag events to provide more useful data.
* All drag events call the function with the given handler name,
* with the signature (index, x, y).
*
* @param {String} handlerName Handler name to wrap.
* @return {Function} Handler function.
*/
onDragHandler(handlerName:string) {
return (e:Event, {node, deltaX, deltaY}: DragCallbackData) => {
if (!this.props[handlerName]) return;
const newPosition: {top: number, left: number} = {top: 0, left: 0};
// Get new XY
switch (handlerName) {
case 'onDragStart':
// ToDo this wont work on nested parents
const parentRect = node.offsetParent.getBoundingClientRect();
const clientRect = node.getBoundingClientRect();
newPosition.left = clientRect.left - parentRect.left;
newPosition.top = clientRect.top - parentRect.top;
this.setState({dragging: newPosition});
break;
case 'onDrag':
if (!this.state.dragging) throw new Error('onDrag called before onDragStart.');
newPosition.left = this.state.dragging.left + deltaX;
newPosition.top = this.state.dragging.top + deltaY;
this.setState({dragging: newPosition});
break;
case 'onDragStop':
if (!this.state.dragging) throw new Error('onDragEnd called before onDragStart.');
newPosition.left = this.state.dragging.left;
newPosition.top = this.state.dragging.top;
this.setState({dragging: null});
break;
default:
throw new Error('onDragHandler called with unrecognized handlerName: ' + handlerName);
}
const {x, y} = this.calcXY(newPosition.top, newPosition.left);
this.props[handlerName](this.props.i, x, y, {e, node, newPosition});
};
}
/**
* Wrapper around drag events to provide more useful data.
* All drag events call the function with the given handler name,
* with the signature (index, x, y).
*
* @param {String} handlerName Handler name to wrap.
* @return {Function} Handler function.
*/
onResizeHandler(handlerName: string) {
return (e:Event, {node, size}: {node: HTMLElement, size: Position}) => {
if (!this.props[handlerName]) return;
const {cols, x, i, maxW, minW, maxH, minH} = this.props;
// Get new XY
let {w, h} = this.calcWH(size);
// Cap w at numCols
w = Math.min(w, cols - x);
// Ensure w is at least 1
w = Math.max(w, 1);
// Min/max capping
w = Math.max(Math.min(w, maxW), minW);
h = Math.max(Math.min(h, maxH), minH);
this.setState({resizing: handlerName === 'onResizeStop' ? null : size});
this.props[handlerName](i, w, h, {e, node, size});
};
}
render(): React.Element<any> {
const {x, y, w, h, isDraggable, isResizable, useCSSTransforms} = this.props;
const pos = this.calcPosition(x, y, w, h, this.state);
const child = React.Children.only(this.props.children);
// Create the child element. We clone the existing element but modify its className and style.
let newChild = React.cloneElement(child, {
// Munge a classname. Use passed in classnames and resizing.
// React will merge the classNames.
className: [
'react-grid-item',
child.props.className || '',
this.props.className,
this.props.static ? 'static' : '',
this.state.resizing ? 'resizing' : '',
this.state.dragging ? 'react-draggable-dragging' : '',
useCSSTransforms ? 'cssTransforms' : ''
].join(' '),
// We can set the width and height on the child, but unfortunately we can't set the position.
style: {...this.props.style, ...child.props.style, ...this.createStyle(pos)}
});
// Resizable support. This is usually on but the user can toggle it off.
if (isResizable) newChild = this.mixinResizable(newChild, pos);
// Draggable support. This is always on, except for with placeholders.
if (isDraggable) newChild = this.mixinDraggable(newChild);
return newChild;
}
} |
Beta Was this translation helpful? Give feedback.
-
well I am continuing my "journey" to kotlinjs with blueprintjs ... also dived a bit deeper into dukat. also having all React Types in wrapped Kotlinjs (by dukat). now trying to get @blueprintjs/select module things to work ... so if you fancy ... trying to wrap blueprintjs to kotlinjs ... @blueprintjs/core package worked fine so far, but I got stuck on the select package.
my try is:
so itemRenderer is
how can I implement such a funtion returning a ReactElement but not being "in scope" ?? my try so far is:
but obviously the lambda has no child(MenuItem::class) method I can use ... If I bother you too much, just say a word :) |
Beta Was this translation helpful? Give feedback.
-
Hi, I am a backend developer doing my first steps with frontend (still loving typesafe kotlinJS now in multiplatform projects). As I am playing around with tutorials and ramp up my knowledge (on how to integrate/import external npm packages), I just copied your
sw-ui/src/main/kotlin/com/palantir/blueprintjs/
package contents into my project and it works like a charm! (I am jealous).As I tried to e.g. integrate https://github.com/STRML/react-grid-layout and failed to succeed, I ask myself "how did you come up with the code that wraps blueprintjs to be usable via kotlinJS?
How did you "know" which things to "extract" to kotlin from the javascript node module?
Any tips/recommendations/hints/pointers for me on how to do so?
Thanks in advance and I appologize for the off-topic question here ...
Beta Was this translation helpful? Give feedback.
All reactions