diff --git a/images/arrow-bottom.png b/images/arrow-bottom.png new file mode 100644 index 00000000..9539792e Binary files /dev/null and b/images/arrow-bottom.png differ diff --git a/images/arrow-bottom1.png b/images/arrow-bottom1.png new file mode 100644 index 00000000..3d73fc77 Binary files /dev/null and b/images/arrow-bottom1.png differ diff --git a/images/arrow-top.png b/images/arrow-top.png new file mode 100644 index 00000000..65ca815d Binary files /dev/null and b/images/arrow-top.png differ diff --git a/images/arrow-top1.png b/images/arrow-top1.png new file mode 100644 index 00000000..64f00a8e Binary files /dev/null and b/images/arrow-top1.png differ diff --git a/src/index.ts b/src/index.ts index cec71c8f..fdb95d58 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,7 @@ export { PropertySuggestionParams, PropertyScore } from './ontodia/widgets/conne export * from './ontodia/workspace/toolbar'; export { Workspace, WorkspaceProps, WorkspaceLanguage, renderTo } from './ontodia/workspace/workspace'; export { WorkspaceEventHandler, WorkspaceEventKey } from './ontodia/workspace/workspaceContext'; +export { DraggableHandle } from './ontodia/workspace/draggableHandle'; import * as InternalApi from './internalApi'; export { InternalApi }; diff --git a/src/ontodia/data/sparql/sparqlDataProviderSettings.ts b/src/ontodia/data/sparql/sparqlDataProviderSettings.ts index 3a3696bf..7c6881ce 100644 --- a/src/ontodia/data/sparql/sparqlDataProviderSettings.ts +++ b/src/ontodia/data/sparql/sparqlDataProviderSettings.ts @@ -461,3 +461,13 @@ const DBPediaOverride: Partial = { `, }; export const DBPediaSettings: SparqlDataProviderSettings = {...OWLRDFSSettings, ...DBPediaOverride}; + +const StardogOverride: Partial = { + fullTextSearch: { + prefix: '', + queryPattern: ` + ?inst rdfs:label ?searchLabel. + (?searchLabel ?score) "\${text}". + `} +}; +export const StardogSettings: SparqlDataProviderSettings = {...OWLRDFSSettings, ...StardogOverride}; diff --git a/src/ontodia/widgets/dialog.tsx b/src/ontodia/widgets/dialog.tsx index 9f735d48..950e2386 100644 --- a/src/ontodia/widgets/dialog.tsx +++ b/src/ontodia/widgets/dialog.tsx @@ -10,26 +10,43 @@ import { computePolylineLength, getPointAlongPolyline, Vector, - Rect, } from '../diagram/geometry'; +import { DraggableHandle } from '../workspace/draggableHandle'; + +const DEFAULT_WIDTH = 300; +const DEFAULT_HEIGHT = 300; +const MIN_WIDTH = 250; +const MIN_HEIGHT = 250; +const MAX_WIDTH = 800; +const MAX_HEIGHT = 800; -const WIDTH = 300; -const HEIGHT = 300; const ELEMENT_OFFSET = 40; const LINK_OFFSET = 20; const FOCUS_OFFSET = 20; +const CLASS_NAME = 'ontodia-dialog'; + export interface Props extends PaperWidgetProps { view: DiagramView; target: Element | Link; } -export class Dialog extends React.Component { +export interface State { + width?: number; + height?: number; +} + +export class Dialog extends React.Component { private unsubscribeFromTarget: Unsubscribe | undefined = undefined; private readonly handler = new EventObserver(); private updateAll = () => this.forceUpdate(); + constructor(props: Props) { + super(props); + this.state = {}; + } + componentDidMount() { this.listenToTarget(this.props.target); this.focusOn(); @@ -95,7 +112,7 @@ export class Dialog extends React.Component { return { x: x1 + ELEMENT_OFFSET, - y: (y0 + y1) / 2 - (HEIGHT / 2), + y: (y0 + y1) / 2 - (DEFAULT_HEIGHT / 2), }; } @@ -151,8 +168,8 @@ export class Dialog extends React.Component { y: y - FOCUS_OFFSET, }; const max = { - x: min.x + WIDTH + FOCUS_OFFSET * 2, - y: min.y + HEIGHT + FOCUS_OFFSET * 2, + x: min.x + DEFAULT_WIDTH + FOCUS_OFFSET * 2, + y: min.y + DEFAULT_HEIGHT + FOCUS_OFFSET * 2, }; return {min, max}; } @@ -190,13 +207,66 @@ export class Dialog extends React.Component { paperArea.centerTo(paperCenter); } + private startSize: Vector; + private onStartDragging = (e: React.MouseEvent) => { + this.preventSelection(); + this.startSize = {x: this.state.width || DEFAULT_WIDTH, y: this.state.height || DEFAULT_HEIGHT}; + } + + private onDragHandleBottom = (e: MouseEvent, dx: number, dy: number) => { + const height = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, this.startSize.y + dy)); + this.setState({height}); + } + + private onDragHandleRight = (e: MouseEvent, dx: number) => { + const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, this.startSize.x + dx)); + this.setState({width}); + } + + private onDragHandleBottomRight = (e: MouseEvent, dx: number, dy: number) => { + const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, this.startSize.x + dx)); + const height = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, this.startSize.y + dy)); + this.setState({width, height}); + } + + private preventSelection = () => { + const onMouseUp = () => { + document.body.classList.remove('ontodia--unselectable'); + document.removeEventListener('mouseup', onMouseUp); + }; + document.addEventListener('mouseup', onMouseUp); + document.body.classList.add('ontodia--unselectable'); + } + render() { const {x, y} = this.calculatePosition(); - const style = {top: y, left: x, height: HEIGHT, width: WIDTH}; + const width = this.state.width || DEFAULT_WIDTH; + const height = this.state.height || DEFAULT_HEIGHT; + const style = { + top: y, + left: x, + width, + height, + }; return ( -
+
{this.props.children} + + + + + +
); } diff --git a/src/ontodia/workspace/resizableSidebar.tsx b/src/ontodia/workspace/resizableSidebar.tsx index 7d4d9238..289576d8 100644 --- a/src/ontodia/workspace/resizableSidebar.tsx +++ b/src/ontodia/workspace/resizableSidebar.tsx @@ -5,9 +5,9 @@ import { DraggableHandle } from './draggableHandle'; export interface Props { className?: string; dockSide?: DockSide; - defaultWidth?: number; - minWidth?: number; - maxWidth?: number; + defaultLength?: number; + minLength?: number; + maxLength?: number; isOpen?: boolean; onOpenOrClose?: (open: boolean) => void; onStartResize: () => void; @@ -17,11 +17,13 @@ export interface Props { export enum DockSide { Left = 1, Right, + Top, + Bottom, } export interface State { readonly open?: boolean; - readonly width?: number; + readonly length?: number; } const CLASS_NAME = 'ontodia-drag-resizable-column'; @@ -29,9 +31,9 @@ const CLASS_NAME = 'ontodia-drag-resizable-column'; export class ResizableSidebar extends React.Component { static readonly defaultProps: Partial = { dockSide: DockSide.Left, - minWidth: 0, - maxWidth: 500, - defaultWidth: 275, + minLength: 0, + maxLength: 500, + defaultLength: 275, isOpen: true, }; @@ -41,7 +43,7 @@ export class ResizableSidebar extends React.Component { super(props); this.state = { open: this.props.isOpen, - width: this.defaultWidth(), + length: this.defaultWidth(), }; } @@ -52,21 +54,37 @@ export class ResizableSidebar extends React.Component { } private defaultWidth() { - const {defaultWidth, maxWidth} = this.props; - return Math.min(defaultWidth, maxWidth); + const {defaultLength, maxLength} = this.props; + return Math.min(defaultLength, maxLength); + } + + private getSideClass() { + switch (this.props.dockSide) { + case DockSide.Left: return `${CLASS_NAME}--docked-left`; + case DockSide.Right: return `${CLASS_NAME}--docked-right`; + case DockSide.Top: return `${CLASS_NAME}--docked-top`; + case DockSide.Bottom: return `${CLASS_NAME}--docked-bottom`; + default: return 'docked-right'; + } + } + + private get isHorizontal(): boolean { + return this.props.dockSide === DockSide.Top || + this.props.dockSide === DockSide.Bottom; } render() { - const isDockedLeft = this.props.dockSide === DockSide.Left; - const {open, width} = this.state; + const {open, length} = this.state; const className = `${CLASS_NAME} ` + - `${CLASS_NAME}--${isDockedLeft ? 'docked-left' : 'docked-right'} ` + + `${this.getSideClass()} ` + `${CLASS_NAME}--${open ? 'opened' : 'closed'} ` + `${this.props.className || ''}`; + const style: any = {}; + style[this.isHorizontal ? 'height' : 'width'] = open ? length : 0; return
+ style={style}> {open ? this.props.children : null} { } private onBeginDragHandle = () => { - this.originWidth = this.state.open ? this.state.width : 0; + this.originWidth = this.state.open ? this.state.length : 0; this.props.onStartResize(); } private onDragHandle = (e: MouseEvent, dx: number, dy: number) => { - let xDifference = dx; + let difference = this.isHorizontal ? dy : dx; if (this.props.dockSide === DockSide.Right) { - xDifference = -xDifference; + difference = -difference; } - const newWidth = this.originWidth + xDifference; - const clampedWidth = Math.max(Math.min(newWidth, this.props.maxWidth), this.props.minWidth); - this.toggle({open: clampedWidth > this.props.minWidth, newWidth: clampedWidth}); + const newWidth = this.originWidth + difference; + const clampedWidth = Math.max(Math.min(newWidth, this.props.maxLength), this.props.minLength); + const isOpen = this.props.minLength > 0 || clampedWidth > this.props.minLength; + this.toggle({open: isOpen, newWidth: clampedWidth}); } private toggle(params: { @@ -105,11 +124,11 @@ export class ResizableSidebar extends React.Component { } }; - const useDefaultWidth = open && this.state.width === 0 && newWidth === undefined; + const useDefaultWidth = open && this.state.length === 0 && newWidth === undefined; if (useDefaultWidth) { - this.setState({open, width: this.defaultWidth()}, onStateChanged); + this.setState({open, length: this.defaultWidth()}, onStateChanged); } else { - this.setState(newWidth === undefined ? {open} : {open, width: newWidth}, onStateChanged); + this.setState(newWidth === undefined ? {open} : {open, length: newWidth}, onStateChanged); } } } diff --git a/styles/widgets/_dialog.scss b/styles/widgets/_dialog.scss index c33656c5..323889f5 100644 --- a/styles/widgets/_dialog.scss +++ b/styles/widgets/_dialog.scss @@ -4,3 +4,56 @@ box-shadow: 0 4px 15px 7px rgba(51, 51, 51, 0.05); position: absolute; } + +.ontodia-dialog__bottom-right-handle { + position: absolute; + bottom: 0; + right: 0; + width: 0; + height: 0; + border-style: solid; + border-width: 0 0 10px 10px; + border-color: transparent transparent #00000060 transparent; + cursor: nwse-resize; + + &::before { + content: ""; + position: absolute; + bottom: -10px; + right: 0; + width: 0; + height: 0; + border-style: solid; + border-width: 0 0 5px 5px; + border-color: transparent transparent #00000060 transparent; + } + + &:hover { + border-color: transparent transparent #00000080 transparent; + } +} + +.ontodia-dialog__bottom-handle, .ontodia-dialog__right-handle { + position: absolute; + opacity: 0; + background-color: black; + + &:hover { + opacity: 0.1; + } +} + +.ontodia-dialog__bottom-handle { + bottom: 0; + width: 100%; + height: 5px; + cursor: ns-resize; +} + +.ontodia-dialog__right-handle { + top: 0; + right: 0; + width: 5px; + height: 100%; + cursor: ew-resize; +} \ No newline at end of file diff --git a/styles/workspace/_resizableSidebar.scss b/styles/workspace/_resizableSidebar.scss index d88b3df3..36ae3a95 100644 --- a/styles/workspace/_resizableSidebar.scss +++ b/styles/workspace/_resizableSidebar.scss @@ -1,3 +1,23 @@ +.ontodia-drag-resizable-column.ontodia-drag-resizable-column--docked-bottom, +.ontodia-drag-resizable-column.ontodia-drag-resizable-column--docked-top { + .ontodia-drag-resizable-column__handle { + height: 8px; + width: 100%; + top: initial; + left: 0; + cursor: ns-resize; + } + + .ontodia-drag-resizable-column__handle-btn { + height: 100%; + width: 40px; + top: 0; + left: 50%; + margin-left: -20px; + margin-top: 0px; + } +} + .ontodia-drag-resizable-column { display: flex; flex-direction: column; @@ -5,10 +25,11 @@ &__handle { background: #fff url("../images/resizable-column-handle.png") repeat; + width: 8px; height: 100%; position: absolute; top: 0; - width: 8px; + left: initial; z-index: 2; -webkit-transition: 0.3s; -moz-transition: 0.3s; @@ -70,4 +91,18 @@ &::before { background-image: url("../images/arrow-right.png"); } &:hover::before { background-image: url("../images/arrow-right1.png"); } } + + &--docked-top { margin-bottom: 8px; } + &--docked-top &__handle { bottom: -8px; } + &--docked-top &__handle-btn { + &::before { background-image: url("../images/arrow-top.png"); } + &:hover::before { background-image: url("../images/arrow-top1.png"); } + } + + &--docked-bottom { margin-top: 8px; } + &--docked-bottom &__handle { top: -8px; } + &--docked-bottom &__handle-btn { + &::before { background-image: url("../images/arrow-bottom.png"); } + &:hover::before { background-image: url("../images/arrow-bottom1.png"); } + } }