diff --git a/ui/locales/app.de.json b/ui/locales/app.de.json index 8976af170..1075d03a9 100644 --- a/ui/locales/app.de.json +++ b/ui/locales/app.de.json @@ -1,4 +1,9 @@ { + "menu_layers": "Angezeigte Daten", + "menu_tools": "Werkzeuge", + "menu_share": "Teilen", + "menu_projects": "Projekte", + "menu_settings": "Einstellungen", "app_cancel_btn_label": "Abbrechen", "by": "von", "cam_configuration_header": "Kamera-Einstellungen", @@ -87,12 +92,16 @@ "dtd_slice_arrows_hidden": "Um die Slicing-Box zu ändern, schalten Sie die Überhöhung aus", "dtd_time_journey": "Zeitreise", "dtd_topic_kml_tag": "Themen-Objekt", - "dtd_user_content_label": "Eigenen Inhalt hinzufügen", "dtd_voxel_filter": "Filter öffnen", "dtd_zoom_to": "Heranzoomen", + "dtd_tab_labels": { + "catalog": "Datenkatalog", + "upload": "Upload", + "options": "Einstellungen" + }, "duplicate_to_project": "Zu Projekt duplizieren", "edit_project": "Projekt bearbeiten", - "header_search_placeholder": "Suche...", + "header_search_placeholder": "Suche nach...", "lsb_cesium_toolbar_label": "Cesium-Toolbar", "lsb_debug_tools": "Debugtools", "lsb_settings": "Einstellungen", diff --git a/ui/locales/app.en.json b/ui/locales/app.en.json index e7a72090d..c13414ebe 100644 --- a/ui/locales/app.en.json +++ b/ui/locales/app.en.json @@ -1,4 +1,9 @@ { + "menu_layers": "Angezeigte Daten", + "menu_tools": "Werkzeuge", + "menu_share": "Teilen", + "menu_projects": "Projekte", + "menu_settings": "Einstellungen", "app_cancel_btn_label": "Cancel", "by": "by", "cam_configuration_header": "Camera configuration", @@ -87,12 +92,16 @@ "dtd_slice_arrows_hidden": "To change the slicing box, switch off the exaggeration", "dtd_time_journey": "Journey through time", "dtd_topic_kml_tag": "Topic object", - "dtd_user_content_label": "Add user content", "dtd_voxel_filter": "Open Filter", "dtd_zoom_to": "Zoom to", + "dtd_tab_labels": { + "catalog": "Data Catalog", + "upload": "Upload", + "options": "Options" + }, "duplicate_to_project": "Duplicate to my projects", "edit_project": "Edit project", - "header_search_placeholder": "Search...", + "header_search_placeholder": "Search by...", "lsb_cesium_toolbar_label": "Cesium Toolbar", "lsb_debug_tools": "Debug tools", "lsb_settings": "Settings", diff --git a/ui/locales/app.fr.json b/ui/locales/app.fr.json index 182704e46..733a56f73 100644 --- a/ui/locales/app.fr.json +++ b/ui/locales/app.fr.json @@ -1,4 +1,9 @@ { + "menu_layers": "Données Affichées", + "menu_tools": "Outils", + "menu_share": "Partager", + "menu_projects": "Projets", + "menu_settings": "Paramètres", "app_cancel_btn_label": "Annuler", "by": "de", "cam_configuration_header": "Configuration de la caméra", @@ -87,12 +92,16 @@ "dtd_slice_arrows_hidden": "Pour modifier la boîte de découpage, désactivez l'exagération", "dtd_time_journey": "Voyage dans le temps", "dtd_topic_kml_tag": "Objet du thème", - "dtd_user_content_label": "Ajouter propre contenu", "dtd_voxel_filter": "Ouvrir le filtre", "dtd_zoom_to": "Zoom sur", + "dtd_tab_labels": { + "catalog": "Catalogue de données", + "upload": "Upload", + "options": "Réglages" + }, "duplicate_to_project": "Dupliquer comme projet", "edit_project": "Editer projet", - "header_search_placeholder": "Recherche...", + "header_search_placeholder": "Recherche par...", "lsb_cesium_toolbar_label": "Cesium Toolbar", "lsb_debug_tools": "Debug tools", "lsb_settings": "Paramètres", diff --git a/ui/locales/app.it.json b/ui/locales/app.it.json index 5f6ef7bfc..cc0f5e88d 100644 --- a/ui/locales/app.it.json +++ b/ui/locales/app.it.json @@ -1,4 +1,9 @@ { + "menu_layers": "Dati Visualizzati", + "menu_tools": "Strumenti", + "menu_share": "Condividi", + "menu_projects": "Progetti", + "menu_settings": "Impostazioni", "app_cancel_btn_label": "Cancellare", "by": "di", "cam_configuration_header": "Configurazione della camera", @@ -87,12 +92,16 @@ "dtd_slice_arrows_hidden": "Per modificare il riquadro di affettatura, disattivare l'esagerazione", "dtd_time_journey": "Viaggio nel tempo", "dtd_topic_kml_tag": "Oggetto di tema", - "dtd_user_content_label": "Aggiungere propio contenuto", "dtd_voxel_filter": "Aprire il filtro", "dtd_zoom_to": "Zoom su", + "dtd_tab_labels": { + "catalog": "Catalogo di dati", + "upload": "Upload", + "options": "Impostazioni" + }, "duplicate_to_project": "Duplicare nei progetti", "edit_project": "Editare progetto", - "header_search_placeholder": "Ricercare...", + "header_search_placeholder": "Ricercare per...", "lsb_cesium_toolbar_label": "Cesium Toolbar", "lsb_debug_tools": "Debug tools", "lsb_settings": "Impostazioni", diff --git a/ui/src/cesiumutils.ts b/ui/src/cesiumutils.ts index 8225e257c..0115628a3 100644 --- a/ui/src/cesiumutils.ts +++ b/ui/src/cesiumutils.ts @@ -1,609 +1,610 @@ -import { - ArcType, - BoundingSphere, - Camera, - Cartesian2, - Cartesian3, - Cartographic, - Color, - ColorMaterialProperty, ConstantPositionProperty, - ConstantProperty, - CustomDataSource, DataSource, Ellipsoid, - EntityCollection, - HeadingPitchRoll, - HeightReference, - JulianDate, - KmlDataSource, - Math as CMath, - Matrix3, - OrientedBoundingBox, - Plane, - Rectangle, - Scene, - Viewer -} from 'cesium'; -import type {GeometryTypes} from './toolbox/interfaces'; -import earcut from 'earcut'; -import {DEFAULT_UPLOADED_KML_COLOR} from './constants'; - -const julianDate = new JulianDate(); - -export function pickCenter(scene: Scene): Cartesian3 { - const camera = scene.camera; - const windowPosition = new Cartesian2( - scene.canvas.clientWidth / 2, - scene.canvas.clientHeight / 2 - ); - const ray = camera.getPickRay(windowPosition); - if (!ray) return camera.positionWC; - const center = scene.globe.pick(ray, scene); - return center !== undefined ? center : camera.positionWC; -} - -/** - * Return the position of the point, on the ellipsoid at the center of the Cesium viewport. - */ -export function pickCenterOnEllipsoid(scene: Scene): Cartesian3 | undefined { - const camera = scene.camera; - const windowPosition = new Cartesian2( - scene.canvas.clientWidth / 2, - scene.canvas.clientHeight / 2 - ); - return camera.pickEllipsoid(windowPosition); -} - -export function pickPositionOrVoxel(scene: Scene, windowPosition: Cartesian2): Cartesian3 { - const voxel = scene.pickVoxel(windowPosition); - if (voxel) { - return voxel.orientedBoundingBox.center; - } - return scene.pickPosition(windowPosition); -} - -/** - * Return the position of the point, on the map or object at the center of the Cesium viewport. - */ -export function pickCenterOnMapOrObject(scene: Scene): Cartesian3 { - const windowPosition = new Cartesian2( - scene.canvas.clientWidth / 2, - scene.canvas.clientHeight / 2 - ); - return pickPositionOrVoxel(scene, windowPosition); -} - -export function verticalDirectionRotate(camera: Camera, angle: number) { - const position = Cartesian3.normalize(camera.position, new Cartesian3()); - const up = Cartesian3.normalize(camera.up, new Cartesian3()); - - const pitch = CMath.toDegrees(camera.pitch); - if (pitch < -90 || pitch > 0) { - angle = -angle; - } - - const tangent = Cartesian3.cross(up, position, new Cartesian3()); - camera.rotate(tangent, angle); -} - -function getPolygonArea(positions: Cartesian3[], holes: number[] = []): number { - const indices = triangulate(positions, holes); - let area = 0; - - for (let i = 0; i < indices.length; i += 3) { - const vector1 = positions[indices[i]]; - const vector2 = positions[indices[i + 1]]; - const vector3 = positions[indices[i + 2]]; - //triangle sides - const a = Cartesian3.distance(vector3, vector2); - const b = Cartesian3.distance(vector1, vector3); - const c = Cartesian3.distance(vector1, vector2); - const p = (a + b + c) / 2; - const triangleArea = Math.sqrt((p - a) * (p - b) * (p - c) * p); - - area += triangleArea; - } - return area * Math.pow(10, -6); -} - -export type Measurements = { - positions: Cartesian3[], - type: GeometryTypes, - numberOfSegments: number, - segmentsLength: number[] - perimeter?: number - area?: number -} -/** - * Returns measurements for geometry - */ -export function getMeasurements(positions: Cartesian3[], type: GeometryTypes): Measurements { - const segmentsLength: number[] = []; - positions.forEach((p, key) => { - if (key > 0) { - segmentsLength.push(Cartesian3.distance(positions[key - 1], p) / 1000); - } - }); - const result: Measurements = { - numberOfSegments: positions.length - 1, - segmentsLength: segmentsLength.map(l => Number(l.toFixed(3))), - positions, - type, - }; - let perimeter = segmentsLength.reduce((a, b) => a + b, 0); - if (type === 'rectangle') { - perimeter *= 2; - } - result.perimeter = perimeter; - if (type === 'rectangle' || (type === 'polygon' && positions.length > 2)) { - result.area = getPolygonArea(positions); - } - return result; -} - -/** - * Sets height in meters for each cartesian3 position in array - * @param positions - * @param [height] - * @param [scene] - * @param assignBack assign value to initial position - */ -export function updateHeightForCartesianPositions( - positions: Cartesian3[], - height?: number, - scene?: Scene, - assignBack: boolean = false -): Cartesian3[] { - return positions.map(p => { - const cartographicPosition = Cartographic.fromCartesian(p); - if (typeof height === 'number' && !isNaN(height)) - cartographicPosition.height = height; - if (scene) { - const altitude = scene.globe.getHeight(cartographicPosition) || 0; - cartographicPosition.height += altitude; - } - return assignBack ? Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p) : Cartographic.toCartesian(cartographicPosition); - }); -} - -/** - * Update exaggeration for each cartesian3 position in array - */ -export function updateExaggerationForCartesianPositions( - positions: Cartesian3[], - exaggeration: number, -): Cartesian3[] { - return positions.map(p => { - const cartographicPosition = Cartographic.fromCartesian(p); - cartographicPosition.height *= exaggeration; - return Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p); - }); -} - -/** - * Creates a straight plane that troughs two provided points - * @param {Cartesian3} point1 - * @param {Cartesian3} point2 - * @param {boolean} negate - if true changes direction from left on the right - */ -export function planeFromTwoPoints(point1: Cartesian3, point2: Cartesian3, negate: boolean = false): Plane { - const p1p2 = Cartesian3.subtract(point2, point1, new Cartesian3()); - const cross = Cartesian3.cross(point1, p1p2, new Cartesian3()); - const normal = Cartesian3.normalize(cross, new Cartesian3()); - if (negate) { - Cartesian3.negate(normal, normal); - } - return Plane.fromPointNormal(point1, normal); -} - -/** - * Extend kml for export with entities properties - * @param {string} kml - kml for export - * @param {EntityCollection} entities - list of entities for export - * @return {string} - */ -export function extendKmlWithProperties(kml: string, entities: EntityCollection): string { - entities.values.forEach(entity => { - let kmlProperties = ''; - entity.properties!.propertyNames.forEach(prop => { - let value = entity.properties![prop] ? entity.properties![prop].getValue() : undefined; - if (value !== undefined && value !== null) { - value = typeof value === 'object' ? JSON.stringify(value) : value; - kmlProperties += `${value}`; - } - }); - kmlProperties += ''; - const placemark = ``; - kml = kml.replace(placemark, `${placemark}${kmlProperties}`); - }); - return kml; -} - - -const scratchVector1 = new Cartesian3(); -const scratchVector2 = new Cartesian3(); -const scratchProjectionVector = new Cartesian3(); - -/** - * Calculates point projection on vector from provided points - */ -export function projectPointOntoVector( - vectorPoint1: Cartesian3, - vectorPoint2: Cartesian3, - pointToProject: Cartesian3, - result: Cartesian3 = new Cartesian3() -): Cartesian3 { - Cartesian3.subtract(vectorPoint2, vectorPoint1, scratchVector1); - Cartesian3.subtract(pointToProject, vectorPoint1, scratchVector2); - Cartesian3.projectVector(scratchVector2, scratchVector1, scratchProjectionVector); - return Cartesian3.add(vectorPoint1, scratchProjectionVector, result); -} - -const minDifferenceScratch = new Cartesian3(); -const maxDifferenceScratch = new Cartesian3(); - -/** - * Wrapper for Cartesian3.lerp. Computes position on segment. - */ -export function clampPosition( - position: Cartesian3, - minPosition: Cartesian3, - maxPosition: Cartesian3, - start: number, - end: number -) { - let distanceScalar = start; - const minDifference = Cartesian3.subtract(minPosition, position, minDifferenceScratch); - const min = minDifference.x + minDifference.y + minDifference.z; - if (min > 0) { - const maxDifference = Cartesian3.subtract(maxPosition, position, maxDifferenceScratch); - const max = maxDifference.x + maxDifference.y + maxDifference.z; - if (max < 0) { - const maxDistance = Cartesian3.distance(minPosition, maxPosition); - const distance = Cartesian3.distance(minPosition, position); - distanceScalar = distance / maxDistance; - distanceScalar = CMath.clamp(distanceScalar, start, end); - } else { - distanceScalar = end; - } - } - Cartesian3.lerp(minPosition, maxPosition, distanceScalar, position); -} - - -export function projectPointOnSegment( - point: Cartesian3, - startPoint: Cartesian3, - endPoint: Cartesian3, - start: number, - end: number, - height: number -): Cartesian3 { - const position = projectPointOntoVector(startPoint, endPoint, point); - clampPosition(position, startPoint, endPoint, start, end); - return updateHeightForCartesianPositions([position], height)[0]; -} - -/** - * Gets distance to point in percentage and returns cartesian position on the line - * - * @param linePositions - * @param ratio - distance to point in percentage (value from 0 to 1 where 0 first point of the line and 1 is last) - * @param result - */ -export function getPointOnPolylineByRatio(linePositions: Cartesian3[], ratio: number, result) { - let indx, segmentRatio = 0; - const distances = linePositions.map((pos, indx) => { - if (indx === 0) return 0; - return Cartesian3.distance(linePositions[indx - 1], pos); - }); - const distance = distances.reduce((partialSum, a) => partialSum + a, 0); - const distanceToPoint = distance * ratio; - let currDist = 0, prevDist = 0; - for (let i = 1; i < distances.length; i++) { - currDist += distances[i]; - if (distanceToPoint > prevDist && distanceToPoint <= currDist) { - const d1 = currDist - prevDist; - const d2 = distanceToPoint - prevDist; - segmentRatio = d2 / d1; - indx = i; - break; - } - prevDist = currDist; - } - - return indx > 0 ? - Cartesian3.clone( - projectPointOnSegment(result, linePositions[indx - 1], linePositions[indx], segmentRatio, segmentRatio, 0), - result - ) : linePositions[0]; -} - -const axisScratch = new Cartesian3(); - -/** - * Returns 1 if 'to' point on left-bottom side of 'from' point or -1 if vice-versa - * @param {Cartesian3} from - * @param {Cartesian3} to - * @return {number} - */ -export function getDirectionFromPoints(from, to) { - const axisVect = Cartesian3.subtract(from, to, axisScratch); - const direction = axisVect.x + axisVect.y + axisVect.z; - return Math.round((1 / direction) * Math.abs(direction)); -} - -const westPointScratch = new Cartesian3(); -const eastPointScratch = new Cartesian3(); - -/** - * Returns vector orthogonal to view vector (vector from camera position to position on map) - * https://user-images.githubusercontent.com/51954170/108503213-abff8580-72bc-11eb-8b75-3385b5fd171e.png - */ -export function getVectorOrthogonalToView(viewer: Viewer): Cartesian3 | undefined { - const hpr = new HeadingPitchRoll(viewer.scene.camera.heading, 0.0, 0.0); - const rotation = Matrix3.fromHeadingPitchRoll(hpr); - const viewRect = viewer.scene.camera.computeViewRectangle(); - if (!viewRect) return undefined; - - const northwest = Cartographic.toCartesian(Rectangle.northwest(viewRect)); - const southwest = Cartographic.toCartesian(Rectangle.southwest(viewRect)); - const northeast = Cartographic.toCartesian(Rectangle.northeast(viewRect)); - const southeast = Cartographic.toCartesian(Rectangle.southeast(viewRect)); - - Cartesian3.midpoint(northwest, southwest, westPointScratch); - Cartesian3.midpoint(northeast, southeast, eastPointScratch); - const viewVect = Cartesian3.subtract(eastPointScratch, westPointScratch, new Cartesian3()); - return Matrix3.multiplyByVector(rotation, viewVect, viewVect); -} - -/** - * Returns left,right points of view rectangle - */ -export function getOrthogonalViewPoints(viewer: Viewer): Cartesian3[] { - const center = pickCenterOnEllipsoid(viewer.scene); - if (!center) return []; - - const left = new Cartesian3(); - const right = new Cartesian3(); - const orthogonalVector = getVectorOrthogonalToView(viewer); - if (!orthogonalVector) return []; - - Cartesian3.divideByScalar(orthogonalVector, 2, orthogonalVector); - Cartesian3.subtract(center, orthogonalVector, left); - Cartesian3.add(center, orthogonalVector, right); - return updateHeightForCartesianPositions([left, right], 0); -} - -export function getValueOrUndefined(prop) { - return prop ? prop.getValue() : undefined; -} - -/** - * Makes the camera look towards a specific point without changing its location - * @param position - * @param camera - */ -export function lookAtPoint(position: Cartesian3, camera: Camera) { - const cameraPosition = camera.position.clone(); - let direction = Cartesian3.subtract( - position, - cameraPosition, - new Cartesian3() - ); - direction = Cartesian3.normalize(direction, direction); - camera.direction = direction; - - // get an "approximate" up vector, which in this case we want to be something like the geodetic surface normal. - const approxUp = Cartesian3.normalize( - cameraPosition, - new Cartesian3() - ); - - // cross view direction with approxUp to get a right normal - let right = Cartesian3.cross( - direction, - approxUp, - new Cartesian3() - ); - right = Cartesian3.normalize(right, right); - camera.right = right; - - // cross right with view direction to get an orthonormal up - let up = Cartesian3.cross( - right, - direction, - new Cartesian3() - ); - up = Cartesian3.normalize(up, up); - camera.up = up; -} - -/** - * @param {Entity} entity - * @return {Color} - */ -export function getEntityColor(entity) { - if (entity.billboard) { - return entity.billboard.color.getValue(julianDate); - } else if (entity.polyline) { - return entity.polyline.material.getValue(julianDate).color; - } else if (entity.polygon) { - return entity.polygon.material.getValue(julianDate).color; - } -} - -/** - * Checks is point lies within the polygon - * @param point - * @param polygonPositions - */ -export function pointInPolygon(point: Cartographic, polygonPositions: Cartographic[]): boolean { - let inside = false; - for (let i = 0, j = polygonPositions.length - 1; i < polygonPositions.length; j = i++) { - const xi = polygonPositions[i].longitude, yi = polygonPositions[i].latitude; - const xj = polygonPositions[j].longitude, yj = polygonPositions[j].latitude; - - const intersect = ((yi > point.latitude) !== (yj > point.latitude)) - && (point.longitude < (xj - xi) * (point.latitude - yi) / (yj - yi) + xi); - if (intersect) inside = !inside; - } - - return inside; -} - -/** - * Triangulate a polygon. - * - * @param {Cartesian2[]} positions Cartesian2 array containing the vertices of the polygon - * @param {Number[]} [holes] An array of the staring indices of the holes. - * @returns {Number[]} Index array representing triangles that fill the polygon - */ -export function triangulate(positions, holes) { - const flattenedPositions = Cartesian2.packArray(positions); - return earcut(flattenedPositions, holes, 2); -} - - -const scratchBoundingSphere: BoundingSphere = new BoundingSphere(); -const scratchPosition = new Cartesian3(); -const moveVector3dScratch = new Cartesian3(); -const axisVector3dScratch = new Cartesian3(); - -/** - * Gets Cartesian3 position and distance in pixels and calculates second Cartesian3 position on passed axes and side - * - * @param scene - * @param firstPoint cartesian3 position of known point - * @param distancePx distance between points in pixels - * @param axis configures on which axis second points should be placed (x or y axis according to map rectangle) - * @param side configures where second point will be placed (left/right or above/below first point) - */ -export function positionFromPxDistance(scene: Scene, firstPoint: Cartesian3, distancePx: number, axis: 'x' | 'y' | 'z', side: 1 | -1) { - const mapRect = scene.globe.cartographicLimitRectangle; - scratchBoundingSphere.center = firstPoint; - const pixelSize = scene.camera.getPixelSize(scratchBoundingSphere, scene.drawingBufferWidth, scene.drawingBufferHeight); - const distance = distancePx * pixelSize; - let corners; - if (axis === 'y') { - corners = [Cartographic.toCartesian(Rectangle.northeast(mapRect)), Cartographic.toCartesian(Rectangle.southeast(mapRect))]; - } else if (axis === 'x') { - corners = [Cartographic.toCartesian(Rectangle.northwest(mapRect)), Cartographic.toCartesian(Rectangle.northeast(mapRect))]; - } else { - corners = [firstPoint, updateHeightForCartesianPositions([firstPoint], distance)[0]]; - } - Cartesian3.midpoint(corners[0], corners[1], scratchPosition); - const pos = projectPointOnSegment(firstPoint, corners[0], corners[1], 0, 1, 0); - Cartesian3.subtract(pos, scratchPosition, axisVector3dScratch); - const scalar3d = distance / Cartesian3.distance(pos, scratchPosition) * side; - Cartesian3.multiplyByScalar(axisVector3dScratch, scalar3d, moveVector3dScratch); - return Cartesian3.add(firstPoint, moveVector3dScratch, new Cartesian3()); -} - -/** - * Checks is geometry of part of geometry inside viewport - */ -export function isGeometryInViewport(viewer: Viewer, positions: Cartesian3[]): boolean { - const camera = viewer.camera; - const frustum = camera.frustum; - const cullingVolume = frustum.computeCullingVolume( - camera.position, - camera.direction, - camera.up - ); - - return cullingVolume.computeVisibility(OrientedBoundingBox.fromPoints(positions)) !== -1; -} - -/** - * Parses KML file with fixes for clampToGround and adding missing properties. - */ -export async function parseKml(viewer: Viewer, data: File | string, dataSource: CustomDataSource, clampToGround: boolean) { - const kmlDataSource = await KmlDataSource.load(data, { - camera: viewer.scene.camera, - canvas: viewer.scene.canvas, - clampToGround - }); - let name = kmlDataSource.name; - kmlDataSource.entities.suspendEvents(); - dataSource.entities.suspendEvents(); - for (const ent of kmlDataSource.entities.values) { - ent.show = true; - if (!name) { - name = ent.name!; - } - if (ent['point']) { - const point = ent['point']; - const color: Color = point.color?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; - if (color.alpha === 0) { - color.alpha = 1; - } - point.color = new ConstantProperty(color); - point.pixelSize = point.pixelSize?.getValue(julianDate) || 1; - point.heightReference = clampToGround ? HeightReference.CLAMP_TO_GROUND : point.heightReference?.getValue(julianDate); - } - if (ent['polygon']) { - const polygon = ent['polygon']; - const color: Color = polygon.material?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; - if (color.alpha === 0) { - color.alpha = 1; - } - polygon.material = new ColorMaterialProperty(color); - polygon.heightReference = clampToGround ? HeightReference.CLAMP_TO_GROUND : polygon.heightReference?.getValue(julianDate); - } - if (ent['polyline']) { - const line = ent['polyline']; - const color: Color = line.material?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; - if (color.alpha === 0) { - color.alpha = 1; - } - line.arcType = new ConstantProperty(ArcType.GEODESIC); - line.clampToGround = new ConstantProperty(clampToGround); - line.material = new ColorMaterialProperty(color); - line.width = line.width?.getValue(julianDate) || 2; - } - dataSource.entities.add(ent); - } - dataSource.entities.resumeEvents(); - - return name; -} - -// workaround to rerender map after dataSources update in requestRenderMode -export async function renderWithDelay(viewer: Viewer) { - await new Promise(resolve => setTimeout(() => { - viewer.scene.requestRender(); - resolve(); - }, 1000)); -} - -export function updateExaggerationForKmlDataSource(dataSource: CustomDataSource | DataSource | undefined, exaggeration: number, prevExaggeration: number) { - if (dataSource && dataSource.show) { - dataSource.entities.suspendEvents(); - const exaggerationScale = exaggeration / prevExaggeration; - dataSource.entities.values.forEach(ent => { - if (ent.position) { - const position = ent.position.getValue(julianDate); - position && updateExaggerationForCartesianPositions([position], exaggerationScale); - ent.position = new ConstantPositionProperty(position); - } - if (ent['polygon']) { - const polygon = ent['polygon']; - const hierarchy = polygon?.hierarchy?.getValue(julianDate); - if (hierarchy?.positions) { - const positions = updateExaggerationForCartesianPositions(hierarchy.positions, exaggerationScale); - polygon.hierarchy = new ConstantProperty({ - holes: [], - positions - }); - } - } - if (ent['polyline']) { - const line = ent['polyline']; - const positions = line.positions?.getValue(julianDate); - if (positions) { - line.positions = new ConstantProperty(updateExaggerationForCartesianPositions(positions, exaggerationScale)); - } - } - }); - dataSource.entities.resumeEvents(); - } -} +import { + ArcType, + BoundingSphere, + Camera, + Cartesian2, + Cartesian3, + Cartographic, + Color, + ColorMaterialProperty, ConstantPositionProperty, + ConstantProperty, + CustomDataSource, DataSource, Ellipsoid, + EntityCollection, + HeadingPitchRoll, + HeightReference, + JulianDate, + KmlDataSource, + Math as CMath, + Matrix3, + OrientedBoundingBox, + Plane, + Rectangle, + Scene, + Viewer +} from 'cesium'; +import type {GeometryTypes} from './toolbox/interfaces'; +import earcut from 'earcut'; +import {DEFAULT_UPLOADED_KML_COLOR} from './constants'; + +const julianDate = new JulianDate(); + +export function pickCenter(scene: Scene): Cartesian3 { + const camera = scene.camera; + const windowPosition = new Cartesian2( + scene.canvas.clientWidth / 2, + scene.canvas.clientHeight / 2 + ); + const ray = camera.getPickRay(windowPosition); + if (!ray) return camera.positionWC; + const center = scene.globe.pick(ray, scene); + return center !== undefined ? center : camera.positionWC; +} + +/** + * Return the position of the point, on the ellipsoid at the center of the Cesium viewport. + */ +export function pickCenterOnEllipsoid(scene: Scene): Cartesian3 | undefined { + const camera = scene.camera; + const windowPosition = new Cartesian2( + scene.canvas.clientWidth / 2, + scene.canvas.clientHeight / 2 + ); + return camera.pickEllipsoid(windowPosition); +} + +export function pickPositionOrVoxel(scene: Scene, windowPosition: Cartesian2): Cartesian3 { + const voxel = scene.pickVoxel(windowPosition); + if (voxel) { + return voxel.orientedBoundingBox.center; + } + return scene.pickPosition(windowPosition); +} + +/** + * Return the position of the point, on the map or object at the center of the Cesium viewport. + */ +export function pickCenterOnMapOrObject(scene: Scene): Cartesian3 { + const windowPosition = new Cartesian2( + scene.canvas.clientWidth / 2, + scene.canvas.clientHeight / 2 + ); + return pickPositionOrVoxel(scene, windowPosition); +} + +export function verticalDirectionRotate(camera: Camera, angle: number) { + const position = Cartesian3.normalize(camera.position, new Cartesian3()); + const up = Cartesian3.normalize(camera.up, new Cartesian3()); + + const pitch = CMath.toDegrees(camera.pitch); + if (pitch < -90 || pitch > 0) { + angle = -angle; + } + + const tangent = Cartesian3.cross(up, position, new Cartesian3()); + camera.rotate(tangent, angle); +} + +function getPolygonArea(positions: Cartesian3[], holes: number[] = []): number { + const indices = triangulate(positions, holes); + let area = 0; + + for (let i = 0; i < indices.length; i += 3) { + const vector1 = positions[indices[i]]; + const vector2 = positions[indices[i + 1]]; + const vector3 = positions[indices[i + 2]]; + //triangle sides + const a = Cartesian3.distance(vector3, vector2); + const b = Cartesian3.distance(vector1, vector3); + const c = Cartesian3.distance(vector1, vector2); + const p = (a + b + c) / 2; + const triangleArea = Math.sqrt((p - a) * (p - b) * (p - c) * p); + + area += triangleArea; + } + return area * Math.pow(10, -6); +} + +export type Measurements = { + positions: Cartesian3[], + type: GeometryTypes, + numberOfSegments: number, + segmentsLength: number[] + perimeter?: number + area?: number +} +/** + * Returns measurements for geometry + */ +export function getMeasurements(positions: Cartesian3[], type: GeometryTypes): Measurements { + const segmentsLength: number[] = []; + positions.forEach((p, key) => { + if (key > 0) { + segmentsLength.push(Cartesian3.distance(positions[key - 1], p) / 1000); + } + }); + const result: Measurements = { + numberOfSegments: positions.length - 1, + segmentsLength: segmentsLength.map(l => Number(l.toFixed(3))), + positions, + type, + }; + let perimeter = segmentsLength.reduce((a, b) => a + b, 0); + if (type === 'rectangle') { + perimeter *= 2; + } + result.perimeter = perimeter; + if (type === 'rectangle' || (type === 'polygon' && positions.length > 2)) { + result.area = getPolygonArea(positions); + } + return result; +} + +/** + * Sets height in meters for each cartesian3 position in array + * @param positions + * @param [height] + * @param [scene] + * @param assignBack assign value to initial position + */ +export function updateHeightForCartesianPositions( + positions: Cartesian3[], + height?: number, + scene?: Scene, + assignBack: boolean = false +): Cartesian3[] { + return positions.map(p => { + const cartographicPosition = Cartographic.fromCartesian(p); + if (typeof height === 'number' && !isNaN(height)) + cartographicPosition.height = height; + if (scene) { + const altitude = scene.globe.getHeight(cartographicPosition) || 0; + cartographicPosition.height += altitude; + } + return assignBack ? Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p) : Cartographic.toCartesian(cartographicPosition); + }); +} + +/** + * Update exaggeration for each cartesian3 position in array + */ +export function updateExaggerationForCartesianPositions( + positions: Cartesian3[], + exaggeration: number, +): Cartesian3[] { + return positions.map(p => { + const cartographicPosition = Cartographic.fromCartesian(p); + cartographicPosition.height *= exaggeration; + return Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p); + }); +} + +/** + * Creates a straight plane that troughs two provided points + * @param {Cartesian3} point1 + * @param {Cartesian3} point2 + * @param {boolean} negate - if true changes direction from left on the right + */ +export function planeFromTwoPoints(point1: Cartesian3, point2: Cartesian3, negate: boolean = false): Plane { + const p1p2 = Cartesian3.subtract(point2, point1, new Cartesian3()); + const cross = Cartesian3.cross(point1, p1p2, new Cartesian3()); + const normal = Cartesian3.normalize(cross, new Cartesian3()); + if (negate) { + Cartesian3.negate(normal, normal); + } + return Plane.fromPointNormal(point1, normal); +} + +/** + * Extend kml for export with entities properties + * @param {string} kml - kml for export + * @param {EntityCollection} entities - list of entities for export + * @return {string} + */ +export function extendKmlWithProperties(kml: string, entities: EntityCollection): string { + entities.values.forEach(entity => { + let kmlProperties = ''; + entity.properties!.propertyNames.forEach(prop => { + let value = entity.properties![prop] ? entity.properties![prop].getValue() : undefined; + if (value !== undefined && value !== null) { + value = typeof value === 'object' ? JSON.stringify(value) : value; + kmlProperties += `${value}`; + } + }); + kmlProperties += ''; + const placemark = ``; + kml = kml.replace(placemark, `${placemark}${kmlProperties}`); + }); + return kml; +} + + +const scratchVector1 = new Cartesian3(); +const scratchVector2 = new Cartesian3(); +const scratchProjectionVector = new Cartesian3(); + +/** + * Calculates point projection on vector from provided points + */ +export function projectPointOntoVector( + vectorPoint1: Cartesian3, + vectorPoint2: Cartesian3, + pointToProject: Cartesian3, + result: Cartesian3 = new Cartesian3() +): Cartesian3 { + Cartesian3.subtract(vectorPoint2, vectorPoint1, scratchVector1); + Cartesian3.subtract(pointToProject, vectorPoint1, scratchVector2); + Cartesian3.projectVector(scratchVector2, scratchVector1, scratchProjectionVector); + return Cartesian3.add(vectorPoint1, scratchProjectionVector, result); +} + +const minDifferenceScratch = new Cartesian3(); +const maxDifferenceScratch = new Cartesian3(); + +/** + * Wrapper for Cartesian3.lerp. Computes position on segment. + */ +export function clampPosition( + position: Cartesian3, + minPosition: Cartesian3, + maxPosition: Cartesian3, + start: number, + end: number +) { + let distanceScalar = start; + const minDifference = Cartesian3.subtract(minPosition, position, minDifferenceScratch); + const min = minDifference.x + minDifference.y + minDifference.z; + if (min > 0) { + const maxDifference = Cartesian3.subtract(maxPosition, position, maxDifferenceScratch); + const max = maxDifference.x + maxDifference.y + maxDifference.z; + if (max < 0) { + const maxDistance = Cartesian3.distance(minPosition, maxPosition); + const distance = Cartesian3.distance(minPosition, position); + distanceScalar = distance / maxDistance; + distanceScalar = CMath.clamp(distanceScalar, start, end); + } else { + distanceScalar = end; + } + } + Cartesian3.lerp(minPosition, maxPosition, distanceScalar, position); +} + + +export function projectPointOnSegment( + point: Cartesian3, + startPoint: Cartesian3, + endPoint: Cartesian3, + start: number, + end: number, + height: number +): Cartesian3 { + const position = projectPointOntoVector(startPoint, endPoint, point); + clampPosition(position, startPoint, endPoint, start, end); + return updateHeightForCartesianPositions([position], height)[0]; +} + +/** + * Gets distance to point in percentage and returns cartesian position on the line + * + * @param linePositions + * @param ratio - distance to point in percentage (value from 0 to 1 where 0 first point of the line and 1 is last) + * @param result + */ +export function getPointOnPolylineByRatio(linePositions: Cartesian3[], ratio: number, result) { + let indx, segmentRatio = 0; + const distances = linePositions.map((pos, indx) => { + if (indx === 0) return 0; + return Cartesian3.distance(linePositions[indx - 1], pos); + }); + const distance = distances.reduce((partialSum, a) => partialSum + a, 0); + const distanceToPoint = distance * ratio; + let currDist = 0, prevDist = 0; + for (let i = 1; i < distances.length; i++) { + currDist += distances[i]; + if (distanceToPoint > prevDist && distanceToPoint <= currDist) { + const d1 = currDist - prevDist; + const d2 = distanceToPoint - prevDist; + segmentRatio = d2 / d1; + indx = i; + break; + } + prevDist = currDist; + } + + return indx > 0 ? + Cartesian3.clone( + projectPointOnSegment(result, linePositions[indx - 1], linePositions[indx], segmentRatio, segmentRatio, 0), + result + ) : linePositions[0]; +} + +const axisScratch = new Cartesian3(); + +/** + * Returns 1 if 'to' point on left-bottom side of 'from' point or -1 if vice-versa + * @param {Cartesian3} from + * @param {Cartesian3} to + * @return {number} + */ +export function getDirectionFromPoints(from, to) { + const axisVect = Cartesian3.subtract(from, to, axisScratch); + const direction = axisVect.x + axisVect.y + axisVect.z; + return Math.round((1 / direction) * Math.abs(direction)); +} + +const westPointScratch = new Cartesian3(); +const eastPointScratch = new Cartesian3(); + +/** + * Returns vector orthogonal to view vector (vector from camera position to position on map) + * https://user-images.githubusercontent.com/51954170/108503213-abff8580-72bc-11eb-8b75-3385b5fd171e.png + */ +export function getVectorOrthogonalToView(viewer: Viewer): Cartesian3 | undefined { + const hpr = new HeadingPitchRoll(viewer.scene.camera.heading, 0.0, 0.0); + const rotation = Matrix3.fromHeadingPitchRoll(hpr); + const viewRect = viewer.scene.camera.computeViewRectangle(); + if (!viewRect) return undefined; + + const northwest = Cartographic.toCartesian(Rectangle.northwest(viewRect)); + const southwest = Cartographic.toCartesian(Rectangle.southwest(viewRect)); + const northeast = Cartographic.toCartesian(Rectangle.northeast(viewRect)); + const southeast = Cartographic.toCartesian(Rectangle.southeast(viewRect)); + + Cartesian3.midpoint(northwest, southwest, westPointScratch); + Cartesian3.midpoint(northeast, southeast, eastPointScratch); + const viewVect = Cartesian3.subtract(eastPointScratch, westPointScratch, new Cartesian3()); + return Matrix3.multiplyByVector(rotation, viewVect, viewVect); +} + +/** + * Returns left,right points of view rectangle + */ +export function getOrthogonalViewPoints(viewer: Viewer): Cartesian3[] { + const center = pickCenterOnEllipsoid(viewer.scene); + if (!center) return []; + + const left = new Cartesian3(); + const right = new Cartesian3(); + const orthogonalVector = getVectorOrthogonalToView(viewer); + if (!orthogonalVector) return []; + + Cartesian3.divideByScalar(orthogonalVector, 2, orthogonalVector); + Cartesian3.subtract(center, orthogonalVector, left); + Cartesian3.add(center, orthogonalVector, right); + return updateHeightForCartesianPositions([left, right], 0); +} + +export function getValueOrUndefined(prop) { + return prop ? prop.getValue() : undefined; +} + +/** + * Makes the camera look towards a specific point without changing its location + * @param position + * @param camera + */ +export function lookAtPoint(position: Cartesian3, camera: Camera) { + const cameraPosition = camera.position.clone(); + let direction = Cartesian3.subtract( + position, + cameraPosition, + new Cartesian3() + ); + direction = Cartesian3.normalize(direction, direction); + camera.direction = direction; + + // get an "approximate" up vector, which in this case we want to be something like the geodetic surface normal. + const approxUp = Cartesian3.normalize( + cameraPosition, + new Cartesian3() + ); + + // cross view direction with approxUp to get a right normal + let right = Cartesian3.cross( + direction, + approxUp, + new Cartesian3() + ); + right = Cartesian3.normalize(right, right); + camera.right = right; + + // cross right with view direction to get an orthonormal up + let up = Cartesian3.cross( + right, + direction, + new Cartesian3() + ); + up = Cartesian3.normalize(up, up); + camera.up = up; +} + +/** + * @param {Entity} entity + * @return {Color} + */ +export function getEntityColor(entity) { + if (entity.billboard) { + return entity.billboard.color.getValue(julianDate); + } else if (entity.polyline) { + return entity.polyline.material.getValue(julianDate).color; + } else if (entity.polygon) { + return entity.polygon.material.getValue(julianDate).color; + } +} + +/** + * Checks is point lies within the polygon + * @param point + * @param polygonPositions + */ +export function pointInPolygon(point: Cartographic, polygonPositions: Cartographic[]): boolean { + let inside = false; + for (let i = 0, j = polygonPositions.length - 1; i < polygonPositions.length; j = i++) { + const xi = polygonPositions[i].longitude, yi = polygonPositions[i].latitude; + const xj = polygonPositions[j].longitude, yj = polygonPositions[j].latitude; + + const intersect = ((yi > point.latitude) !== (yj > point.latitude)) + && (point.longitude < (xj - xi) * (point.latitude - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside; +} + +/** + * Triangulate a polygon. + * + * @param {Cartesian2[]} positions Cartesian2 array containing the vertices of the polygon + * @param {Number[]} [holes] An array of the staring indices of the holes. + * @returns {Number[]} Index array representing triangles that fill the polygon + */ +export function triangulate(positions, holes) { + const flattenedPositions = Cartesian2.packArray(positions); + return earcut(flattenedPositions, holes, 2); +} + + +const scratchBoundingSphere: BoundingSphere = new BoundingSphere(); +const scratchPosition = new Cartesian3(); +const moveVector3dScratch = new Cartesian3(); +const axisVector3dScratch = new Cartesian3(); + +/** + * Gets Cartesian3 position and distance in pixels and calculates second Cartesian3 position on passed axes and side + * + * @param scene + * @param firstPoint cartesian3 position of known point + * @param distancePx distance between points in pixels + * @param axis configures on which axis second points should be placed (x or y axis according to map rectangle) + * @param side configures where second point will be placed (left/right or above/below first point) + */ +export function positionFromPxDistance(scene: Scene, firstPoint: Cartesian3, distancePx: number, axis: 'x' | 'y' | 'z', side: 1 | -1) { + const mapRect = scene.globe.cartographicLimitRectangle; + scratchBoundingSphere.center = firstPoint; + const pixelSize = scene.camera.getPixelSize(scratchBoundingSphere, scene.drawingBufferWidth, scene.drawingBufferHeight); + const distance = distancePx * pixelSize; + let corners; + if (axis === 'y') { + corners = [Cartographic.toCartesian(Rectangle.northeast(mapRect)), Cartographic.toCartesian(Rectangle.southeast(mapRect))]; + } else if (axis === 'x') { + corners = [Cartographic.toCartesian(Rectangle.northwest(mapRect)), Cartographic.toCartesian(Rectangle.northeast(mapRect))]; + } else { + corners = [firstPoint, updateHeightForCartesianPositions([firstPoint], distance)[0]]; + } + Cartesian3.midpoint(corners[0], corners[1], scratchPosition); + const pos = projectPointOnSegment(firstPoint, corners[0], corners[1], 0, 1, 0); + Cartesian3.subtract(pos, scratchPosition, axisVector3dScratch); + const scalar3d = distance / Cartesian3.distance(pos, scratchPosition) * side; + Cartesian3.multiplyByScalar(axisVector3dScratch, scalar3d, moveVector3dScratch); + return Cartesian3.add(firstPoint, moveVector3dScratch, new Cartesian3()); +} + +/** + * Checks is geometry of part of geometry inside viewport + */ +export function isGeometryInViewport(viewer: Viewer, positions: Cartesian3[]): boolean { + const camera = viewer.camera; + const frustum = camera.frustum; + const cullingVolume = frustum.computeCullingVolume( + camera.position, + camera.direction, + camera.up + ); + + return cullingVolume.computeVisibility(OrientedBoundingBox.fromPoints(positions)) !== -1; +} + +/** + * Parses KML file with fixes for clampToGround and adding missing properties. + */ +export async function parseKml(viewer: Viewer, data: File | string, dataSource: CustomDataSource, clampToGround: boolean) { + const kmlDataSource = await KmlDataSource.load(data, { + camera: viewer.scene.camera, + canvas: viewer.scene.canvas, + clampToGround + }); + let name = kmlDataSource.name; + kmlDataSource.entities.suspendEvents(); + dataSource.entities.suspendEvents(); + for (const ent of kmlDataSource.entities.values) { + ent.show = true; + if (!name) { + name = ent.name!; + } + if (ent['point']) { + const point = ent['point']; + const color: Color = point.color?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; + if (color.alpha === 0) { + color.alpha = 1; + } + point.color = new ConstantProperty(color); + point.pixelSize = point.pixelSize?.getValue(julianDate) || 1; + point.heightReference = clampToGround ? HeightReference.CLAMP_TO_GROUND : point.heightReference?.getValue(julianDate); + } + if (ent['polygon']) { + const polygon = ent['polygon']; + const color: Color = polygon.material?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; + if (color.alpha === 0) { + color.alpha = 1; + } + polygon.material = new ColorMaterialProperty(color); + polygon.heightReference = clampToGround ? HeightReference.CLAMP_TO_GROUND : polygon.heightReference?.getValue(julianDate); + } + if (ent['polyline']) { + const line = ent['polyline']; + const color: Color = line.material?.getValue(julianDate)?.color || DEFAULT_UPLOADED_KML_COLOR; + if (color.alpha === 0) { + color.alpha = 1; + } + line.arcType = new ConstantProperty(ArcType.GEODESIC); + line.clampToGround = new ConstantProperty(clampToGround); + line.material = new ColorMaterialProperty(color); + line.width = line.width?.getValue(julianDate) || 2; + } + dataSource.entities.add(ent); + } + dataSource.entities.resumeEvents(); + + // TODO: remove this and fix data upload + return name ?? 'untitled'; +} + +// workaround to rerender map after dataSources update in requestRenderMode +export async function renderWithDelay(viewer: Viewer) { + await new Promise(resolve => setTimeout(() => { + viewer.scene.requestRender(); + resolve(); + }, 1000)); +} + +export function updateExaggerationForKmlDataSource(dataSource: CustomDataSource | DataSource | undefined, exaggeration: number, prevExaggeration: number) { + if (dataSource && dataSource.show) { + dataSource.entities.suspendEvents(); + const exaggerationScale = exaggeration / prevExaggeration; + dataSource.entities.values.forEach(ent => { + if (ent.position) { + const position = ent.position.getValue(julianDate); + position && updateExaggerationForCartesianPositions([position], exaggerationScale); + ent.position = new ConstantPositionProperty(position); + } + if (ent['polygon']) { + const polygon = ent['polygon']; + const hierarchy = polygon?.hierarchy?.getValue(julianDate); + if (hierarchy?.positions) { + const positions = updateExaggerationForCartesianPositions(hierarchy.positions, exaggerationScale); + polygon.hierarchy = new ConstantProperty({ + holes: [], + positions + }); + } + } + if (ent['polyline']) { + const line = ent['polyline']; + const positions = line.positions?.getValue(julianDate); + if (positions) { + line.positions = new ConstantProperty(updateExaggerationForCartesianPositions(positions, exaggerationScale)); + } + } + }); + dataSource.entities.resumeEvents(); + } +} diff --git a/ui/src/components/core/core-button.ts b/ui/src/components/core/core-button.ts index 5c6a0f365..130f23700 100644 --- a/ui/src/components/core/core-button.ts +++ b/ui/src/components/core/core-button.ts @@ -36,6 +36,7 @@ export class CoreButton extends LitElement { :host([variant='text']) button:hover { color: var(--color-action--light); } + `; } diff --git a/ui/src/components/core/core-checkbox.ts b/ui/src/components/core/core-checkbox.ts new file mode 100644 index 000000000..5934a2e25 --- /dev/null +++ b/ui/src/components/core/core-checkbox.ts @@ -0,0 +1,106 @@ +import {css, html, LitElement} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import './core-icon'; + +@customElement('ngm-core-checkbox') +export class CoreCheckbox extends LitElement { + @property({type: String}) + accessor label: string | null = null; + + @property({type: Boolean, attribute: 'is-active', reflect: true}) + accessor isActive: boolean = false; + + private handleClick(e: Event) { + e.stopPropagation(); + e.preventDefault(); + this.dispatchEvent(new CustomEvent('update', {bubbles: true, composed: true})); + } + + readonly render = () => html` + + `; + + static readonly styles = css` + :host, :host * { + box-sizing: border-box; + } + + :host { + color: var(--color-highlight--darker); + + display: flex; + align-items: center; + } + + :host([is-active]) { + color: var(--color-action); + } + + label { + display: flex; + align-items: center; + cursor: pointer; + gap: 10px; + } + + label:hover { + color: var(--color-action--light); + } + + :host([is-active]) > label:not(:hover) { + color: var(--color-action); + } + + input { + display: none; + } + + .icon { + width: 19px; + height: 18px; + display: block; + position: relative; + border-radius: 2px; + border: 2px solid var(--color-highlight--darker); + transition: all 0.2s ease; + } + + label:hover > .icon { + border-color: var(--color-action--light); + background-color: var(--color-action--light); + } + + :host([is-active]) > label:not(:hover) > .icon { + border-color: var(--color-action); + background-color: var(--color-action); + } + + .icon::before { + content: ""; + top: -2px; + left: 3px; + width: 7px; + height: 12px; + display: none; + position: absolute; + transform: rotate(45deg); + transition: all 0.2s ease; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; + } + + input:checked + .icon::before { + display: block; + } + `; +} + +export type Variant = + | 'default' + | 'text' diff --git a/ui/src/components/core/core-icon.ts b/ui/src/components/core/core-icon.ts index 840407d6d..7f0df4b77 100644 --- a/ui/src/components/core/core-icon.ts +++ b/ui/src/components/core/core-icon.ts @@ -1,49 +1,27 @@ import {css, html, LitElement} from 'lit'; import {customElement, property} from 'lit/decorators.js'; +import {IconKey, icons} from '../../icons/icons'; @customElement('ngm-core-icon') export class CoreIcon extends LitElement { @property() - accessor icon: IconName | null = null; + accessor icon: IconKey = 'config' @property({type: Boolean, attribute: 'interactive'}) - accessor isInteractive: boolean = false; - - readonly render = () => html``; + accessor isInteractive: boolean = false static readonly styles = css` :host { - display: inline-block; - - --size: var(--icon-size--normal); - width: var(--size); - height: var(--size); - background-color: var(--color-bg-contrast); - - mask: var(--mask, none) no-repeat center; - -webkit-mask: var(--mask, none) no-repeat center; - - /* Hide element if no valid icon has been specified. */ - visibility: hidden; + color: currentColor; } :host([interactive]:hover) { cursor: pointer; - background-color: var(--color-action); - } - - :host([icon='close']) { - visibility: initial; - --mask: url('images/i_close.svg'); - } - - :host([icon='dropdown']) { - visibility: initial; - --mask: url('images/i_menu-1.svg') + color: var(--color-action); } `; -} -export type IconName = - | 'close' - | 'dropdown' + readonly render = () => { + return html`${icons[this.icon]}`; + }; +} diff --git a/ui/src/components/core/index.ts b/ui/src/components/core/index.ts index 5446187a9..d877db3e2 100644 --- a/ui/src/components/core/index.ts +++ b/ui/src/components/core/index.ts @@ -1,2 +1,3 @@ import './core-button'; +import './core-checkbox'; import './core-icon'; diff --git a/ui/src/components/layers/layers-catalog.ts b/ui/src/components/layer/layer-catalog.ts similarity index 92% rename from ui/src/components/layers/layers-catalog.ts rename to ui/src/components/layer/layer-catalog.ts index 732b450de..725b875e1 100644 --- a/ui/src/components/layers/layers-catalog.ts +++ b/ui/src/components/layer/layer-catalog.ts @@ -5,16 +5,16 @@ import i18next from 'i18next'; import auth from '../../store/auth'; import type {LayerTreeNode} from '../../layertree'; import $ from 'jquery'; - +import '../core'; import fomanticTransitionCss from 'fomantic-ui-css/components/transition.css'; import fomanticAccordionCss from 'fomantic-ui-css/components/accordion.css'; import 'fomantic-ui-css/components/transition.js'; -import {LayerEvent} from './layers-display'; +import {LayerEvent} from './layer-display'; -@customElement('ngm-layers-catalog') -export class NgmLayersCatalog extends LitElementI18n { +@customElement('ngm-layer-catalog') +export class NgmLayerCatalog extends LitElementI18n { @property({type: Array}) accessor layers: LayerTreeNode[] = []; @@ -115,21 +115,24 @@ export class NgmLayersCatalog extends LitElementI18n { cursor: pointer; } - .category > .title.active > label { + label { + font-family: var(--font); + } + + .category > .title.active > label, + .category > .title.active > ngm-core-icon { color: var(--color-action); } - .category > .title:hover > label { + .category > .title:hover > label, + .category > .title:hover > ngm-core-icon { color: var(--color-action--light); } .category > .title > ngm-core-icon { - background-color: var(--color-highlight--darker); + color: var(--color-highlight--darker); } - .category > .title:hover > ngm-core-icon { - background-color: var(--color-action--light); - } .category > .title.first-level { font-weight: 700; @@ -141,14 +144,14 @@ export class NgmLayersCatalog extends LitElementI18n { margin-left: 10px; } - .category > .title.active > ngm-core-icon { - transform: rotate(90deg); + .category > .title:not(.active) > ngm-core-icon { + transform: rotate(-90deg); } .ngm-checkbox { display: flex; align-items: center; - margin-bottom: 12px; + margin: 0 0 12px 5px; cursor: pointer; } diff --git a/ui/src/components/layers/layers-display.ts b/ui/src/components/layer/layer-display.ts similarity index 64% rename from ui/src/components/layers/layers-display.ts rename to ui/src/components/layer/layer-display.ts index 5164b0e02..f0f15350d 100644 --- a/ui/src/components/layers/layers-display.ts +++ b/ui/src/components/layer/layer-display.ts @@ -1,21 +1,20 @@ -import {customElement, property, state} from 'lit/decorators.js'; +import {customElement, property, query, state} from 'lit/decorators.js'; import {LitElementI18n} from '../../i18n.js'; import {css, html} from 'lit'; import i18next from 'i18next'; -import {CustomDataSource, Viewer} from 'cesium'; +import {Viewer} from 'cesium'; import {PropertyValues} from '@lit/reactive-element'; import LayersActions from '../../layers/LayersActions'; -import {DEFAULT_LAYER_OPACITY, LayerConfig, LayerTreeNode} from '../../layertree'; +import {LayerConfig, LayerTreeNode} from '../../layertree'; import '../../layers/ngm-layers'; import '../../layers/ngm-layers-sort'; import MainStore from '../../store/main'; import {Subscription} from 'rxjs'; import {classMap} from 'lit/directives/class-map.js'; -import {query} from 'lit/decorators.js'; -import {parseKml, renderWithDelay} from '../../cesiumutils'; +import './upload/layer-upload'; -@customElement('ngm-layers-display') -export class NgmLayersDisplay extends LitElementI18n { +@customElement('ngm-layer-display') +export class NgmLayerDisplay extends LitElementI18n { @property({type: Array}) accessor layers: LayerTreeNode[] = [] @@ -48,7 +47,6 @@ export class NgmLayersDisplay extends LitElementI18n { this.handleReordering = this.handleReordering.bind(this); this.toggleReordering = this.toggleReordering.bind(this); this.handleLayerUpdate = this.handleLayerUpdate.bind(this); - this.handleKmlUpload = this.handleKmlUpload.bind(this); } private initializeViewer(): void { @@ -60,63 +58,8 @@ export class NgmLayersDisplay extends LitElementI18n { })); } - readonly render = () => html` -
- -
-
- ${this.isReordering ? i18next.t('dtd_finish_ordering_label') : i18next.t('dtd_change_order_label')} -
- ${this.isReordering - ? this.renderSortableLayers() - : this.renderLayers()} -
${i18next.t('dtd_user_content_label')}
- - - -
- ${i18next.t('dtd_background_map_label')} -
- ${this.globeQueueLength} -
-
- -
-
-
- `; - - private readonly renderLayers = () => html` - - - `; - - private readonly renderSortableLayers = () => html` - - - `; - updated(changedProperties: PropertyValues): void { - if (changedProperties.has('viewer' as keyof NgmLayersDisplay)) { + if (changedProperties.has('viewer' as keyof NgmLayerDisplay)) { this.actions = this.viewer == null ? null : new LayersActions(this.viewer); } } @@ -151,40 +94,6 @@ export class NgmLayersDisplay extends LitElementI18n { this.updateLayer(e.detail); } - // TODO Cleanup/Refactor this function. - // As of now, this function remains unchanged to before the navigation-catalog refactoring. - private async handleKmlUpload(file: File, clampToGround: boolean): Promise { - if (this.viewer == null) { - return; - } - - const dataSource = new CustomDataSource(); - const name = await parseKml(this.viewer, file, dataSource, clampToGround); - const layer = `${name.replace(' ', '_')}_${Date.now()}`; - - // name used as id for datasource - dataSource.name = layer; - MainStore.addUploadedKmlName(dataSource.name); - await this.viewer.dataSources.add(dataSource); - await renderWithDelay(this.viewer); - - // done like this to have correct rerender of component - const dataSourcePromise = Promise.resolve(dataSource); - const config: LayerConfig = { - load() { return dataSourcePromise; }, - label: name, - layer, - promise: dataSourcePromise, - opacity: DEFAULT_LAYER_OPACITY, - notSaveToPermalink: true, - ownKml: true, - opacityDisabled: true - }; - this.clickLayer(config); - await this.viewer.zoomTo(dataSource); - this.requestUpdate(); - } - private updateLayers(layers: LayerTreeNode[]): void { this.dispatchEvent(new CustomEvent('layers-update', { detail: { @@ -209,38 +118,70 @@ export class NgmLayersDisplay extends LitElementI18n { }) satisfies LayerEvent); } - private clickLayer(layer: LayerConfig): void { - this.dispatchEvent(new CustomEvent('layer-click', { - bubbles: true, - composed: true, - detail: { - layer, - }, - }) satisfies LayerEvent); - } - - private openIonModal(): void { - this.dispatchEvent(new CustomEvent('openIonModal', { - bubbles: true, - composed: true, - })); - } - // TODO Make all children of this component use the Shadow DOM so we can remove this. createRenderRoot() { return this; } + readonly render = () => html` +
+ +
+
+ ${this.isReordering ? i18next.t('dtd_finish_ordering_label') : i18next.t('dtd_change_order_label')} +
+ ${this.isReordering + ? this.renderSortableLayers() + : this.renderLayers()} +
+ ${i18next.t('dtd_background_map_label')} +
+ ${this.globeQueueLength} +
+
+ +
+
+ `; + + private readonly renderLayers = () => html` + + + `; + + private readonly renderSortableLayers = () => html` + + + `; + static readonly styles = css` - ngm-layers-display .actions { + + ngm-layer-display, ngm-layer-display * { + box-sizing: border-box; + } + + ngm-layer-display { + display: block; + padding-inline: 16px; + } + + ngm-layer-display .actions { display: flex; justify-content: flex-end; padding-top: 9px; } - - ngm-layers-display * { - box-sizing: border-box; - } `; } @@ -248,9 +189,10 @@ export type LayersUpdateEvent = CustomEvent<{ layers: LayerTreeNode[] }> -export type LayerEvent = CustomEvent<{ +export type LayerEvent = CustomEvent +export interface LayerEventDetails { layer: LayerConfig | LayerTreeNode -}> +} type LayerRemovalEvent = CustomEvent<{ idx: number diff --git a/ui/src/components/layer/layer-tabs.ts b/ui/src/components/layer/layer-tabs.ts new file mode 100644 index 000000000..1055fe10e --- /dev/null +++ b/ui/src/components/layer/layer-tabs.ts @@ -0,0 +1,134 @@ +import {customElement, property, query, state} from 'lit/decorators.js'; +import {css, html} from 'lit'; +import {LitElementI18n} from '../../i18n'; +import {LayerConfig} from '../../layertree'; +import './layer-catalog'; +import {Viewer} from 'cesium'; +import MainStore from '../../store/main'; +import {Subscription} from 'rxjs'; +import './options/layer-options'; +import {classMap} from 'lit/directives/class-map.js'; +import i18next from 'i18next'; + +@customElement('ngm-layer-tabs') +export class NgmLayerTabs extends LitElementI18n { + @property() + public accessor layers: LayerConfig[] | null = null + + @property({type: Function}) + accessor onKmlUpload!: (file: File, clampToGround: boolean) => Promise | void + + @state() + private accessor activeTab: Tab = Tab.Catalog + + @state() + private accessor viewer: Viewer | null = null + + private readonly subscription = new Subscription(); + + //TODO: where is this from exactly? + @query('.ngm-side-bar-panel > .ngm-toast-placeholder') + accessor toastPlaceholder!: HTMLElement + + connectedCallback() { + super.connectedCallback(); + this.subscription.add(MainStore.viewer.subscribe((viewer) => { + this.viewer = viewer; + })); + } + + readonly render = () => html` +
+ ${this.renderTabButton(Tab.Catalog)} + ${this.renderTabSeparator(Tab.Catalog, Tab.Upload)} + ${this.renderTabButton(Tab.Upload)} + ${this.renderTabSeparator(Tab.Upload, Tab.Options)} + ${this.renderTabButton(Tab.Options)} +
+
+ ${this.renderCatalog()} +
+
+ +
+
+ +
+ `; + + readonly renderTabButton = (tab: Tab) => html` + + `; + + readonly renderTabSeparator = (a: Tab, b: Tab) => html` +
+ `; + + private readonly renderCatalog = () => html` + + `; + + static readonly styles = css` + :host, :host * { + box-sizing: border-box; + } + + :host { + display: block; + padding: 16px; + } + + .tabs { + display: flex; + justify-content: space-evenly; + align-items: center; + background-color: white; + min-height: 52px; + border-radius: 4px; + padding: 6px; + } + + .tabs > button { + background-color: transparent; + border: none; + padding: 8px; + cursor: pointer; + border-radius: 4px; + flex: 1; + color: var(--color-main); + font-family: var(--font); + font-size: 14px; + font-weight: 500; + line-height: 20px; + } + + .tabs > button.is-active { + background-color: var(--color-rest-active); + color: var(--color-medium-emphasis); + } + + .tabs > .separator { + border: 1px solid #E0E1E4; + height: 18px; + } + + .tabs > .separator:not(.is-active) { + display: none; + } + `; +} + +enum Tab { + Catalog = 'catalog', + Upload = 'upload', + Options = 'options', +} diff --git a/ui/src/components/layer/options/layer-options.ts b/ui/src/components/layer/options/layer-options.ts new file mode 100644 index 000000000..cdd58e99f --- /dev/null +++ b/ui/src/components/layer/options/layer-options.ts @@ -0,0 +1,112 @@ +import {customElement, state} from 'lit/decorators.js'; +import {LitElementI18n} from '../../../i18n'; +import {CustomDataSource, DataSource, DataSourceCollection, Viewer} from 'cesium'; +import MainStore from '../../../store/main'; +import {css, html, unsafeCSS} from 'lit'; +import i18next from 'i18next'; +import {debounce} from '../../../utils'; +import {setExaggeration} from '../../../permalink'; +import NavToolsStore from '../../../store/navTools'; +import {updateExaggerationForKmlDataSource} from '../../../cesiumutils'; +import {classMap} from 'lit/directives/class-map.js'; + + +import iconsCss from '../../../style/icons.css'; +import layersCss from '../../../style/layers.css'; +import sliderCss from '../../../style/ngm-slider.css'; +import fomanticTransitionCss from 'fomantic-ui-css/components/transition.css'; + +@customElement('ngm-layer-options') +export class NgmLayerOptions extends LitElementI18n { + @state() + private accessor viewer: Viewer | null | undefined + + @state() + private accessor exaggeration: number = 1 + + @state() + private accessor hideExaggeration = false + + private prevExaggeration: number = 1; + + constructor() { + super(); + MainStore.viewer.subscribe(viewer => { + this.viewer = viewer; + this.exaggeration = this.viewer?.scene.verticalExaggeration ?? 1; + this.prevExaggeration = this.exaggeration; + this.viewer?.dataSources.dataSourceAdded.addEventListener((_collection: DataSourceCollection, dataSource: DataSource | CustomDataSource) => { + if (MainStore.uploadedKmlNames.includes(dataSource.name)) { + const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; + updateExaggerationForKmlDataSource(dataSource, exaggeration, 1); + } + }); + }); + } + + private updateExaggerationForKmls() { + const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; + MainStore.uploadedKmlNames.forEach(name => { + const dataSource = this.viewer?.dataSources.getByName(name)[0]; + updateExaggerationForKmlDataSource(dataSource, exaggeration, this.prevExaggeration); + }); + this.prevExaggeration = exaggeration; + this.viewer?.scene.requestRender(); + } + + private updateExaggeration(evt: InputEvent) { + if (this.viewer == null) { + return; + } + this.hideExaggeration = false; + this.exaggeration = Number((evt.target).value); + this.viewer.scene.verticalExaggeration = this.exaggeration; + // workaround for billboards positioning + setTimeout(() => this.viewer!.scene.requestRender(), 500); + setExaggeration(this.exaggeration); + NavToolsStore.exaggerationChanged.next(this.exaggeration); + } + + readonly render = () => html` +
+
{ + if (!this.viewer) return; + this.hideExaggeration = !this.hideExaggeration; + const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; + this.viewer.scene.verticalExaggeration = exaggeration; + this.updateExaggerationForKmls(); + NavToolsStore.exaggerationChanged.next(exaggeration); + this.viewer.scene.requestRender(); + }}>HIDE +
+
+
+ + +
+ this.updateExaggeration(evt)} + @pointerup=${debounce(() => this.updateExaggerationForKmls(), 300)} + > +
+
+ `; + + static readonly styles = css` + ${unsafeCSS(fomanticTransitionCss)} + ${unsafeCSS(iconsCss)} + ${unsafeCSS(layersCss.replaceAll('ngm-layers-item', ':host'))} + ${unsafeCSS(sliderCss)} + `; +} diff --git a/ui/src/components/layer/upload/layer-upload-kml.ts b/ui/src/components/layer/upload/layer-upload-kml.ts new file mode 100644 index 000000000..852092ac5 --- /dev/null +++ b/ui/src/components/layer/upload/layer-upload-kml.ts @@ -0,0 +1,159 @@ +import {css, html, unsafeCSS} from 'lit'; +import {customElement, property, query, state} from 'lit/decorators.js'; +import i18next from 'i18next'; +import $ from 'jquery'; +import {classMap} from 'lit-html/directives/class-map.js'; +import {LitElementI18n} from '../../../i18n'; +import {showBannerError, showSnackbarInfo} from '../../../notifications'; +import fomanticButtonCss from 'fomantic-ui-css/components/button.css'; +import fomanticLoaderCss from 'fomantic-ui-css/components/loader.css'; +import '../../core'; + +@customElement('ngm-layer-upload-kml') +export default class NgmLayerUploadKml extends LitElementI18n { + @property({type: Object}) + accessor toastPlaceholder!: HTMLElement; + + @property({type: Number}) + accessor maxFileSize: number | null = null; + + @state() + private accessor isLoading = false; + + @state() + private accessor isClampingToGround = true; + + @query('.ngm-upload-kml') + private accessor uploadKmlInput!: HTMLInputElement; + + private handleDrop(event: DragEvent): void { + event.preventDefault(); + (event.target as HTMLElement).classList.remove('active'); + for (const file of event.dataTransfer!.files) { + this.uploadKml(file); + } + } + + private uploadKml(file: File): void { + if (!file) { + showSnackbarInfo(i18next.t('dtd_no_file_to_upload_warn')); + return; + } + if (!file.name.toLowerCase().endsWith('kml')) { + showBannerError(this.toastPlaceholder, i18next.t('dtd_file_not_kml')); + return; + } + if (this.isFileTooLarge(file)) { + showBannerError(this.toastPlaceholder, `${i18next.t('dtd_max_size_exceeded_warn')} ${this.maxFileSize}MB`); + return; + } + this.isLoading = true; + try { + this.dispatchEvent(new CustomEvent('upload', { + detail: { + file, + isClampingToGround: this.isClampingToGround, + } + })); + this.uploadKmlInput.value = ''; + } catch (e) { + console.error(e); + showBannerError(this.toastPlaceholder, i18next.t('dtd_cant_upload_kml_error')); + } finally { + this.isLoading = false; + } + } + + private isFileTooLarge(file: File): boolean { + return typeof this.maxFileSize === 'number' && !isNaN(this.maxFileSize) && file.size > this.maxFileSize * 1024 * 1024; + } + + readonly render = () => html` + + { + const file = (e.target as HTMLInputElement | null)?.files?.[0]; + if (file != null) { + this.uploadKml(file); + } + }} + /> + this.isClampingToGround = !this.isClampingToGround} + > + `; + + static readonly styles = css` + ${unsafeCSS(fomanticButtonCss)} + ${unsafeCSS(fomanticLoaderCss)} + + :host, :host * { + box-sizing: border-box; + } + + :host { + display: flex; + justify-content: space-between; + + font-family: var(--font); + font-size: 14px; + } + + button.upload { + font-family: var(--font); + cursor: pointer; + display: flex; + align-items: center; + letter-spacing: 0.25px; + font-weight: bold; + color: var(--ngm-interaction); + background-color: #F1F3F5; + border: 2px dashed var(--ngm-interaction); + margin: 9px 0 16px 0; + padding-left: 10px; + height: 46px; + width: 325px; + } + + button.upload:hover { + color: var(--color-action--light); + border: 2px dashed var(--color-action--light); + } + + button.upload > ngm-core-icon { + pointer-events: none; + color: var(--color-highlight--darker); + margin-top: 0; + margin-left: 12px; + width: 20px; + height: 20px; + } + + button.upload:hover > ngm-core-icon { + color: var(--color-action--light); + } + `; +} + +export type KmlUploadEvent = CustomEvent +export interface KmlUploadEventDetails { + file: File + isClampingToGround: boolean +} diff --git a/ui/src/components/layer/upload/layer-upload.ts b/ui/src/components/layer/upload/layer-upload.ts new file mode 100644 index 000000000..9910ac687 --- /dev/null +++ b/ui/src/components/layer/upload/layer-upload.ts @@ -0,0 +1,123 @@ +import {customElement, property, state} from 'lit/decorators.js'; +import {LitElementI18n} from '../../../i18n'; +import {css, html, unsafeCSS} from 'lit'; +import i18next from 'i18next'; +import './layer-upload-kml'; +import type {KmlUploadEvent} from './layer-upload-kml'; +import {CustomDataSource, Viewer} from 'cesium'; +import {parseKml, renderWithDelay} from '../../../cesiumutils'; +import MainStore from '../../../store/main'; +import {DEFAULT_LAYER_OPACITY, LayerConfig} from '../../../layertree'; +import {LayerEventDetails} from '../layer-display'; +import {Subscription} from 'rxjs'; +import fomanticButtonCss from 'fomantic-ui-css/components/button.css'; +import fomanticLoaderCss from 'fomantic-ui-css/components/loader.css'; + + +@customElement('ngm-layer-upload') +export class NgmLayerUpload extends LitElementI18n { + @property({type: Object}) + accessor toastPlaceholder!: HTMLElement + + @state() + private accessor viewer: Viewer | null = null + + private readonly subscription = new Subscription(); + + connectedCallback() { + super.connectedCallback(); + this.subscription.add(MainStore.viewer.subscribe((viewer) => { + this.viewer = viewer; + })); + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.subscription.unsubscribe(); + } + + // TODO Cleanup/Refactor this function. + // As of now, this function remains unchanged to before the navigation-catalog refactoring. + private async handleKmlUpload(e: KmlUploadEvent): Promise { + if (this.viewer == null) { + return; + } + + const dataSource = new CustomDataSource(); + const name = await parseKml(this.viewer, e.detail.file, dataSource, e.detail.isClampingToGround); + const layer = `${name.replace(' ', '_')}_${Date.now()}`; + + // name used as id for datasource + dataSource.name = layer; + MainStore.addUploadedKmlName(dataSource.name); + await this.viewer.dataSources.add(dataSource); + await renderWithDelay(this.viewer); + + // done like this to have correct rerender of component + const dataSourcePromise = Promise.resolve(dataSource); + const config: LayerConfig = { + load() { + return dataSourcePromise; + }, + label: name, + layer, + promise: dataSourcePromise, + opacity: DEFAULT_LAYER_OPACITY, + notSaveToPermalink: true, + ownKml: true, + opacityDisabled: true, + }; + this.emitLayerClick(config); + await this.viewer.zoomTo(dataSource); + this.requestUpdate(); + } + + private emitLayerClick(layer: LayerConfig): void { + this.dispatchEvent(new CustomEvent('layer-click', { + bubbles: true, + composed: true, + detail: { + layer, + }, + })); + } + + private emitIonModalOpening(): void { + this.dispatchEvent(new CustomEvent('openIonModal', { + bubbles: true, + composed: true, + })); + } + + readonly render = () => html` + + + + `; + + static readonly styles = css` + ${unsafeCSS(fomanticButtonCss)} + ${unsafeCSS(fomanticLoaderCss)} + + button.ui.button { + display: flex; + justify-content: center; + overflow-wrap: break-word; + align-items: center; + width: 325px; + height: 36px; + min-height: 36px; + letter-spacing: 0.25px; + box-shadow: 0 1px 3px #00000033; + background-color: #357183; + color: white; + } + `; +} diff --git a/ui/src/components/navigation/navigation-layer-panel.ts b/ui/src/components/navigation/navigation-layer-panel.ts index cc5bd770f..7464b84e3 100644 --- a/ui/src/components/navigation/navigation-layer-panel.ts +++ b/ui/src/components/navigation/navigation-layer-panel.ts @@ -5,9 +5,10 @@ import {customElement, property} from 'lit/decorators.js'; import {LayerConfig} from '../../layertree'; import './navigation-panel'; import './navigation-panel-header'; -import '../layers/layers-catalog'; -import '../layers/layers-display'; -import {LayerEvent, LayersUpdateEvent} from '../layers/layers-display'; +import '../layer/layer-catalog'; +import '../layer/layer-display'; +import '../layer/layer-tabs'; +import type {LayerEvent, LayersUpdateEvent} from '../layer/layer-display'; @customElement('ngm-navigation-layer-panel') @@ -33,34 +34,24 @@ export class NavigationLayerPanel extends LitElementI18n {
- ${i18next.t('lyr_geocatalog_label')} + ${i18next.t('dtd_displayed_data_label')} - ${this.renderCatalog()} + ${this.renderLayers()}
- - ${i18next.t('dtd_displayed_data_label')} - - ${this.renderDisplay()} +
`; - private readonly renderCatalog = () => html` - - `; - - private readonly renderDisplay = () => html` - html` + + > `; connectedCallback(): void { @@ -102,41 +93,27 @@ export class NavigationLayerPanel extends LitElementI18n { } static readonly styles = css` - ngm-navigation-layer-panel * { + ngm-navigation-layer-panel, ngm-navigation-layer-panel * { box-sizing: border-box; } - ngm-navigation-layer-panel ngm-navigation-panel { - --display-height: 500px; - --catalog-height: calc(var(--panel-height) - var(--display-height) - 34px); - - display: grid; - grid-template-columns: 1fr; - grid-template-rows: repeat(2, 1fr); - max-height: calc(100vh - var(--ngm-header-height)); - padding: 0; - } - ngm-navigation-layer-panel ngm-navigation-panel > section { - --section-height: calc(var(--panel-height) / 2); - position: relative; - max-height: var(--section-height); background-color: var(--color-bg--dark); - } + overflow-y: auto; + &:not(:last-child) { + max-height: 50%; + } + } ngm-navigation-layer-panel ngm-navigation-panel > section > * { max-width: calc(100vw); - padding: 0 var(--panel-padding); } - ngm-navigation-layer-panel ngm-layers-catalog, - ngm-navigation-layer-panel ngm-layers-display { + ngm-navigation-layer-panel ngm-layer-catalog, + ngm-navigation-layer-panel ngm-layer-display { display: block; - height: calc(var(--section-height) - 34px); - max-height: calc(var(--section-height) - 34px); - overflow-y: auto; } `; } diff --git a/ui/src/components/navigation/navigation-panel-header.ts b/ui/src/components/navigation/navigation-panel-header.ts index b53cdcdca..3a0bf3542 100644 --- a/ui/src/components/navigation/navigation-panel-header.ts +++ b/ui/src/components/navigation/navigation-panel-header.ts @@ -43,7 +43,7 @@ export class NavigationPanelHeader extends LitElementI18n { align-items: center; font-weight: 700; height: 34px; - padding: 4px 0 2px 0; + padding: 4px 16px 2px 16px; color: var(--color-bg-contrast--light); } `; diff --git a/ui/src/components/navigation/navigation-panel.ts b/ui/src/components/navigation/navigation-panel.ts index 54aa3027a..2a84b397f 100644 --- a/ui/src/components/navigation/navigation-panel.ts +++ b/ui/src/components/navigation/navigation-panel.ts @@ -16,12 +16,12 @@ export class NavigationPanel extends LitElementI18n { max-width: 100vw; height: var(--panel-height); max-height: var(--panel-height); - max-width: calc(100vw); - padding: 0 var(--panel-padding); + padding: 0; display: flex; flex-direction: column; - overflow-y: auto; + align-content: flex-start; + overflow-y: hidden; box-shadow: 4px 0 4px #00000029; z-index: 5; diff --git a/ui/src/components/search/search-input.ts b/ui/src/components/search/search-input.ts index 4b243a2ea..6d0943a65 100644 --- a/ui/src/components/search/search-input.ts +++ b/ui/src/components/search/search-input.ts @@ -79,8 +79,8 @@ export class SearchInput extends LitElementI18n {
    - - + + `; protected updated(changedProperties: PropertyValues): void { @@ -394,9 +394,9 @@ export class SearchInput extends LitElementI18n { align-self: center; flex: 2; - --padding-h: 1rem; - --padding-v: 1rem; - --icon-size: 24px; + --padding-h: 12px; + --padding-v: 6px; + --icon-size: 20px; } :host { @@ -410,9 +410,9 @@ export class SearchInput extends LitElementI18n { @media (width >= 700px) { :host { position: relative; - height: calc(var(--ngm-header-height) - var(--header-padding-v) * 2); - min-width: 260px; - max-width: 496px; + height: 56px; + width: 500px; + max-width: 500px; } } @@ -432,10 +432,9 @@ export class SearchInput extends LitElementI18n { @media (min-width: 700px) { ga-search, :host(:not(.is-active)) ga-search { display: flex; - min-width: 260px; - max-width: 496px; + width: 500px; + max-width: 500px; z-index: auto; - margin-left: 100px; } } @@ -448,40 +447,23 @@ export class SearchInput extends LitElementI18n { font-family: var(--font); font-size: 1rem; + line-height: 24px; + letter-spacing: calc(1rem * 0.001); flex: 1; width: 100%; height: 100%; - padding: var(--padding-v) calc(var(--padding-v) * 2 + var(--icon-size)) var(--padding-v) var(--padding-h); + padding: var(--padding-v) var(--padding-h) var(--padding-v) calc(var(--padding-h) * 2 + var(--icon-size)); - border: 2px solid transparent; - border-radius: 4px; - } - - input:hover { - background-color: var(--color-bg); - } - - input:focus { - background-color: var(--color-highlight); - } - - input:focus, input:hover { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-color: transparent; - border-bottom-color: var(--color-highlight--dark); + border: none; + border-bottom: 2px solid var(--color-main); + border-radius: 6px; } input::placeholder { color: var(--color-bg-contrast); } - input:focus::placeholder { - color: var(--color-bg-contrast--lighter); - opacity: 1; - } - input::-webkit-search-decoration, input::-webkit-search-cancel-button, input::-webkit-search-results-button, @@ -504,8 +486,8 @@ export class SearchInput extends LitElementI18n { text-align: left; font-family: var(--font); font-size: 1rem; - line-height: 1.5rem; - letter-spacing: 0.16px; + line-height: 24px; + letter-spacing: calc(1rem * 0.001); } ul > li { @@ -545,45 +527,37 @@ export class SearchInput extends LitElementI18n { } /* icon */ - - .icon { + ngm-core-icon { width: var(--icon-size); height: var(--icon-size); - mask: var(--icon, none) no-repeat center; - -webkit-mask: var(--icon, none) no-repeat center; - background-color: var(--color-highlight--darker); + color: var(--color-bg-contrast); position: absolute; - right: var(--padding-h); + left: var(--padding-h); top: 0; bottom: 0; margin: auto; cursor: pointer; } - .icon.is-close { - --icon: url('./images/i_close.svg'); + ngm-core-icon[icon="close"] { right: calc(var(--padding-h) + 54px) } - .icon.is-search { - --icon: url('./images/icon_search.svg'); - } - - :host(.is-active) .icon.is-search { + :host(.is-active) ngm-core-icon[icon="search"] { color: var(--color-highlight); } - ga-search:has(input:placeholder-shown) ~ .icon.is-close { + ga-search:has(input:placeholder-shown) ~ ngm-core-icon[icon="close"] { display: none; } @media (min-width: 700px) { - .icon.is-close { - right: var(--padding-h); + ngm-core-icon[icon="close"] { + display: none; } - ga-search:has(input:not(:placeholder-shown)) ~ .icon.is-search { + ga-search:has(input:not(:placeholder-shown)) ~ ngm-core-icon[icon="search"] { display: none; } } diff --git a/ui/src/elements/dashboard/ngm-project-assets-section.ts b/ui/src/elements/dashboard/ngm-project-assets-section.ts index f73fdb4da..ab0708207 100644 --- a/ui/src/elements/dashboard/ngm-project-assets-section.ts +++ b/ui/src/elements/dashboard/ngm-project-assets-section.ts @@ -6,6 +6,7 @@ import {classMap} from 'lit/directives/class-map.js'; import {Asset} from './ngm-dashboard'; import '../../layers/ngm-layers-upload'; import {PROJECT_ASSET_MAX_SIZE} from '../../constants'; +import type {KmlUploadEvent} from '../../components/layer/upload/layer-upload-kml'; @customElement('ngm-project-assets-section') export class NgmProjectAssetsSection extends LitElementI18n { @@ -44,6 +45,12 @@ export class NgmProjectAssetsSection extends LitElementI18n { `; } + handleKmlUpload(e: KmlUploadEvent): void { + if (this.onKmlUpload != null) { + this.onKmlUpload(e.detail.file); + } + } + render() { return html`
    @@ -53,10 +60,12 @@ export class NgmProjectAssetsSection extends LitElementI18n {
    ${this.viewMode ? '' : html` - `} + + `} ${this.assets?.map((kml, index) => { return html`
    @@ -86,4 +95,4 @@ export class NgmProjectAssetsSection extends LitElementI18n { return this; } -} \ No newline at end of file +} diff --git a/ui/src/elements/ngm-auth.ts b/ui/src/elements/ngm-auth.ts index d5726ac93..e8aef4bb6 100644 --- a/ui/src/elements/ngm-auth.ts +++ b/ui/src/elements/ngm-auth.ts @@ -1,13 +1,13 @@ -import {html} from 'lit'; +import {css, html} from 'lit'; import type {AuthUser} from '../authService'; import AuthService from '../authService'; import {LitElementI18n} from '../i18n.js'; import auth from '../store/auth'; -import {classMap} from 'lit/directives/class-map.js'; import {customElement, property, state} from 'lit/decorators.js'; import DashboardStore from '../store/dashboard'; import {consume} from '@lit/context'; import {authServiceContext} from '../context'; +import '../components/core'; /** * Authentication component @@ -61,16 +61,29 @@ export class NgmAuth extends LitElementI18n { this.authService.logout(); } - render() { - return html` -
    -
    -
    `; - } + readonly render = () => html` +
    + +
    + `; - createRenderRoot() { - // no shadow dom - return this; - } + static readonly styles = css` + div { + color: var(--color-bg); + background-color: var(--color-main); + width: 36px; + height: 36px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + } + + ngm-core-icon { + width: 20px; + height: 20px; + } + `; } diff --git a/ui/src/elements/ngm-cursor-information.ts b/ui/src/elements/ngm-cursor-information.ts index ec909c66c..3419f6102 100644 --- a/ui/src/elements/ngm-cursor-information.ts +++ b/ui/src/elements/ngm-cursor-information.ts @@ -1,4 +1,4 @@ -import {html} from 'lit'; +import {css, html} from 'lit'; import i18next from 'i18next'; import {LitElementI18n} from '../i18n.js'; import type {Viewer} from 'cesium'; @@ -73,23 +73,62 @@ export class NgmCursorInformation extends LitElementI18n { render() { if (!this.coordinates || this.height === undefined) return ''; return html` -
    - - - +
    + + + +
    +
    +
    + ${this.showTerrainHeight ? i18next.t('nav_terrain_height_label') : i18next.t('nav_object_height_label')}
    -
    -
    - ${this.showTerrainHeight ? i18next.t('nav_terrain_height_label') : i18next.t('nav_object_height_label')} -
    -
    - ${this.height !== undefined && this.integerFormat.format(this.height)} m -
    +
    + ${this.height !== undefined && this.integerFormat.format(this.height)} m
    - `; +
    + `; } - createRenderRoot() { - return this; - } + + static readonly styles = css` + :host { + min-width: 0; + } + + .ngm-nci-height, + .ngm-nci-position { + display: none; + } + + @media (min-width: 1200px) { + :host .ngm-nci-value { + font-weight: 600; + min-width: 75px; + } + + .ngm-nci-position { + display: unset; + width: 83px; + margin-right: 20px; + } + + .ngm-nci-height { + display: flex; + flex-direction: column; + white-space: nowrap; + margin-right: 10px; + } + + :host { + flex-wrap: wrap; + align-self: center; + color: #212529; + text-align: left; + font: normal normal normal 14px/20px Inter, sans-serif; + letter-spacing: 0; + min-width: 255px; + padding-right:48px + } + } + `; } diff --git a/ui/src/elements/ngm-map-configuration.ts b/ui/src/elements/ngm-map-configuration.ts index 0bd72df6e..8afc1785a 100644 --- a/ui/src/elements/ngm-map-configuration.ts +++ b/ui/src/elements/ngm-map-configuration.ts @@ -4,16 +4,14 @@ import i18next from 'i18next'; import {LitElementI18n} from '../i18n.js'; import {classMap} from 'lit-html/directives/class-map.js'; import './ngm-map-chooser'; -import {getMapOpacityParam, setExaggeration, syncMapOpacityParam} from '../permalink'; +import {getMapOpacityParam, syncMapOpacityParam} from '../permalink'; import MainStore from '../store/main'; import { - CustomDataSource, DataSource, DataSourceCollection, + Viewer } from 'cesium'; import type MapChooser from '../MapChooser.js'; import {debounce} from '../utils'; -import {updateExaggerationForKmlDataSource} from '../cesiumutils'; -import NavToolsStore from '../store/navTools'; @customElement('ngm-map-configuration') export class NgmMapConfiguration extends LitElementI18n { @@ -24,29 +22,16 @@ export class NgmMapConfiguration extends LitElementI18n { @state() accessor opacity: number = getMapOpacityParam(); @state() - accessor exaggeration: number = 1; - @state() - accessor hideExaggeration = false; - @state() accessor baseMapId = 'ch.swisstopo.pixelkarte-grau'; @query('ngm-map-chooser') accessor mapChooserElement; private readonly debouncedOpacityUpdate = debounce((evt: Event) => this.updateOpacity(Number((evt.target).value)), 250); - private prevExaggeration: number = 1; constructor() { super(); MainStore.viewer.subscribe(viewer => { this.viewer = viewer; - this.exaggeration = this.viewer?.scene.verticalExaggeration || 1; - this.prevExaggeration = this.exaggeration; - this.viewer?.dataSources.dataSourceAdded.addEventListener((_collection: DataSourceCollection, dataSource: DataSource | CustomDataSource) => { - if (MainStore.uploadedKmlNames.includes(dataSource.name)) { - const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; - updateExaggerationForKmlDataSource(dataSource, exaggeration, 1); - } - }); }); MainStore.mapChooser.subscribe(chooser => { this.mapChooser = chooser; @@ -97,29 +82,6 @@ export class NgmMapConfiguration extends LitElementI18n { this.requestUpdate(); } - updateExaggerationForKmls() { - const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; - MainStore.uploadedKmlNames.forEach(name => { - const dataSource = this.viewer?.dataSources.getByName(name)[0]; - updateExaggerationForKmlDataSource(dataSource, exaggeration, this.prevExaggeration); - }); - this.prevExaggeration = exaggeration; - this.viewer?.scene.requestRender(); - } - - updateExaggeration(evt: InputEvent) { - if (this.viewer) { - if (this.hideExaggeration) { - this.hideExaggeration = false; - } - this.exaggeration = Number((evt.target).value); - this.viewer.scene.verticalExaggeration = this.exaggeration; - // workaround for billboards positioning - setTimeout(() => this.viewer!.scene.requestRender(), 500); - setExaggeration(this.exaggeration); - NavToolsStore.exaggerationChanged.next(this.exaggeration); - } - } render() { return html` @@ -153,36 +115,6 @@ export class NgmMapConfiguration extends LitElementI18n {
    -
    -
    { - if (!this.viewer) return; - this.hideExaggeration = !this.hideExaggeration; - const exaggeration = this.hideExaggeration ? 1 : this.exaggeration; - this.viewer.scene.verticalExaggeration = exaggeration; - this.updateExaggerationForKmls(); - NavToolsStore.exaggerationChanged.next(exaggeration); - this.viewer.scene.requestRender(); - }}>
    -
    -
    - - -
    - this.updateExaggeration(evt)} - @pointerup=${debounce(() => this.updateExaggerationForKmls(), 300)}> -
    -
    `; } diff --git a/ui/src/elements/ngm-side-bar.ts b/ui/src/elements/ngm-side-bar.ts index 2358a7a63..77b0c3aa5 100644 --- a/ui/src/elements/ngm-side-bar.ts +++ b/ui/src/elements/ngm-side-bar.ts @@ -4,6 +4,8 @@ import '../toolbox/ngm-toolbox'; import '../layers/ngm-layers'; import '../layers/ngm-layers-sort'; import './dashboard/ngm-dashboard'; +import './sidebar/ngm-menu-item'; +import '../components/core'; import '../components/navigation/navigation-layer-panel'; import LayersActions from '../layers/LayersActions'; import {DEFAULT_LAYER_OPACITY, LayerType} from '../constants'; @@ -46,7 +48,7 @@ import type QueryManager from '../query/QueryManager'; import DashboardStore from '../store/dashboard'; import {getAssets} from '../api-ion'; -import {LayerEvent, LayersUpdateEvent} from '../components/layers/layers-display'; +import {LayerEvent, LayersUpdateEvent} from '../components/layer/layer-display'; export type SearchLayer = | SearchLayerWithLayer @@ -87,7 +89,7 @@ export class SideBar extends LitElementI18n { // TODO change this back to `null` @state() - accessor activePanel: string | null = null; + accessor activePanel: string | null = 'data'; @state() accessor showHeader = false; @state() @@ -181,10 +183,20 @@ export class SideBar extends LitElementI18n { }); const sliceOptions = getSliceParam(); - if (sliceOptions && sliceOptions.type && sliceOptions.slicePoints) + if (sliceOptions?.type && sliceOptions.slicePoints) this.activePanel = 'tools'; } + private readonly renderMenuItem = (icon: string, title: string, panel: string) => html` + this.togglePanel(panel)} + > + `; + render() { if (!this.queryManager) { return ''; @@ -193,16 +205,17 @@ export class SideBar extends LitElementI18n { this.queryManager.activeLayers = this.activeLayers .filter(config => config.visible && !config.noQuery); - const shareBtn = html` - `; - const settingsBtn = html` -
    this.togglePanel('settings')}> -
    -
    `; + + const layerBtn = this.renderMenuItem('layer', 'menu_layers', 'data'); + const toolsBtn = this.renderMenuItem('tools', 'menu_tools', 'tools'); + const projectsBtn = this.renderMenuItem('projects', 'menu_projects', 'dashboard'); + const shareBtn = this.renderMenuItem('share', 'menu_share', 'share'); + const settingsBtn = this.renderMenuItem('config', 'menu_settings', 'settings'); + const mobileExpandBtn = html` + this.mobileShowAll = !this.mobileShowAll} + >`; return html`
    @@ -212,31 +225,15 @@ export class SideBar extends LitElementI18n {
    -
    -
    -
    this.togglePanel('dashboard')}> -
    -
    -
    this.togglePanel('data')}> -
    -
    -
    this.togglePanel('tools', false)}> -
    -
    +
    +
    + ${layerBtn} + ${toolsBtn} ${!this.mobileView ? shareBtn : ''} -
    this.mobileShowAll = !this.mobileShowAll}> -
    -
    + ${projectsBtn} + ${this.mobileView ? mobileExpandBtn : ''}
    -
    +
    ${settingsBtn}
    @@ -300,7 +297,6 @@ export class SideBar extends LitElementI18n { return; } this.activePanel = panelName; - // if (this.activePanel === 'data' && !this.mobileView) this.hideDataDisplayed = false; } async syncActiveLayers() { @@ -417,7 +413,7 @@ export class SideBar extends LitElementI18n { if (panelElement) { for (let i = 0; i < panelElement.childElementCount; i++) { const element = panelElement.children.item(i); - if (element && element.classList.contains('accordion')) { + if (element?.classList.contains('accordion')) { $(element).accordion({duration: 150}); } } @@ -559,7 +555,7 @@ export class SideBar extends LitElementI18n { if (zoomToPosition) { let altitude = 0, cartesianPosition: Cartesian3 | undefined, windowPosition: Cartesian2 | undefined; const updateValues = () => { - altitude = this.viewer!.scene.globe.getHeight(this.viewer!.scene.camera.positionCartographic) || 0; + altitude = this.viewer!.scene.globe.getHeight(this.viewer!.scene.camera.positionCartographic) ?? 0; cartesianPosition = Cartesian3.fromDegrees(zoomToPosition.longitude, zoomToPosition.latitude, zoomToPosition.height + altitude); windowPosition = this.viewer!.scene.cartesianToCanvasCoordinates(cartesianPosition); }; diff --git a/ui/src/elements/sidebar/ngm-menu-item.ts b/ui/src/elements/sidebar/ngm-menu-item.ts new file mode 100644 index 000000000..2e60d90dc --- /dev/null +++ b/ui/src/elements/sidebar/ngm-menu-item.ts @@ -0,0 +1,118 @@ +import {customElement, property} from 'lit/decorators.js'; +import {LitElementI18n} from '../../i18n.js'; +import {css, html} from 'lit'; +import {classMap} from 'lit/directives/class-map.js'; +import {IconKey} from '../../icons/icons'; +import i18next from 'i18next'; +import '../../components/core'; + +@customElement('ngm-menu-item') +export class MenuItem extends LitElementI18n { + @property({type: String}) + accessor title: string = ''; + @property() + accessor icon: IconKey = 'config'; + @property({type: Boolean, attribute: 'isactive', reflect: true}) + accessor isActive: boolean = false; + @property({type: Boolean}) + accessor isMobile: boolean = false; + + static readonly styles = css` + :host { + position: relative; + width: 68px; + height: 58px; + display: flex; + justify-content: center; + align-items: center; + color: var(--color-main); + padding-inline: 5px; + z-index: 10; + } + + :host > .container { + position: relative; + text-decoration: none; + width: 100%; + height: 100%; + cursor: pointer; + } + + .container .box { + position: absolute; + width: 58px; + min-width: 58px; + left: 0; + top: 9px; + height: 40px; + display: flex; + justify-content: flex-start; + align-items: center; + border-radius: 4px; + background-color: transparent; + color: var(--color-main); + transition: ease-out 100ms; + transition-property: color, background-color; + } + + .container .box > .icon { + display: flex; + justify-content: center; + align-items: center; + min-width: 58px; + height: 40px; + color: var(--color-main); + } + + .container .box > .title { + transform: scaleX(0); + transition: ease-out 100ms; + transition-property: transform; + transform-origin: left; + } + + .container:hover .box { + background-color: var(--color-main); + color: var(--color-bg); + width: unset; + white-space: nowrap; + } + + .container:hover .box > .title { + transform: scaleX(1); + } + + .container:hover .box .icon { + color: var(--color-bg); + } + + @media (min-width: 599px) { + .container:hover .box { + padding-right: 22px; + } + } + + :host([isactive]) .container .box { + background-color: var(--color-active); + } + + :host([isactive]) .container .box .icon { + color: var(--color-bg); + } + `; + + render() { + return html` +
    +
    +
    + +
    +
    + ${i18next.t(this.title)} +
    +
    +
    + `; + } +} diff --git a/ui/src/icons/i_checkmark.ts b/ui/src/icons/i_checkmark.ts new file mode 100644 index 000000000..b11fd9adb --- /dev/null +++ b/ui/src/icons/i_checkmark.ts @@ -0,0 +1,8 @@ +import {html} from 'lit'; + +export const checkmarkIcon = html` + + + + +`; diff --git a/ui/src/icons/i_close.ts b/ui/src/icons/i_close.ts new file mode 100644 index 000000000..a0cb0d77f --- /dev/null +++ b/ui/src/icons/i_close.ts @@ -0,0 +1,7 @@ +import {html} from 'lit'; + +export const closeIcon = html` + + + +`; diff --git a/ui/src/icons/i_config.ts b/ui/src/icons/i_config.ts new file mode 100644 index 000000000..e91ad3ea5 --- /dev/null +++ b/ui/src/icons/i_config.ts @@ -0,0 +1,8 @@ +import {html} from 'lit'; + +export const configIcon = html` + + +`; + + diff --git a/ui/src/icons/i_dropdown.ts b/ui/src/icons/i_dropdown.ts new file mode 100644 index 000000000..70a32a9a2 --- /dev/null +++ b/ui/src/icons/i_dropdown.ts @@ -0,0 +1,7 @@ +import {html} from 'lit'; + +export const dropdownIcon = html` + + + +`; diff --git a/ui/src/icons/i_kml_upload.ts b/ui/src/icons/i_kml_upload.ts new file mode 100644 index 000000000..13d688460 --- /dev/null +++ b/ui/src/icons/i_kml_upload.ts @@ -0,0 +1,10 @@ +import {html} from 'lit'; + +export const kmlUploadIcon = html` + + + + +`; diff --git a/ui/src/icons/i_layer.ts b/ui/src/icons/i_layer.ts new file mode 100644 index 000000000..b21e6223b --- /dev/null +++ b/ui/src/icons/i_layer.ts @@ -0,0 +1,5 @@ +import {html} from 'lit'; + +export const layerIcon = html` + +`; diff --git a/ui/src/icons/i_projects.ts b/ui/src/icons/i_projects.ts new file mode 100644 index 000000000..22b91d676 --- /dev/null +++ b/ui/src/icons/i_projects.ts @@ -0,0 +1,5 @@ +import {html} from 'lit'; + +export const projectsIcon = html` + +`; diff --git a/ui/src/icons/i_search.ts b/ui/src/icons/i_search.ts new file mode 100644 index 000000000..e0b59d40b --- /dev/null +++ b/ui/src/icons/i_search.ts @@ -0,0 +1,7 @@ +import {html} from 'lit'; + +export const searchIcon = html` + + + +`; diff --git a/ui/src/icons/i_share.ts b/ui/src/icons/i_share.ts new file mode 100644 index 000000000..f01d623cf --- /dev/null +++ b/ui/src/icons/i_share.ts @@ -0,0 +1,5 @@ +import {html} from 'lit'; + +export const shareIcon = html` + +`; diff --git a/ui/src/icons/i_tools.ts b/ui/src/icons/i_tools.ts new file mode 100644 index 000000000..64f14c77b --- /dev/null +++ b/ui/src/icons/i_tools.ts @@ -0,0 +1,5 @@ +import {html} from 'lit'; + +export const toolsIcon = html` + +`; diff --git a/ui/src/icons/i_user.ts b/ui/src/icons/i_user.ts new file mode 100644 index 000000000..368e0a5bb --- /dev/null +++ b/ui/src/icons/i_user.ts @@ -0,0 +1,7 @@ +import {html} from 'lit'; + +export const userIcon = html` + + + +`; diff --git a/ui/src/images/i_view_all.svg b/ui/src/icons/i_view_all.ts similarity index 75% rename from ui/src/images/i_view_all.svg rename to ui/src/icons/i_view_all.ts index 462e1658f..22b01bdb2 100644 --- a/ui/src/images/i_view_all.svg +++ b/ui/src/icons/i_view_all.ts @@ -1,4 +1,6 @@ - - - - +import {html} from 'lit'; + +export const viewAllIcon = html` + + +`; diff --git a/ui/src/images/i_view_less.svg b/ui/src/icons/i_view_less.ts similarity index 71% rename from ui/src/images/i_view_less.svg rename to ui/src/icons/i_view_less.ts index 9243f0daa..a7b4fb731 100644 --- a/ui/src/images/i_view_less.svg +++ b/ui/src/icons/i_view_less.ts @@ -1,4 +1,6 @@ - - - - +import {html} from 'lit'; + +export const viewLessIcon = html` + + +`; diff --git a/ui/src/icons/icons.ts b/ui/src/icons/icons.ts new file mode 100644 index 000000000..a0d00664c --- /dev/null +++ b/ui/src/icons/icons.ts @@ -0,0 +1,31 @@ +import {layerIcon} from './i_layer'; +import {toolsIcon} from './i_tools'; +import {shareIcon} from './i_share'; +import {projectsIcon} from './i_projects'; +import {configIcon} from './i_config'; +import {viewAllIcon} from './i_view_all'; +import {viewLessIcon} from './i_view_less'; +import {userIcon} from './i_user'; +import {checkmarkIcon} from './i_checkmark'; +import {kmlUploadIcon} from './i_kml_upload'; +import {searchIcon} from './i_search'; +import {closeIcon} from './i_close'; +import {dropdownIcon} from './i_dropdown'; + +export const icons = { + checkmark: checkmarkIcon, + close: closeIcon, + config: configIcon, + kmlUpload: kmlUploadIcon, + dropdown: dropdownIcon, + layer: layerIcon, + projects: projectsIcon, + search: searchIcon, + share: shareIcon, + tools: toolsIcon, + user: userIcon, + viewAll: viewAllIcon, + viewLess: viewLessIcon, +}; + +export type IconKey = keyof typeof icons; diff --git a/ui/src/images/i_config.svg b/ui/src/images/i_config.svg deleted file mode 100644 index 80c21976c..000000000 --- a/ui/src/images/i_config.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/images/i_layer.svg b/ui/src/images/i_layer.svg deleted file mode 100644 index 239346727..000000000 --- a/ui/src/images/i_layer.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/images/i_menu.svg b/ui/src/images/i_menu.svg deleted file mode 100644 index b3770918e..000000000 --- a/ui/src/images/i_menu.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/images/i_share.svg b/ui/src/images/i_share.svg deleted file mode 100644 index c26a36047..000000000 --- a/ui/src/images/i_share.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/images/i_tools.svg b/ui/src/images/i_tools.svg deleted file mode 100644 index 89c7636f1..000000000 --- a/ui/src/images/i_tools.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/images/i_user.svg b/ui/src/images/i_user.svg deleted file mode 100644 index dfa5bb37a..000000000 --- a/ui/src/images/i_user.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ui/src/layers/ngm-layers-upload.ts b/ui/src/layers/ngm-layers-upload.ts index b55d8cc39..5263b4a11 100644 --- a/ui/src/layers/ngm-layers-upload.ts +++ b/ui/src/layers/ngm-layers-upload.ts @@ -6,7 +6,7 @@ import {showBannerError, showSnackbarInfo} from '../notifications'; import $ from 'jquery'; import {classMap} from 'lit-html/directives/class-map.js'; -@customElement('ngm-layers-upload') +@customElement('ngm-layers-upload-action') export default class LayersUpload extends LitElementI18n { @property({type: Object}) accessor toastPlaceholder!: HTMLElement; diff --git a/ui/src/ngm-app.ts b/ui/src/ngm-app.ts index 7c82c81f7..09cceb0a1 100644 --- a/ui/src/ngm-app.ts +++ b/ui/src/ngm-app.ts @@ -20,8 +20,9 @@ import './elements/ngm-ion-modal'; import './elements/ngm-wmts-date-picker'; import './components/search/search-input'; import 'fomantic-ui-css/components/dropdown'; - +import 'fomantic-ui-css/components/dropdown.js'; import '@geoblocks/cesium-view-cube'; +import './components/core'; import {COGNITO_VARIABLES, DEFAULT_VIEW, SUPPORTED_LANGUAGES} from './constants'; @@ -59,6 +60,7 @@ import $ from 'jquery'; import {clientConfigContext} from './context'; import {consume} from '@lit/context'; import {ClientConfig} from './api/client-config'; +import {styleMap} from 'lit/directives/style-map.js'; const SKIP_STEP2_TIMEOUT = 5000; @@ -248,6 +250,8 @@ export class NgmApp extends LitElementI18n { } async firstUpdated() { + this.querySelectorAll('.menu').forEach((it) => $(it).dropdown()); + setTimeout(() => this.determinateLoading = true, 3000); setupI18n(); rewriteParams(); @@ -411,25 +415,32 @@ export class NgmApp extends LitElementI18n { render() { return html`
    - - - -
    swissgeol
    -
    - +
    diff --git a/ui/src/style/header.css b/ui/src/style/header.css index d11679134..cab69e446 100644 --- a/ui/src/style/header.css +++ b/ui/src/style/header.css @@ -1,7 +1,7 @@ header { display: flex; align-items: center; - justify-content: space-between; + padding: 0 16px; height: var(--ngm-header-height); box-shadow: 0 3px 5px rgba(57, 63, 72, 0.3); border-bottom: 1px solid rgba(0, 0, 0, 0.1); @@ -9,8 +9,6 @@ header { --header-padding-l: 56px; --header-padding-r: 16px; - --header-padding-v: 10px; - padding: var(--header-padding-v) var(--header-padding-r) var(--header-padding-v) var(--header-padding-l); --content-width: calc(100vw - var(--header-padding-l) - var(--header-padding-r)); --suffix-width: 90px; @@ -18,7 +16,6 @@ header { @media (min-width: 700px) { header { - --header-padding-v: 16px; justify-content: flex-start; } } @@ -27,12 +24,19 @@ header > * { display: flex; } -header > a { +header > .left { + display: inline-flex; + align-items: center; + gap: 160px; +} + +header a#ngm-home-link { display: flex; align-items: center; font-size: 16px; color: #212529; z-index: 1; + height: fit-content; } @media (min-width: 700px) { @@ -76,6 +80,7 @@ header .ngm-header-suffix { max-width: var(--suffix-width); display: flex; align-items: center; + gap: 8px; } @media (min-width: 700px) { diff --git a/ui/src/style/icons.css b/ui/src/style/icons.css index 0b90643d6..e87a2e9a2 100644 --- a/ui/src/style/icons.css +++ b/ui/src/style/icons.css @@ -1,375 +1,319 @@ -.ngm-icon.disabled { - cursor: default; - pointer-events: none; - opacity: 0.45; -} - -.ngm-search-icon { - mask: url('./images/icon_search.svg') no-repeat center; - -webkit-mask: url('./images/icon_search.svg') no-repeat center; -} - -.ngm-close-icon { - mask: url('./images/i_close.svg') no-repeat center; - -webkit-mask: url('./images/i_close.svg') no-repeat center; -} - -.ngm-dashboard-icon { - width: 20px; - height: 14px; - mask: url('./images/i_menu.svg') no-repeat center; - -webkit-mask: url('./images/i_menu.svg') no-repeat center; -} - -.ngm-data-icon { - width: 24px; - height: 20px; - mask: url('./images/i_layer.svg') no-repeat center; - -webkit-mask: url('./images/i_layer.svg') no-repeat center; -} - -.ngm-tools-icon { - width: 22px; - height: 22px; - mask: url('./images/i_tools.svg') no-repeat center; - -webkit-mask: url('./images/i_tools.svg') no-repeat center; -} - -.ngm-share-icon { - width: 22px; - height: 22px; - mask: url('./images/i_share.svg') no-repeat center; - -webkit-mask: url('./images/i_share.svg') no-repeat center; -} - -.ngm-user-icon { - width: 18px; - height: 18px; - mask: url('./images/i_user.svg') no-repeat center; - -webkit-mask: url('./images/i_user.svg') no-repeat center; -} - -.ngm-settings-icon { - width: 20px; - height: 20px; - mask: url('./images/i_config.svg') no-repeat center; - -webkit-mask: url('./images/i_config.svg') no-repeat center; -} - -.ngm-zoom-p-icon { - width: 24px; - height: 24px; - mask: url('./images/i_zoom_plus.svg') no-repeat center; - -webkit-mask: url('./images/i_zoom_plus.svg') no-repeat center; -} - -.ngm-zoom-o-icon { - width: 24px; - height: 24px; - mask: url('./images/i_zoom_origin.svg') no-repeat center; - -webkit-mask: url('./images/i_zoom_origin.svg') no-repeat center; -} - -.ngm-zoom-m-icon { - width: 24px; - height: 24px; - mask: url('./images/i_zoom_minus.svg') no-repeat center; - -webkit-mask: url('./images/i_zoom_minus.svg') no-repeat center; -} - -.ngm-map-icon { - width: 24px; - height: 24px; - mask: url('./images/i_switzerland.svg') no-repeat center; - -webkit-mask: url('./images/i_switzerland.svg') no-repeat center; -} - -.ngm-cam-icon { - width: 24px; - height: 14px; - mask: url('./images/i_cam.svg') no-repeat center; - -webkit-mask: url('./images/i_cam.svg') no-repeat center; -} - -.ngm-cam-behind-icon { - width: 24px; - height: 24px; - mask: url('./images/i_cam_behind.svg') no-repeat center; - -webkit-mask: url('./images/i_cam_behind.svg') no-repeat center; -} - -.ngm-cam-h-icon { - width: 24px; - height: 24px; - mask: url('./images/i_cam_height.svg') no-repeat center; - -webkit-mask: url('./images/i_cam_height.svg') no-repeat center; -} - -.ngm-cam-d-icon { - width: 24px; - height: 24px; - mask: url('./images/i_cam_angle.svg') no-repeat center; - -webkit-mask: url('./images/i_cam_angle.svg') no-repeat center; -} - -.ngm-cam-t-icon { - width: 24px; - height: 22px; - mask: url('./images/i_cam_tilt.svg') no-repeat center; - -webkit-mask: url('./images/i_cam_tilt.svg') no-repeat center; -} - -.ngm-dropdown-icon { - width: 24px; - height: 24px; - mask: url('./images/i_menu-1.svg') no-repeat center; - -webkit-mask: url('./images/i_menu-1.svg') no-repeat center; -} - -.ngm-visible-icon { - width: 24px; - height: 24px; - mask: url('./images/i_visible.svg') no-repeat center; - -webkit-mask: url('./images/i_visible.svg') no-repeat center; -} - -.ngm-invisible-icon { - width: 24px; - height: 24px; - mask: url('./images/i_invisible.svg') no-repeat center; - -webkit-mask: url('./images/i_invisible.svg') no-repeat center; -} - -.ngm-zoom-plus-icon { - width: 20px; - height: 20px; - mask: url('./images/i_heranzoomen.svg') no-repeat center; - -webkit-mask: url('./images/i_heranzoomen.svg') no-repeat center; -} - -.ngm-delete-icon { - width: 24px; - height: 24px; - mask: url('./images/i_delete.svg') no-repeat center; - -webkit-mask: url('./images/i_delete.svg') no-repeat center; -} - -.ngm-coords-icon { - width: 22px; - height: 24px; - mask: url('./images/i_coordinates.svg') no-repeat center; - -webkit-mask: url('./images/i_coordinates.svg') no-repeat center; -} - -.ngm-vector-icon { - width: 24px; - height: 24px; - mask: url('./images/i_vector.svg') no-repeat center; - -webkit-mask: url('./images/i_vector.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-line-draw-icon { - width: 24px; - height: 24px; - mask: url('./images/i_line_draw.svg') no-repeat center; - -webkit-mask: url('./images/i_line_draw.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-polygon-draw-icon { - width: 24px; - height: 24px; - mask: url('./images/i_polygon_draw.svg') no-repeat center; - -webkit-mask: url('./images/i_polygon_draw.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-point-draw-icon { - width: 24px; - height: 24px; - mask: url('./images/i_point_draw.svg') no-repeat center; - -webkit-mask: url('./images/i_point_draw.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-rectangle-draw-icon { - width: 24px; - height: 24px; - mask: url('./images/i_rectangle_draw.svg') no-repeat center; - -webkit-mask: url('./images/i_rectangle_draw.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-file-upload-icon { - width: 24px; - height: 24px; - mask: url('./images/i_kml_gpx_upload.svg') no-repeat center; - -webkit-mask: url('./images/i_kml_gpx_upload.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-file-download-icon { - width: 24px; - height: 24px; - mask: url('./images/i_download_kml.svg') no-repeat center; - -webkit-mask: url('./images/i_download_kml.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-action-menu-icon { - width: 24px; - height: 24px; - mask: url('./images/i_action_menu.svg') no-repeat center; - -webkit-mask: url('./images/i_action_menu.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-slicing-icon { - width: 24px; - height: 24px; - mask: url('./images/i_slicing.svg') no-repeat center; - -webkit-mask: url('./images/i_slicing.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-in-box-icon { - width: 24px; - height: 24px; - mask: url('./images/i_box_inside.svg') no-repeat center; - -webkit-mask: url('./images/i_box_inside.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-out-box-icon { - width: 24px; - height: 24px; - mask: url('./images/i_box_outside.svg') no-repeat center; - -webkit-mask: url('./images/i_box_outside.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-back-icon { - width: 24px; - height: 24px; - mask: url('./images/i_back.svg') no-repeat center; - -webkit-mask: url('./images/i_back.svg') no-repeat center; - background-color: #000000; -} - -.ngm-slice-left-icon { - width: 24px; - height: 24px; - mask: url('./images/i_slice_left.svg') no-repeat center; - -webkit-mask: url('./images/i_slice_left.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-slice-right-icon { - width: 24px; - height: 24px; - mask: url('./images/i_slice_right.svg') no-repeat center; - -webkit-mask: url('./images/i_slice_right.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-upload-icon { - width: 24px; - height: 24px; - mask: url('./images/i_upload.svg') no-repeat center; - -webkit-mask: url('./images/i_upload.svg') no-repeat center; -} - -.ngm-extrusion-icon { - width: 20px; - height: 24px; - mask: url('./images/i_extrusion.svg') no-repeat center; - -webkit-mask: url('./images/i_extrusion.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-edit-icon { - width: 24px; - height: 24px; - mask: url('./images/i_edit.svg') no-repeat center; - -webkit-mask: url('./images/i_edit.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-download-icon { - width: 24px; - height: 24px; - mask: url('./images/i_download.svg') no-repeat center; - -webkit-mask: url('./images/i_download.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-screenshot-icon { - width: 18px; - height: 18px; - mask: url('./images/i_screenshot.svg') no-repeat center; - -webkit-mask: url('./images/i_screenshot.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-gst-icon { - width: 24px; - height: 24px; - mask: url('./images/i_query_3d_models.svg') no-repeat center; - -webkit-mask: url('./images/i_query_3d_models.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-play-last-icon { - width: 24px; - height: 24px; - mask: url('./images/i_play_the_last.svg') no-repeat center; - -webkit-mask: url('./images/i_play_the_last.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-play-icon { - width: 24px; - height: 24px; - mask: url('./images/i_play.svg') no-repeat center; - -webkit-mask: url('./images/i_play.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-view-all-icon { - width: 24px; - height: 24px; - mask: url('./images/i_view_all.svg') no-repeat center; - -webkit-mask: url('./images/i_view_all.svg') no-repeat center; -} - -.ngm-view-less-icon { - width: 24px; - height: 24px; - mask: url('./images/i_view_less.svg') no-repeat center; - -webkit-mask: url('./images/i_view_less.svg') no-repeat center; -} - -.ngm-profile-icon { - width: 24px; - height: 24px; - mask: url('./images/i_terrain.svg') no-repeat center; - -webkit-mask: url('./images/i_terrain.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-save-icon { - width: 24px; - height: 24px; - mask: url('./images/i_save.svg') no-repeat center; - -webkit-mask: url('./images/i_save.svg') no-repeat center; - background-color: var(--ngm-interaction); -} - -.ngm-copy-icon { - width: 24px; - height: 24px; - mask: url('./images/i_copy.svg') no-repeat center; - -webkit-mask: url('./images/i_copy.svg') no-repeat center; - background-color: var(--ngm-interaction); -} +.ngm-icon.disabled { + cursor: default; + pointer-events: none; + opacity: 0.45; +} + +.ngm-search-icon { + mask: url('./images/icon_search.svg') no-repeat center; + -webkit-mask: url('./images/icon_search.svg') no-repeat center; +} + +.ngm-close-icon { + mask: url('./images/i_close.svg') no-repeat center; + -webkit-mask: url('./images/i_close.svg') no-repeat center; +} + +.ngm-zoom-p-icon { + width: 24px; + height: 24px; + mask: url('./images/i_zoom_plus.svg') no-repeat center; + -webkit-mask: url('./images/i_zoom_plus.svg') no-repeat center; +} + +.ngm-zoom-o-icon { + width: 24px; + height: 24px; + mask: url('./images/i_zoom_origin.svg') no-repeat center; + -webkit-mask: url('./images/i_zoom_origin.svg') no-repeat center; +} + +.ngm-zoom-m-icon { + width: 24px; + height: 24px; + mask: url('./images/i_zoom_minus.svg') no-repeat center; + -webkit-mask: url('./images/i_zoom_minus.svg') no-repeat center; +} + +.ngm-map-icon { + width: 24px; + height: 24px; + mask: url('./images/i_switzerland.svg') no-repeat center; + -webkit-mask: url('./images/i_switzerland.svg') no-repeat center; +} + +.ngm-cam-icon { + width: 24px; + height: 14px; + mask: url('./images/i_cam.svg') no-repeat center; + -webkit-mask: url('./images/i_cam.svg') no-repeat center; +} + +.ngm-cam-behind-icon { + width: 24px; + height: 24px; + mask: url('./images/i_cam_behind.svg') no-repeat center; + -webkit-mask: url('./images/i_cam_behind.svg') no-repeat center; +} + +.ngm-cam-h-icon { + width: 24px; + height: 24px; + mask: url('./images/i_cam_height.svg') no-repeat center; + -webkit-mask: url('./images/i_cam_height.svg') no-repeat center; +} + +.ngm-cam-d-icon { + width: 24px; + height: 24px; + mask: url('./images/i_cam_angle.svg') no-repeat center; + -webkit-mask: url('./images/i_cam_angle.svg') no-repeat center; +} + +.ngm-cam-t-icon { + width: 24px; + height: 22px; + mask: url('./images/i_cam_tilt.svg') no-repeat center; + -webkit-mask: url('./images/i_cam_tilt.svg') no-repeat center; +} + +.ngm-dropdown-icon { + width: 24px; + height: 24px; + mask: url('./images/i_menu-1.svg') no-repeat center; + -webkit-mask: url('./images/i_menu-1.svg') no-repeat center; +} + +.ngm-visible-icon { + width: 24px; + height: 24px; + mask: url('./images/i_visible.svg') no-repeat center; + -webkit-mask: url('./images/i_visible.svg') no-repeat center; +} + +.ngm-invisible-icon { + width: 24px; + height: 24px; + mask: url('./images/i_invisible.svg') no-repeat center; + -webkit-mask: url('./images/i_invisible.svg') no-repeat center; +} + +.ngm-zoom-plus-icon { + width: 20px; + height: 20px; + mask: url('./images/i_heranzoomen.svg') no-repeat center; + -webkit-mask: url('./images/i_heranzoomen.svg') no-repeat center; +} + +.ngm-delete-icon { + width: 24px; + height: 24px; + mask: url('./images/i_delete.svg') no-repeat center; + -webkit-mask: url('./images/i_delete.svg') no-repeat center; +} + +.ngm-coords-icon { + width: 22px; + height: 24px; + mask: url('./images/i_coordinates.svg') no-repeat center; + -webkit-mask: url('./images/i_coordinates.svg') no-repeat center; +} + +.ngm-vector-icon { + width: 24px; + height: 24px; + mask: url('./images/i_vector.svg') no-repeat center; + -webkit-mask: url('./images/i_vector.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-line-draw-icon { + width: 24px; + height: 24px; + mask: url('./images/i_line_draw.svg') no-repeat center; + -webkit-mask: url('./images/i_line_draw.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-polygon-draw-icon { + width: 24px; + height: 24px; + mask: url('./images/i_polygon_draw.svg') no-repeat center; + -webkit-mask: url('./images/i_polygon_draw.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-point-draw-icon { + width: 24px; + height: 24px; + mask: url('./images/i_point_draw.svg') no-repeat center; + -webkit-mask: url('./images/i_point_draw.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-rectangle-draw-icon { + width: 24px; + height: 24px; + mask: url('./images/i_rectangle_draw.svg') no-repeat center; + -webkit-mask: url('./images/i_rectangle_draw.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-file-upload-icon { + width: 24px; + height: 24px; + mask: url('./images/i_kml_gpx_upload.svg') no-repeat center; + -webkit-mask: url('./images/i_kml_gpx_upload.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-file-download-icon { + width: 24px; + height: 24px; + mask: url('./images/i_download_kml.svg') no-repeat center; + -webkit-mask: url('./images/i_download_kml.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-action-menu-icon { + width: 24px; + height: 24px; + mask: url('./images/i_action_menu.svg') no-repeat center; + -webkit-mask: url('./images/i_action_menu.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-slicing-icon { + width: 24px; + height: 24px; + mask: url('./images/i_slicing.svg') no-repeat center; + -webkit-mask: url('./images/i_slicing.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-in-box-icon { + width: 24px; + height: 24px; + mask: url('./images/i_box_inside.svg') no-repeat center; + -webkit-mask: url('./images/i_box_inside.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-out-box-icon { + width: 24px; + height: 24px; + mask: url('./images/i_box_outside.svg') no-repeat center; + -webkit-mask: url('./images/i_box_outside.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-back-icon { + width: 24px; + height: 24px; + mask: url('./images/i_back.svg') no-repeat center; + -webkit-mask: url('./images/i_back.svg') no-repeat center; + background-color: #000000; +} + +.ngm-slice-left-icon { + width: 24px; + height: 24px; + mask: url('./images/i_slice_left.svg') no-repeat center; + -webkit-mask: url('./images/i_slice_left.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-slice-right-icon { + width: 24px; + height: 24px; + mask: url('./images/i_slice_right.svg') no-repeat center; + -webkit-mask: url('./images/i_slice_right.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-upload-icon { + width: 24px; + height: 24px; + mask: url('./images/i_upload.svg') no-repeat center; + -webkit-mask: url('./images/i_upload.svg') no-repeat center; +} + +.ngm-extrusion-icon { + width: 20px; + height: 24px; + mask: url('./images/i_extrusion.svg') no-repeat center; + -webkit-mask: url('./images/i_extrusion.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-edit-icon { + width: 24px; + height: 24px; + mask: url('./images/i_edit.svg') no-repeat center; + -webkit-mask: url('./images/i_edit.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-download-icon { + width: 24px; + height: 24px; + mask: url('./images/i_download.svg') no-repeat center; + -webkit-mask: url('./images/i_download.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-screenshot-icon { + width: 18px; + height: 18px; + mask: url('./images/i_screenshot.svg') no-repeat center; + -webkit-mask: url('./images/i_screenshot.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-gst-icon { + width: 24px; + height: 24px; + mask: url('./images/i_query_3d_models.svg') no-repeat center; + -webkit-mask: url('./images/i_query_3d_models.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-play-last-icon { + width: 24px; + height: 24px; + mask: url('./images/i_play_the_last.svg') no-repeat center; + -webkit-mask: url('./images/i_play_the_last.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-play-icon { + width: 24px; + height: 24px; + mask: url('./images/i_play.svg') no-repeat center; + -webkit-mask: url('./images/i_play.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-profile-icon { + width: 24px; + height: 24px; + mask: url('./images/i_terrain.svg') no-repeat center; + -webkit-mask: url('./images/i_terrain.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-save-icon { + width: 24px; + height: 24px; + mask: url('./images/i_save.svg') no-repeat center; + -webkit-mask: url('./images/i_save.svg') no-repeat center; + background-color: var(--ngm-interaction); +} + +.ngm-copy-icon { + width: 24px; + height: 24px; + mask: url('./images/i_copy.svg') no-repeat center; + -webkit-mask: url('./images/i_copy.svg') no-repeat center; + background-color: var(--ngm-interaction); +} diff --git a/ui/src/style/index.css b/ui/src/style/index.css index 0772d67ab..7e97e5bf3 100644 --- a/ui/src/style/index.css +++ b/ui/src/style/index.css @@ -36,7 +36,6 @@ @import 'keyboard-navigation.css'; @import 'layers.css'; @import 'ngm-map-configuration.css'; -@import 'ngm-cursor-information.css'; @import 'ngm-point-edit.css'; @import 'ngm-position-edit.css'; @import 'ngm-map-chooser.css'; @@ -63,6 +62,7 @@ @import 'ngm-coordinate-popup.css'; @import 'ngm-ion-modal.css'; @import 'ngm-info-table.css'; +@import '../styles/index.css'; @import '../styles/index.css'; @@ -204,18 +204,48 @@ cesium-view-cube { } .ngm-lang-dropdown { - background-color: #F1F3F5; display: flex; - justify-content: center; + justify-content: space-between; align-items: center; - margin-right: 20px; + gap: 6px; + width: 69px; + + padding: 8px 12px; + border-radius: 4px; + color: var(--color-main); +} + +.ngm-lang-dropdown:hover { + background-color: var(--color-hovered); + color: var(--color-main--dark); +} + +.ngm-lang-dropdown.active ngm-icon[icon="dropdown"] { + transform: rotate(180deg); } .ngm-lang-title { - padding: 5px 5px 5px 10px; display: flex; justify-content: center; align-items: center; + gap: 6px; +} + +.ngm-lang-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + gap: 12px; + width: 85px; + font-size: 14px; + font-family: var(--font); + line-height: 20px; + letter-spacing: calc(14px * 0.0025); +} + +.ngm-lang-item:hover { + background-color: var(--color-pressed); } .ngm-lang-title .ngm-dropdown-icon { @@ -223,12 +253,18 @@ cesium-view-cube { transform: rotate(90deg); } -.ngm-lang-dropdown.active .ngm-lang-title .ngm-dropdown-icon { - transform: rotate(-90deg); +.ui.dropdown .menu { + box-shadow: + 0 2px 4px -1px #0000000F, + 0 4px 10px -1px #00000014; } -:lang(de) .lang-de, :lang(fr) .lang-fr, :lang(it) .lang-it, :lang(en) .lang-en { - color: #A83526 !important; +.ui.dropdown .menu .ngm-lang-dropdown.active.item { + font-weight: 500; +} + +.ngm-lang-dropdown.active .ngm-lang-title .ngm-dropdown-icon { + transform: rotate(-90deg); } .ui.checkbox label { @@ -244,42 +280,6 @@ cesium-view-cube { z-index: 10; } -ngm-auth { - cursor: pointer; -} - -ngm-auth > .ngm-user { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -ngm-auth > a { - cursor: pointer; -} - -ngm-auth .ngm-user-icon { - margin-bottom: 5px; - background-color: #030303; -} - -ngm-auth:hover .ngm-user-icon { - background-color: var(--ngm-interaction-hover); -} - -ngm-auth:hover { - color: var(--ngm-interaction-hover); -} - -ngm-auth .ngm-active-section > div:nth-child(1) { - background-color: var(--ngm-interaction-active); -} - -ngm-auth .ngm-active-section { - color: var(--ngm-interaction-active); -} - ngm-gst-interaction > .ui.tiny.icon.buttons { display: flex; } @@ -612,6 +612,7 @@ body:after { /* Beta label */ color: #fff; line-height: 27px; transform: rotate(-45deg); + z-index: 6; } .cursor-preloader, diff --git a/ui/src/style/layers.css b/ui/src/style/layers.css index 1f5481a02..375609001 100644 --- a/ui/src/style/layers.css +++ b/ui/src/style/layers.css @@ -69,20 +69,20 @@ ngm-layers-item .ngm-displayed-menu .ngm-layer-icon.ngm-action-menu-icon { } /* KML upload */ -.ngm-data-panel ngm-layers-upload { +.ngm-data-panel ngm-layers-upload-action { display: flex; column-gap: 12px; } -ngm-layers-upload .ngm-checkbox label { +ngm-layers-upload-action .ngm-checkbox label { margin-left: 8px; } -ngm-layers-upload .ngm-checkbox .ngm-checkbox-icon { +ngm-layers-upload-action .ngm-checkbox .ngm-checkbox-icon { min-width: 19px; } -ngm-layers-upload button { +ngm-layers-upload-action button { cursor: pointer; display: flex; align-items: center; @@ -97,7 +97,7 @@ ngm-layers-upload button { width: 325px; } -ngm-layers-upload .ngm-file-upload-icon { +ngm-layers-upload-action .ngm-file-upload-icon { pointer-events: none; background-color: var(--ngm-interaction); margin-top: 0; @@ -106,34 +106,34 @@ ngm-layers-upload .ngm-file-upload-icon { height: 20px; } -ngm-layers-upload .ui.inline.loader { +ngm-layers-upload-action .ui.inline.loader { margin-left: 12px; } -ngm-layers-upload button:hover { +ngm-layers-upload-action button:hover { color: var(--ngm-interaction-hover); border: 2px dashed var(--ngm-interaction-hover); } -ngm-layers-upload button:hover .ngm-file-upload-icon { +ngm-layers-upload-action button:hover .ngm-file-upload-icon { background-color: var(--ngm-interaction-hover); } -ngm-layers-upload button.active { +ngm-layers-upload-action button.active { color: var(--ngm-interaction-hover); border: 2px dashed var(--ngm-interaction-hover); } -ngm-layers-upload button.active .ngm-file-upload-icon { +ngm-layers-upload-action button.active .ngm-file-upload-icon { background-color: var(--ngm-interaction-hover); } /* FIXME: Grey out action menue until further specification */ -ngm-layers-upload .ui.dropdown { +ngm-layers-upload-action .ui.dropdown { pointer-events: none; } -ngm-layers-upload .ngm-action-menu-icon { +ngm-layers-upload-action .ngm-action-menu-icon { background-color: gray; } @@ -153,7 +153,7 @@ ngm-layers-item .ui.dropdown .menu .item:hover { } @media (min-width: 600px) { - ngm-layers-upload button { + ngm-layers-upload-action button { min-width: 325px; } } diff --git a/ui/src/style/ngm-cursor-information.css b/ui/src/style/ngm-cursor-information.css index 4f1b7b878..e69de29bb 100644 --- a/ui/src/style/ngm-cursor-information.css +++ b/ui/src/style/ngm-cursor-information.css @@ -1,41 +0,0 @@ -ngm-cursor-information { - display: flex; - flex-wrap: wrap; - align-self: center; - color: #212529; - text-align: left; - font: normal normal normal 14px/20px Inter, sans-serif; - letter-spacing: 0; - min-width: 255px; -} - -ngm-cursor-information .ngm-nci-value { - font-weight: 600; - min-width: 75px; -} - -.ngm-nci-position { - width: 83px; - margin-right: 20px; -} - -.ngm-nci-height { - display: flex; - flex-direction: column; - white-space: nowrap; - margin-right: 10px; -} - -ngm-cursor-information > img { - width: 24px; -} - -@media (max-width: 1193px) { - .ngm-nci-height, - .ngm-nci-position { - display: none; - } - ngm-cursor-information { - min-width: 0; - } -} diff --git a/ui/src/style/ngm-side-bar.css b/ui/src/style/ngm-side-bar.css index c360956b5..b0630ceab 100644 --- a/ui/src/style/ngm-side-bar.css +++ b/ui/src/style/ngm-side-bar.css @@ -4,74 +4,47 @@ ngm-side-bar { max-width: 100vw; } -.ngm-menu, -.ngm-menu-mobile, -.ngm-menu-1, -.ngm-menu-2, -.ngm-menu-1 > div, -.ngm-menu-2 > div, -.ngm-menu-mobile > div { +.ngm-menu-mobile { display: flex; - flex-direction: column; align-items: center; - text-align: center; -} - - -.ngm-menu, -.ngm-menu-mobile { - width: var(--ngm-left-side-bar-width); - justify-content: space-between; - font: normal normal normal 12px/15px Inter, sans-serif; - color: #222529; - background-color: #FFFFFF; -} - -.ngm-menu-1 { - margin-top: 24px; -} - -.ngm-menu-2 { - margin-bottom: 40px; -} - -.ngm-menu-1 > div, -.ngm-menu-2 > div { - margin-bottom: 28px; - cursor: pointer; + bottom: var(--ngm-bottom-menu-height); + width: 100%; + height: 56px; + position: absolute; + z-index: 9999; + justify-content: center; + background-color: white; } -.ngm-menu-2 > .ngm-settings { +.ngm-menu-mobile > div { margin-bottom: 0; + width: 68px; } -.ngm-menu-1 > div > div:nth-child(1), -.ngm-menu-2 > div > div:nth-child(1), -.ngm-menu-mobile > div > div:nth-child(1) { - background-color: #030303; -} - -.ngm-menu-1 > div:hover > div:nth-child(1), -.ngm-menu-2 > div:hover > div:nth-child(1), -.ngm-menu-mobile > div:hover > div:nth-child(1) { - background-color: var(--ngm-interaction-hover); -} +.ngm-menu { + position: absolute; + bottom: 0; + width: 100%; + height: 56px; + display: flex; + justify-content: center; -.ngm-menu-1 > div:hover, -.ngm-menu-2 > div:hover, -.ngm-menu-mobile > div:hover { - color: var(--ngm-interaction-hover); + .ngm-menu-top { + display: flex; + align-items: center; + justify-content: center; + } } .ngm-side-bar-panel { - width: 250px; - height: calc(100vh - var(--ngm-header-height)); + width: 100%; + height: calc(100% - (var(--ngm-header-height-mobile) + var(--ngm-bottom-menu-height))); max-width: 1028px; background-color: var(--color-bg--dark); box-shadow: 4px 0 4px #00000029; padding: var(--panel-padding); position: absolute; - margin-left: var(--ngm-left-side-bar-width); + margin-left: 0; z-index: 5; display: flex; flex-direction: column; @@ -79,28 +52,28 @@ ngm-side-bar { } ngm-navigation-catalog .ngm-side-bar-panel { - margin-left: 0; -} - -.inner-toolbar-settings { - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 10px; - margin-top: 14px; + margin-left: 0; } .ngm-side-bar-panel.ngm-large-panel { - width: calc(100vw - 144px); + width: 100%; } .ngm-side-bar-panel.ngm-share-panel { - width: 436px; + width: 100% } .ngm-side-bar-panel.ngm-extension-panel { - width: 528px; - left: 250px; + width: 100%; + left: 0; +} + +.ngm-side-bar-panel.ngm-layer-catalog { + z-index: 5; +} + +.ngm-side-bar-panel .ngm-panel-content { + max-height: calc(100vh - (var(--ngm-header-height-mobile) + var(--ngm-bottom-menu-height) + 34px)); } .ngm-panel-header { @@ -128,27 +101,30 @@ ngm-navigation-catalog .ngm-side-bar-panel { cursor: pointer; } -.ngm-panel-content { +.inner-toolbar-settings { display: flex; flex-direction: column; - margin-top: 9px; - overflow-y: auto; - overflow-x: hidden; - padding-bottom: 40px; + justify-content: space-between; + gap: 10px; + margin-top: 14px; } -.ngm-menu-1 > div.ngm-active-section > div:nth-child(1), -.ngm-menu-2 > div.ngm-active-section > div:nth-child(1) { - background-color: var(--ngm-interaction-active); +.ngm-label-btn { + margin-left: auto; + font: normal normal 500 14px/20px Inter, sans-serif; + letter-spacing: 0; + color: var(--ngm-interaction); + opacity: 1; + margin-right: 17px; + cursor: pointer; } -.ngm-menu-1 > div.ngm-active-section, -.ngm-menu-2 > div.ngm-active-section { +.ngm-label-btn.active { color: var(--ngm-interaction-active); } -.ngm-side-bar-panel.ngm-layer-catalog { - z-index: 5; +.ngm-label-btn:hover { + color: var(--ngm-interaction-hover); } .ngm-background-label.ui.header { @@ -161,22 +137,12 @@ ngm-navigation-catalog .ngm-side-bar-panel { margin-left: 5px; } -.ngm-label-btn { - margin-left: auto; - font: normal normal 500 14px/20px Inter, sans-serif; - letter-spacing: 0; - color: var(--ngm-interaction); - opacity: 1; - margin-right: 17px; - cursor: pointer; -} - -.ngm-label-btn.active { - color: var(--ngm-interaction-active); -} - -.ngm-label-btn:hover { - color: var(--ngm-interaction-hover); +.ngm-panel-content { + display: flex; + flex-direction: column; + margin-top: 9px; + overflow-y: auto; + overflow-x: hidden; } .ngm-configure-data { @@ -184,77 +150,58 @@ ngm-navigation-catalog .ngm-side-bar-panel { margin-top: 9px; } -@media (max-height: 629px), (max-width: 599px) { - .ngm-menu, - .ngm-menu-1, - .ngm-menu-2 { - flex-direction: row; - align-items: center; - justify-content: start; - } +.ngm-data-catalog-label { + margin-right: 10px; +} - .ngm-menu, - .ngm-menu-mobile { - width: 100%; - height: var(--ngm-bottom-menu-height); - position: absolute; - z-index: 9999; - bottom: 0; - justify-content: center; - } +.ngm-data-catalog-label.active { + color: var(--ngm-interaction-active); +} - .ngm-menu-1 { - align-items: start; - margin: 0; - } +@media (min-height: 629px) and (min-width: 599px) { + .ngm-menu { + position: relative; + height: 100%; + width: var(--ngm-left-side-bar-width); + justify-content: space-between; + flex-direction: column; + padding: 12px 6px; - .ngm-menu-1 > div, - .ngm-menu-mobile > div { - margin-bottom: 0; - width: 68px; + .ngm-menu-top { + flex-direction: column; + } } - .ngm-menu-mobile > div:first-child, - .ngm-menu-1 > div:first-child { - margin-left: 12px; + .ngm-side-bar-panel { + width: 250px; + margin-left: var(--ngm-left-side-bar-width); + height: calc(100vh - var(--ngm-header-height)); } - .ngm-menu-mobile { - display: flex; - flex-direction: row; - align-items: start; - bottom: var(--ngm-bottom-menu-height); - height: 62px; + .ngm-side-bar-panel.ngm-large-panel { + width: calc(100vw - 144px); } - .ngm-menu-mobile > div { - margin-top: 10px; + .ngm-side-bar-panel.ngm-share-panel { + width: 436px; } - .ngm-side-bar-panel { - margin-left: 0; - height: calc(100% - (var(--ngm-header-height-mobile) + var(--ngm-bottom-menu-height))); + .ngm-side-bar-panel.ngm-extension-panel { + left: 250px; + width: 528px; } - .ngm-side-bar-panel .ngm-panel-content { - max-height: calc(100vh - (var(--ngm-header-height-mobile) + var(--ngm-bottom-menu-height) + 34px)); - } - - .ngm-side-bar-panel.ngm-large-panel { - width: calc(100vw - 64px); - } - - .ngm-side-bar-panel.ngm-share-panel { - width: 100% + max-height: unset; } +} - .ngm-menu-1 > div > div:nth-child(1), - .ngm-menu-mobile > div > div:nth-child(1) { - margin-bottom: 1px; - background-color: #030303; - height: 24px; +@media (max-width: 350px) { + .ngm-menu-mobile > div { + width: 60px; } +} +@media (max-height: 629px), (max-width: 599px) { .ngm-side-bar-panel.ngm-large-panel .ngm-proj-information .ngm-proj-description { margin-left: 0; margin-top: 12px; @@ -270,27 +217,11 @@ ngm-navigation-catalog .ngm-side-bar-panel { margin-bottom: 12px; } - .ngm-side-bar-panel.ngm-side-bar-panel, - .ngm-side-bar-panel.ngm-large-panel { - width: 100%; - } - .ngm-side-bar-panel.ngm-share-panel, .ngm-side-bar-panel.ngm-large-panel { overflow: auto; } - .ngm-side-bar-panel.ngm-extension-panel { - left: 0; - } - - .ngm-data-catalog-label { - margin-right: 10px; - } - - .ngm-data-catalog-label.active { - color: var(--ngm-interaction-active); - } ngm-map-configuration .base-map-labels { display: flex; @@ -314,9 +245,3 @@ ngm-navigation-catalog .ngm-side-bar-panel { } } -@media (max-width: 350px) { - .ngm-menu-1 > div, - .ngm-menu-mobile > div { - width: 60px; - } -} diff --git a/ui/src/style/search.css b/ui/src/style/search.css index 88ad85804..9dedcb32f 100644 --- a/ui/src/style/search.css +++ b/ui/src/style/search.css @@ -1,28 +1,28 @@ .ngm-search-icon-container { - width: 30px; - height: 100%; - position: absolute; - right: 12px; - color: #66D9E8; - background-color: var(--ngm-interaction); + width: 30px; + height: 100%; + position: absolute; + left: 12px; + color: #66D9E8; + background-color: var(--ngm-interaction); } .ngm-search-icon-container.ngm-close-icon { - cursor: pointer; + cursor: pointer; } .mobile-search-active ga-search .ngm-search-icon { - display: none; + display: none; } @media (max-width: 690px) { - ga-search { - min-width: 180px; - max-width: 180px; - } + ga-search { + min-width: 180px; + max-width: 180px; + } - ga-search > input { - padding: 11px 16px !important; - width: 150px; - } + ga-search > input { + padding: 11px 16px !important; + width: 150px; + } } diff --git a/ui/src/style/variables.css b/ui/src/style/variables.css index a1d685572..9ded405e7 100644 --- a/ui/src/style/variables.css +++ b/ui/src/style/variables.css @@ -1,13 +1,13 @@ -:root { - --ngm-interaction: #0B7285; - --ngm-interaction-active: #B9271A; - --ngm-interaction-hover: #FF0000; - --ngm-action-hover: #A83526; - --ngm-hover: #C5F6FA; - --ngm-warning: #C92A2A; - --ngm-header-height: 88px; - --ngm-header-height-mobile: 48px; - --ngm-bottom-menu-height: 56px; - --ngm-panel-header-height: 34px; - --ngm-left-side-bar-width: 56px; -} +:root { + --ngm-interaction: #0B7285; + --ngm-interaction-active: #B9271A; + --ngm-interaction-hover: #FF0000; + --ngm-action-hover: #A83526; + --ngm-hover: #C5F6FA; + --ngm-warning: #C92A2A; + --ngm-header-height: 88px; + --ngm-header-height-mobile: 48px; + --ngm-bottom-menu-height: 56px; + --ngm-panel-header-height: 34px; + --ngm-left-side-bar-width: 80px; +} diff --git a/ui/src/styles/index.css b/ui/src/styles/index.css index c12807fe2..6b5e041c8 100644 --- a/ui/src/styles/index.css +++ b/ui/src/styles/index.css @@ -1,4 +1,3 @@ - :root { --color-bg: #FFF; --color-bg--dark: #F1F3F5; @@ -11,8 +10,18 @@ --color-highlight--dark: #66D9E8; --color-highlight--darker: #0B7285; + --color-main: #337083; + --color-main--dark: #2F4356; + + --color-hovered: #D6E2E6; + --color-pressed: #DFE4E9; + + --color-active: #607D52; + --color-rest-active: #B2D2A2; + --color-action: #a83526; --color-action--light: #FF0000; + --color-medium-emphasis: #2F4356; --panel-padding: 10px; @@ -22,4 +31,3 @@ font-family: var(--font); font-size: 16px; } -