Merge remote-tracking branch 'origin/devel' into devel

This commit is contained in:
Serge Rider
2021-03-26 18:00:32 +03:00
27 changed files with 834 additions and 51 deletions
@@ -3,3 +3,4 @@ export * from './SplitControls';
export * from './Pane';
export * from './ResizerControls';
export * from './Split';
export * from './useSplit';
@@ -0,0 +1,14 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ISplitState, SplitContext } from 'go-split';
import { useContext } from 'react';
export function useSplit(): ISplitState {
return useContext(SplitContext);
}
@@ -1,24 +1,24 @@
const { resolve, join } = require('path');
const ModuleDependencyWarning = require("webpack/lib/ModuleDependencyWarning");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const { resolve, join } = require('path')
const ModuleDependencyWarning = require('webpack/lib/ModuleDependencyWarning')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
// const ESLintPlugin = require('eslint-webpack-plugin');
class IgnoreNotFoundExportPlugin {
apply(compiler) {
const messageRegExp = /export '.*'( \(imported as '.*'\))? was not found in/
function doneHook(stats) {
stats.compilation.warnings = stats.compilation.warnings.filter(function (warn) {
apply (compiler) {
const messageRegExp = /export '.*'( \(imported as '.*'\))? was not found in/;
function doneHook (stats) {
stats.compilation.warnings = stats.compilation.warnings.filter(warn => {
if (warn instanceof ModuleDependencyWarning && messageRegExp.test(warn.message)) {
return false
return false;
}
return true;
})
return true
});
}
if (compiler.hooks) {
compiler.hooks.done.tap("IgnoreNotFoundExportPlugin", doneHook)
compiler.hooks.done.tap('IgnoreNotFoundExportPlugin', doneHook);
} else {
compiler.plugin("done", doneHook)
compiler.plugin('done', doneHook);
}
}
}
@@ -26,38 +26,38 @@ class IgnoreNotFoundExportPlugin {
const nodeModules = [
resolve('node_modules'), // product
resolve('../../node_modules'), // workspace
resolve('../../node_modules/@cloudbeaver/core-cli/node_modules') // core-cli
]
resolve('../../node_modules/@cloudbeaver/core-cli/node_modules'), // core-cli
];
module.exports = (env, argv) => {
process.env.NODE_ENV = argv.mode;
const devTool = 'source-map' in env && 'source-map';
const devMode = argv.mode !== 'production';
function getBaseStyleLoaders() {
const loaders = [];
process.env.NODE_ENV = argv.mode
const devTool = 'source-map' in env && 'source-map'
const devMode = argv.mode !== 'production'
function getBaseStyleLoaders () {
const loaders = []
// Broke styles order in dev mode
// if(devMode) {
// loaders.push('style-loader')
// }else{
loaders.push(MiniCssExtractPlugin.loader);
// if (devMode) {
// loaders.push('style-loader');
// } else {
loaders.push(MiniCssExtractPlugin.loader)
// }
return loaders;
return loaders
}
function generateStyleLoaders(options = { hasModule: false }) {
const moduleScope = options.hasModule ? 'local' : 'global';
function generateStyleLoaders (options = { hasModule: false }) {
const moduleScope = options.hasModule ? 'local' : 'global'
const modules = {
mode: moduleScope,
localIdentName: '[local]___[hash:base64:5]',
};
}
const postCssPlugins = [
require('postcss-preset-env')({ stage: 0 }),
// require('postcss-discard-comments'),
require('reshadow/postcss')({ scopeBehaviour: moduleScope })
];
require('reshadow/postcss')({ scopeBehaviour: moduleScope }),
]
return [
...getBaseStyleLoaders(),
@@ -66,15 +66,16 @@ module.exports = (env, argv) => {
options: {
modules: modules,
sourceMap: true,
}
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: postCssPlugins,
sourceMap: true,
}
},
},
},
{
@@ -83,11 +84,11 @@ module.exports = (env, argv) => {
sourceMap: true,
sassOptions: {
implementation: require('node-sass'),
includePaths: nodeModules
includePaths: nodeModules,
},
}
}
];
},
},
]
}
var babelLoader = {
@@ -95,7 +96,7 @@ module.exports = (env, argv) => {
options: {
configFile: join(__dirname, 'babel.config.js'),
},
}
};
return {
// target: !devMode ? "web" : "browserslist",
@@ -111,7 +112,7 @@ module.exports = (env, argv) => {
},
},
resolveLoader: {
modules: nodeModules
modules: nodeModules,
},
module: {
rules: [
@@ -124,8 +125,8 @@ module.exports = (env, argv) => {
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
babelLoader
]
babelLoader,
],
},
{
test: /\.(css|s[ac]ss)$/,
@@ -139,15 +140,23 @@ module.exports = (env, argv) => {
use: [
...getBaseStyleLoaders(),
'css-loader',
'sass-loader'
]
'sass-loader',
],
},
{
use: generateStyleLoaders({ hasModule: false }),
}
]
},
],
},
]
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
},
],
},
],
},
devtool: devTool,
plugins: [
@@ -168,6 +177,6 @@ module.exports = (env, argv) => {
chunkFilename: devMode ? '[id].css' : '[id].[contenthash].css',
ignoreOrder: false, // Enable to remove warnings about conflicting order
}),
]
};
};
],
}
}
+2 -1
View File
@@ -44,10 +44,11 @@
"source-map-loader": "^2.0.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.0.14",
"url-loader": "^4.1.1",
"webpack": "^5.15.0",
"webpack-bundle-analyzer": "^4.3.0",
"webpack-cli": "^4.3.1",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3"
}
}
}
@@ -24,6 +24,7 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
const selectionContext = useContext(DataGridSelectionContext);
const editingContext = useContext(EditingContext);
const editor = dataGridContext?.model.source.getEditor(dataGridContext.resultIndex);
const dataGridApi = dataGridContext?.getDataGridApi();
const classes: string[] = [];
const { rowIdx, column } = props;
@@ -41,6 +42,10 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
}
const handleMouseDown = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (dataGridApi) {
dataGridApi.selectCell({ idx: column.idx, rowIdx });
}
selectionContext?.select(
{
colIdx: column.idx,
@@ -7,6 +7,7 @@
*/
import { createContext } from 'react';
import type { DataGridHandle } from 'react-data-grid';
import type { IExecutor } from '@cloudbeaver/core-executor';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
@@ -21,6 +22,7 @@ export interface IDataGridContext {
resultIndex: number;
columnResize: IExecutor<IColumnResizeInfo>;
getEditorPortal: () => HTMLDivElement | null;
getDataGridApi: () => DataGridHandle | null;
}
export const DataGridContext = createContext<IDataGridContext | null>(null);
@@ -0,0 +1,16 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IDatabaseDataAction } from '../IDatabaseDataAction';
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
import type { IResultSetElementKey } from './ResultSet/IResultSetElementKey';
export interface IDatabaseResultDataAction<TKey, TResult extends IDatabaseDataResult>
extends IDatabaseDataAction<TResult> {
getCellValue: (cell: IResultSetElementKey) => any;
}
@@ -0,0 +1,29 @@
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IDatabaseResultDataAction } from '../IDatabaseDataAction';
import type { IResultSetElementKey } from './IResultSetElementKey';
@databaseDataAction()
export class ResultSetDataAction implements IDatabaseResultDataAction<IResultSetElementKey, IDatabaseResultSet> {
static dataFormat = ResultDataFormat.Resultset;
result: IDatabaseResultSet;
constructor(
result: IDatabaseResultSet) {
this.result = result;
}
getCellValue(cell: IResultSetElementKey): any {
if (cell.row === undefined || cell.column === undefined || !this.result.data?.rows) {
return undefined;
}
return this.result.data.rows[cell.row][cell.column];
}
updateResult(result: IDatabaseResultSet) {
this.result = result;
}
}
@@ -7,6 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { useRef, useState } from 'react';
import styled, { css } from 'reshadow';
import { TabList, TabPanelList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-blocks';
@@ -69,9 +70,29 @@ export const ValuePanel: DataPresentationComponent<any, IDatabaseResultSet> = ob
resultIndex,
}) {
const service = useService(DataValuePanelService);
const [currentTabId, setCurrentTabId] = useState('');
const lastTabId = useRef('');
const displayed = service.getDisplayed({ dataFormat, model, resultIndex });
if (displayed.length > 0) {
const firstTabId = displayed[0].key;
if (firstTabId !== lastTabId.current) {
setCurrentTabId(firstTabId);
lastTabId.current = firstTabId;
}
}
return styled(useStyles(styles, UNDERLINE_TAB_STYLES))(
<TabsState container={service.tabs} dataFormat={dataFormat} model={model} resultIndex={resultIndex} lazy>
<TabsState
currentTabId={currentTabId}
container={service.tabs}
dataFormat={dataFormat}
model={model}
resultIndex={resultIndex}
lazy
onChange={tab => setCurrentTabId(tab.tabId)}
>
<TabList style={[styles, UNDERLINE_TAB_STYLES]} />
<TabPanelList style={[styles, UNDERLINE_TAB_STYLES]} />
</TabsState>
@@ -23,9 +23,10 @@ export class TextValuePresentationBootstrap extends Bootstrap {
key: 'text-presentation',
options: { dataFormat: [ResultDataFormat.Resultset] },
name: 'data_viewer_presentation_value_text_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => TextValuePresentation,
});
}
load(): void {}
load(): void { }
}
@@ -5,6 +5,7 @@ export default manifest;
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetElementKey';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataAction';
export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator';
export * from './DatabaseDataModel/Actions/IDatabaseDataFormatAction';
export * from './DatabaseDataModel/Actions/IDatabaseDataSelectAction';
@@ -23,6 +24,7 @@ export * from './DatabaseDataModel/IDatabaseResultSet';
// All Services and Components that is provided by this plugin should be exported here
export * from './TableViewer/TableViewerStorageService';
export * from './TableViewer/ValuePanel/DataValuePanelService';
export * from './TableViewer/TableViewer';
export * from './TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService';
@@ -0,0 +1,17 @@
# dependencies
/node_modules
# testing
/coverage
# production
/lib
# misc
.DS_Store
.env*
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -0,0 +1,48 @@
{
"name": "@cloudbeaver/plugin-gis-viewer",
"sideEffects": false,
"version": "0.1.0",
"description": "",
"license": "Apache-2.0",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "src/index.ts",
"scripts": {
"prebuild": "rimraf lib/*",
"build": "webpack --config ../../configs/webpack.plugin.config.js",
"postbuild": "tsc",
"lint": "eslint ./src/ --ext .ts,.tsx",
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
},
"peerDependencies": {
"@cloudbeaver/core-app": "^0.1.0",
"@cloudbeaver/core-blocks": "^0.1.0",
"@cloudbeaver/core-di": "^0.1.0",
"@cloudbeaver/core-dialogs": "^0.1.0",
"@cloudbeaver/core-events": "^0.1.0",
"@cloudbeaver/core-extensions": "^0.1.0",
"@cloudbeaver/core-localization": "^0.1.0",
"@cloudbeaver/core-plugin": "^0.1.0",
"@cloudbeaver/core-product": "^0.1.0",
"@cloudbeaver/core-root": "^0.1.0",
"@cloudbeaver/core-sdk": "^0.1.0",
"@cloudbeaver/core-settings": "^0.1.0",
"@cloudbeaver/core-theming": "^0.1.0",
"@cloudbeaver/core-utils": "^0.1.0",
"@cloudbeaver/core-view": "^0.1.0",
"@cloudbeaver/plugin-data-viewer": "^0.1.0",
"mobx": "^5.x.x",
"mobx-react-lite": "^3.x.x",
"react": "^16.x.x",
"reshadow": "^0.x.x",
"rxjs": "^6.x.x"
},
"dependencies": {
"leaflet": "^1.7.1",
"react-leaflet": "^3.1.0",
"terraformer-wkt-parser": "^1.2.1"
},
"devDependencies": {
"@types/react-leaflet": "^2.8.1"
}
}
@@ -0,0 +1,82 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useCallback, useMemo } from 'react';
import wkt from 'terraformer-wkt-parser';
import { TabContainerPanelComponent, TextPlaceholder } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { IDataValuePanelProps, IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer';
import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap';
import { ResultSetGISAction } from './ResultSetGISAction';
export const GISValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(function GISValuePresentation({
model,
resultIndex,
}) {
const translate = useTranslate();
const modelResultData = model.getResult(resultIndex);
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const gis = model.source.getAction(resultIndex, ResultSetGISAction);
const focusedCell = selection.getFocusedElement() as Required<IResultSetElementKey> | null;
const selectedCells = selection.getSelectedElements();
if (selectedCells.length === 0 && focusedCell) {
selectedCells.push(focusedCell);
}
const parsedGISData = useMemo(() => {
const result: IGeoJSONFeature[] = [];
for (const cell of selectedCells) {
const cellValue = gis.getCellValue(cell);
if (!cellValue) {
continue;
}
const parsedCellValue = wkt.parse(cellValue.mapText || cellValue.text);
result.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } });
}
return result;
}, [selectedCells, gis]);
const getAssociatedValues = useCallback((cell: Required<IResultSetElementKey>): IAssociatedValue[] => {
if (!modelResultData?.data?.columns || !modelResultData?.data?.rows) {
return [];
}
const { column: columnIndex, row: rowIndex } = cell;
return modelResultData.data.columns.reduce((result: IAssociatedValue[], column, i) => {
if (i !== columnIndex) {
result.push({
key: column.name!,
value: model.source
.getEditor(resultIndex)
.getCell(rowIndex, i),
});
}
return result;
}, []);
}, [modelResultData, model, resultIndex]);
if (!parsedGISData.length) {
return <TextPlaceholder>{translate('gis_presentation_placeholder')}</TextPlaceholder>;
}
return (
<LeafletMap geoJSON={parsedGISData} getAssociatedValues={getAssociatedValues} />
);
});
@@ -0,0 +1,57 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DataValuePanelService, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
import { GISValuePresentation } from './GISValuePresentation';
import { ResultSetGISAction } from './ResultSetGISAction';
@injectable()
export class GISViewerBootstrap extends Bootstrap {
constructor(private readonly dataValuePanelService: DataValuePanelService) {
super();
}
register(): void | Promise<void> {
this.dataValuePanelService.add({
key: 'gis-presentation',
options: { dataFormat: [ResultDataFormat.Resultset] },
name: 'gis_presentation_title',
order: 10,
panel: () => GISValuePresentation,
isHidden: (_, context) => {
if (!context || !context.model.source.hasResult(context.resultIndex)) {
return true;
}
const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction);
const gis = context.model.source.getAction(context.resultIndex, ResultSetGISAction);
const focusedElement = selection.getFocusedElement();
if (focusedElement) {
return !gis.isGISFormat(focusedElement);
}
const selectedCells = selection.getSelectedElements();
if (selectedCells.length > 0) {
const firstSelectedCell = selectedCells[0];
return !gis.isGISFormat(firstSelectedCell);
}
return true;
},
});
}
load(): void { }
}
@@ -0,0 +1,18 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IDatabaseDataResult, IDatabaseDataAction, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer';
import type { IGISType } from './ResultSetGISAction';
export interface IDatabaseDataGISAction<TKey, TResult extends IDatabaseDataResult>
extends IDatabaseDataAction<TResult> {
getGISDataFor: (selectedCells: Array<Required<IResultSetElementKey>>) => Array<Required<IResultSetElementKey>>;
getCellValue: (cell: IResultSetElementKey) => IGISType | undefined;
isGISFormat: (cell: IResultSetElementKey) => boolean;
}
@@ -0,0 +1,215 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="./react-leaflet.d.ts" />
import 'leaflet/dist/leaflet.css';
import type geojson from 'geojson';
import leaflet from 'leaflet';
import { useCallback, useEffect, useState } from 'react';
import { MapContainer, GeoJSON, LayersControl, TileLayer } from 'react-leaflet';
import type { TileLayerProps } from 'react-leaflet';
import styled, { css } from 'reshadow';
import { useSplit } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer';
export interface IAssociatedValue {
key: string;
value: any;
}
interface IFeatureProperties {
srid: number;
associatedCell: Required<IResultSetElementKey>;
}
export interface IGeoJSONFeature extends GeoJSON.Feature<GeoJSON.GeometryObject, IFeatureProperties> {
type: 'Feature';
bbox?: geojson.BBox;
}
interface IBaseTile extends TileLayerProps {
name: string;
checked?: boolean;
}
interface Props {
geoJSON: IGeoJSONFeature[];
getAssociatedValues: (cell: Required<IResultSetElementKey>) => IAssociatedValue[];
}
const baseTiles: Record<'street' | 'topography', IBaseTile> = {
street: {
name: 'gis_presentation_base_tile_street_name',
checked: true,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
maxZoom: 18,
id: 'osm.streets',
},
topography: {
name: 'gis_presentation_base_tile_topography_name',
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://viewfinderpanoramas.org" target="_blank">SRTM</a>,'
+ ' &copy; <a href="https://opentopomap.org" target="_blank">OpenTopoMap</a>',
url: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
maxZoom: 17,
},
};
const geojsonMarkerOptions: leaflet.CircleMarkerOptions = {
radius: 4,
weight: 3,
};
const popupOption: leaflet.PopupOptions = {
closeButton: true,
minWidth: 260,
maxWidth: 800,
maxHeight: 500,
};
function polyStyle() {
const objColor = 'blue';
return {
weight: 2,
color: objColor,
};
}
function getCRS(feature?: IGeoJSONFeature): leaflet.CRS {
switch (feature?.properties?.srid) {
case 0:
return leaflet.CRS.Simple;
case 3857:
return leaflet.CRS.EPSG3857;
case 4326:
return leaflet.CRS.EPSG4326;
case 3395:
return leaflet.CRS.EPSG3395;
case 900913:
return leaflet.CRS.EPSG900913;
default:
return leaflet.CRS.EPSG3857;
}
}
const styles = css`
MapContainer {
width: 100%;
height: 100%;
}
`;
export const LeafletMap: React.FC<Props> = function LeafletMap({ geoJSON, getAssociatedValues }) {
const splitContext = useSplit();
const translate = useTranslate();
const [mapRef, setMapRef] = useState<leaflet.Map | null>(null);
const [geoJSONLayerRef, setGeoJSONLayerRef] = useState<leaflet.GeoJSON | null>(null);
const crs = getCRS(geoJSON[0]);
const onEachFeature = useCallback((feature: IGeoJSONFeature, layer: leaflet.Layer) => {
const associatedValues = getAssociatedValues(feature.properties.associatedCell);
if (associatedValues.length > 0) {
let popupContent = '';
popupContent += '<table>';
for (let i = 0; i < associatedValues.length; i++) {
const { key, value } = associatedValues[i];
if (value === null || value === undefined) {
continue;
}
popupContent += '<tr><td>' + key + '</td><td>' + value + '</td></tr>';
}
popupContent += '</table>';
layer.bindPopup(popupContent, popupOption);
}
}, [getAssociatedValues]);
useEffect(() => {
if (geoJSONLayerRef) {
geoJSONLayerRef.clearLayers();
for (let i = 0; i < geoJSON.length; i++) {
geoJSONLayerRef.addData(geoJSON[i]);
}
}
}, [geoJSON, geoJSONLayerRef]);
useEffect(() => {
if (mapRef && geoJSONLayerRef) {
mapRef.invalidateSize();
const bounds = geoJSONLayerRef.getBounds();
if (Object.keys(bounds).length <= 0) {
return;
}
if (crs === leaflet.CRS.Simple) {
const maxDimension = Math.max(bounds.getNorth() - bounds.getSouth(), bounds.getEast() - bounds.getWest());
mapRef.setMinZoom(-5);
if (maxDimension > 0) {
mapRef.fitBounds(bounds);
} else {
mapRef.setView(bounds.getCenter(), mapRef.getZoom());
}
} else {
mapRef.fitBounds(bounds);
}
}
}, [splitContext.isResizing, splitContext.mode, mapRef, geoJSONLayerRef, crs, geoJSON]);
return styled(styles)(
<MapContainer crs={crs} whenCreated={setMapRef} zoom={12}>
<GeoJSON
// data is not optional property, see react-leaflet.d.ts
// data={[]}
ref={setGeoJSONLayerRef}
style={polyStyle}
pointToLayer={(_, latlng) => leaflet.circleMarker(latlng, geojsonMarkerOptions)}
onEachFeature={onEachFeature}
/>
<LayersControl>
<LayersControl.BaseLayer
name={translate(baseTiles.street.name)}
checked={baseTiles.street.checked}
>
<TileLayer
attribution={baseTiles.street.attribution}
url={baseTiles.street.url}
maxZoom={baseTiles.street.maxZoom}
id={baseTiles.street.id}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer
name={translate(baseTiles.topography.name)}
checked={baseTiles.topography.checked}
>
<TileLayer
attribution={baseTiles.topography.attribution}
url={baseTiles.topography.url}
maxZoom={baseTiles.topography.maxZoom}
id={baseTiles.topography.id}
/>
</LayersControl.BaseLayer>
</LayersControl>
</MapContainer>
);
}
;
@@ -0,0 +1,32 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@injectable()
export class LocaleService extends Bootstrap {
constructor(private localizationService: LocalizationService) {
super();
}
register(): void | Promise<void> {
this.localizationService.addProvider(this.provider.bind(this));
}
load(): void | Promise<void> { }
private async provider(locale: string) {
switch (locale) {
case 'ru':
return (await import('./locales/ru')).default;
default:
return (await import('./locales/en')).default;
}
}
}
@@ -0,0 +1,72 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { databaseDataAction, IResultSetElementKey, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer';
import type { IDatabaseDataGISAction } from './IDatabaseDataGISAction';
export interface IGISType {
$type: string;
srid: number;
text: string;
mapText: string | null;
properties: Record<string, any> | null;
}
@databaseDataAction()
export class ResultSetGISAction implements IDatabaseDataGISAction<IResultSetElementKey, IDatabaseResultSet> {
private readonly GISValueType = 'geometry';
static dataFormat = ResultDataFormat.Resultset;
result: IDatabaseResultSet;
constructor(
result: IDatabaseResultSet) {
this.result = result;
}
isGISFormat(cell: IResultSetElementKey): boolean {
if (cell.row === undefined || cell.column === undefined) {
return false;
}
const value = this.result?.data?.rows?.[cell.row][cell.column];
if (value !== null && typeof value === 'object' && '$type' in value) {
return value.$type === this.GISValueType;
}
return false;
}
getGISDataFor(cells: Array<Required<IResultSetElementKey>>): Array<Required<IResultSetElementKey>> {
const result: Array<Required<IResultSetElementKey>> = [];
if (!this.result.data?.rows) {
return result;
}
for (const cell of cells) {
if (this.isGISFormat(cell)) {
result.push(cell);
}
}
return result;
}
getCellValue(cell: IResultSetElementKey): IGISType | undefined {
if (cell.row === undefined || cell.column === undefined || !this.result.data?.rows || !this.isGISFormat(cell)) {
return undefined;
}
return this.result.data.rows[cell.row][cell.column];
}
updateResult(result: IDatabaseResultSet) {
this.result = result;
}
}
@@ -0,0 +1,5 @@
import { manifest } from './manifest';
export default manifest;
export * from './ResultSetGISAction';
@@ -0,0 +1,6 @@
export default [
['gis_presentation_title', 'Map'],
['gis_presentation_placeholder', 'No data available'],
['gis_presentation_base_tile_street_name', 'Street'],
['gis_presentation_base_tile_topography_name', 'Topography'],
];
@@ -0,0 +1,6 @@
export default [
['gis_presentation_title', 'Карта'],
['gis_presentation_placeholder', 'Нет доступных данных'],
['gis_presentation_base_tile_street_name', 'Схема'],
['gis_presentation_base_tile_topography_name', 'Топографическая'],
];
@@ -0,0 +1,14 @@
import type { PluginManifest } from '@cloudbeaver/core-di';
import { GISViewerBootstrap } from './GISViewerBootstrap';
import { LocaleService } from './LocaleService';
export const manifest: PluginManifest = {
info: {
name: 'GIS Viewer plugin',
},
providers: [
GISViewerBootstrap,
LocaleService,
],
};
@@ -0,0 +1,15 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type geojson from 'geojson';
import * as ReactLeaflet from 'react-leaflet';
declare module 'react-leaflet' {
interface GeoJSONProps {
data?: geojson.GeoJsonObject;
}
}
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": [
"src"
],
"exclude": [
"**/node_modules",
"**/.*/"
],
}
@@ -10,6 +10,7 @@ import dataExport from '@cloudbeaver/plugin-data-export';
import spreadsheetNew from '@cloudbeaver/plugin-data-spreadsheet-new';
import dataViewer from '@cloudbeaver/plugin-data-viewer';
import ddlViewer from '@cloudbeaver/plugin-ddl-viewer';
import gisViewer from '@cloudbeaver/plugin-gis-viewer';
import objectViewer from '@cloudbeaver/plugin-object-viewer';
import productPlugin from '@cloudbeaver/plugin-product';
import sqlEditor from '@cloudbeaver/plugin-sql-editor';
@@ -25,6 +26,7 @@ const PLUGINS = [
connectionTemplate,
dataExport,
dataViewer,
gisViewer,
ddlViewer,
objectViewer,
sqlEditor,
+80
View File
@@ -2557,6 +2557,11 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.6.0.tgz#f022195afdfc942e088ee2101285a1d31c7d727f"
integrity sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw==
"@react-leaflet/core@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-1.0.2.tgz#39c6a73f61c666d5dcf673741cea2672fa4bbae1"
integrity sha512-QbleYZTMcgujAEyWGki8Lx6cXQqWkNtQlqf5c7NImlIp8bKW66bFpez/6EVatW7+p9WKBOEOVci/9W7WW70EZg==
"@reshadow/babel@^0.0.1-alpha.76", "@reshadow/babel@^0.0.1-issue-119.0":
version "0.0.1-issue-119.0"
resolved "https://registry.yarnpkg.com/@reshadow/babel/-/babel-0.0.1-issue-119.0.tgz#ef0e9228e06f821e3d2287f01569fb076153dbb5"
@@ -2745,6 +2750,16 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
"@types/geojson@*", "@types/geojson@^7946.0.0 || ^1.0.0":
version "7946.0.7"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==
"@types/geojson@^1.0.0":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf"
integrity sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==
"@types/glob@^7.1.1":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@@ -2792,6 +2807,13 @@
dependencies:
"@types/node" "*"
"@types/leaflet@*":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.7.0.tgz#3700fb5d29a8214fbd496565b08ec28e40cee808"
integrity sha512-ltv5jR+VjKSMtoDkxH61Rsbo0zLU7iqyOXpVPkAX4F+79fg2eymC7t0msWsfNaEZO1FGTIQATCCCQe+ijWoicg==
dependencies:
"@types/geojson" "*"
"@types/md5@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.2.1.tgz#0e2d65d8f6cc91aafbc9be5a4c08fee9250406ce"
@@ -2841,6 +2863,23 @@
dependencies:
"@types/react" "^16"
"@types/react-leaflet@^2.8.1":
version "2.8.1"
resolved "https://registry.yarnpkg.com/@types/react-leaflet/-/react-leaflet-2.8.1.tgz#acda592cf1b7dfe20ac81071c21c1e5521fe71a4"
integrity sha512-BbFyH9R/MecfXaz1Yt29oGBG8AxIKILgAniO61/kv9/sRe5/qj9yDxf3PW7M+nDl5Wn3V7Mp9inKF0LflV+KkQ==
dependencies:
"@types/leaflet" "*"
"@types/react" "*"
"@types/react@*":
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^16", "@types/react@^16.9.49":
version "16.14.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
@@ -2849,6 +2888,11 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
"@types/tern@*":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460"
@@ -8247,6 +8291,11 @@ latest-version@5.1.0:
dependencies:
package-json "^6.3.0"
leaflet@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
lerna@^3.22.1:
version "3.22.1"
resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62"
@@ -11102,6 +11151,13 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-leaflet@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-3.1.0.tgz#42deb5e454518016eff8bc85511ae58812f910f5"
integrity sha512-kdZS8NYbYFPmkQr7zSDR2gkKGFeWvkxqoqcmZEckzHL4d5c85dJ2gbbqhaPDpmWWgaRw9O29uA/77qpKmK4mTQ==
dependencies:
"@react-leaflet/core" "^1.0.2"
react-popper@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.4.tgz#d2ad3d2474ac9f1abf93df3099d408e5aa6a2e22"
@@ -12769,6 +12825,21 @@ temp-write@^3.4.0:
temp-dir "^1.0.0"
uuid "^3.0.1"
terraformer-wkt-parser@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.1.tgz#8041e2aeb0c9f2b4cbbec8ec2c5c00c45ddfee02"
integrity sha512-+CJyNLWb3lJ9RsZMTM66BY0MT3yIo4l4l22Jd9CrZuwzk54fsu4Sc7zejuS9fCITTuTQy3p06d4MZMVI7v5wSg==
dependencies:
"@types/geojson" "^1.0.0"
terraformer "~1.0.5"
terraformer@~1.0.5:
version "1.0.12"
resolved "https://registry.yarnpkg.com/terraformer/-/terraformer-1.0.12.tgz#39e08f9c753606421acce02e122440c72dfa12d3"
integrity sha512-MokUp0+MFal4CmJDVL6VAO1bKegeXcBM2RnPVfqcFIp2IIv8EbPAjG0j/vEy/vuKB8NVMMSF2vfpVS/QLe4DBg==
optionalDependencies:
"@types/geojson" "^7946.0.0 || ^1.0.0"
terser-webpack-plugin@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673"
@@ -13249,6 +13320,15 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
url-loader@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
dependencies:
loader-utils "^2.0.0"
mime-types "^2.1.27"
schema-utils "^3.0.0"
url-parse-lax@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"