Compare commits
147 Commits
7bcb31a8f8
...
dev/tasks-
| Author | SHA1 | Date | |
|---|---|---|---|
| ea14d44a7f | |||
| e414fe1264 | |||
| 03547c2bac | |||
| fc1df1effb | |||
| 486d00ae88 | |||
| 60e0d396f2 | |||
| 3f0f43aebf | |||
| 0b97dd4095 | |||
| 08e7060b1b | |||
| e9fbee43e3 | |||
| cc8bdd4ea1 | |||
| bbb2e830b3 | |||
| dbc617ad81 | |||
| fa7d83fc36 | |||
| a062f08ed7 | |||
| bd576665c8 | |||
| 5bfdda2340 | |||
| acfc50a244 | |||
| b4ee8fa6e0 | |||
| e1cf6f21f5 | |||
| ffc53f23e2 | |||
|
|
dd15e3468a | ||
| af820b5384 | |||
| 950ac25828 | |||
| 5b77b0b524 | |||
| bc37051619 | |||
| 9463530ee5 | |||
| e68ddd0ccf | |||
| b18811c453 | |||
| 879d7c5858 | |||
| be7bbfc28b | |||
| f23897018c | |||
| c39667e368 | |||
| cb8994b493 | |||
| 62ba9b5345 | |||
| 360eb3744a | |||
| 601c13012c | |||
| 6200b061f2 | |||
| fa984e7391 | |||
| 125c255115 | |||
| 04e692a6dd | |||
| 25cf797294 | |||
| 62ae9f18b0 | |||
| 5cadc5c1b4 | |||
| 9ca215c36d | |||
| 04f96b7aba | |||
| ffe81cdf88 | |||
| c9040b2f4e | |||
| 9c8e641557 | |||
| 076e82213d | |||
| 6a721fde7c | |||
| 8179498333 | |||
| ec4c9fa8a9 | |||
| 8d67c0c0b9 | |||
| ade9ca2128 | |||
| 2993a443a7 | |||
|
|
025cd975f0 | ||
|
|
0b02b435ef | ||
|
|
c52dbf618e | ||
|
|
2b1f7e3a38 | ||
| 4926433c35 | |||
| aae29618b3 | |||
| a828a3878e | |||
| f107d97b87 | |||
| 7e6b55abd4 | |||
| d98d6a6713 | |||
| 3e73dee40b | |||
| adf863934e | |||
| 27d41c0847 | |||
| 9d7eb25d1f | |||
| f30a01615e | |||
| 2c02a61791 | |||
| a38cf999f2 | |||
| 78bdc22781 | |||
| f810ed520c | |||
| 2b3fcb6ebf | |||
| 5fe7522f5f | |||
| f8f0d514bb | |||
| 5e8b4745f1 | |||
| c3569d6b98 | |||
| 7dea362c89 | |||
| 11a062dd84 | |||
| aec6a9bfb6 | |||
| 94d4308d3f | |||
| a37c1ffe01 | |||
| 8151390835 | |||
| 99cf34671a | |||
| f9187a3e84 | |||
| 94100b9371 | |||
| 84ce0de870 | |||
| 391cec07c8 | |||
| d3cfcbab25 | |||
| 1eafc27c53 | |||
| dbacae3f2e | |||
|
|
de9724b820 | ||
| 556c5ea4b9 | |||
| 1dfffd93e5 | |||
| b3879d812f | |||
| f0a88b13d1 | |||
|
|
7aa0cad5fb | ||
| e459a86edb | |||
| adc2b71eb7 | |||
| b6bedc788b | |||
| 583bbcd27d | |||
| 2a24f486b5 | |||
| d29f484993 | |||
| 3f72ef3bc4 | |||
| a0cbe29f97 | |||
|
|
15705dac97 | ||
| c8f4832a95 | |||
| 1714e33fa7 | |||
|
|
5e66e73db6 | ||
| 1337b0dca3 | |||
| 765b74ceec | |||
| 871e43aef5 | |||
| bfc21d8fb6 | |||
|
|
250ca9909d | ||
| c249c8e049 | |||
| 958f15a7a4 | |||
| 855b69f95d | |||
| 77df35926d | |||
| 65ef7f07c9 | |||
| 6eca88e941 | |||
| 801131985d | |||
| 4707e73421 | |||
| 241ec75323 | |||
| d3297d2a3c | |||
| c7e9444903 | |||
| 72315b4030 | |||
| 403d81b132 | |||
| 4f35ddeafe | |||
| 19b038d4f5 | |||
| 4554ea3ff0 | |||
| 0083a05482 | |||
| 041d1603dc | |||
| b3a4ba6d4a | |||
| 04dc118598 | |||
| dec36d4053 | |||
| d2f2b577be | |||
| a640ed1b78 | |||
| 7c0c0b5048 | |||
| 50035a524d | |||
| e8ce6c6f36 | |||
| 97ab6a52ab | |||
| b8f1c32a22 | |||
| 0aeee9a168 | |||
|
|
1f8e3935bb |
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
||||
# Example Environment Configfile
|
||||
POSTGRES_HOSTNAME=postgres_host
|
||||
POSTGRES_PORT=postgres_port
|
||||
POSTGRES_DB=postgres_database
|
||||
POSTGRES_USER=postgres_user
|
||||
POSTGRES_PASSWORD=
|
||||
ADMIN_PASSWORD=
|
||||
MUNICIPALITY_SLUG=lohne
|
||||
8
.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Specifies Line Feed (LF) Line Endings for Shell Scripts
|
||||
*.sh text eol=lf
|
||||
|
||||
# # Specifies Line Feed (LF) Line Endings for SQL Files
|
||||
*.sql text eol=lf
|
||||
|
||||
# Letd Git decide for other Files
|
||||
* text=auto
|
||||
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.env
|
||||
.vscode/
|
||||
*.log
|
||||
scripts
|
||||
|
||||
public/uploads/photos/*
|
||||
!public/uploads/photos/.gitkeep
|
||||
78
EXTENSION.md
Normal file
@@ -0,0 +1,78 @@
|
||||
## Neue Ideenkarte anlegen
|
||||
1. DNS record ```<name>``` A 195.59.32.237 600s
|
||||
2. Nginx Weiterleitung in ```default.conf```:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name <name>.endex-geodaten.de;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/endex-geodaten.de/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/endex-geodaten.de/privkey.pem;
|
||||
|
||||
root /var/www/webgis-<name>/public;
|
||||
index index.php index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass webgis-<name>-php:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Docker container für UI
|
||||
```
|
||||
webgis-<name>-php:
|
||||
build: php-docker/
|
||||
container_name: webgis-<name>-php
|
||||
volumes:
|
||||
- ./webgis-<name>:/var/www/webgis-<name>
|
||||
networks:
|
||||
- frontend
|
||||
- webgis-<name>-nw
|
||||
```
|
||||
|
||||
und Datenbank anlegen.
|
||||
|
||||
```
|
||||
webgis-<name>db:
|
||||
image: postgis/postgis:15-3.3
|
||||
container_name: webgis-<name>-db
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:543<ID>:5432" # inside the container always 5432
|
||||
environment:
|
||||
- POSTGRES_USER=${WEBGIS_DB_USER} # maybe go back to default username
|
||||
- POSTGRES_PASSWORD=${WEBGIS_DB_PW} # must be secure and unique
|
||||
- POSTGRES_DB=${WEBGIS_DB_NAME} #same as container name
|
||||
volumes:
|
||||
- ./webgis-<name>-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- webgis-<name>-nw
|
||||
```
|
||||
|
||||
4. nginx Volume für neue Stadt in ```docker-compose.yml``` anlegen
|
||||
```
|
||||
./webgis-<name>:/var/www/webgis-<name>
|
||||
```
|
||||
|
||||
|
||||
5. Frontend source code nach ```webgis-<name>``` klonen
|
||||
```
|
||||
git submodule add -b <branch-name> https://git.endex-geodaten.de/lukas.uptmoor/webgis-<name>.git
|
||||
```
|
||||
|
||||
Jede Kommune sollte ein eigenes Repo kriegen, da Features am Anfang variieren.
|
||||
|
||||
|
||||
6. Mit der Datenbank verbinden über SSH-Tunnel
|
||||
```
|
||||
ssh -L 5433:localhost:543<ID> root@endex-geodaten.de
|
||||
```
|
||||
und Datenbank für Anwendung vorbereiten.
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
// ToDo's
|
||||
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
|
||||
|
||||
|
||||
include 'init.php';
|
||||
|
||||
$request = htmlspecialchars($_POST['request'], ENT_QUOTES);
|
||||
|
||||
if ($request=='buildings') {
|
||||
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
|
||||
|
||||
try {
|
||||
|
||||
$pdo -> query("DELETE FROM buildings WHERE webgis_id = '$webgis_id'");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "ERROR ".$e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($request == 'pipelines') {
|
||||
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
|
||||
|
||||
try {
|
||||
|
||||
$pdo -> query("DELETE from pipelines where webgis_id= '$webgis_id' ");
|
||||
|
||||
} catch(PDOException $e) {
|
||||
echo "ERROR ".$e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($request == 'valves') {
|
||||
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
|
||||
|
||||
try {
|
||||
|
||||
$pdo -> query("DELETE from valves where webgis_id= '$webgis_id' ");
|
||||
|
||||
} catch(PDOException $e) {
|
||||
echo "ERROR ".$e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
// ToDo's
|
||||
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
|
||||
|
||||
// PostgreSQL-Serververbindung
|
||||
include 'init.php';
|
||||
|
||||
// HTTP-POST-Methode für Formulardaten
|
||||
$table = htmlspecialchars($_POST['table'], ENT_QUOTES);
|
||||
$field = htmlspecialchars($_POST['field'], ENT_QUOTES);
|
||||
$value = htmlspecialchars($_POST['value'], ENT_QUOTES);
|
||||
|
||||
try {
|
||||
// Datenbankabfrage
|
||||
$result = $pdo -> query("SELECT *, ST_AsGeoJSON(geom) as geojson FROM $table WHERE $field = '$value'");
|
||||
|
||||
$features = [];
|
||||
|
||||
foreach($result as $row) {
|
||||
// PHP-Objekt erstellen
|
||||
$geometry = json_decode($row['geojson']);
|
||||
|
||||
// PHP-Objekt bereinigen
|
||||
unset($row['geom']);
|
||||
unset($row['geojson']);
|
||||
|
||||
// JSON-Feature hinzufügen
|
||||
$feature = [
|
||||
"type"=>"Feature",
|
||||
"geometry"=>$geometry,
|
||||
"properties"=>$row
|
||||
];
|
||||
|
||||
array_push($features, $feature);
|
||||
};
|
||||
|
||||
// Feature-Collection hinzufügen
|
||||
$featureCollection = [
|
||||
"type"=>"FeatureCollection",
|
||||
"features"=>$features
|
||||
];
|
||||
|
||||
echo json_encode($featureCollection);
|
||||
|
||||
// Fehlernachricht ausgeben
|
||||
} catch(PDOException $e) {
|
||||
echo "ERROR ".$e->getMessage();
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
26
init.php
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
$host = 'webgis-db'; // Matches the service name in docker-compose
|
||||
$db = getenv('POSTGRES_DB');
|
||||
$user = getenv('POSTGRES_USER');
|
||||
$pass = getenv('POSTGRES_PASSWORD');
|
||||
|
||||
ob_start();
|
||||
session_start();
|
||||
|
||||
try {
|
||||
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
];
|
||||
|
||||
$dsn = "pgsql:host=localhost;dbname=$db;port=5432";
|
||||
$pdo = new PDO($dsn, $user, $pass, $opt);
|
||||
|
||||
// Error-Message
|
||||
} catch(PDOException $e) {
|
||||
echo "Error: ".$e->getMessage();
|
||||
}
|
||||
?>
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
// ToDo's
|
||||
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
|
||||
|
||||
// PostgreSQL-Serververbindung
|
||||
include 'init.php';
|
||||
|
||||
$request = htmlspecialchars($_POST['request'], ENT_QUOTES);
|
||||
|
||||
if ($request == 'valves') {
|
||||
$valve_id = htmlspecialchars($_POST['valve_id'], ENT_QUOTES);
|
||||
$valve_type = htmlspecialchars($_POST['valve_type'], ENT_QUOTES);
|
||||
$valve_dma_id = htmlspecialchars($_POST['valve_dma_id'], ENT_QUOTES);
|
||||
$valve_diameter = htmlspecialchars($_POST['valve_diameter'], ENT_QUOTES);
|
||||
$valve_visibility = htmlspecialchars($_POST['valve_visibility'], ENT_QUOTES);
|
||||
$valve_location = htmlspecialchars($_POST['valve_location'], ENT_QUOTES);
|
||||
$valve_geometry = $_POST['valve_geometry'];
|
||||
|
||||
$result = $pdo -> query("SELECT * FROM valves WHERE valve_id = '$valve_id'");
|
||||
|
||||
if ($result->rowCount()>0) {
|
||||
echo "ERROR: Valve ID already exists. Please type in another ID!";
|
||||
} else {
|
||||
// Datenbankabfrage
|
||||
$result = $pdo -> query("INSERT INTO valves(valve_id, valve_type, valve_dma_id, valve_diameter, valve_location, valve_visibility, geom) VALUES ('$valve_id', '$valve_type', '$valve_dma_id', '$valve_diameter', '$valve_location', '$valve_visibility', ST_SetSRID(ST_GeomFromGeoJSON('$valve_geometry'), 4326))");
|
||||
}
|
||||
}
|
||||
|
||||
if ($request == 'pipelines') {
|
||||
$pipeline_id = htmlspecialchars($_POST['pipeline_id'], ENT_QUOTES);
|
||||
$pipeline_category = htmlspecialchars($_POST['pipeline_category'], ENT_QUOTES);
|
||||
$pipeline_dma_id = htmlspecialchars($_POST['pipeline_dma_id'], ENT_QUOTES);
|
||||
$pipeline_diameter = htmlspecialchars($_POST['pipeline_diameter'], ENT_QUOTES);
|
||||
$pipeline_method = htmlspecialchars($_POST['pipeline_method'], ENT_QUOTES);
|
||||
$pipeline_location = htmlspecialchars($_POST['pipeline_location'], ENT_QUOTES);
|
||||
$pipeline_geometry = $_POST['pipeline_geometry'];
|
||||
|
||||
$result = $pdo -> query("SELECT * FROM pipelines WHERE pipeline_id = '$pipeline_id'");
|
||||
|
||||
if ($result->rowCount()>0) {
|
||||
echo "ERROR: Pipeline ID already exists. Please type in another ID!";
|
||||
} else {
|
||||
// Datenbankabfrage
|
||||
$result = $pdo -> query("INSERT INTO pipelines(pipeline_id, pipeline_category, pipeline_dma_id, pipeline_diameter, pipeline_method, pipeline_location, geom) VALUES ('$pipeline_id', '$pipeline_category', '$pipeline_dma_id', '$pipeline_diameter', '$pipeline_method', '$pipeline_location', ST_SetSRID(ST_GeomFromGeoJSON('$pipeline_geometry'), 4326))");
|
||||
}
|
||||
}
|
||||
|
||||
if ($request == 'buildings') {
|
||||
|
||||
$account_no = htmlspecialchars($_POST['account_no'], ENT_QUOTES);
|
||||
$building_category = htmlspecialchars($_POST['building_category'], ENT_QUOTES);
|
||||
$building_dma_id = htmlspecialchars($_POST['building_dma_id'], ENT_QUOTES);
|
||||
$building_storey = htmlspecialchars($_POST['building_storey'], ENT_QUOTES);
|
||||
$building_population = htmlspecialchars($_POST['building_population'], ENT_QUOTES);
|
||||
$building_location = htmlspecialchars($_POST['building_location'], ENT_QUOTES);
|
||||
$building_geometry = $_POST['building_geometry'];
|
||||
|
||||
$result = $pdo -> query("SELECT *from buildings where account_no= '$account_no'");
|
||||
|
||||
if ($result->rowCount()>0) {
|
||||
echo "ERROR: Building ID already exists. Please type in another ID!";
|
||||
} else {
|
||||
$sql = $pdo -> query("INSERT INTO buildings(account_no, building_category, building_dma_id, building_storey, building_population, building_location, geom) VALUES ('$account_no', '$building_category', '$building_dma_id', '$building_storey', '$building_population', '$building_location', ST_Force3DZ(ST_SetSRID(ST_GeomFromGeoJSON('$building_geometry'), 4326)))");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
?>
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
// ToDo's
|
||||
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
|
||||
|
||||
// PostgreSQL-Serververbindung
|
||||
include 'init.php';
|
||||
|
||||
// HTTP-POST-Methode für Formulardaten
|
||||
$table = htmlspecialchars($_POST['table'], ENT_QUOTES);
|
||||
$dma_id = htmlspecialchars($_POST['dma_id'], ENT_QUOTES);
|
||||
|
||||
if($table == 'valves') {
|
||||
$dma_id_field = "valve_dma_id";
|
||||
}
|
||||
|
||||
if($table == 'buildings') {
|
||||
$dma_id_field = "building_dma_id";
|
||||
}
|
||||
|
||||
if($table == 'pipelines') {
|
||||
$dma_id_field = "pipeline_dma_id";
|
||||
}
|
||||
|
||||
try {
|
||||
// Datenbankabfrage
|
||||
$result = $pdo -> query("SELECT *, ST_AsGeoJSON(geom) as geojson FROM $table WHERE $dma_id_field = '$dma_id'");
|
||||
|
||||
$features = [];
|
||||
|
||||
foreach($result as $row) {
|
||||
// PHP-Objekt erstellen
|
||||
$geometry = json_decode($row['geojson']);
|
||||
|
||||
// PHP-Objekt bereinigen
|
||||
unset($row['geom']);
|
||||
unset($row['geojson']);
|
||||
|
||||
// JSON-Feature hinzufügen
|
||||
$feature = [
|
||||
"type"=>"Feature",
|
||||
"geometry"=>$geometry,
|
||||
"properties"=>$row
|
||||
];
|
||||
|
||||
array_push($features, $feature);
|
||||
};
|
||||
|
||||
// Feature-Collection hinzufügen
|
||||
$featureCollection = [
|
||||
"type"=>"FeatureCollection",
|
||||
"features"=>$features
|
||||
];
|
||||
|
||||
echo json_encode($featureCollection);
|
||||
|
||||
// Fehlernachricht ausgeben
|
||||
} catch(PDOException $e) {
|
||||
echo "ERROR ".$e->getMessage();
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
169
migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,169 @@
|
||||
-- =====================================================================
|
||||
-- WebGIS Citizen Participation Portal — Initial Schema
|
||||
-- Migration: 001_initial_schema.sql
|
||||
-- Description: Creates Core Tables for a multi-tenant Citizen
|
||||
-- Participation Platform with Point/Line/Polygon
|
||||
-- Contributions, Voting, and Moderation Workflow.
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Checks PostGIS Extension
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 2: Creates Table "municipalities"
|
||||
-- One Row per Municipalitiy using the Portal (multi-tenant setup).
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE municipalities (
|
||||
municipality_id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL UNIQUE, -- Municipalitiy Name
|
||||
slug VARCHAR(50) NOT NULL UNIQUE, -- URL-safe Identifier, e.g. "lohne"
|
||||
center_lat DOUBLE PRECISION NOT NULL, -- Map Center Latitude
|
||||
center_lng DOUBLE PRECISION NOT NULL, -- Map Center Longitude
|
||||
default_zoom SMALLINT NOT NULL DEFAULT 13, -- Map Default Zoom Level
|
||||
logo_path VARCHAR(255), -- Relative Path to Municipality Logo
|
||||
primary_color VARCHAR(7) DEFAULT '#6a6a6a', -- HexColor for UI Theme
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE municipalities IS 'Configuration Per Municipality (Tenant) using the Citizen Participation Portal.';
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 3: Table "contributions"
|
||||
-- Citizen and Administration Contributions as Points, Lines, and
|
||||
-- Polygons stored together in one mixed-geometry Column.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE contributions (
|
||||
contribution_id SERIAL PRIMARY KEY,
|
||||
municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id) ON DELETE CASCADE,
|
||||
geom GEOMETRY(Geometry, 4326) NOT NULL, -- Mixed Geometry: Point, Line, Polygon, ... (WGS84)
|
||||
geom_type VARCHAR(20) NOT NULL, -- 'point' | 'line' | 'polygon'
|
||||
category VARCHAR(50) NOT NULL, -- Contribution Category
|
||||
title VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
author_name VARCHAR(100) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
likes_count INTEGER NOT NULL DEFAULT 0,
|
||||
dislikes_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT contributions_geom_type_check
|
||||
CHECK (geom_type IN ('point', 'line', 'polygon')),
|
||||
CONSTRAINT contributions_status_check
|
||||
CHECK (status IN ('pending', 'approved', 'rejected', 'in_progress', 'done'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE contributions IS 'Citizen and Administration Contributions with mixed Geometry Types.';
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 4: Indexes for fast Queries
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE INDEX contributions_geom_idx ON contributions USING GIST (geom);
|
||||
CREATE INDEX contributions_municipality_idx ON contributions (municipality_id);
|
||||
CREATE INDEX contributions_status_idx ON contributions (status);
|
||||
CREATE INDEX contributions_category_idx ON contributions (category);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 5: Table "votes"
|
||||
-- Individual like and dislike Records. UNIQUE Constraint prevents the
|
||||
-- same voter from liking or disliking the same contribution multiple times.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE votes (
|
||||
vote_id SERIAL PRIMARY KEY,
|
||||
contribution_id INTEGER NOT NULL REFERENCES contributions(contribution_id) ON DELETE CASCADE,
|
||||
voter_name VARCHAR(100) NOT NULL, -- ToDo: Replace with user_id once Authentification exists
|
||||
vote_type VARCHAR(10) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT votes_unique_per_voter UNIQUE (contribution_id, voter_name),
|
||||
CONSTRAINT votes_vote_type_check CHECK (vote_type IN ('like', 'dislike'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE votes IS 'Individual Votes to prevent duplicate Likes and Dislikes.';
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 6: Trigger Functions
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- Automatically Refresh updated_at on every UPDATE.
|
||||
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER contributions_updated_at
|
||||
BEFORE UPDATE ON contributions
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TRIGGER municipalities_updated_at
|
||||
BEFORE UPDATE ON municipalities
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
|
||||
-- Keeps likes_count / dislikes_count synchronized with the votes Table.
|
||||
CREATE OR REPLACE FUNCTION update_vote_counts()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
IF NEW.vote_type = 'like' THEN
|
||||
UPDATE contributions SET likes_count = likes_count + 1
|
||||
WHERE contribution_id = NEW.contribution_id;
|
||||
ELSE
|
||||
UPDATE contributions SET dislikes_count = dislikes_count + 1
|
||||
WHERE contribution_id = NEW.contribution_id;
|
||||
END IF;
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
IF OLD.vote_type = 'like' THEN
|
||||
UPDATE contributions SET likes_count = GREATEST(likes_count - 1, 0)
|
||||
WHERE contribution_id = OLD.contribution_id;
|
||||
ELSE
|
||||
UPDATE contributions SET dislikes_count = GREATEST(dislikes_count - 1, 0)
|
||||
WHERE contribution_id = OLD.contribution_id;
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER votes_count_sync
|
||||
AFTER INSERT OR DELETE ON votes
|
||||
FOR EACH ROW EXECUTE FUNCTION update_vote_counts();
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 7: Typed Geometry Views for QGIS
|
||||
-- QGIS handles mixed-geometry Tables awkwardly, so one View per
|
||||
-- Geometry Type is created. Reflects live Data from the Contributions Table.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE VIEW contributions_points AS
|
||||
SELECT * FROM contributions WHERE geom_type = 'point';
|
||||
|
||||
CREATE VIEW contributions_lines AS
|
||||
SELECT * FROM contributions WHERE geom_type = 'line';
|
||||
|
||||
CREATE VIEW contributions_polygons AS
|
||||
SELECT * FROM contributions WHERE geom_type = 'polygon';
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 8: Seed Data — Initial Municipality
|
||||
-- ---------------------------------------------------------------------
|
||||
INSERT INTO municipalities (name, slug, center_lat, center_lng, default_zoom, primary_color)
|
||||
VALUES ('Lohne (Oldenburg)', 'lohne', 52.66639, 8.23306, 14, '#00376D');
|
||||
|
||||
|
||||
-- =====================================================================
|
||||
-- End of migration 001_initial_schema.sql
|
||||
-- =====================================================================
|
||||
48
migrations/002_add_votes_index.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- =====================================================================
|
||||
-- WebGIS Citizen Participation Portal
|
||||
-- Migration: 002_add_votes_index.sql
|
||||
-- Description: Adds missing Index on votes.contribution_id for fast
|
||||
-- Vote Lookups per Contribution.
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Index for fast Queries
|
||||
-- The UNIQUE Constraint on contribution_id and voter_name creates a
|
||||
-- composite Index, but Queries filtering only by contribution_id
|
||||
-- cannot use it efficiently. This single-column Index covers that Case.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE INDEX votes_contribution_idx ON votes (contribution_id);
|
||||
|
||||
|
||||
-- =====================================================================
|
||||
-- ToDo's for future Migrations
|
||||
-- =====================================================================
|
||||
--
|
||||
-- 1. Categories Table
|
||||
-- Create a "categories" Table with municipality_id, slug, label,
|
||||
-- icon (FontAwesome), color, and sort_order. Replace the free-text
|
||||
-- "category" Column in Contributions with a Foreign Key Reference.
|
||||
-- This prevents Typos and inconsistent Category Names, and allows
|
||||
-- each Municipality to define its own Set of Categories.
|
||||
--
|
||||
-- 2. Soft Delete
|
||||
-- Add "deleted_at TIMESTAMPTZ DEFAULT NULL" to Contributions.
|
||||
-- Instead of DELETE, set deleted_at = NOW(). Filter all Queries
|
||||
-- with "WHERE deleted_at IS NULL". Allows Moderation Audit Trail
|
||||
-- and accidental Deletion Recovery.
|
||||
--
|
||||
-- 3. Audit Log
|
||||
-- Create an "audit_log" Table recording who changed what and when.
|
||||
-- Columns: audit_id, table_name, record_id, action (insert/update/
|
||||
-- delete), changed_by, old_values (JSONB), new_values (JSONB),
|
||||
-- created_at. Populate via Triggers on Contributions and Votes.
|
||||
--
|
||||
-- 4. Geometry Validation
|
||||
-- Add CHECK Constraint "ST_IsValid(geom)" on Contributions, or
|
||||
-- validate in the API Layer before Insert. Prevents self-crossing
|
||||
-- Polygons and other invalid Geometries.
|
||||
--
|
||||
-- =====================================================================
|
||||
-- End of migration 002_add_votes_index.sql
|
||||
-- =====================================================================
|
||||
44
migrations/003_news_table.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- =====================================================================
|
||||
-- Migration 004: Creates News Table for Municipality Announcements
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Creates Table "news"
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS news (
|
||||
news_id SERIAL PRIMARY KEY,
|
||||
municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
author_name VARCHAR(100) NOT NULL DEFAULT 'Stadtverwaltung',
|
||||
published_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 2: Trigger Functions
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- Automatically Refresh updated_at on every UPDATE.
|
||||
CREATE TRIGGER set_news_updated_at
|
||||
BEFORE UPDATE ON news
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 3 Indexes for fast Queries
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE INDEX idx_news_municipality ON news(municipality_id);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 4: Seed Data — Initial News Article
|
||||
-- ---------------------------------------------------------------------
|
||||
INSERT INTO news (municipality_id, title, content)
|
||||
SELECT municipality_id, 'Mitmachkarte gestartet',
|
||||
'Die Mitmachkarte als Bürgerbeteiligungsportal der Stadt Lohne (Oldenburg) wird nun freigeschaltet. Wir freuen uns auf Ihre Hinweise und Vorschläge!'
|
||||
FROM municipalities WHERE slug = 'lohne';
|
||||
8
migrations/004_reverse_geocoding.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- =====================================================================
|
||||
-- Migration 004: Adds Address Column for Reverse Geocoding
|
||||
-- =====================================================================
|
||||
|
||||
ALTER TABLE contributions
|
||||
ADD COLUMN address VARCHAR(255) DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN contributions.address IS 'Reverse geocoded Address, stored automatically on Creation.';
|
||||
27
migrations/005_browser_id.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
-- =====================================================================
|
||||
-- Migration 005: Adds Browser ID for anonymous User Identification
|
||||
-- =====================================================================
|
||||
|
||||
-- Adds browser_id Column to Contributions
|
||||
ALTER TABLE contributions
|
||||
ADD COLUMN browser_id VARCHAR(36) DEFAULT NULL;
|
||||
|
||||
-- Adds browser_id Column to Votes
|
||||
-- Replaces voter_name for Identification
|
||||
ALTER TABLE votes
|
||||
ADD COLUMN browser_id VARCHAR(36) DEFAULT NULL;
|
||||
|
||||
-- Index for fast Vote Lookup by Browser
|
||||
CREATE INDEX idx_votes_browser ON votes(browser_id);
|
||||
|
||||
|
||||
-- New UNIQUE Constraint: One Vote per Browser per Contribution
|
||||
|
||||
-- Drops old Constraint voter_name based
|
||||
ALTER TABLE votes
|
||||
DROP CONSTRAINT IF EXISTS votes_unique_per_voter;
|
||||
|
||||
-- Creates new Constraint browser_id based
|
||||
ALTER TABLE votes
|
||||
ADD CONSTRAINT votes_contribution_browser_unique
|
||||
UNIQUE (contribution_id, browser_id);
|
||||
35
migrations/006_comments_and_photos.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- =====================================================================
|
||||
-- Migration 006: Comments Table and Photo Support
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Creates Table "comments"
|
||||
-- Stores Comments on Contributions. Comments is linked to
|
||||
-- Contributions and identified by browser_id.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS comments (
|
||||
comment_id SERIAL PRIMARY KEY,
|
||||
contribution_id INTEGER NOT NULL REFERENCES contributions(contribution_id) ON DELETE CASCADE,
|
||||
author_name VARCHAR(100) NOT NULL,
|
||||
browser_id VARCHAR(36) DEFAULT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 2: Indexes for fast Comment Queries
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE INDEX idx_comments_contribution ON comments(contribution_id);
|
||||
CREATE INDEX idx_comments_browser ON comments(browser_id);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 3: Adds Photo Path Column to Contributions
|
||||
-- Stores relative Path to uploaded Photo File.
|
||||
-- ---------------------------------------------------------------------
|
||||
ALTER TABLE contributions
|
||||
ADD COLUMN photo_path VARCHAR(255) DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN contributions.photo_path IS 'Relative Path to uploaded Photo. NULL = no Photo.';
|
||||
14
migrations/007_comment_moderation.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- =====================================================================
|
||||
-- Migration 007: Adds Status Column to Comments for Moderation
|
||||
-- =====================================================================
|
||||
|
||||
-- Adds Status Column with Default 'pending'
|
||||
ALTER TABLE comments
|
||||
ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'approved', 'rejected'));
|
||||
|
||||
-- Index for fast Status Filtering
|
||||
CREATE INDEX idx_comments_status ON comments(status);
|
||||
|
||||
-- Approves existing Comments
|
||||
UPDATE comments SET status = 'approved';
|
||||
65
migrations/008_comment_count_trigger.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- =====================================================================
|
||||
-- Migration 008: Adds comment_count Column with automatic Trigger
|
||||
-- Mirrors Pattern from likes_count and dislikes_count.
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Adds comment_count Column to Contributions
|
||||
-- ---------------------------------------------------------------------
|
||||
ALTER TABLE contributions
|
||||
ADD COLUMN comment_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 2: Backfills existing Comment Counts
|
||||
-- ---------------------------------------------------------------------
|
||||
UPDATE contributions c
|
||||
SET comment_count = (
|
||||
SELECT COUNT(*)
|
||||
FROM comments cm
|
||||
WHERE cm.contribution_id = c.contribution_id
|
||||
AND cm.status = 'approved'
|
||||
);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 3: Trigger Function to update comment_count
|
||||
-- Fires on Status Change on comments. Only counts approved Comments
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION update_comment_count()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
||||
UPDATE contributions
|
||||
SET comment_count = (
|
||||
SELECT COUNT(*) FROM comments
|
||||
WHERE contribution_id = NEW.contribution_id
|
||||
AND status = 'approved'
|
||||
)
|
||||
WHERE contribution_id = NEW.contribution_id;
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.contribution_id != NEW.contribution_id) THEN
|
||||
UPDATE contributions
|
||||
SET comment_count = (
|
||||
SELECT COUNT(*) FROM comments
|
||||
WHERE contribution_id = OLD.contribution_id
|
||||
AND status = 'approved'
|
||||
)
|
||||
WHERE contribution_id = OLD.contribution_id;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 4: Attaches Trigger to comments Table
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TRIGGER trigger_update_comment_count
|
||||
AFTER INSERT OR DELETE OR UPDATE OF status
|
||||
ON comments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_comment_count();
|
||||
183
migrations/009_tasks-module.sql
Normal file
@@ -0,0 +1,183 @@
|
||||
-- =====================================================================
|
||||
-- Migration 009: Tasks Module — Tasks with Reward System
|
||||
-- =====================================================================
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 1: Tasks Table
|
||||
-- Stores Tasks with Geometry, Moderation and Completion.
|
||||
-- Status Flow from pending to rejected or approved to completed to verified
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
task_id SERIAL PRIMARY KEY,
|
||||
municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id),
|
||||
geom GEOMETRY(Geometry, 4326) NOT NULL,
|
||||
geom_type VARCHAR(10) NOT NULL CHECK (geom_type IN ('point', 'line', 'polygon')),
|
||||
category VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
points_reward INTEGER NOT NULL DEFAULT 25,
|
||||
author_name VARCHAR(100) NOT NULL,
|
||||
browser_id VARCHAR(36),
|
||||
photo_path VARCHAR(255),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'rejected', 'approved', 'completed', 'verified')),
|
||||
address VARCHAR(255),
|
||||
|
||||
-- Completion Fields empty before completed
|
||||
completed_by_name VARCHAR(100),
|
||||
completed_by_browser VARCHAR(36),
|
||||
completion_photo VARCHAR(255),
|
||||
completion_comment TEXT,
|
||||
completed_at TIMESTAMP,
|
||||
|
||||
-- Counters updated via Triggers
|
||||
likes_count INTEGER NOT NULL DEFAULT 0,
|
||||
dislikes_count INTEGER NOT NULL DEFAULT 0,
|
||||
comment_count INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tasks_municipality ON tasks(municipality_id);
|
||||
CREATE INDEX idx_tasks_status ON tasks(status);
|
||||
CREATE INDEX idx_tasks_category ON tasks(category);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 2: Citizen Points Table
|
||||
-- One Row per Completion. Leaderboard via SUM and GROUP BY.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS user_points (
|
||||
points_id SERIAL PRIMARY KEY,
|
||||
municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id),
|
||||
user_name VARCHAR(100) NOT NULL,
|
||||
points INTEGER NOT NULL DEFAULT 25,
|
||||
task_id INTEGER NOT NULL REFERENCES tasks(task_id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_user_points_municipality ON user_points(municipality_id);
|
||||
CREATE INDEX idx_user_points_user ON user_points(user_name);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 3: Adapts Votes Table for Tasks
|
||||
-- Either contribution_id OR task_id
|
||||
-- ---------------------------------------------------------------------
|
||||
ALTER TABLE votes
|
||||
ADD COLUMN task_id INTEGER REFERENCES tasks(task_id) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX idx_votes_task ON votes(task_id);
|
||||
|
||||
-- Unique Vote per Browser per Task
|
||||
ALTER TABLE votes
|
||||
ADD CONSTRAINT votes_task_browser_unique
|
||||
UNIQUE (task_id, browser_id);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 4: Adapts Comments Table for Tasks
|
||||
-- Either contribution_id OR task_id
|
||||
-- ---------------------------------------------------------------------
|
||||
ALTER TABLE comments
|
||||
ADD COLUMN task_id INTEGER REFERENCES tasks(task_id) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX idx_comments_task ON comments(task_id);
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 5: Trigger Updated Timestamp for Tasks
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TRIGGER set_tasks_updated_at
|
||||
BEFORE UPDATE ON tasks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 6: Trigger Vote Counts for Tasks
|
||||
-- Mirrors Pattern from Contributions.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION update_task_vote_counts()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
||||
IF NEW.task_id IS NOT NULL THEN
|
||||
UPDATE tasks SET
|
||||
likes_count = (SELECT COUNT(*) FROM votes WHERE task_id = NEW.task_id AND vote_type = 'like'),
|
||||
dislikes_count = (SELECT COUNT(*) FROM votes WHERE task_id = NEW.task_id AND vote_type = 'dislike')
|
||||
WHERE task_id = NEW.task_id;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.task_id IS NOT NULL) THEN
|
||||
UPDATE tasks SET
|
||||
likes_count = (SELECT COUNT(*) FROM votes WHERE task_id = OLD.task_id AND vote_type = 'like'),
|
||||
dislikes_count = (SELECT COUNT(*) FROM votes WHERE task_id = OLD.task_id AND vote_type = 'dislike')
|
||||
WHERE task_id = OLD.task_id;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_update_task_vote_counts ON votes;
|
||||
|
||||
CREATE TRIGGER trigger_update_task_vote_counts
|
||||
AFTER INSERT OR DELETE OR UPDATE ON votes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_task_vote_counts();
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 7: Trigger Comment Count for Tasks
|
||||
-- Mirrors Pattern from Contributions.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION update_task_comment_count()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
||||
IF NEW.task_id IS NOT NULL THEN
|
||||
UPDATE tasks
|
||||
SET comment_count = (
|
||||
SELECT COUNT(*) FROM comments
|
||||
WHERE task_id = NEW.task_id AND status = 'approved'
|
||||
)
|
||||
WHERE task_id = NEW.task_id;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.task_id IS NOT NULL) THEN
|
||||
UPDATE tasks
|
||||
SET comment_count = (
|
||||
SELECT COUNT(*) FROM comments
|
||||
WHERE task_id = OLD.task_id AND status = 'approved'
|
||||
)
|
||||
WHERE task_id = OLD.task_id;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_update_task_comment_count ON comments;
|
||||
|
||||
CREATE TRIGGER trigger_update_task_comment_count
|
||||
AFTER INSERT OR DELETE OR UPDATE OF status ON comments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_task_comment_count();
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Block 8: Views for QGIS
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW tasks_points AS
|
||||
SELECT * FROM tasks WHERE geom_type = 'point';
|
||||
|
||||
CREATE OR REPLACE VIEW tasks_lines AS
|
||||
SELECT * FROM tasks WHERE geom_type = 'line';
|
||||
|
||||
CREATE OR REPLACE VIEW tasks_polygons AS
|
||||
SELECT * FROM tasks WHERE geom_type = 'polygon';
|
||||
@@ -1,575 +0,0 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
'use strict';
|
||||
var immediate = require('immediate');
|
||||
|
||||
/* istanbul ignore next */
|
||||
function INTERNAL() {}
|
||||
|
||||
var handlers = {};
|
||||
|
||||
var REJECTED = ['REJECTED'];
|
||||
var FULFILLED = ['FULFILLED'];
|
||||
var PENDING = ['PENDING'];
|
||||
|
||||
module.exports = exports = Promise;
|
||||
|
||||
function Promise(resolver) {
|
||||
if (typeof resolver !== 'function') {
|
||||
throw new TypeError('resolver must be a function');
|
||||
}
|
||||
this.state = PENDING;
|
||||
this.queue = [];
|
||||
this.outcome = void 0;
|
||||
if (resolver !== INTERNAL) {
|
||||
safelyResolveThenable(this, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.prototype["catch"] = function (onRejected) {
|
||||
return this.then(null, onRejected);
|
||||
};
|
||||
Promise.prototype.then = function (onFulfilled, onRejected) {
|
||||
if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
|
||||
typeof onRejected !== 'function' && this.state === REJECTED) {
|
||||
return this;
|
||||
}
|
||||
var promise = new this.constructor(INTERNAL);
|
||||
if (this.state !== PENDING) {
|
||||
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
|
||||
unwrap(promise, resolver, this.outcome);
|
||||
} else {
|
||||
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
function QueueItem(promise, onFulfilled, onRejected) {
|
||||
this.promise = promise;
|
||||
if (typeof onFulfilled === 'function') {
|
||||
this.onFulfilled = onFulfilled;
|
||||
this.callFulfilled = this.otherCallFulfilled;
|
||||
}
|
||||
if (typeof onRejected === 'function') {
|
||||
this.onRejected = onRejected;
|
||||
this.callRejected = this.otherCallRejected;
|
||||
}
|
||||
}
|
||||
QueueItem.prototype.callFulfilled = function (value) {
|
||||
handlers.resolve(this.promise, value);
|
||||
};
|
||||
QueueItem.prototype.otherCallFulfilled = function (value) {
|
||||
unwrap(this.promise, this.onFulfilled, value);
|
||||
};
|
||||
QueueItem.prototype.callRejected = function (value) {
|
||||
handlers.reject(this.promise, value);
|
||||
};
|
||||
QueueItem.prototype.otherCallRejected = function (value) {
|
||||
unwrap(this.promise, this.onRejected, value);
|
||||
};
|
||||
|
||||
function unwrap(promise, func, value) {
|
||||
immediate(function () {
|
||||
var returnValue;
|
||||
try {
|
||||
returnValue = func(value);
|
||||
} catch (e) {
|
||||
return handlers.reject(promise, e);
|
||||
}
|
||||
if (returnValue === promise) {
|
||||
handlers.reject(promise, new TypeError('Cannot resolve promise with itself'));
|
||||
} else {
|
||||
handlers.resolve(promise, returnValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handlers.resolve = function (self, value) {
|
||||
var result = tryCatch(getThen, value);
|
||||
if (result.status === 'error') {
|
||||
return handlers.reject(self, result.value);
|
||||
}
|
||||
var thenable = result.value;
|
||||
|
||||
if (thenable) {
|
||||
safelyResolveThenable(self, thenable);
|
||||
} else {
|
||||
self.state = FULFILLED;
|
||||
self.outcome = value;
|
||||
var i = -1;
|
||||
var len = self.queue.length;
|
||||
while (++i < len) {
|
||||
self.queue[i].callFulfilled(value);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
};
|
||||
handlers.reject = function (self, error) {
|
||||
self.state = REJECTED;
|
||||
self.outcome = error;
|
||||
var i = -1;
|
||||
var len = self.queue.length;
|
||||
while (++i < len) {
|
||||
self.queue[i].callRejected(error);
|
||||
}
|
||||
return self;
|
||||
};
|
||||
|
||||
function getThen(obj) {
|
||||
// Make sure we only access the accessor once as required by the spec
|
||||
var then = obj && obj.then;
|
||||
if (obj && typeof obj === 'object' && typeof then === 'function') {
|
||||
return function appyThen() {
|
||||
then.apply(obj, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function safelyResolveThenable(self, thenable) {
|
||||
// Either fulfill, reject or reject with error
|
||||
var called = false;
|
||||
function onError(value) {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
called = true;
|
||||
handlers.reject(self, value);
|
||||
}
|
||||
|
||||
function onSuccess(value) {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
called = true;
|
||||
handlers.resolve(self, value);
|
||||
}
|
||||
|
||||
function tryToUnwrap() {
|
||||
thenable(onSuccess, onError);
|
||||
}
|
||||
|
||||
var result = tryCatch(tryToUnwrap);
|
||||
if (result.status === 'error') {
|
||||
onError(result.value);
|
||||
}
|
||||
}
|
||||
|
||||
function tryCatch(func, value) {
|
||||
var out = {};
|
||||
try {
|
||||
out.value = func(value);
|
||||
out.status = 'success';
|
||||
} catch (e) {
|
||||
out.status = 'error';
|
||||
out.value = e;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
exports.resolve = resolve;
|
||||
function resolve(value) {
|
||||
if (value instanceof this) {
|
||||
return value;
|
||||
}
|
||||
return handlers.resolve(new this(INTERNAL), value);
|
||||
}
|
||||
|
||||
exports.reject = reject;
|
||||
function reject(reason) {
|
||||
var promise = new this(INTERNAL);
|
||||
return handlers.reject(promise, reason);
|
||||
}
|
||||
|
||||
exports.all = all;
|
||||
function all(iterable) {
|
||||
var self = this;
|
||||
if (Object.prototype.toString.call(iterable) !== '[object Array]') {
|
||||
return this.reject(new TypeError('must be an array'));
|
||||
}
|
||||
|
||||
var len = iterable.length;
|
||||
var called = false;
|
||||
if (!len) {
|
||||
return this.resolve([]);
|
||||
}
|
||||
|
||||
var values = new Array(len);
|
||||
var resolved = 0;
|
||||
var i = -1;
|
||||
var promise = new this(INTERNAL);
|
||||
|
||||
while (++i < len) {
|
||||
allResolver(iterable[i], i);
|
||||
}
|
||||
return promise;
|
||||
function allResolver(value, i) {
|
||||
self.resolve(value).then(resolveFromAll, function (error) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
handlers.reject(promise, error);
|
||||
}
|
||||
});
|
||||
function resolveFromAll(outValue) {
|
||||
values[i] = outValue;
|
||||
if (++resolved === len && !called) {
|
||||
called = true;
|
||||
handlers.resolve(promise, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.race = race;
|
||||
function race(iterable) {
|
||||
var self = this;
|
||||
if (Object.prototype.toString.call(iterable) !== '[object Array]') {
|
||||
return this.reject(new TypeError('must be an array'));
|
||||
}
|
||||
|
||||
var len = iterable.length;
|
||||
var called = false;
|
||||
if (!len) {
|
||||
return this.resolve([]);
|
||||
}
|
||||
|
||||
var i = -1;
|
||||
var promise = new this(INTERNAL);
|
||||
|
||||
while (++i < len) {
|
||||
resolver(iterable[i]);
|
||||
}
|
||||
return promise;
|
||||
function resolver(value) {
|
||||
self.resolve(value).then(function (response) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
handlers.resolve(promise, response);
|
||||
}
|
||||
}, function (error) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
handlers.reject(promise, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},{"immediate":2}],2:[function(require,module,exports){
|
||||
(function (global){
|
||||
'use strict';
|
||||
var Mutation = global.MutationObserver || global.WebKitMutationObserver;
|
||||
|
||||
var scheduleDrain;
|
||||
|
||||
{
|
||||
if (Mutation) {
|
||||
var called = 0;
|
||||
var observer = new Mutation(nextTick);
|
||||
var element = global.document.createTextNode('');
|
||||
observer.observe(element, {
|
||||
characterData: true
|
||||
});
|
||||
scheduleDrain = function () {
|
||||
element.data = (called = ++called % 2);
|
||||
};
|
||||
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
|
||||
var channel = new global.MessageChannel();
|
||||
channel.port1.onmessage = nextTick;
|
||||
scheduleDrain = function () {
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
|
||||
scheduleDrain = function () {
|
||||
|
||||
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
|
||||
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
|
||||
var scriptEl = global.document.createElement('script');
|
||||
scriptEl.onreadystatechange = function () {
|
||||
nextTick();
|
||||
|
||||
scriptEl.onreadystatechange = null;
|
||||
scriptEl.parentNode.removeChild(scriptEl);
|
||||
scriptEl = null;
|
||||
};
|
||||
global.document.documentElement.appendChild(scriptEl);
|
||||
};
|
||||
} else {
|
||||
scheduleDrain = function () {
|
||||
setTimeout(nextTick, 0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var draining;
|
||||
var queue = [];
|
||||
//named nextTick for less confusing stack traces
|
||||
function nextTick() {
|
||||
draining = true;
|
||||
var i, oldQueue;
|
||||
var len = queue.length;
|
||||
while (len) {
|
||||
oldQueue = queue;
|
||||
queue = [];
|
||||
i = -1;
|
||||
while (++i < len) {
|
||||
oldQueue[i]();
|
||||
}
|
||||
len = queue.length;
|
||||
}
|
||||
draining = false;
|
||||
}
|
||||
|
||||
module.exports = immediate;
|
||||
function immediate(task) {
|
||||
if (queue.push(task) === 1 && !draining) {
|
||||
scheduleDrain();
|
||||
}
|
||||
}
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{}],3:[function(require,module,exports){
|
||||
(function (global){
|
||||
'use strict';
|
||||
var jsonp = require('./jsonp');
|
||||
var Promise = require('lie');
|
||||
|
||||
module.exports = function (url, options) {
|
||||
options = options || {};
|
||||
if (options.jsonp) {
|
||||
return jsonp(url, options);
|
||||
}
|
||||
var request;
|
||||
var cancel;
|
||||
var out = new Promise(function (resolve, reject) {
|
||||
cancel = reject;
|
||||
if (global.XMLHttpRequest === undefined) {
|
||||
reject('XMLHttpRequest is not supported');
|
||||
}
|
||||
var response;
|
||||
request = new global.XMLHttpRequest();
|
||||
request.open('GET', url);
|
||||
if (options.headers) {
|
||||
Object.keys(options.headers).forEach(function (key) {
|
||||
request.setRequestHeader(key, options.headers[key]);
|
||||
});
|
||||
}
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === 4) {
|
||||
if ((request.status < 400 && options.local) || request.status === 200) {
|
||||
if (global.JSON) {
|
||||
response = JSON.parse(request.responseText);
|
||||
} else {
|
||||
reject(new Error('JSON is not supported'));
|
||||
}
|
||||
resolve(response);
|
||||
} else {
|
||||
if (!request.status) {
|
||||
reject('Attempted cross origin request without CORS enabled');
|
||||
} else {
|
||||
reject(request.statusText);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send();
|
||||
});
|
||||
out.catch(function (reason) {
|
||||
request.abort();
|
||||
return reason;
|
||||
});
|
||||
out.abort = cancel;
|
||||
return out;
|
||||
};
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./jsonp":5,"lie":1}],4:[function(require,module,exports){
|
||||
(function (global){
|
||||
'use strict';
|
||||
var L = global.L || require('leaflet');
|
||||
var Promise = require('lie');
|
||||
var ajax = require('./ajax');
|
||||
L.GeoJSON.AJAX = L.GeoJSON.extend({
|
||||
defaultAJAXparams: {
|
||||
dataType: 'json',
|
||||
callbackParam: 'callback',
|
||||
local: false,
|
||||
middleware: function (f) {
|
||||
return f;
|
||||
}
|
||||
},
|
||||
initialize: function (url, options) {
|
||||
this.urls = [];
|
||||
if (url) {
|
||||
if (typeof url === 'string') {
|
||||
this.urls.push(url);
|
||||
} else if (typeof url.pop === 'function') {
|
||||
this.urls = this.urls.concat(url);
|
||||
} else {
|
||||
options = url;
|
||||
url = undefined;
|
||||
}
|
||||
}
|
||||
var ajaxParams = L.Util.extend({}, this.defaultAJAXparams);
|
||||
|
||||
for (var i in options) {
|
||||
if (this.defaultAJAXparams.hasOwnProperty(i)) {
|
||||
ajaxParams[i] = options[i];
|
||||
}
|
||||
}
|
||||
this.ajaxParams = ajaxParams;
|
||||
this._layers = {};
|
||||
L.Util.setOptions(this, options);
|
||||
this.on('data:loaded', function () {
|
||||
if (this.filter) {
|
||||
this.refilter(this.filter);
|
||||
}
|
||||
}, this);
|
||||
var self = this;
|
||||
if (this.urls.length > 0) {
|
||||
new Promise(function (resolve) {
|
||||
resolve();
|
||||
}).then(function () {
|
||||
self.addUrl();
|
||||
});
|
||||
}
|
||||
},
|
||||
clearLayers: function () {
|
||||
this.urls = [];
|
||||
L.GeoJSON.prototype.clearLayers.call(this);
|
||||
return this;
|
||||
},
|
||||
addUrl: function (url) {
|
||||
var self = this;
|
||||
if (url) {
|
||||
if (typeof url === 'string') {
|
||||
self.urls.push(url);
|
||||
} else if (typeof url.pop === 'function') {
|
||||
self.urls = self.urls.concat(url);
|
||||
}
|
||||
}
|
||||
var loading = self.urls.length;
|
||||
var done = 0;
|
||||
self.fire('data:loading');
|
||||
self.urls.forEach(function (url) {
|
||||
if (self.ajaxParams.dataType.toLowerCase() === 'json') {
|
||||
ajax(url, self.ajaxParams).then(function (d) {
|
||||
var data = self.ajaxParams.middleware(d);
|
||||
self.addData(data);
|
||||
self.fire('data:progress', data);
|
||||
}, function (err) {
|
||||
self.fire('data:progress', {
|
||||
error: err
|
||||
});
|
||||
});
|
||||
} else if (self.ajaxParams.dataType.toLowerCase() === 'jsonp') {
|
||||
L.Util.jsonp(url, self.ajaxParams).then(function (d) {
|
||||
var data = self.ajaxParams.middleware(d);
|
||||
self.addData(data);
|
||||
self.fire('data:progress', data);
|
||||
}, function (err) {
|
||||
self.fire('data:progress', {
|
||||
error: err
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
self.on('data:progress', function () {
|
||||
if (++done === loading) {
|
||||
self.fire('data:loaded');
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function (url) {
|
||||
url = url || this.urls;
|
||||
this.clearLayers();
|
||||
this.addUrl(url);
|
||||
},
|
||||
refilter: function (func) {
|
||||
if (typeof func !== 'function') {
|
||||
this.filter = false;
|
||||
this.eachLayer(function (a) {
|
||||
a.setStyle({
|
||||
stroke: true,
|
||||
clickable: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.filter = func;
|
||||
this.eachLayer(function (a) {
|
||||
if (func(a.feature)) {
|
||||
a.setStyle({
|
||||
stroke: true,
|
||||
clickable: true
|
||||
});
|
||||
} else {
|
||||
a.setStyle({
|
||||
stroke: false,
|
||||
clickable: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
L.Util.Promise = Promise;
|
||||
L.Util.ajax = ajax;
|
||||
L.Util.jsonp = require('./jsonp');
|
||||
L.geoJson.ajax = function (geojson, options) {
|
||||
return new L.GeoJSON.AJAX(geojson, options);
|
||||
};
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./ajax":3,"./jsonp":5,"leaflet":undefined,"lie":1}],5:[function(require,module,exports){
|
||||
(function (global){
|
||||
'use strict';
|
||||
var L = global.L || require('leaflet');
|
||||
var Promise = require('lie');
|
||||
|
||||
module.exports = function (url, options) {
|
||||
options = options || {};
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var scriptNode = L.DomUtil.create('script', '', head);
|
||||
var cbName, ourl, cbSuffix, cancel;
|
||||
var out = new Promise(function (resolve, reject) {
|
||||
cancel = reject;
|
||||
var cbParam = options.cbParam || 'callback';
|
||||
if (options.callbackName) {
|
||||
cbName = options.callbackName;
|
||||
} else {
|
||||
cbSuffix = '_' + ('' + Math.random()).slice(2);
|
||||
cbName = '_leafletJSONPcallbacks.' + cbSuffix;
|
||||
}
|
||||
scriptNode.type = 'text/javascript';
|
||||
if (cbSuffix) {
|
||||
if (!global._leafletJSONPcallbacks) {
|
||||
global._leafletJSONPcallbacks = {
|
||||
length: 0
|
||||
};
|
||||
}
|
||||
global._leafletJSONPcallbacks.length++;
|
||||
global._leafletJSONPcallbacks[cbSuffix] = function (data) {
|
||||
head.removeChild(scriptNode);
|
||||
delete global._leafletJSONPcallbacks[cbSuffix];
|
||||
global._leafletJSONPcallbacks.length--;
|
||||
if (!global._leafletJSONPcallbacks.length) {
|
||||
delete global._leafletJSONPcallbacks;
|
||||
}
|
||||
resolve(data);
|
||||
};
|
||||
}
|
||||
if (url.indexOf('?') === -1) {
|
||||
ourl = url + '?' + cbParam + '=' + cbName;
|
||||
} else {
|
||||
ourl = url + '&' + cbParam + '=' + cbName;
|
||||
}
|
||||
scriptNode.src = ourl;
|
||||
}).then(null, function (reason) {
|
||||
head.removeChild(scriptNode);
|
||||
delete L.Util.ajax.cb[cbSuffix];
|
||||
return reason;
|
||||
});
|
||||
out.abort = cancel;
|
||||
return out;
|
||||
};
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"leaflet":undefined,"lie":1}]},{},[4]);
|
||||
1
plugins/minimap/Control.MiniMap.min.css
vendored
@@ -1 +0,0 @@
|
||||
.leaflet-control-minimap{border:rgba(255,255,255,1) solid;box-shadow:0 1px 5px rgba(0,0,0,.65);border-radius:3px;background:#f8f8f9;transition:all .6s}.leaflet-control-minimap a{background-color:rgba(255,255,255,1);background-repeat:no-repeat;z-index:99999;transition:all .6s}.leaflet-control-minimap a.minimized-bottomright{-webkit-transform:rotate(180deg);transform:rotate(180deg);border-radius:0}.leaflet-control-minimap a.minimized-topleft{-webkit-transform:rotate(0deg);transform:rotate(0deg);border-radius:0}.leaflet-control-minimap a.minimized-bottomleft{-webkit-transform:rotate(270deg);transform:rotate(270deg);border-radius:0}.leaflet-control-minimap a.minimized-topright{-webkit-transform:rotate(90deg);transform:rotate(90deg);border-radius:0}.leaflet-control-minimap-toggle-display{background-image:url(images/toggle.svg);background-size:cover;position:absolute;border-radius:3px 0 0}.leaflet-oldie .leaflet-control-minimap-toggle-display{background-image:url(images/toggle.png)}.leaflet-control-minimap-toggle-display-bottomright{bottom:0;right:0}.leaflet-control-minimap-toggle-display-topleft{top:0;left:0;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.leaflet-control-minimap-toggle-display-bottomleft{bottom:0;left:0;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.leaflet-control-minimap-toggle-display-topright{top:0;right:0;-webkit-transform:rotate(270deg);transform:rotate(270deg)}.leaflet-oldie .leaflet-control-minimap{border:1px solid #999}.leaflet-oldie .leaflet-control-minimap a{background-color:#fff}.leaflet-oldie .leaflet-control-minimap a.minimized{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2)}
|
||||
1
plugins/minimap/Control.MiniMap.min.js
vendored
|
Before Width: | Height: | Size: 219 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18"><defs><marker orient="auto" overflow="visible"><path d="M-2.6-2.828L-5.428 0-2.6 2.828.228 0-2.6-2.828z" fill-rule="evenodd" stroke="#000" stroke-width=".4pt"/></marker><marker orient="auto" overflow="visible"><g fill="none" stroke="#000" stroke-width=".8" stroke-linecap="round"><path d="M4.566 4.75L-.652 0"/><path d="M1.544 4.75L-3.674 0"/><path d="M-1.566 4.75L-6.784 0"/><path d="M4.566-5.013L-.652-.263"/><path d="M1.544-5.013l-5.218 4.75"/><path d="M-1.566-5.013l-5.218 4.75"/></g></marker><marker orient="auto" overflow="visible"><path d="M-5.6-5.657L-11.257 0-5.6 5.657.057 0-5.6-5.657z" fill-rule="evenodd" stroke="#000" stroke-width=".8pt"/></marker><marker orient="auto" overflow="visible"><path d="M4.616 0l-6.92 4v-8l6.92 4z" fill-rule="evenodd" stroke="#000" stroke-width=".8pt"/></marker><marker orient="auto" overflow="visible"><path d="M-10.69-4.437L1.328-.017l-12.018 4.42c1.92-2.61 1.91-6.18 0-8.84z" font-size="12" fill-rule="evenodd" stroke-width=".6875" stroke-linejoin="round"/></marker><marker orient="auto" overflow="visible"><path d="M-4.616 0l6.92-4v8l-6.92-4z" fill-rule="evenodd" stroke="#000" stroke-width=".8pt"/></marker><marker orient="auto" overflow="visible"><path d="M10 0l4-4L0 0l14 4-4-4z" fill-rule="evenodd" stroke="#000" stroke-width=".8pt"/></marker><marker orient="auto" overflow="visible"><path d="M10.69 4.437L-1.328.017l12.018-4.42c-1.92 2.61-1.91 6.18 0 8.84z" font-size="12" fill-rule="evenodd" stroke-width=".6875" stroke-linejoin="round"/></marker></defs><path d="M13.18 13.146v-5.81l-5.81 5.81h5.81z" stroke="#000" stroke-width="1.643"/><path d="M12.762 12.727l-6.51-6.51" fill="none" stroke="#000" stroke-width="2.482" stroke-linecap="round"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,9 +0,0 @@
|
||||
.leaflet-container .leaflet-control-mouseposition {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px #bbb;
|
||||
padding: 0 5px;
|
||||
margin:0;
|
||||
color: #333;
|
||||
font: 11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
L.Control.MousePosition = L.Control.extend({
|
||||
options: {
|
||||
position: 'bottomleft',
|
||||
separator: ' : ',
|
||||
emptyString: 'Unavailable',
|
||||
lngFirst: false,
|
||||
numDigits: 5,
|
||||
lngFormatter: undefined,
|
||||
latFormatter: undefined,
|
||||
prefix: ""
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
this._container = L.DomUtil.create('div', 'leaflet-control-mouseposition');
|
||||
L.DomEvent.disableClickPropagation(this._container);
|
||||
map.on('mousemove', this._onMouseMove, this);
|
||||
this._container.innerHTML=this.options.emptyString;
|
||||
return this._container;
|
||||
},
|
||||
|
||||
onRemove: function (map) {
|
||||
map.off('mousemove', this._onMouseMove)
|
||||
},
|
||||
|
||||
_onMouseMove: function (e) {
|
||||
var lng = this.options.lngFormatter ? this.options.lngFormatter(e.latlng.lng) : L.Util.formatNum(e.latlng.lng, this.options.numDigits);
|
||||
var lat = this.options.latFormatter ? this.options.latFormatter(e.latlng.lat) : L.Util.formatNum(e.latlng.lat, this.options.numDigits);
|
||||
var value = this.options.lngFirst ? lng + this.options.separator + lat : lat + this.options.separator + lng;
|
||||
var prefixAndValue = this.options.prefix + ' ' + value;
|
||||
this._container.innerHTML = prefixAndValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Map.mergeOptions({
|
||||
positionControl: false
|
||||
});
|
||||
|
||||
L.Map.addInitHook(function () {
|
||||
if (this.options.positionControl) {
|
||||
this.positionControl = new L.Control.MousePosition();
|
||||
this.addControl(this.positionControl);
|
||||
}
|
||||
});
|
||||
|
||||
L.control.mousePosition = function (options) {
|
||||
return new L.Control.MousePosition(options);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
*.min.js
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports = {
|
||||
extends: 'eslint:recommended',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { args: 'none' }]
|
||||
},
|
||||
overrides: [{
|
||||
files: ['gulpfile.js'],
|
||||
env: {
|
||||
browser: false,
|
||||
node: true,
|
||||
}
|
||||
}]
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
github: Turbo87
|
||||
custom: https://paypal.me/tobiasbieniek
|
||||
@@ -1,21 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: eslint
|
||||
versions:
|
||||
- 7.18.0
|
||||
- 7.19.0
|
||||
- 7.20.0
|
||||
- 7.21.0
|
||||
- 7.22.0
|
||||
- 7.23.0
|
||||
- 7.24.0
|
||||
commit-message:
|
||||
prefix: ""
|
||||
@@ -1,20 +0,0 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
||||
|
||||
dist
|
||||
build
|
||||
|
||||
.eslintcache
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'stylelint-scss',
|
||||
],
|
||||
extends: [
|
||||
'stylelint-config-recommended-scss',
|
||||
],
|
||||
rules: {
|
||||
'no-descending-specificity': null,
|
||||
},
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
language: node_js
|
||||
node_js: stable
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
install:
|
||||
- yarn install
|
||||
|
||||
script:
|
||||
- yarn lint:css
|
||||
- yarn lint:js
|
||||
|
||||
# build the processed/minified assets
|
||||
- yarn gulp
|
||||
|
||||
# check that they results match the committed state
|
||||
- git diff --exit-code
|
||||
@@ -1,44 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## v0.4.0 (2017-10-25)
|
||||
|
||||
- ol3 version without jquery dependency ([#97](https://github.com/Turbo87/sidebar-v2/pull/97))
|
||||
- Allow non-tab links in the sidebar
|
||||
- add type definition for leaflet-sidebar ([#112](https://github.com/Turbo87/sidebar-v2/pull/112))
|
||||
- Remove reference to L.Mixin.Evented ([#124](https://github.com/Turbo87/sidebar-v2/pull/124))
|
||||
- Fix Chrome 62 list-style-type bug ([#127](https://github.com/Turbo87/sidebar-v2/pull/127))
|
||||
|
||||
## v0.3.1 (2016-11-01)
|
||||
|
||||
- fix `ol3` example map layer ([#77](https://github.com/Turbo87/sidebar-v2/pull/77))
|
||||
- leaflet: deprecate `removeFrom()` in favor of `remove()` ([#73](https://github.com/Turbo87/sidebar-v2/pull/73))
|
||||
- leaflet: allow non-tab links on the sidebar ([#87](https://github.com/Turbo87/sidebar-v2/pull/87))
|
||||
- leaflet: fix CDN location on example pages ([#94](https://github.com/Turbo87/sidebar-v2/pull/94))
|
||||
- ol3: move "scale-line" together with the zoom controls ([#93](https://github.com/Turbo87/sidebar-v2/pull/93))
|
||||
|
||||
## v0.3.0 (2016-01-19)
|
||||
|
||||
- ol2: move scale line control too when sidebar opens/closes
|
||||
- hide scrollbars when collapsed ([#21](https://github.com/Turbo87/sidebar-v2/issues/21))
|
||||
- fix tab clicking on devices with touch screen *and* mouse ([#34](https://github.com/Turbo87/sidebar-v2/issues/35))
|
||||
- new `.sidebar-header` CSS class for styled headings
|
||||
- new `.sidebar-close` CSS class for close buttons in headings
|
||||
- fix broken Google Maps code (until Google changes things again...)
|
||||
- allow `.disabled` on `<li>` elements in `.sidebar-tabs` element
|
||||
- allow second tabbar at the bottom
|
||||
- new `position: 'right'` option
|
||||
|
||||
|
||||
## v0.2.1 (2014-09-29)
|
||||
|
||||
- ol2, ol3: fixed sidebar content scrolling
|
||||
|
||||
|
||||
## v0.2.0 (2014-09-29)
|
||||
|
||||
- jQuery API and events
|
||||
|
||||
|
||||
## v0.1.0 (2014-09-12)
|
||||
|
||||
- first beta release
|
||||
@@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Tobias Bieniek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,42 +0,0 @@
|
||||
# sidebar-v2
|
||||
|
||||
A responsive sidebar for mapping libraries like [Leaflet](#leaflet) or [OpenLayers](#openlayers-3).
|
||||
|
||||
It is more or less a successor of the [leaflet-sidebar](https://github.com/turbo87/leaflet-sidebar/) plugin, thus the `v2` suffix.
|
||||
|
||||
<a href="https://flattr.com/submit/auto?user_id=turbo&url=https%3A%2F%2Fgithub.com%2FTurbo87%2Fsidebar-v2" target="_blank"><img src="https://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0"></a>
|
||||
|
||||

|
||||
|
||||
|
||||
## [Leaflet](http://leafletjs.com/)
|
||||
|
||||
 
|
||||
|
||||
Example code at [`examples/index.html`](examples/index.html) ([Preview](http://turbo87.github.io/sidebar-v2/examples/index.html))
|
||||
|
||||
|
||||
## [OpenLayers 3](http://openlayers.org/)
|
||||
|
||||
 
|
||||
|
||||
Example code at [`examples/ol3.html`](examples/ol3.html) ([Preview](http://turbo87.github.io/sidebar-v2/examples/ol3.html))
|
||||
|
||||
|
||||
## [OpenLayers 2](http://openlayers.org/two/)
|
||||
|
||||
 
|
||||
|
||||
Example code at [`examples/ol2.html`](examples/ol2.html) ([Preview](http://turbo87.github.io/sidebar-v2/examples/ol2.html))
|
||||
|
||||
|
||||
## [Google Maps](https://developers.google.com/maps/)
|
||||
|
||||
 
|
||||
|
||||
Example code at [`examples/gmaps.html`](examples/gmaps.html) ([Preview](http://turbo87.github.io/sidebar-v2/examples/gmaps.html))
|
||||
|
||||
|
||||
## License
|
||||
|
||||
sidebar-v2 is free software, and may be redistributed under the [MIT license](LICENSE).
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "sidebar-v2",
|
||||
"version": "0.3.1",
|
||||
"homepage": "https://github.com/Turbo87/sidebar-v2",
|
||||
"authors": [
|
||||
"Tobias Bieniek <tobias.bieniek@qsc.de>"
|
||||
],
|
||||
"description": "A responsive sidebar for mapping libraries like Leaflet or OpenLayers",
|
||||
"main": [
|
||||
"css/gmaps-sidebar.css",
|
||||
"css/leaflet-sidebar.css",
|
||||
"css/ol2-sidebar.css",
|
||||
"css/ol3-sidebar.css",
|
||||
"js/jquery-sidebar.js",
|
||||
"js/leaflet-sidebar.js",
|
||||
"js/ol3-sidebar.js"
|
||||
],
|
||||
"keywords": [
|
||||
"gis",
|
||||
"leaflet",
|
||||
"openlayers",
|
||||
"map"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"doc",
|
||||
"examples",
|
||||
".gitignore"
|
||||
]
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 2000; }
|
||||
.sidebar.collapsed {
|
||||
width: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
transition: width 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar {
|
||||
width: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar {
|
||||
width: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 460px; } }
|
||||
|
||||
.sidebar-left {
|
||||
left: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
left: 10px; } }
|
||||
|
||||
.sidebar-right {
|
||||
right: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
right: 10px; } }
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background-color: #fff; }
|
||||
.sidebar-left .sidebar-tabs {
|
||||
left: 0; }
|
||||
.sidebar-right .sidebar-tabs {
|
||||
right: 0; }
|
||||
.sidebar-tabs, .sidebar-tabs > ul {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
.sidebar-tabs > li, .sidebar-tabs > ul > li {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: #666;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
transition: all 80ms; }
|
||||
.sidebar-tabs > li:hover, .sidebar-tabs > ul > li:hover {
|
||||
color: #000;
|
||||
background-color: #fff9e6; }
|
||||
.sidebar-tabs > li.active, .sidebar-tabs > ul > li.active {
|
||||
color: #000;
|
||||
background-color: #febf00; }
|
||||
.sidebar-tabs > li.disabled, .sidebar-tabs > ul > li.disabled {
|
||||
color: rgba(102, 102, 102, 0.4); }
|
||||
.sidebar-tabs > li.disabled:hover, .sidebar-tabs > ul > li.disabled:hover {
|
||||
background: transparent; }
|
||||
.sidebar-tabs > li.disabled > a, .sidebar-tabs > ul > li.disabled > a {
|
||||
cursor: default; }
|
||||
.sidebar-tabs > li > a, .sidebar-tabs > ul > li > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center; }
|
||||
.sidebar-tabs > ul + ul {
|
||||
bottom: 0; }
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; }
|
||||
.sidebar-left .sidebar-content {
|
||||
left: 40px;
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-content {
|
||||
left: 0;
|
||||
right: 40px; }
|
||||
.sidebar.collapsed > .sidebar-content {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px; }
|
||||
.sidebar-pane.active {
|
||||
display: block; }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-pane {
|
||||
min-width: 265px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-pane {
|
||||
min-width: 350px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-pane {
|
||||
min-width: 420px; } }
|
||||
|
||||
.sidebar-header {
|
||||
margin: -10px -20px 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
font-size: 14.4pt;
|
||||
color: #000;
|
||||
background-color: #febf00; }
|
||||
.sidebar-right .sidebar-header {
|
||||
padding-left: 40px; }
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer; }
|
||||
.sidebar-left .sidebar-close {
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-close {
|
||||
left: 0; }
|
||||
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 0; } }
|
||||
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 0; } }
|
||||
|
||||
.sidebar {
|
||||
border-right: 0;
|
||||
box-shadow: rgba(0, 0, 0, 0.298039) 0 1px 4px -1px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
border: 0;
|
||||
border-radius: 2px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
bottom: 35px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
transition: margin-left 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-left ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
margin-left: 325px !important; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-left ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
margin-left: 410px !important; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-left ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
margin-left: 480px !important; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left.collapsed ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
margin-left: 60px !important; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
bottom: 24px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
transition: margin-right 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-right ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
margin-right: 325px !important; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-right ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
margin-right: 410px !important; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-right ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
margin-right: 480px !important; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right.collapsed ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
margin-right: 60px !important; } }
|
||||
@@ -1 +0,0 @@
|
||||
.sidebar{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;z-index:2000;border-right:0;box-shadow:rgba(0,0,0,.298039) 0 1px 4px -1px}.sidebar.collapsed{width:40px}@media (min-width:768px) and (max-width:991px){.sidebar{width:305px}.sidebar-pane{min-width:265px}}@media (min-width:992px) and (max-width:1199px){.sidebar{width:390px}}@media (min-width:1200px){.sidebar{width:460px}}.sidebar-left{left:0}.sidebar-right{right:0}@media (min-width:768px){.sidebar{top:10px;bottom:10px;transition:width .5s}.sidebar-left{left:10px}.sidebar-right{right:10px}}.sidebar-tabs{top:0;bottom:0;height:100%;background-color:#fff}.sidebar-left .sidebar-tabs{left:0}.sidebar-right .sidebar-tabs{right:0}.sidebar-tabs,.sidebar-tabs>ul{position:absolute;width:40px;margin:0;padding:0;list-style-type:none}.sidebar-tabs>li,.sidebar-tabs>ul>li{width:100%;height:40px;color:#666;font-size:12pt;overflow:hidden;transition:80ms}.sidebar-tabs>li:hover,.sidebar-tabs>ul>li:hover{color:#000;background-color:#fff9e6}.sidebar-tabs>li.active,.sidebar-tabs>ul>li.active{color:#000;background-color:#febf00}.sidebar-tabs>li.disabled,.sidebar-tabs>ul>li.disabled{color:rgba(102,102,102,.4)}.sidebar-tabs>li.disabled:hover,.sidebar-tabs>ul>li.disabled:hover{background:0 0}.sidebar-tabs>li.disabled>a,.sidebar-tabs>ul>li.disabled>a{cursor:default}.sidebar-tabs>li>a,.sidebar-tabs>ul>li>a{display:block;width:100%;height:100%;line-height:40px;color:inherit;text-decoration:none;text-align:center}.sidebar-tabs>ul+ul{bottom:0}.sidebar-content{position:absolute;top:0;bottom:0;background-color:rgba(255,255,255,.95);overflow-x:hidden;overflow-y:auto}.sidebar-left .sidebar-content{left:40px;right:0}.sidebar-right .sidebar-content{left:0;right:40px}.sidebar.collapsed>.sidebar-content{overflow-y:hidden}.sidebar-pane{display:none;left:0;right:0;box-sizing:border-box;padding:10px 20px}.sidebar-pane.active{display:block}.sidebar-header{margin:-10px -20px 0;height:40px;padding:0 20px;line-height:40px;font-size:14.4pt;color:#000;background-color:#febf00}.sidebar-right .sidebar-header{padding-left:40px}.sidebar-close{position:absolute;top:0;width:40px;height:40px;text-align:center;cursor:pointer}.sidebar-left .sidebar-close{right:0}.sidebar-right .sidebar-close{left:0}.sidebar-left~.sidebar-map{margin-left:40px}.sidebar-right~.sidebar-map{margin-right:40px}@media (min-width:768px) and (max-width:991px){.sidebar-left~.sidebar-map .gm-style>div.gmnoprint[style*="left: 0px"]{margin-left:325px!important}.sidebar-right~.sidebar-map .gm-style>div.gmnoprint[style*="right: 28px"]{margin-right:325px!important}}@media (min-width:992px) and (max-width:1199px){.sidebar-pane{min-width:350px}.sidebar-left~.sidebar-map .gm-style>div.gmnoprint[style*="left: 0px"]{margin-left:410px!important}.sidebar-right~.sidebar-map .gm-style>div.gmnoprint[style*="right: 28px"]{margin-right:410px!important}}@media (min-width:1200px){.sidebar-pane{min-width:420px}.sidebar-left~.sidebar-map .gm-style>div.gmnoprint[style*="left: 0px"]{margin-left:480px!important}.sidebar-right~.sidebar-map .gm-style>div.gmnoprint[style*="right: 28px"]{margin-right:480px!important}}@media (min-width:768px){.sidebar-left~.sidebar-map{margin-left:0}.sidebar-right~.sidebar-map{margin-right:0}.sidebar{border:0;border-radius:2px}.sidebar-left{bottom:35px}.sidebar-left~.sidebar-map .gm-style>div.gmnoprint[style*="left: 0px"]{transition:margin-left .5s}.sidebar-left.collapsed~.sidebar-map .gm-style>div.gmnoprint[style*="left: 0px"]{margin-left:60px!important}.sidebar-right{bottom:24px}.sidebar-right~.sidebar-map .gm-style>div.gmnoprint[style*="right: 28px"]{transition:margin-right .5s}.sidebar-right.collapsed~.sidebar-map .gm-style>div.gmnoprint[style*="right: 28px"]{margin-right:60px!important}}
|
||||
@@ -1,200 +0,0 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 2000; }
|
||||
.sidebar.collapsed {
|
||||
width: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
transition: width 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar {
|
||||
width: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar {
|
||||
width: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 460px; } }
|
||||
|
||||
.sidebar-left {
|
||||
left: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
left: 10px; } }
|
||||
|
||||
.sidebar-right {
|
||||
right: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
right: 10px; } }
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background-color: #fff; }
|
||||
.sidebar-left .sidebar-tabs {
|
||||
left: 0; }
|
||||
.sidebar-right .sidebar-tabs {
|
||||
right: 0; }
|
||||
.sidebar-tabs, .sidebar-tabs > ul {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
.sidebar-tabs > li, .sidebar-tabs > ul > li {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
transition: all 80ms; }
|
||||
.sidebar-tabs > li:hover, .sidebar-tabs > ul > li:hover {
|
||||
color: #000;
|
||||
background-color: #eee; }
|
||||
.sidebar-tabs > li.active, .sidebar-tabs > ul > li.active {
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-tabs > li.disabled, .sidebar-tabs > ul > li.disabled {
|
||||
color: rgba(51, 51, 51, 0.4); }
|
||||
.sidebar-tabs > li.disabled:hover, .sidebar-tabs > ul > li.disabled:hover {
|
||||
background: transparent; }
|
||||
.sidebar-tabs > li.disabled > a, .sidebar-tabs > ul > li.disabled > a {
|
||||
cursor: default; }
|
||||
.sidebar-tabs > li > a, .sidebar-tabs > ul > li > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center; }
|
||||
.sidebar-tabs > ul + ul {
|
||||
bottom: 0; }
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; }
|
||||
.sidebar-left .sidebar-content {
|
||||
left: 40px;
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-content {
|
||||
left: 0;
|
||||
right: 40px; }
|
||||
.sidebar.collapsed > .sidebar-content {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px; }
|
||||
.sidebar-pane.active {
|
||||
display: block; }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-pane {
|
||||
min-width: 265px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-pane {
|
||||
min-width: 350px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-pane {
|
||||
min-width: 420px; } }
|
||||
|
||||
.sidebar-header {
|
||||
margin: -10px -20px 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
font-size: 14.4pt;
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-right .sidebar-header {
|
||||
padding-left: 40px; }
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer; }
|
||||
.sidebar-left .sidebar-close {
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-close {
|
||||
left: 0; }
|
||||
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 0; } }
|
||||
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 0; } }
|
||||
|
||||
.sidebar {
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); }
|
||||
.sidebar.leaflet-touch {
|
||||
box-shadow: none;
|
||||
border-right: 2px solid rgba(0, 0, 0, 0.2); }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
border-radius: 4px; }
|
||||
.sidebar.leaflet-touch {
|
||||
border: 2px solid rgba(0, 0, 0, 0.2); } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
transition: left 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 315px; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 400px; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 470px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left.collapsed ~ .sidebar-map .leaflet-left {
|
||||
left: 50px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
transition: right 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 315px; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 400px; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 470px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right.collapsed ~ .sidebar-map .leaflet-right {
|
||||
right: 50px; } }
|
||||
@@ -1 +0,0 @@
|
||||
.sidebar{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;z-index:2000;box-shadow:0 1px 5px rgba(0,0,0,.65)}.sidebar.collapsed{width:40px}@media (min-width:768px) and (max-width:991px){.sidebar{width:305px}.sidebar-pane{min-width:265px}}@media (min-width:992px) and (max-width:1199px){.sidebar{width:390px}}@media (min-width:1200px){.sidebar{width:460px}}.sidebar-left{left:0}.sidebar-right{right:0}@media (min-width:768px){.sidebar{top:10px;bottom:10px;transition:width .5s}.sidebar-left{left:10px}.sidebar-right{right:10px}}.sidebar-tabs{top:0;bottom:0;height:100%;background-color:#fff}.sidebar-left .sidebar-tabs{left:0}.sidebar-right .sidebar-tabs{right:0}.sidebar-tabs,.sidebar-tabs>ul{position:absolute;width:40px;margin:0;padding:0;list-style-type:none}.sidebar-tabs>li,.sidebar-tabs>ul>li{width:100%;height:40px;color:#333;font-size:12pt;overflow:hidden;transition:80ms}.sidebar-tabs>li:hover,.sidebar-tabs>ul>li:hover{color:#000;background-color:#eee}.sidebar-tabs>li.active,.sidebar-tabs>ul>li.active{color:#fff;background-color:#0074d9}.sidebar-tabs>li.disabled,.sidebar-tabs>ul>li.disabled{color:rgba(51,51,51,.4)}.sidebar-tabs>li.disabled:hover,.sidebar-tabs>ul>li.disabled:hover{background:0 0}.sidebar-tabs>li.disabled>a,.sidebar-tabs>ul>li.disabled>a{cursor:default}.sidebar-tabs>li>a,.sidebar-tabs>ul>li>a{display:block;width:100%;height:100%;line-height:40px;color:inherit;text-decoration:none;text-align:center}.sidebar-tabs>ul+ul{bottom:0}.sidebar-content{position:absolute;top:0;bottom:0;background-color:rgba(255,255,255,.95);overflow-x:hidden;overflow-y:auto}.sidebar-left .sidebar-content{left:40px;right:0}.sidebar-right .sidebar-content{left:0;right:40px}.sidebar.collapsed>.sidebar-content{overflow-y:hidden}.sidebar-pane{display:none;left:0;right:0;box-sizing:border-box;padding:10px 20px}.sidebar-pane.active{display:block}.sidebar-header{margin:-10px -20px 0;height:40px;padding:0 20px;line-height:40px;font-size:14.4pt;color:#fff;background-color:#0074d9}.sidebar-right .sidebar-header{padding-left:40px}.sidebar-close{position:absolute;top:0;width:40px;height:40px;text-align:center;cursor:pointer}.sidebar-left .sidebar-close{right:0}.sidebar-right .sidebar-close{left:0}.sidebar-left~.sidebar-map{margin-left:40px}.sidebar-right~.sidebar-map{margin-right:40px}.sidebar.leaflet-touch{box-shadow:none;border-right:2px solid rgba(0,0,0,.2)}@media (min-width:768px) and (max-width:991px){.sidebar-left~.sidebar-map .leaflet-left{left:315px}.sidebar-right~.sidebar-map .leaflet-right{right:315px}}@media (min-width:992px) and (max-width:1199px){.sidebar-pane{min-width:350px}.sidebar-left~.sidebar-map .leaflet-left{left:400px}.sidebar-right~.sidebar-map .leaflet-right{right:400px}}@media (min-width:1200px){.sidebar-pane{min-width:420px}.sidebar-left~.sidebar-map .leaflet-left{left:470px}.sidebar-right~.sidebar-map .leaflet-right{right:470px}}@media (min-width:768px){.sidebar-left~.sidebar-map{margin-left:0}.sidebar-right~.sidebar-map{margin-right:0}.sidebar{border-radius:4px}.sidebar.leaflet-touch{border:2px solid rgba(0,0,0,.2)}.sidebar-left~.sidebar-map .leaflet-left{transition:left .5s}.sidebar-left.collapsed~.sidebar-map .leaflet-left{left:50px}.sidebar-right~.sidebar-map .leaflet-right{transition:right .5s}.sidebar-right.collapsed~.sidebar-map .leaflet-right{right:50px}}
|
||||
@@ -1,218 +0,0 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 2000; }
|
||||
.sidebar.collapsed {
|
||||
width: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
transition: width 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar {
|
||||
width: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar {
|
||||
width: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 460px; } }
|
||||
|
||||
.sidebar-left {
|
||||
left: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
left: 8px; } }
|
||||
|
||||
.sidebar-right {
|
||||
right: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
right: 8px; } }
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 60, 136, 0.5); }
|
||||
.sidebar-left .sidebar-tabs {
|
||||
left: 0; }
|
||||
.sidebar-right .sidebar-tabs {
|
||||
right: 0; }
|
||||
.sidebar-tabs, .sidebar-tabs > ul {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
.sidebar-tabs > li, .sidebar-tabs > ul > li {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: #fff;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
transition: all 80ms; }
|
||||
.sidebar-tabs > li:hover, .sidebar-tabs > ul > li:hover {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 60, 136, 0.6); }
|
||||
.sidebar-tabs > li.active, .sidebar-tabs > ul > li.active {
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-tabs > li.disabled, .sidebar-tabs > ul > li.disabled {
|
||||
color: rgba(255, 255, 255, 0.4); }
|
||||
.sidebar-tabs > li.disabled:hover, .sidebar-tabs > ul > li.disabled:hover {
|
||||
background: transparent; }
|
||||
.sidebar-tabs > li.disabled > a, .sidebar-tabs > ul > li.disabled > a {
|
||||
cursor: default; }
|
||||
.sidebar-tabs > li > a, .sidebar-tabs > ul > li > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center; }
|
||||
.sidebar-tabs > ul + ul {
|
||||
bottom: 0; }
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; }
|
||||
.sidebar-left .sidebar-content {
|
||||
left: 40px;
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-content {
|
||||
left: 0;
|
||||
right: 40px; }
|
||||
.sidebar.collapsed > .sidebar-content {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px; }
|
||||
.sidebar-pane.active {
|
||||
display: block; }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-pane {
|
||||
min-width: 265px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-pane {
|
||||
min-width: 350px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-pane {
|
||||
min-width: 420px; } }
|
||||
|
||||
.sidebar-header {
|
||||
margin: -10px -20px 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
font-size: 14.4pt;
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-right .sidebar-header {
|
||||
padding-left: 40px; }
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer; }
|
||||
.sidebar-left .sidebar-close {
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-close {
|
||||
left: 0; }
|
||||
|
||||
.sidebar {
|
||||
background-color: rgba(255, 255, 255, 0.4); }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
border: 3px solid transparent;
|
||||
border-radius: 4px; } }
|
||||
|
||||
.sidebar-left {
|
||||
border-right: 3px solid transparent; }
|
||||
|
||||
.sidebar-right {
|
||||
border-left: 3px solid transparent; }
|
||||
|
||||
.sidebar-tabs {
|
||||
overflow: hidden; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-tabs {
|
||||
border-radius: 4px 0 0 4px; }
|
||||
.collapsed .sidebar-tabs {
|
||||
border-radius: 4px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-content {
|
||||
border-radius: 0 4px 4px 0; } }
|
||||
|
||||
.sidebar-left ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left ~ .sidebar-map .olScaleLine {
|
||||
margin-left: 46px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left ~ .sidebar-map .olScaleLine {
|
||||
transition: margin-left 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-left ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left ~ .sidebar-map .olScaleLine {
|
||||
margin-left: 319px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-left ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left ~ .sidebar-map .olScaleLine {
|
||||
margin-left: 404px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-left ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left ~ .sidebar-map .olScaleLine {
|
||||
margin-left: 474px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left.collapsed ~ .sidebar-map .olControlZoom,
|
||||
.sidebar-left.collapsed ~ .sidebar-map .olScaleLine {
|
||||
margin-left: 54px; } }
|
||||
|
||||
.sidebar-right ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right ~ .sidebar-map .olControlMousePosition {
|
||||
margin-right: 46px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right ~ .sidebar-map .olControlMousePosition {
|
||||
transition: margin-right 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-right ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right ~ .sidebar-map .olControlMousePosition {
|
||||
margin-right: 319px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-right ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right ~ .sidebar-map .olControlMousePosition {
|
||||
margin-right: 404px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-right ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right ~ .sidebar-map .olControlMousePosition {
|
||||
margin-right: 474px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right.collapsed ~ .sidebar-map .olControlAttribution,
|
||||
.sidebar-right.collapsed ~ .sidebar-map .olControlPermalink,
|
||||
.sidebar-right.collapsed ~ .sidebar-map .olControlMousePosition {
|
||||
margin-right: 54px; } }
|
||||
@@ -1 +0,0 @@
|
||||
.sidebar{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;z-index:2000}.sidebar.collapsed{width:40px}@media (min-width:768px) and (max-width:991px){.sidebar{width:305px}.sidebar-pane{min-width:265px}}@media (min-width:992px) and (max-width:1199px){.sidebar{width:390px}}@media (min-width:1200px){.sidebar{width:460px}}.sidebar-left{left:0;border-right:3px solid transparent}.sidebar-right{right:0;border-left:3px solid transparent}@media (min-width:768px){.sidebar{top:8px;bottom:8px;transition:width .5s;border:3px solid transparent;border-radius:4px}.sidebar-left{left:8px}.sidebar-right{right:8px}}.sidebar-tabs{top:0;bottom:0;height:100%;background-color:rgba(0,60,136,.5)}.sidebar-left .sidebar-tabs{left:0}.sidebar-right .sidebar-tabs{right:0}.sidebar-tabs,.sidebar-tabs>ul{position:absolute;width:40px;margin:0;padding:0;list-style-type:none}.sidebar-tabs>li,.sidebar-tabs>ul>li{width:100%;height:40px;color:#fff;font-size:12pt;overflow:hidden;transition:80ms}.sidebar-tabs>li:hover,.sidebar-tabs>ul>li:hover{color:#fff;background-color:rgba(0,60,136,.6)}.sidebar-tabs>li.active,.sidebar-tabs>ul>li.active{color:#fff;background-color:#0074d9}.sidebar-tabs>li.disabled,.sidebar-tabs>ul>li.disabled{color:rgba(255,255,255,.4)}.sidebar-tabs>li.disabled:hover,.sidebar-tabs>ul>li.disabled:hover{background:0 0}.sidebar-tabs>li.disabled>a,.sidebar-tabs>ul>li.disabled>a{cursor:default}.sidebar-tabs>li>a,.sidebar-tabs>ul>li>a{display:block;width:100%;height:100%;line-height:40px;color:inherit;text-decoration:none;text-align:center}.sidebar-tabs>ul+ul{bottom:0}.sidebar-content{position:absolute;top:0;bottom:0;background-color:rgba(255,255,255,.95);overflow-x:hidden;overflow-y:auto}.sidebar-left .sidebar-content{left:40px;right:0}.sidebar-right .sidebar-content{left:0;right:40px}.sidebar.collapsed>.sidebar-content{overflow-y:hidden}.sidebar-pane{display:none;left:0;right:0;box-sizing:border-box;padding:10px 20px}.sidebar-pane.active{display:block}.sidebar-header{margin:-10px -20px 0;height:40px;padding:0 20px;line-height:40px;font-size:14.4pt;color:#fff;background-color:#0074d9}.sidebar-right .sidebar-header{padding-left:40px}.sidebar-close{position:absolute;top:0;width:40px;height:40px;text-align:center;cursor:pointer}.sidebar-left .sidebar-close{right:0}.sidebar-right .sidebar-close{left:0}.sidebar{background-color:rgba(255,255,255,.4)}.sidebar-tabs{overflow:hidden}.sidebar-left~.sidebar-map .olControlZoom,.sidebar-left~.sidebar-map .olScaleLine{margin-left:46px}.sidebar-right~.sidebar-map .olControlAttribution,.sidebar-right~.sidebar-map .olControlMousePosition,.sidebar-right~.sidebar-map .olControlPermalink{margin-right:46px}@media (min-width:768px) and (max-width:991px){.sidebar-left~.sidebar-map .olControlZoom,.sidebar-left~.sidebar-map .olScaleLine{margin-left:319px}.sidebar-right~.sidebar-map .olControlAttribution,.sidebar-right~.sidebar-map .olControlMousePosition,.sidebar-right~.sidebar-map .olControlPermalink{margin-right:319px}}@media (min-width:992px) and (max-width:1199px){.sidebar-pane{min-width:350px}.sidebar-left~.sidebar-map .olControlZoom,.sidebar-left~.sidebar-map .olScaleLine{margin-left:404px}.sidebar-right~.sidebar-map .olControlAttribution,.sidebar-right~.sidebar-map .olControlMousePosition,.sidebar-right~.sidebar-map .olControlPermalink{margin-right:404px}}@media (min-width:1200px){.sidebar-pane{min-width:420px}.sidebar-left~.sidebar-map .olControlZoom,.sidebar-left~.sidebar-map .olScaleLine{margin-left:474px}.sidebar-right~.sidebar-map .olControlAttribution,.sidebar-right~.sidebar-map .olControlMousePosition,.sidebar-right~.sidebar-map .olControlPermalink{margin-right:474px}}@media (min-width:768px){.sidebar-tabs{border-radius:4px 0 0 4px}.collapsed .sidebar-tabs{border-radius:4px}.sidebar-content{border-radius:0 4px 4px 0}.sidebar-left~.sidebar-map .olControlZoom,.sidebar-left~.sidebar-map .olScaleLine{transition:margin-left .5s}.sidebar-left.collapsed~.sidebar-map .olControlZoom,.sidebar-left.collapsed~.sidebar-map .olScaleLine{margin-left:54px}.sidebar-right~.sidebar-map .olControlAttribution,.sidebar-right~.sidebar-map .olControlMousePosition,.sidebar-right~.sidebar-map .olControlPermalink{transition:margin-right .5s}.sidebar-right.collapsed~.sidebar-map .olControlAttribution,.sidebar-right.collapsed~.sidebar-map .olControlMousePosition,.sidebar-right.collapsed~.sidebar-map .olControlPermalink{margin-right:54px}}
|
||||
@@ -1,212 +0,0 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 2000; }
|
||||
.sidebar.collapsed {
|
||||
width: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
top: 6px;
|
||||
bottom: 6px;
|
||||
transition: width 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar {
|
||||
width: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar {
|
||||
width: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 460px; } }
|
||||
|
||||
.sidebar-left {
|
||||
left: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
left: 6px; } }
|
||||
|
||||
.sidebar-right {
|
||||
right: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
right: 6px; } }
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 60, 136, 0.5); }
|
||||
.sidebar-left .sidebar-tabs {
|
||||
left: 0; }
|
||||
.sidebar-right .sidebar-tabs {
|
||||
right: 0; }
|
||||
.sidebar-tabs, .sidebar-tabs > ul {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
.sidebar-tabs > li, .sidebar-tabs > ul > li {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: #fff;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
transition: all 80ms; }
|
||||
.sidebar-tabs > li:hover, .sidebar-tabs > ul > li:hover {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 60, 136, 0.6); }
|
||||
.sidebar-tabs > li.active, .sidebar-tabs > ul > li.active {
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-tabs > li.disabled, .sidebar-tabs > ul > li.disabled {
|
||||
color: rgba(255, 255, 255, 0.4); }
|
||||
.sidebar-tabs > li.disabled:hover, .sidebar-tabs > ul > li.disabled:hover {
|
||||
background: transparent; }
|
||||
.sidebar-tabs > li.disabled > a, .sidebar-tabs > ul > li.disabled > a {
|
||||
cursor: default; }
|
||||
.sidebar-tabs > li > a, .sidebar-tabs > ul > li > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center; }
|
||||
.sidebar-tabs > ul + ul {
|
||||
bottom: 0; }
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; }
|
||||
.sidebar-left .sidebar-content {
|
||||
left: 40px;
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-content {
|
||||
left: 0;
|
||||
right: 40px; }
|
||||
.sidebar.collapsed > .sidebar-content {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px; }
|
||||
.sidebar-pane.active {
|
||||
display: block; }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-pane {
|
||||
min-width: 265px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-pane {
|
||||
min-width: 350px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-pane {
|
||||
min-width: 420px; } }
|
||||
|
||||
.sidebar-header {
|
||||
margin: -10px -20px 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
font-size: 14.4pt;
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-right .sidebar-header {
|
||||
padding-left: 40px; }
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer; }
|
||||
.sidebar-left .sidebar-close {
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-close {
|
||||
left: 0; }
|
||||
|
||||
.sidebar {
|
||||
background-color: rgba(255, 255, 255, 0.4); }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
border: 3px solid transparent;
|
||||
border-radius: 4px; } }
|
||||
|
||||
.sidebar-left {
|
||||
border-right: 3px solid transparent; }
|
||||
|
||||
.sidebar-right {
|
||||
border-left: 3px solid transparent; }
|
||||
|
||||
.sidebar-tabs {
|
||||
overflow: hidden; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-tabs {
|
||||
border-radius: 2px 0 0 2px; }
|
||||
.collapsed .sidebar-tabs {
|
||||
border-radius: 2px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-content {
|
||||
border-radius: 0 2px 2px 0; } }
|
||||
|
||||
.sidebar-left ~ .sidebar-map .ol-zoom, .sidebar-left ~ .sidebar-map .ol-scale-line {
|
||||
margin-left: 46px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map .ol-zoom, .sidebar-left ~ .sidebar-map .ol-scale-line {
|
||||
transition: margin-left 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-left ~ .sidebar-map .ol-zoom, .sidebar-left ~ .sidebar-map .ol-scale-line {
|
||||
margin-left: 317px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-left ~ .sidebar-map .ol-zoom, .sidebar-left ~ .sidebar-map .ol-scale-line {
|
||||
margin-left: 402px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-left ~ .sidebar-map .ol-zoom, .sidebar-left ~ .sidebar-map .ol-scale-line {
|
||||
margin-left: 472px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left.collapsed ~ .sidebar-map .ol-zoom, .sidebar-left.collapsed ~ .sidebar-map .ol-scale-line {
|
||||
margin-left: 52px; } }
|
||||
|
||||
.sidebar-right ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right ~ .sidebar-map .ol-full-screen {
|
||||
margin-right: 46px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right ~ .sidebar-map .ol-full-screen {
|
||||
transition: margin-right 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-right ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right ~ .sidebar-map .ol-full-screen {
|
||||
margin-right: 317px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-right ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right ~ .sidebar-map .ol-full-screen {
|
||||
margin-right: 402px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-right ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right ~ .sidebar-map .ol-full-screen {
|
||||
margin-right: 472px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right.collapsed ~ .sidebar-map .ol-rotate,
|
||||
.sidebar-right.collapsed ~ .sidebar-map .ol-attribution,
|
||||
.sidebar-right.collapsed ~ .sidebar-map .ol-full-screen {
|
||||
margin-right: 52px; } }
|
||||
@@ -1 +0,0 @@
|
||||
.sidebar{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;z-index:2000}.sidebar.collapsed{width:40px}@media (min-width:768px) and (max-width:991px){.sidebar{width:305px}.sidebar-pane{min-width:265px}}@media (min-width:992px) and (max-width:1199px){.sidebar{width:390px}}@media (min-width:1200px){.sidebar{width:460px}}.sidebar-left{left:0;border-right:3px solid transparent}.sidebar-right{right:0;border-left:3px solid transparent}@media (min-width:768px){.sidebar{top:6px;bottom:6px;transition:width .5s;border:3px solid transparent;border-radius:4px}.sidebar-left{left:6px}.sidebar-right{right:6px}}.sidebar-tabs{top:0;bottom:0;height:100%;background-color:rgba(0,60,136,.5)}.sidebar-left .sidebar-tabs{left:0}.sidebar-right .sidebar-tabs{right:0}.sidebar-tabs,.sidebar-tabs>ul{position:absolute;width:40px;margin:0;padding:0;list-style-type:none}.sidebar-tabs>li,.sidebar-tabs>ul>li{width:100%;height:40px;color:#fff;font-size:12pt;overflow:hidden;transition:80ms}.sidebar-tabs>li:hover,.sidebar-tabs>ul>li:hover{color:#fff;background-color:rgba(0,60,136,.6)}.sidebar-tabs>li.active,.sidebar-tabs>ul>li.active{color:#fff;background-color:#0074d9}.sidebar-tabs>li.disabled,.sidebar-tabs>ul>li.disabled{color:rgba(255,255,255,.4)}.sidebar-tabs>li.disabled:hover,.sidebar-tabs>ul>li.disabled:hover{background:0 0}.sidebar-tabs>li.disabled>a,.sidebar-tabs>ul>li.disabled>a{cursor:default}.sidebar-tabs>li>a,.sidebar-tabs>ul>li>a{display:block;width:100%;height:100%;line-height:40px;color:inherit;text-decoration:none;text-align:center}.sidebar-tabs>ul+ul{bottom:0}.sidebar-content{position:absolute;top:0;bottom:0;background-color:rgba(255,255,255,.95);overflow-x:hidden;overflow-y:auto}.sidebar-left .sidebar-content{left:40px;right:0}.sidebar-right .sidebar-content{left:0;right:40px}.sidebar.collapsed>.sidebar-content{overflow-y:hidden}.sidebar-pane{display:none;left:0;right:0;box-sizing:border-box;padding:10px 20px}.sidebar-pane.active{display:block}.sidebar-header{margin:-10px -20px 0;height:40px;padding:0 20px;line-height:40px;font-size:14.4pt;color:#fff;background-color:#0074d9}.sidebar-right .sidebar-header{padding-left:40px}.sidebar-close{position:absolute;top:0;width:40px;height:40px;text-align:center;cursor:pointer}.sidebar-left .sidebar-close{right:0}.sidebar-right .sidebar-close{left:0}.sidebar{background-color:rgba(255,255,255,.4)}.sidebar-tabs{overflow:hidden}.sidebar-left~.sidebar-map .ol-scale-line,.sidebar-left~.sidebar-map .ol-zoom{margin-left:46px}.sidebar-right~.sidebar-map .ol-attribution,.sidebar-right~.sidebar-map .ol-full-screen,.sidebar-right~.sidebar-map .ol-rotate{margin-right:46px}@media (min-width:768px) and (max-width:991px){.sidebar-left~.sidebar-map .ol-scale-line,.sidebar-left~.sidebar-map .ol-zoom{margin-left:317px}.sidebar-right~.sidebar-map .ol-attribution,.sidebar-right~.sidebar-map .ol-full-screen,.sidebar-right~.sidebar-map .ol-rotate{margin-right:317px}}@media (min-width:992px) and (max-width:1199px){.sidebar-pane{min-width:350px}.sidebar-left~.sidebar-map .ol-scale-line,.sidebar-left~.sidebar-map .ol-zoom{margin-left:402px}.sidebar-right~.sidebar-map .ol-attribution,.sidebar-right~.sidebar-map .ol-full-screen,.sidebar-right~.sidebar-map .ol-rotate{margin-right:402px}}@media (min-width:1200px){.sidebar-pane{min-width:420px}.sidebar-left~.sidebar-map .ol-scale-line,.sidebar-left~.sidebar-map .ol-zoom{margin-left:472px}.sidebar-right~.sidebar-map .ol-attribution,.sidebar-right~.sidebar-map .ol-full-screen,.sidebar-right~.sidebar-map .ol-rotate{margin-right:472px}}@media (min-width:768px){.sidebar-tabs{border-radius:2px 0 0 2px}.collapsed .sidebar-tabs{border-radius:2px}.sidebar-content{border-radius:0 2px 2px 0}.sidebar-left~.sidebar-map .ol-scale-line,.sidebar-left~.sidebar-map .ol-zoom{transition:margin-left .5s}.sidebar-left.collapsed~.sidebar-map .ol-scale-line,.sidebar-left.collapsed~.sidebar-map .ol-zoom{margin-left:52px}.sidebar-right~.sidebar-map .ol-attribution,.sidebar-right~.sidebar-map .ol-full-screen,.sidebar-right~.sidebar-map .ol-rotate{transition:margin-right .5s}.sidebar-right.collapsed~.sidebar-map .ol-attribution,.sidebar-right.collapsed~.sidebar-map .ol-full-screen,.sidebar-right.collapsed~.sidebar-map .ol-rotate{margin-right:52px}}
|
||||
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 559 KiB |
@@ -1,202 +0,0 @@
|
||||
# sidebar-v2 Documentation
|
||||
|
||||
## Installation
|
||||
|
||||
### NPM
|
||||
|
||||
```
|
||||
npm install sidebar-v2 --save
|
||||
```
|
||||
|
||||
### CDN hosted
|
||||
|
||||
OpenLayers 3+
|
||||
|
||||
```HTML
|
||||
<!-- inside the <head> element -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Turbo87/sidebar-v2@v0.4.0/css/ol3-sidebar.css">
|
||||
<!-- at the end of the <body> element -->
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/Turbo87/sidebar-v2@v0.4.0/js/ol3-sidebar.js"></script>
|
||||
```
|
||||
|
||||
### Self hosted
|
||||
|
||||
Download the [latest release](https://github.com/Turbo87/sidebar-v2/releases/latest),
|
||||
unpack the downloaded file, and load the CSS and JavaScript into your
|
||||
document, for instance (OpenLayers 3+):
|
||||
|
||||
```HTML
|
||||
<!-- inside the <head> element -->
|
||||
<link rel="stylesheet" href="sidebar-v2/css/ol3-sidebar.css">
|
||||
<!-- at the end of the <body> element -->
|
||||
<script type="text/javascript" src="sidebar-v2/js/ol3-sidebar.js"></script>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
In your HTML ensure that you have loaded the
|
||||
[OpenLayers](https://openlayers.org/) and `sidebar-v2` CSS. In the example
|
||||
code below we also use [FontAwesome](https://fontawesome.com/) so that nice
|
||||
symbols can be used for the sidebar's buttons.
|
||||
|
||||
```HTML
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v5.3.2/css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="sidebar-v2/css/ol3-sidebar.css">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
```
|
||||
|
||||
Then create the `div` element within the HTML `body` for the map similarly
|
||||
to how one would for plain OpenLayers maps. However note that you need to
|
||||
use `class="sidebar-map"` instead of `class="map"` and the map `div` needs
|
||||
to *follow* the `div` for the sidebar:
|
||||
|
||||
```HTML
|
||||
<!-- follows sidebar div -->
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
```
|
||||
|
||||
Now define the sidebar (by default in a collapsed state) via the `sidebar`
|
||||
and `collapsed` classes:
|
||||
|
||||
```HTML
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
</div>
|
||||
```
|
||||
|
||||
Each sidebar element consists of a navigation tab connected to a tab pane
|
||||
containing the content of the sidebar element.
|
||||
|
||||
The navigation tabs are a simple unordered list of anchors linking to the
|
||||
respective tab pane:
|
||||
|
||||
```HTML
|
||||
<!-- navigation tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
The content of a given tab is contained in a sidebar tab pane (note the `id`
|
||||
attribute pointing back to the relevant navigation tab). A pane includes a
|
||||
header (via the `sidebar-header` class), which contains the `span` element
|
||||
needed to close the pane, and then simple HTML text, for instance `p`
|
||||
elements:
|
||||
|
||||
```HTML
|
||||
<!-- tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
Pane header text
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>Pane text</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Now that the HTML has been set up, we can add the sidebar to the OpenLayers
|
||||
map within JavaScript by adding a `script` element at the end of the `body`.
|
||||
|
||||
Don't forget to load the OpenLayers and sidebar-v2 JavaScript:
|
||||
|
||||
```HTML
|
||||
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v5.3.2/build/ol.js"></script>
|
||||
<script src="sidebar-v2/js/ol3-sidebar.js"></script>
|
||||
```
|
||||
|
||||
Then set up the OpenLayers map, in this case using an
|
||||
[OpenStreetMap](https://www.openstreetmap.org/) source:
|
||||
|
||||
```HTML
|
||||
<script>
|
||||
var map = new ol.Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
})
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.transform([7, 51.2], 'EPSG:4326', 'EPSG:3857'),
|
||||
zoom: 4
|
||||
})
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
To add the sidebar, simply create a new `Sidebar` object which links to the
|
||||
sidebar `div` created above, and then add it as a new control to the map:
|
||||
|
||||
```javascript
|
||||
var sidebar = new ol.control.Sidebar({ element: 'sidebar', position: 'left' });
|
||||
map.addControl(sidebar);
|
||||
```
|
||||
|
||||
Putting it all together we get:
|
||||
|
||||
```HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 usage example</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="sidebar-v2/css/ol3-sidebar.css">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- navigation tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
Pane header text
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>Pane text</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<script src="https://openlayers.org/en/v4.6.5/build/ol.js" type="text/javascript"></script>
|
||||
<script src="sidebar-v2/js/ol3-sidebar.js" type="text/javascript"></script>
|
||||
<script>
|
||||
var map = new ol.Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
})
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.transform([7, 51.2], 'EPSG:4326', 'EPSG:3857'),
|
||||
zoom: 4
|
||||
})
|
||||
});
|
||||
|
||||
var sidebar = new ol.control.Sidebar({ element: 'sidebar', position: 'left' });
|
||||
map.addControl(sidebar);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
For a more complete examples, have a look at the files in the `examples/`
|
||||
directory of the distribution.
|
||||
@@ -1,99 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 example</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="../css/gmaps-sidebar.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.lorem {
|
||||
font-style: italic;
|
||||
color: #AAA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="initialize()">
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- Nav tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
<li><a href="#profile" role="tab"><i class="fa fa-user"></i></a></li>
|
||||
<li class="disabled"><a href="#messages" role="tab"><i class="fa fa-envelope"></i></a></li>
|
||||
<li><a href="https://github.com/Turbo87/sidebar-v2" role="tab" target="_blank"><i class="fa fa-github"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
sidebar-v2
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>A responsive sidebar for mapping libraries like <a href="http://leafletjs.com/">Leaflet</a> or <a href="http://openlayers.org/">OpenLayers</a>.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="profile">
|
||||
<h1 class="sidebar-header">Profile<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="messages">
|
||||
<h1 class="sidebar-header">Messages<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="settings">
|
||||
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<a href="https://github.com/Turbo87/sidebar-v2/"><img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
||||
<script src="https://maps.googleapis.com/maps/api/js" type="text/javascript"></script>
|
||||
<script src="../js/jquery-sidebar.js"></script>
|
||||
|
||||
<script>
|
||||
function initialize() {
|
||||
var map = new google.maps.Map(document.getElementById("map"), {
|
||||
center: new google.maps.LatLng(51.2, 7),
|
||||
zoom: 5,
|
||||
mapTypeId: google.maps.MapTypeId.TERRAIN,
|
||||
});
|
||||
}
|
||||
|
||||
var sidebar = $('#sidebar').sidebar();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 example</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="https://cdn.leafletjs.com/leaflet-0.7.2/leaflet.ie.css" /><![endif]-->
|
||||
|
||||
<link rel="stylesheet" href="../css/leaflet-sidebar.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.lorem {
|
||||
font-style: italic;
|
||||
color: #AAA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- Nav tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
<li><a href="#profile" role="tab"><i class="fa fa-user"></i></a></li>
|
||||
<li class="disabled"><a href="#messages" role="tab"><i class="fa fa-envelope"></i></a></li>
|
||||
<li><a href="https://github.com/Turbo87/sidebar-v2" role="tab" target="_blank"><i class="fa fa-github"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
sidebar-v2
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>A responsive sidebar for mapping libraries like <a href="http://leafletjs.com/">Leaflet</a> or <a href="http://openlayers.org/">OpenLayers</a>.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="profile">
|
||||
<h1 class="sidebar-header">Profile<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="messages">
|
||||
<h1 class="sidebar-header">Messages<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="settings">
|
||||
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<a href="https://github.com/Turbo87/sidebar-v2/"><img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
|
||||
<script src="../js/leaflet-sidebar.js"></script>
|
||||
|
||||
<script>
|
||||
var map = L.map('map');
|
||||
map.setView([51.2, 7], 9);
|
||||
|
||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
var marker = L.marker([51.2, 7]).addTo(map);
|
||||
|
||||
var sidebar = L.control.sidebar('sidebar').addTo(map);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,103 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 example</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="../css/ol2-sidebar.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.lorem {
|
||||
font-style: italic;
|
||||
color: #AAA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- Nav tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
<li><a href="#profile" role="tab"><i class="fa fa-user"></i></a></li>
|
||||
<li class="disabled"><a href="#messages" role="tab"><i class="fa fa-envelope"></i></a></li>
|
||||
<li><a href="https://github.com/Turbo87/sidebar-v2" role="tab" target="_blank"><i class="fa fa-github"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
sidebar-v2
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>A responsive sidebar for mapping libraries like <a href="http://leafletjs.com/">Leaflet</a> or <a href="http://openlayers.org/">OpenLayers</a>.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="profile">
|
||||
<h1 class="sidebar-header">Profile<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="messages">
|
||||
<h1 class="sidebar-header">Messages<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="settings">
|
||||
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<a href="https://github.com/Turbo87/sidebar-v2/"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 5000" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
||||
<script src="https://openlayers.org/api/OpenLayers.js" type="text/javascript"></script>
|
||||
<script src="../js/jquery-sidebar.js"></script>
|
||||
|
||||
<script>
|
||||
var map = new OpenLayers.Map('map');
|
||||
|
||||
var wms = new OpenLayers.Layer.WMS(
|
||||
"OpenLayers WMS",
|
||||
"http://vmap0.tiles.osgeo.org/wms/vmap0", {
|
||||
layers: 'basic'
|
||||
});
|
||||
|
||||
map.addLayer(wms);
|
||||
map.setCenter([7, 51.2], 6);
|
||||
|
||||
var sidebar = $('#sidebar').sidebar();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,107 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 example</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/css/ol.css" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="../css/ol3-sidebar.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.lorem {
|
||||
font-style: italic;
|
||||
color: #AAA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- Nav tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
<li><a href="#profile" role="tab"><i class="fa fa-user"></i></a></li>
|
||||
<li class="disabled"><a href="#messages" role="tab"><i class="fa fa-envelope"></i></a></li>
|
||||
<li><a href="https://github.com/Turbo87/sidebar-v2" role="tab" target="_blank"><i class="fa fa-github"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
sidebar-v2
|
||||
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>A responsive sidebar for mapping libraries like <a href="http://leafletjs.com/">Leaflet</a> or <a href="http://openlayers.org/">OpenLayers</a>.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="profile">
|
||||
<h1 class="sidebar-header">Profile<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="messages">
|
||||
<h1 class="sidebar-header">Messages<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="settings">
|
||||
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<a href="https://github.com/Turbo87/sidebar-v2/"><img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/build/ol.js"></script>
|
||||
<script src="../js/ol3-sidebar.js"></script>
|
||||
|
||||
<script>
|
||||
var map = new ol.Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
})
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.transform([7, 51.2], 'EPSG:4326', 'EPSG:3857'),
|
||||
zoom: 4
|
||||
})
|
||||
});
|
||||
|
||||
var sidebar = new ol.control.Sidebar({ element: 'sidebar', position: 'left' });
|
||||
|
||||
map.addControl(sidebar);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>sidebar-v2 example</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="https://cdn.leafletjs.com/leaflet-0.7.2/leaflet.ie.css" /><![endif]-->
|
||||
|
||||
<link rel="stylesheet" href="../css/leaflet-sidebar.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.lorem {
|
||||
font-style: italic;
|
||||
color: #AAA;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar" class="sidebar collapsed">
|
||||
<!-- Nav tabs -->
|
||||
<div class="sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
||||
<li><a href="#profile" role="tab"><i class="fa fa-user"></i></a></li>
|
||||
<li class="disabled"><a href="#messages" role="tab"><i class="fa fa-envelope"></i></a></li>
|
||||
<li><a href="https://github.com/Turbo87/sidebar-v2" role="tab" target="_blank"><i class="fa fa-github"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-pane" id="home">
|
||||
<h1 class="sidebar-header">
|
||||
sidebar-v2
|
||||
<span class="sidebar-close"><i class="fa fa-caret-right"></i></span>
|
||||
</h1>
|
||||
|
||||
<p>A responsive sidebar for mapping libraries like <a href="http://leafletjs.com/">Leaflet</a> or <a href="http://openlayers.org/">OpenLayers</a>.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
|
||||
<p class="lorem">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="profile">
|
||||
<h1 class="sidebar-header">Profile<span class="sidebar-close"><i class="fa fa-caret-right"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="messages">
|
||||
<h1 class="sidebar-header">Messages<span class="sidebar-close"><i class="fa fa-caret-right"></i></span></h1>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-pane" id="settings">
|
||||
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-right"></i></span></h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="sidebar-map"></div>
|
||||
|
||||
<a href="https://github.com/Turbo87/sidebar-v2/"><img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
|
||||
<script src="../js/leaflet-sidebar.js"></script>
|
||||
|
||||
<script>
|
||||
var map = L.map('map');
|
||||
map.setView([51.2, 7], 9);
|
||||
|
||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
var marker = L.marker([51.2, 7]).addTo(map);
|
||||
|
||||
var sidebar = L.control.sidebar('sidebar', {position: 'right'}).addTo(map);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
var cleanCSS = require('gulp-clean-css');
|
||||
var sass = require('gulp-sass');
|
||||
var rename = require('gulp-rename');
|
||||
var uglify = require('gulp-uglify');
|
||||
|
||||
// SASS compilation
|
||||
gulp.task('sass', function() {
|
||||
return gulp.src('scss/*sidebar.scss')
|
||||
.pipe(sass())
|
||||
.on('error', sass.logError)
|
||||
.pipe(gulp.dest('css'));
|
||||
});
|
||||
|
||||
// Minify JS + CSS
|
||||
gulp.task('minify:js', function() {
|
||||
return gulp.src('js/*sidebar.js')
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest('js'));
|
||||
});
|
||||
|
||||
gulp.task('minify:css', gulp.series('sass', function() {
|
||||
return gulp.src('css/*sidebar.css')
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(cleanCSS({level: 2}))
|
||||
.pipe(gulp.dest('css'));
|
||||
}));
|
||||
|
||||
gulp.task('minify', gulp.parallel('minify:js', 'minify:css'));
|
||||
|
||||
// Watch JS + CSS Files
|
||||
gulp.task('watch', gulp.series('minify', function() {
|
||||
gulp.watch('js/*.js', ['minify:js']);
|
||||
gulp.watch('scss/*.scss', ['minify:css']);
|
||||
}));
|
||||
|
||||
// Default
|
||||
gulp.task('default', gulp.series('minify'));
|
||||
@@ -1,84 +0,0 @@
|
||||
/* global $ */
|
||||
|
||||
/**
|
||||
* Create a new sidebar on this jQuery object.
|
||||
*
|
||||
* @example
|
||||
* var sidebar = $('#sidebar').sidebar();
|
||||
*
|
||||
* @param {Object} [options] - Optional options object
|
||||
* @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right'
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
$.fn.sidebar = function(options) {
|
||||
var $sidebar = this;
|
||||
var $tabs = $sidebar.find('ul.sidebar-tabs, .sidebar-tabs > ul');
|
||||
var $container = $sidebar.children('.sidebar-content').first();
|
||||
|
||||
options = $.extend({
|
||||
position: 'left'
|
||||
}, options || {});
|
||||
|
||||
$sidebar.addClass('sidebar-' + options.position);
|
||||
|
||||
$tabs.children('li').children('a[href^="#"]').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var $tab = $(this).closest('li');
|
||||
|
||||
if ($tab.hasClass('active'))
|
||||
$sidebar.close();
|
||||
else if (!$tab.hasClass('disabled'))
|
||||
$sidebar.open(this.hash.slice(1), $tab);
|
||||
});
|
||||
|
||||
$sidebar.find('.sidebar-close').on('click', function() {
|
||||
$sidebar.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Open sidebar (if necessary) and show the specified tab.
|
||||
*
|
||||
* @param {string} id - The id of the tab to show (without the # character)
|
||||
* @param {jQuery} [$tab] - The jQuery object representing the tab node (used internally for efficiency)
|
||||
*/
|
||||
$sidebar.open = function(id, $tab) {
|
||||
if (typeof $tab === 'undefined')
|
||||
$tab = $tabs.find('li > a[href="#' + id + '"]').parent();
|
||||
|
||||
// hide old active contents
|
||||
$container.children('.sidebar-pane.active').removeClass('active');
|
||||
|
||||
// show new content
|
||||
$container.children('#' + id).addClass('active');
|
||||
|
||||
// remove old active highlights
|
||||
$tabs.children('li.active').removeClass('active');
|
||||
|
||||
// set new highlight
|
||||
$tab.addClass('active');
|
||||
|
||||
$sidebar.trigger('content', { 'id': id });
|
||||
|
||||
if ($sidebar.hasClass('collapsed')) {
|
||||
// open sidebar
|
||||
$sidebar.trigger('opening');
|
||||
$sidebar.removeClass('collapsed');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the sidebar (if necessary).
|
||||
*/
|
||||
$sidebar.close = function() {
|
||||
// remove old active highlights
|
||||
$tabs.children('li.active').removeClass('active');
|
||||
|
||||
if (!$sidebar.hasClass('collapsed')) {
|
||||
// close sidebar
|
||||
$sidebar.trigger('closing');
|
||||
$sidebar.addClass('collapsed');
|
||||
}
|
||||
};
|
||||
|
||||
return $sidebar;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
$.fn.sidebar=function(e){var s=this,i=s.find("ul.sidebar-tabs, .sidebar-tabs > ul"),a=s.children(".sidebar-content").first();return e=$.extend({position:"left"},e||{}),s.addClass("sidebar-"+e.position),i.children("li").children('a[href^="#"]').on("click",function(e){e.preventDefault();var i=$(this).closest("li");i.hasClass("active")?s.close():i.hasClass("disabled")||s.open(this.hash.slice(1),i)}),s.find(".sidebar-close").on("click",function(){s.close()}),s.open=function(e,l){void 0===l&&(l=i.find('li > a[href="#'+e+'"]').parent()),a.children(".sidebar-pane.active").removeClass("active"),a.children("#"+e).addClass("active"),i.children("li.active").removeClass("active"),l.addClass("active"),s.trigger("content",{id:e}),s.hasClass("collapsed")&&(s.trigger("opening"),s.removeClass("collapsed"))},s.close=function(){i.children("li.active").removeClass("active"),s.hasClass("collapsed")||(s.trigger("closing"),s.addClass("collapsed"))},s};
|
||||
@@ -1,216 +0,0 @@
|
||||
/* global L */
|
||||
|
||||
/**
|
||||
* @name Sidebar
|
||||
* @class L.Control.Sidebar
|
||||
* @extends L.Control
|
||||
* @param {string} id - The id of the sidebar element (without the # character)
|
||||
* @param {Object} [options] - Optional options object
|
||||
* @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right'
|
||||
* @see L.control.sidebar
|
||||
*/
|
||||
L.Control.Sidebar = L.Control.extend(/** @lends L.Control.Sidebar.prototype */ {
|
||||
includes: (L.Evented.prototype || L.Mixin.Events),
|
||||
|
||||
options: {
|
||||
position: 'left'
|
||||
},
|
||||
|
||||
initialize: function (id, options) {
|
||||
var i, child;
|
||||
|
||||
L.setOptions(this, options);
|
||||
|
||||
// Find sidebar HTMLElement
|
||||
this._sidebar = L.DomUtil.get(id);
|
||||
|
||||
// Attach .sidebar-left/right class
|
||||
L.DomUtil.addClass(this._sidebar, 'sidebar-' + this.options.position);
|
||||
|
||||
// Attach touch styling if necessary
|
||||
if (L.Browser.touch)
|
||||
L.DomUtil.addClass(this._sidebar, 'leaflet-touch');
|
||||
|
||||
// Find sidebar > div.sidebar-content
|
||||
for (i = this._sidebar.children.length - 1; i >= 0; i--) {
|
||||
child = this._sidebar.children[i];
|
||||
if (child.tagName == 'DIV' &&
|
||||
L.DomUtil.hasClass(child, 'sidebar-content'))
|
||||
this._container = child;
|
||||
}
|
||||
|
||||
// Find sidebar ul.sidebar-tabs > li, sidebar .sidebar-tabs > ul > li
|
||||
this._tabitems = this._sidebar.querySelectorAll('ul.sidebar-tabs > li, .sidebar-tabs > ul > li');
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
this._tabitems[i]._sidebar = this;
|
||||
}
|
||||
|
||||
// Find sidebar > div.sidebar-content > div.sidebar-pane
|
||||
this._panes = [];
|
||||
this._closeButtons = [];
|
||||
for (i = this._container.children.length - 1; i >= 0; i--) {
|
||||
child = this._container.children[i];
|
||||
if (child.tagName == 'DIV' &&
|
||||
L.DomUtil.hasClass(child, 'sidebar-pane')) {
|
||||
this._panes.push(child);
|
||||
|
||||
var closeButtons = child.querySelectorAll('.sidebar-close');
|
||||
for (var j = 0, len = closeButtons.length; j < len; j++)
|
||||
this._closeButtons.push(closeButtons[j]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this sidebar to the specified map.
|
||||
*
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
addTo: function (map) {
|
||||
var i, child;
|
||||
|
||||
this._map = map;
|
||||
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
var sub = child.querySelector('a');
|
||||
if (sub.hasAttribute('href') && sub.getAttribute('href').slice(0,1) == '#') {
|
||||
L.DomEvent
|
||||
.on(sub, 'click', L.DomEvent.preventDefault )
|
||||
.on(sub, 'click', this._onClick, child);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = this._closeButtons.length - 1; i >= 0; i--) {
|
||||
child = this._closeButtons[i];
|
||||
L.DomEvent.on(child, 'click', this._onCloseClick, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated - Please use remove() instead of removeFrom(), as of Leaflet 0.8-dev, the removeFrom() has been replaced with remove()
|
||||
* Removes this sidebar from the map.
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
removeFrom: function(map) {
|
||||
console.log('removeFrom() has been deprecated, please use remove() instead as support for this function will be ending soon.');
|
||||
this.remove(map);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove this sidebar from the map.
|
||||
*
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
remove: function (map) {
|
||||
var i, child;
|
||||
|
||||
this._map = null;
|
||||
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
L.DomEvent.off(child.querySelector('a'), 'click', this._onClick);
|
||||
}
|
||||
|
||||
for (i = this._closeButtons.length - 1; i >= 0; i--) {
|
||||
child = this._closeButtons[i];
|
||||
L.DomEvent.off(child, 'click', this._onCloseClick, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open sidebar (if necessary) and show the specified tab.
|
||||
*
|
||||
* @param {string} id - The id of the tab to show (without the # character)
|
||||
*/
|
||||
open: function(id) {
|
||||
var i, child;
|
||||
|
||||
// hide old active contents and show new content
|
||||
for (i = this._panes.length - 1; i >= 0; i--) {
|
||||
child = this._panes[i];
|
||||
if (child.id == id)
|
||||
L.DomUtil.addClass(child, 'active');
|
||||
else if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
// remove old active highlights and set new highlight
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
if (child.querySelector('a').hash == '#' + id)
|
||||
L.DomUtil.addClass(child, 'active');
|
||||
else if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
this.fire('content', { id: id });
|
||||
|
||||
// open sidebar (if necessary)
|
||||
if (L.DomUtil.hasClass(this._sidebar, 'collapsed')) {
|
||||
this.fire('opening');
|
||||
L.DomUtil.removeClass(this._sidebar, 'collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the sidebar (if necessary).
|
||||
*/
|
||||
close: function() {
|
||||
// remove old active highlights
|
||||
for (var i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
var child = this._tabitems[i];
|
||||
if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
// close sidebar
|
||||
if (!L.DomUtil.hasClass(this._sidebar, 'collapsed')) {
|
||||
this.fire('closing');
|
||||
L.DomUtil.addClass(this._sidebar, 'collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClick: function() {
|
||||
if (L.DomUtil.hasClass(this, 'active'))
|
||||
this._sidebar.close();
|
||||
else if (!L.DomUtil.hasClass(this, 'disabled'))
|
||||
this._sidebar.open(this.querySelector('a').hash.slice(1));
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onCloseClick: function () {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new sidebar.
|
||||
*
|
||||
* @example
|
||||
* var sidebar = L.control.sidebar('sidebar').addTo(map);
|
||||
*
|
||||
* @param {string} id - The id of the sidebar element (without the # character)
|
||||
* @param {Object} [options] - Optional options object
|
||||
* @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right'
|
||||
* @returns {Sidebar} A new sidebar instance
|
||||
*/
|
||||
L.control.sidebar = function (id, options) {
|
||||
return new L.Control.Sidebar(id, options);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
L.Control.Sidebar=L.Control.extend({includes:L.Evented.prototype||L.Mixin.Events,options:{position:"left"},initialize:function(t,s){var i,e;for(L.setOptions(this,s),this._sidebar=L.DomUtil.get(t),L.DomUtil.addClass(this._sidebar,"sidebar-"+this.options.position),L.Browser.touch&&L.DomUtil.addClass(this._sidebar,"leaflet-touch"),i=this._sidebar.children.length-1;i>=0;i--)"DIV"==(e=this._sidebar.children[i]).tagName&&L.DomUtil.hasClass(e,"sidebar-content")&&(this._container=e);for(this._tabitems=this._sidebar.querySelectorAll("ul.sidebar-tabs > li, .sidebar-tabs > ul > li"),i=this._tabitems.length-1;i>=0;i--)this._tabitems[i]._sidebar=this;for(this._panes=[],this._closeButtons=[],i=this._container.children.length-1;i>=0;i--)if("DIV"==(e=this._container.children[i]).tagName&&L.DomUtil.hasClass(e,"sidebar-pane")){this._panes.push(e);for(var o=e.querySelectorAll(".sidebar-close"),a=0,l=o.length;a<l;a++)this._closeButtons.push(o[a])}},addTo:function(t){var s,i;for(this._map=t,s=this._tabitems.length-1;s>=0;s--){var e=(i=this._tabitems[s]).querySelector("a");e.hasAttribute("href")&&"#"==e.getAttribute("href").slice(0,1)&&L.DomEvent.on(e,"click",L.DomEvent.preventDefault).on(e,"click",this._onClick,i)}for(s=this._closeButtons.length-1;s>=0;s--)i=this._closeButtons[s],L.DomEvent.on(i,"click",this._onCloseClick,this);return this},removeFrom:function(t){console.log("removeFrom() has been deprecated, please use remove() instead as support for this function will be ending soon."),this.remove(t)},remove:function(t){var s,i;for(this._map=null,s=this._tabitems.length-1;s>=0;s--)i=this._tabitems[s],L.DomEvent.off(i.querySelector("a"),"click",this._onClick);for(s=this._closeButtons.length-1;s>=0;s--)i=this._closeButtons[s],L.DomEvent.off(i,"click",this._onCloseClick,this);return this},open:function(t){var s,i;for(s=this._panes.length-1;s>=0;s--)(i=this._panes[s]).id==t?L.DomUtil.addClass(i,"active"):L.DomUtil.hasClass(i,"active")&&L.DomUtil.removeClass(i,"active");for(s=this._tabitems.length-1;s>=0;s--)(i=this._tabitems[s]).querySelector("a").hash=="#"+t?L.DomUtil.addClass(i,"active"):L.DomUtil.hasClass(i,"active")&&L.DomUtil.removeClass(i,"active");return this.fire("content",{id:t}),L.DomUtil.hasClass(this._sidebar,"collapsed")&&(this.fire("opening"),L.DomUtil.removeClass(this._sidebar,"collapsed")),this},close:function(){for(var t=this._tabitems.length-1;t>=0;t--){var s=this._tabitems[t];L.DomUtil.hasClass(s,"active")&&L.DomUtil.removeClass(s,"active")}return L.DomUtil.hasClass(this._sidebar,"collapsed")||(this.fire("closing"),L.DomUtil.addClass(this._sidebar,"collapsed")),this},_onClick:function(){L.DomUtil.hasClass(this,"active")?this._sidebar.close():L.DomUtil.hasClass(this,"disabled")||this._sidebar.open(this.querySelector("a").hash.slice(1))},_onCloseClick:function(){this.close()}}),L.control.sidebar=function(t,s){return new L.Control.Sidebar(t,s)};
|
||||
@@ -1,132 +0,0 @@
|
||||
/* global ol */
|
||||
|
||||
ol.control.Sidebar = function (settings) {
|
||||
|
||||
var defaults = {
|
||||
element: null,
|
||||
position: 'left'
|
||||
}, i, child;
|
||||
|
||||
this._options = Object.assign({}, defaults, settings);
|
||||
|
||||
ol.control.Control.call(this, {
|
||||
element: document.getElementById(this._options.element),
|
||||
target: this._options.target
|
||||
});
|
||||
|
||||
// Attach .sidebar-left/right class
|
||||
this.element.classList.add('sidebar-' + this._options.position);
|
||||
|
||||
// Find sidebar > div.sidebar-content
|
||||
for (i = this.element.children.length - 1; i >= 0; i--) {
|
||||
child = this.element.children[i];
|
||||
if (child.tagName === 'DIV' &&
|
||||
child.classList.contains('sidebar-content')) {
|
||||
this._container = child;
|
||||
}
|
||||
}
|
||||
|
||||
// Find sidebar ul.sidebar-tabs > li, sidebar .sidebar-tabs > ul > li
|
||||
this._tabitems = this.element.querySelectorAll('ul.sidebar-tabs > li, .sidebar-tabs > ul > li');
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
this._tabitems[i]._sidebar = this;
|
||||
}
|
||||
|
||||
// Find sidebar > div.sidebar-content > div.sidebar-pane
|
||||
this._panes = [];
|
||||
this._closeButtons = [];
|
||||
for (i = this._container.children.length - 1; i >= 0; i--) {
|
||||
child = this._container.children[i];
|
||||
if (child.tagName == 'DIV' &&
|
||||
child.classList.contains('sidebar-pane')) {
|
||||
this._panes.push(child);
|
||||
|
||||
var closeButtons = child.querySelectorAll('.sidebar-close');
|
||||
for (var j = 0, len = closeButtons.length; j < len; j++) {
|
||||
this._closeButtons.push(closeButtons[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ('inherits' in ol) {
|
||||
ol.inherits(ol.control.Sidebar, ol.control.Control);
|
||||
} else {
|
||||
ol.control.Sidebar.prototype = Object.create(ol.control.Control.prototype);
|
||||
ol.control.Sidebar.prototype.constructor = ol.control.Sidebar;
|
||||
}
|
||||
|
||||
ol.control.Sidebar.prototype.setMap = function(map) {
|
||||
var i, child;
|
||||
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
var sub = child.querySelector('a');
|
||||
if (sub.hasAttribute('href') && sub.getAttribute('href').slice(0,1) == '#') {
|
||||
sub.onclick = this._onClick.bind(child);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = this._closeButtons.length - 1; i >= 0; i--) {
|
||||
child = this._closeButtons[i];
|
||||
child.onclick = this._onCloseClick.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
ol.control.Sidebar.prototype.open = function(id) {
|
||||
var i, child;
|
||||
|
||||
// hide old active contents and show new content
|
||||
for (i = this._panes.length - 1; i >= 0; i--) {
|
||||
child = this._panes[i];
|
||||
if (child.id == id)
|
||||
child.classList.add('active');
|
||||
else if (child.classList.contains('active'))
|
||||
child.classList.remove('active');
|
||||
}
|
||||
|
||||
// remove old active highlights and set new highlight
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
if (child.querySelector('a').hash == '#' + id)
|
||||
child.classList.add('active');
|
||||
else if (child.classList.contains('active'))
|
||||
child.classList.remove('active');
|
||||
}
|
||||
|
||||
// open sidebar (if necessary)
|
||||
if (this.element.classList.contains('collapsed')) {
|
||||
this.element.classList.remove('collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ol.control.Sidebar.prototype.close = function() {
|
||||
// remove old active highlights
|
||||
for (var i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
var child = this._tabitems[i];
|
||||
if (child.classList.contains('active'))
|
||||
child.classList.remove('active');
|
||||
}
|
||||
|
||||
// close sidebar
|
||||
if (!this.element.classList.contains('collapsed')) {
|
||||
this.element.classList.add('collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ol.control.Sidebar.prototype._onClick = function(evt) {
|
||||
evt.preventDefault();
|
||||
if (this.classList.contains('active')) {
|
||||
this._sidebar.close();
|
||||
} else if (!this.classList.contains('disabled')) {
|
||||
this._sidebar.open(this.querySelector('a').hash.slice(1));
|
||||
}
|
||||
};
|
||||
|
||||
ol.control.Sidebar.prototype._onCloseClick = function() {
|
||||
this.close();
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
ol.control.Sidebar=function(t){var e,s;for(this._options=Object.assign({},{element:null,position:"left"},t),ol.control.Control.call(this,{element:document.getElementById(this._options.element),target:this._options.target}),this.element.classList.add("sidebar-"+this._options.position),e=this.element.children.length-1;e>=0;e--)"DIV"===(s=this.element.children[e]).tagName&&s.classList.contains("sidebar-content")&&(this._container=s);for(this._tabitems=this.element.querySelectorAll("ul.sidebar-tabs > li, .sidebar-tabs > ul > li"),e=this._tabitems.length-1;e>=0;e--)this._tabitems[e]._sidebar=this;for(this._panes=[],this._closeButtons=[],e=this._container.children.length-1;e>=0;e--)if("DIV"==(s=this._container.children[e]).tagName&&s.classList.contains("sidebar-pane")){this._panes.push(s);for(var i=s.querySelectorAll(".sidebar-close"),o=0,l=i.length;o<l;o++)this._closeButtons.push(i[o])}},"inherits"in ol?ol.inherits(ol.control.Sidebar,ol.control.Control):(ol.control.Sidebar.prototype=Object.create(ol.control.Control.prototype),ol.control.Sidebar.prototype.constructor=ol.control.Sidebar),ol.control.Sidebar.prototype.setMap=function(t){var e,s;for(e=this._tabitems.length-1;e>=0;e--){var i=(s=this._tabitems[e]).querySelector("a");i.hasAttribute("href")&&"#"==i.getAttribute("href").slice(0,1)&&(i.onclick=this._onClick.bind(s))}for(e=this._closeButtons.length-1;e>=0;e--)(s=this._closeButtons[e]).onclick=this._onCloseClick.bind(this)},ol.control.Sidebar.prototype.open=function(t){var e,s;for(e=this._panes.length-1;e>=0;e--)(s=this._panes[e]).id==t?s.classList.add("active"):s.classList.contains("active")&&s.classList.remove("active");for(e=this._tabitems.length-1;e>=0;e--)(s=this._tabitems[e]).querySelector("a").hash=="#"+t?s.classList.add("active"):s.classList.contains("active")&&s.classList.remove("active");return this.element.classList.contains("collapsed")&&this.element.classList.remove("collapsed"),this},ol.control.Sidebar.prototype.close=function(){for(var t=this._tabitems.length-1;t>=0;t--){var e=this._tabitems[t];e.classList.contains("active")&&e.classList.remove("active")}return this.element.classList.contains("collapsed")||this.element.classList.add("collapsed"),this},ol.control.Sidebar.prototype._onClick=function(t){t.preventDefault(),this.classList.contains("active")?this._sidebar.close():this.classList.contains("disabled")||this._sidebar.open(this.querySelector("a").hash.slice(1))},ol.control.Sidebar.prototype._onCloseClick=function(){this.close()};
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "sidebar-v2",
|
||||
"version": "0.4.0",
|
||||
"description": "A responsive sidebar for mapping libraries like Leaflet or OpenLayers",
|
||||
"keywords": [
|
||||
"gis",
|
||||
"leaflet",
|
||||
"map",
|
||||
"openlayers"
|
||||
],
|
||||
"homepage": "https://github.com/turbo87/sidebar-v2",
|
||||
"bugs": "https://github.com/turbo87/sidebar-v2/issues",
|
||||
"license": "MIT",
|
||||
"author": "Tobias Bieniek <tobias.bieniek@gmx.de>",
|
||||
"files": [
|
||||
"css",
|
||||
"js",
|
||||
"scss"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/turbo87/sidebar-v2.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:css": "stylelint **/*.scss",
|
||||
"lint:js": "eslint . --cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.8.0",
|
||||
"gulp": "~4.0.0",
|
||||
"gulp-clean-css": "~4.3.0",
|
||||
"gulp-concat": "~2.6.1",
|
||||
"gulp-rename": "~2.0.0",
|
||||
"gulp-sass": "^4.1.0",
|
||||
"gulp-uglify": "~3.0.2",
|
||||
"stylelint": "^13.12.0",
|
||||
"stylelint-config-recommended-scss": "^4.2.0",
|
||||
"stylelint-scss": "^3.19.0"
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
$threshold-lg: 1200px !default;
|
||||
$threshold-md: 992px !default;
|
||||
$threshold-sm: 768px !default;
|
||||
|
||||
$width-lg: 460px !default;
|
||||
$width-md: 390px !default;
|
||||
$width-sm: 305px !default;
|
||||
$width-xs: 100% !default;
|
||||
|
||||
$sidebar-z-index: 2000 !default;
|
||||
$sidebar-transition: 500ms !default;
|
||||
|
||||
$tab-size: 40px !default;
|
||||
$tab-font-size: 12pt !default;
|
||||
$tab-bg: null !default;
|
||||
$tab-transition: 80ms !default;
|
||||
|
||||
$header-fg: $tab-active-fg !default;
|
||||
$header-bg: $tab-active-bg !default;
|
||||
|
||||
$content-bg: rgba(255, 255, 255, 0.95) !default;
|
||||
$content-padding-vertical: 10px !default;
|
||||
$content-padding-horizontal: 20px !default;
|
||||
|
||||
$move-map-in-xs: true !default;
|
||||
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: $width-xs;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
z-index: $sidebar-z-index;
|
||||
|
||||
&.collapsed {
|
||||
width: $tab-size;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
top: $sidebar-margins;
|
||||
bottom: $sidebar-margins;
|
||||
|
||||
transition: width $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
width: $width-sm;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
width: $width-md;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
width: $width-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
left: 0;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
left: $sidebar-margins;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
right: 0;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
right: $sidebar-margins;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
|
||||
.sidebar-left & {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sidebar-right & {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
background-color: $tabs-bg;
|
||||
|
||||
&, & > ul {
|
||||
position: absolute;
|
||||
|
||||
width: $tab-size;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
list-style-type: none;
|
||||
|
||||
& > li {
|
||||
width: 100%;
|
||||
height: $tab-size;
|
||||
|
||||
color: $tab-fg;
|
||||
@if $tab-bg { background: $tab-bg; }
|
||||
|
||||
font-size: $tab-font-size;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
transition: all $tab-transition;
|
||||
|
||||
&:hover {
|
||||
color: $tab-hover-fg;
|
||||
background-color: $tab-hover-bg;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $tab-active-fg;
|
||||
background-color: $tab-active-bg;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: fade-out($tab-fg, 0.6);
|
||||
|
||||
&:hover {
|
||||
@if $tab-bg {
|
||||
background: $tab-bg;
|
||||
} @else {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
& > a {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
line-height: $tab-size;
|
||||
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > ul + ul {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
|
||||
.sidebar-left & {
|
||||
left: $tab-size;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidebar-right & {
|
||||
left: 0;
|
||||
right: $tab-size;
|
||||
}
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
background-color: $content-bg;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
.sidebar.collapsed > & {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: $content-padding-vertical $content-padding-horizontal;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
min-width: $width-sm - $tab-size;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
min-width: $width-md - $tab-size;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
min-width: $width-lg - $tab-size;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
margin: (-$content-padding-vertical) (-$content-padding-horizontal) 0;
|
||||
height: $tab-size;
|
||||
padding: 0 $content-padding-horizontal;
|
||||
line-height: $tab-size;
|
||||
font-size: $tab-font-size * 1.2;
|
||||
color: $header-fg;
|
||||
background-color: $header-bg;
|
||||
|
||||
.sidebar-right & {
|
||||
padding-left: $tab-size;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: $tab-size;
|
||||
height: $tab-size;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
.sidebar-left & {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidebar-right & {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@if $move-map-in-xs {
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: $tab-size;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: $tab-size;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
$sidebar-bg: rgba(255, 255, 255, 0.4) !default;
|
||||
$sidebar-border-width: 3px !default;
|
||||
$sidebar-border-radius: 4px !default;
|
||||
$sidebar-border: $sidebar-border-width solid transparent !default;
|
||||
|
||||
$tab-fg: #fff !default;
|
||||
$tabs-bg: rgba(0, 60, 136, 0.5) !default;
|
||||
$tab-hover-fg: #fff !default;
|
||||
$tab-hover-bg: rgba(0, 60, 136, 0.6) !default;
|
||||
$tab-active-fg: #fff !default;
|
||||
$tab-active-bg: #0074d9 !default;
|
||||
|
||||
$move-map-in-xs: false !default;
|
||||
|
||||
@import 'base';
|
||||
|
||||
.sidebar {
|
||||
background-color: $sidebar-bg;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
border: $sidebar-border;
|
||||
border-radius: $sidebar-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
border-right: $sidebar-border;
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
border-left: $sidebar-border;
|
||||
}
|
||||
|
||||
.sidebar-tabs {
|
||||
overflow: hidden;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
border-radius: $sidebar-inner-border-radius 0 0 $sidebar-inner-border-radius;
|
||||
|
||||
.collapsed & {
|
||||
border-radius: $sidebar-inner-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
@media(min-width:$threshold-sm) {
|
||||
border-radius: 0 $sidebar-inner-border-radius $sidebar-inner-border-radius 0;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
$sidebar-margins: 10px !default;
|
||||
$sidebar-left-bottom-margin: $sidebar-margins + 25px !default;
|
||||
$sidebar-right-bottom-margin: $sidebar-margins + 14px !default;
|
||||
$sidebar-border: 0 !default;
|
||||
$sidebar-border-radius: 2px !default;
|
||||
$sidebar-shadow: rgba(0, 0, 0, 0.298039) 0 1px 4px -1px !default;
|
||||
|
||||
$tab-fg: #666 !default;
|
||||
$tabs-bg: #fff !default;
|
||||
$tab-active-fg: #000 !default;
|
||||
$tab-active-bg: #febf00 !default;
|
||||
$tab-hover-fg: #000 !default;
|
||||
$tab-hover-bg: ($tabs-bg * 9 + $tab-active-bg) / 10 !default;
|
||||
|
||||
@import 'base';
|
||||
|
||||
.sidebar {
|
||||
border-right: $sidebar-border;
|
||||
box-shadow: $sidebar-shadow;
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
border: $sidebar-border;
|
||||
border-radius: $sidebar-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
@media(min-width:$threshold-sm) {
|
||||
bottom: $sidebar-left-bottom-margin;
|
||||
}
|
||||
|
||||
& ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
@media(min-width:$threshold-sm) {
|
||||
transition: margin-left $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
margin-left: $width-sm + $sidebar-margins * 2 !important;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
margin-left: $width-md + $sidebar-margins * 2 !important;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
margin-left: $width-lg + $sidebar-margins * 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
&.collapsed ~ .sidebar-map .gm-style > div.gmnoprint[style*="left: 0px"] {
|
||||
margin-left: $tab-size + $sidebar-margins * 2 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.sidebar-right {
|
||||
@media(min-width:$threshold-sm) {
|
||||
bottom: $sidebar-right-bottom-margin;
|
||||
}
|
||||
|
||||
& ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
@media(min-width:$threshold-sm) {
|
||||
transition: margin-right $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
margin-right: $width-sm + $sidebar-margins * 2 !important;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
margin-right: $width-md + $sidebar-margins * 2 !important;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
margin-right: $width-lg + $sidebar-margins * 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
&.collapsed ~ .sidebar-map .gm-style > div.gmnoprint[style*="right: 28px"] {
|
||||
margin-right: $tab-size + $sidebar-margins * 2 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
$sidebar-margins: 10px !default;
|
||||
$sidebar-border-radius: 4px !default;
|
||||
$sidebar-touch-border: 2px solid rgba(0, 0, 0, 0.2) !default;
|
||||
$sidebar-shadow: 0 1px 5px rgba(0, 0, 0, 0.65) !default;
|
||||
|
||||
$tab-fg: #333 !default;
|
||||
$tabs-bg: #fff !default;
|
||||
$tab-hover-fg: #000 !default;
|
||||
$tab-hover-bg: #eee !default;
|
||||
$tab-active-fg: #fff !default;
|
||||
$tab-active-bg: #0074d9 !default;
|
||||
|
||||
@import 'base';
|
||||
|
||||
.sidebar {
|
||||
box-shadow: $sidebar-shadow;
|
||||
|
||||
&.leaflet-touch {
|
||||
box-shadow: none;
|
||||
border-right: $sidebar-touch-border;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
border-radius: $sidebar-border-radius;
|
||||
|
||||
&.leaflet-touch {
|
||||
border: $sidebar-touch-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
|
||||
& ~ .sidebar-map .leaflet-left {
|
||||
@media(min-width:$threshold-sm) {
|
||||
transition: left $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
left: $width-sm + $sidebar-margins;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
left: $width-md + $sidebar-margins;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
left: $width-lg + $sidebar-margins;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map .leaflet-left {
|
||||
@media(min-width:$threshold-sm) {
|
||||
left: $tab-size + $sidebar-margins;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
|
||||
& ~ .sidebar-map .leaflet-right {
|
||||
@media(min-width:$threshold-sm) {
|
||||
transition: right $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-sm) and (max-width:$threshold-md - 1px) {
|
||||
right: $width-sm + $sidebar-margins;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-md) and (max-width:$threshold-lg - 1px) {
|
||||
right: $width-md + $sidebar-margins;
|
||||
}
|
||||
|
||||
@media(min-width:$threshold-lg) {
|
||||
right: $width-lg + $sidebar-margins;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map .leaflet-right {
|
||||
@media(min-width:$threshold-sm) {
|
||||
right: $tab-size + $sidebar-margins;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
$sidebar-margins: 8px !default;
|
||||
$sidebar-inner-border-radius: 4px !default;
|
||||
|
||||
@import 'ol-base';
|
||||
|
||||
.sidebar-left {
|
||||
|
||||
& ~ .sidebar-map {
|
||||
|
||||
.olControlZoom,
|
||||
.olScaleLine {
|
||||
margin-left: $tab-size + $sidebar-border-width * 2;
|
||||
|
||||
@media(min-width: $threshold-sm) {
|
||||
transition: margin-left $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-sm) and (max-width: $threshold-md - 1px) {
|
||||
margin-left: $width-sm + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-md) and (max-width: $threshold-lg - 1px) {
|
||||
margin-left: $width-md + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-lg) {
|
||||
margin-left: $width-lg + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map {
|
||||
|
||||
.olControlZoom,
|
||||
.olScaleLine {
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-left: $tab-size + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
|
||||
& ~ .sidebar-map {
|
||||
|
||||
.olControlAttribution,
|
||||
.olControlPermalink,
|
||||
.olControlMousePosition {
|
||||
margin-right: $tab-size + $sidebar-border-width * 2;
|
||||
|
||||
@media(min-width: $threshold-sm) {
|
||||
transition: margin-right $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-sm) and (max-width: $threshold-md - 1px) {
|
||||
margin-right: $width-sm + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-md) and (max-width: $threshold-lg - 1px) {
|
||||
margin-right: $width-md + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-lg) {
|
||||
margin-right: $width-lg + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map {
|
||||
|
||||
.olControlAttribution,
|
||||
.olControlPermalink,
|
||||
.olControlMousePosition {
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-right: $tab-size + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
$sidebar-margins: 6px !default;
|
||||
$sidebar-inner-border-radius: 2px !default;
|
||||
|
||||
@import 'ol-base';
|
||||
|
||||
.sidebar-left {
|
||||
|
||||
& ~ .sidebar-map {
|
||||
|
||||
.ol-zoom, .ol-scale-line {
|
||||
margin-left: $tab-size + $sidebar-border-width * 2;
|
||||
|
||||
@media(min-width: $threshold-sm) {
|
||||
transition: margin-left $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-sm) and (max-width: $threshold-md - 1px) {
|
||||
margin-left: $width-sm + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-md) and (max-width: $threshold-lg - 1px) {
|
||||
margin-left: $width-md + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-lg) {
|
||||
margin-left: $width-lg + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map {
|
||||
|
||||
.ol-zoom, .ol-scale-line {
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-left: $tab-size + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.sidebar-right {
|
||||
|
||||
& ~ .sidebar-map {
|
||||
|
||||
.ol-rotate,
|
||||
.ol-attribution,
|
||||
.ol-full-screen {
|
||||
|
||||
margin-right: $tab-size + $sidebar-border-width * 2;
|
||||
|
||||
@media(min-width: $threshold-sm) {
|
||||
transition: margin-right $sidebar-transition;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-sm) and (max-width: $threshold-md - 1px) {
|
||||
margin-right: $width-sm + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-md) and (max-width: $threshold-lg - 1px) {
|
||||
margin-right: $width-md + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
|
||||
@media(min-width: $threshold-lg) {
|
||||
margin-right: $width-lg + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed ~ .sidebar-map {
|
||||
|
||||
.ol-rotate,
|
||||
.ol-attribution,
|
||||
.ol-full-screen {
|
||||
|
||||
@media(min-width:$threshold-sm) {
|
||||
margin-right: $tab-size + $sidebar-margins + $sidebar-border-width * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/// <reference types="leaflet" />
|
||||
|
||||
declare namespace L {
|
||||
|
||||
namespace Control {
|
||||
|
||||
interface SidebarOptions {
|
||||
position: string;
|
||||
}
|
||||
|
||||
class Sidebar extends Control {
|
||||
constructor(id: string, options?: SidebarOptions);
|
||||
options: Control.ControlOptions;
|
||||
addTo(map: L.Map): this;
|
||||
remove(map: L.Map): this;
|
||||
open(id: string): this;
|
||||
close(): this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace control {
|
||||
function sidebar(id: string, options?: Control.SidebarOptions): L.Control.Sidebar;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
.leaflet-sidebar {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
z-index: 2000; }
|
||||
.leaflet-sidebar.left {
|
||||
left: -500px;
|
||||
transition: left 0.5s, width 0.5s;
|
||||
padding-right: 0; }
|
||||
.leaflet-sidebar.left.visible {
|
||||
left: 0; }
|
||||
.leaflet-sidebar.right {
|
||||
right: -500px;
|
||||
transition: right 0.5s, width 0.5s;
|
||||
padding-left: 0; }
|
||||
.leaflet-sidebar.right.visible {
|
||||
right: 0; }
|
||||
.leaflet-sidebar > .leaflet-control {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 24px;
|
||||
font-size: 1.1em;
|
||||
background: white;
|
||||
box-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px; }
|
||||
.leaflet-touch .leaflet-sidebar > .leaflet-control {
|
||||
box-shadow: none;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
background-clip: padding-box; }
|
||||
@media (max-width: 767px) {
|
||||
.leaflet-sidebar {
|
||||
width: 100%;
|
||||
padding: 0; }
|
||||
.leaflet-sidebar.left.visible ~ .leaflet-left {
|
||||
left: 100%; }
|
||||
.leaflet-sidebar.right.visible ~ .leaflet-right {
|
||||
right: 100%; }
|
||||
.leaflet-sidebar.left {
|
||||
left: -100%; }
|
||||
.leaflet-sidebar.left.visible {
|
||||
left: 0; }
|
||||
.leaflet-sidebar.right {
|
||||
right: -100%; }
|
||||
.leaflet-sidebar.right.visible {
|
||||
right: 0; }
|
||||
.leaflet-sidebar > .leaflet-control {
|
||||
box-shadow: none;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0; }
|
||||
.leaflet-touch .leaflet-sidebar > .leaflet-control {
|
||||
border: 0; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.leaflet-sidebar {
|
||||
width: 305px; }
|
||||
.leaflet-sidebar.left.visible ~ .leaflet-left {
|
||||
left: 305px; }
|
||||
.leaflet-sidebar.right.visible ~ .leaflet-right {
|
||||
right: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.leaflet-sidebar {
|
||||
width: 390px; }
|
||||
.leaflet-sidebar.left.visible ~ .leaflet-left {
|
||||
left: 390px; }
|
||||
.leaflet-sidebar.right.visible ~ .leaflet-right {
|
||||
right: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.leaflet-sidebar {
|
||||
width: 460px; }
|
||||
.leaflet-sidebar.left.visible ~ .leaflet-left {
|
||||
left: 460px; }
|
||||
.leaflet-sidebar.right.visible ~ .leaflet-right {
|
||||
right: 460px; } }
|
||||
.leaflet-sidebar .close {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
color: #333;
|
||||
font-size: 25px;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
background: white;
|
||||
-webkit-border-radius: 16px;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
z-index: 1000; }
|
||||
|
||||
.leaflet-left {
|
||||
transition: left 0.5s; }
|
||||
|
||||
.leaflet-right {
|
||||
transition: right 0.5s; }
|
||||
@@ -1,202 +0,0 @@
|
||||
L.Control.Sidebar = L.Control.extend({
|
||||
|
||||
includes: L.Evented.prototype || L.Mixin.Events,
|
||||
|
||||
options: {
|
||||
closeButton: true,
|
||||
position: 'left',
|
||||
autoPan: true,
|
||||
},
|
||||
|
||||
initialize: function (placeholder, options) {
|
||||
L.setOptions(this, options);
|
||||
|
||||
// Find content container
|
||||
var content = this._contentContainer = L.DomUtil.get(placeholder);
|
||||
|
||||
// Remove the content container from its original parent
|
||||
if(content.parentNode != undefined){
|
||||
content.parentNode.removeChild(content);
|
||||
}
|
||||
var l = 'leaflet-';
|
||||
|
||||
// Create sidebar container
|
||||
var container = this._container =
|
||||
L.DomUtil.create('div', l + 'sidebar ' + this.options.position);
|
||||
|
||||
// Style and attach content container
|
||||
L.DomUtil.addClass(content, l + 'control');
|
||||
container.appendChild(content);
|
||||
|
||||
// Create close button and attach it if configured
|
||||
if (this.options.closeButton) {
|
||||
var close = this._closeButton =
|
||||
L.DomUtil.create('a', 'close', container);
|
||||
close.innerHTML = '×';
|
||||
}
|
||||
},
|
||||
|
||||
addTo: function (map) {
|
||||
var container = this._container;
|
||||
var content = this._contentContainer;
|
||||
|
||||
// Attach event to close button
|
||||
if (this.options.closeButton) {
|
||||
var close = this._closeButton;
|
||||
|
||||
L.DomEvent.on(close, 'click', this.hide, this);
|
||||
}
|
||||
|
||||
L.DomEvent
|
||||
.on(container, 'transitionend',
|
||||
this._handleTransitionEvent, this)
|
||||
.on(container, 'webkitTransitionEnd',
|
||||
this._handleTransitionEvent, this);
|
||||
|
||||
// Attach sidebar container to controls container
|
||||
var controlContainer = map._controlContainer;
|
||||
controlContainer.insertBefore(container, controlContainer.firstChild);
|
||||
|
||||
this._map = map;
|
||||
|
||||
// Make sure we don't drag the map when we interact with the content
|
||||
var stop = L.DomEvent.stopPropagation;
|
||||
var fakeStop = L.DomEvent._fakeStop || stop;
|
||||
L.DomEvent
|
||||
.on(content, 'contextmenu', stop)
|
||||
.on(content, 'click', fakeStop)
|
||||
.on(content, 'mousedown', stop)
|
||||
.on(content, 'touchstart', stop)
|
||||
.on(content, 'dblclick', fakeStop)
|
||||
.on(content, 'mousewheel', stop)
|
||||
.on(content, 'wheel', stop)
|
||||
.on(content, 'scroll', stop)
|
||||
.on(content, 'MozMousePixelScroll', stop);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
removeFrom: function (map) {
|
||||
//if the control is visible, hide it before removing it.
|
||||
this.hide();
|
||||
|
||||
var container = this._container;
|
||||
var content = this._contentContainer;
|
||||
|
||||
// Remove sidebar container from controls container
|
||||
var controlContainer = map._controlContainer;
|
||||
controlContainer.removeChild(container);
|
||||
|
||||
//disassociate the map object
|
||||
this._map = null;
|
||||
|
||||
// Unregister events to prevent memory leak
|
||||
var stop = L.DomEvent.stopPropagation;
|
||||
var fakeStop = L.DomEvent._fakeStop || stop;
|
||||
L.DomEvent
|
||||
.off(content, 'contextmenu', stop)
|
||||
.off(content, 'click', fakeStop)
|
||||
.off(content, 'mousedown', stop)
|
||||
.off(content, 'touchstart', stop)
|
||||
.off(content, 'dblclick', fakeStop)
|
||||
.off(content, 'mousewheel', stop)
|
||||
.off(content, 'wheel', stop)
|
||||
.off(content, 'scroll', stop)
|
||||
.off(content, 'MozMousePixelScroll', stop);
|
||||
|
||||
L.DomEvent
|
||||
.off(container, 'transitionend',
|
||||
this._handleTransitionEvent, this)
|
||||
.off(container, 'webkitTransitionEnd',
|
||||
this._handleTransitionEvent, this);
|
||||
|
||||
if (this._closeButton && this._close) {
|
||||
var close = this._closeButton;
|
||||
|
||||
L.DomEvent.off(close, 'click', this.hide, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
isVisible: function () {
|
||||
return L.DomUtil.hasClass(this._container, 'visible');
|
||||
},
|
||||
|
||||
show: function () {
|
||||
if (!this.isVisible()) {
|
||||
L.DomUtil.addClass(this._container, 'visible');
|
||||
if (this.options.autoPan) {
|
||||
this._map.panBy([-this.getOffset() / 2, 0], {
|
||||
duration: 0.5
|
||||
});
|
||||
}
|
||||
this.fire('show');
|
||||
}
|
||||
},
|
||||
|
||||
hide: function (e) {
|
||||
if (this.isVisible()) {
|
||||
L.DomUtil.removeClass(this._container, 'visible');
|
||||
if (this.options.autoPan) {
|
||||
this._map.panBy([this.getOffset() / 2, 0], {
|
||||
duration: 0.5
|
||||
});
|
||||
}
|
||||
this.fire('hide');
|
||||
}
|
||||
if(e) {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
}
|
||||
},
|
||||
|
||||
toggle: function () {
|
||||
if (this.isVisible()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
},
|
||||
|
||||
getContainer: function () {
|
||||
return this._contentContainer;
|
||||
},
|
||||
|
||||
getCloseButton: function () {
|
||||
return this._closeButton;
|
||||
},
|
||||
|
||||
setContent: function (content) {
|
||||
var container = this.getContainer();
|
||||
|
||||
if (typeof content === 'string') {
|
||||
container.innerHTML = content;
|
||||
} else {
|
||||
// clean current content
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
container.appendChild(content);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getOffset: function () {
|
||||
if (this.options.position === 'right') {
|
||||
return -this._container.offsetWidth;
|
||||
} else {
|
||||
return this._container.offsetWidth;
|
||||
}
|
||||
},
|
||||
|
||||
_handleTransitionEvent: function (e) {
|
||||
if (e.propertyName == 'left' || e.propertyName == 'right')
|
||||
this.fire(this.isVisible() ? 'shown' : 'hidden');
|
||||
}
|
||||
});
|
||||
|
||||
L.control.sidebar = function (placeholder, options) {
|
||||
return new L.Control.Sidebar(placeholder, options);
|
||||
};
|
||||
@@ -1,200 +0,0 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 2000; }
|
||||
.sidebar.collapsed {
|
||||
width: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
transition: width 500ms; } }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar {
|
||||
width: 305px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar {
|
||||
width: 390px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 460px; } }
|
||||
|
||||
.sidebar-left {
|
||||
left: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left {
|
||||
left: 10px; } }
|
||||
|
||||
.sidebar-right {
|
||||
right: 0; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right {
|
||||
right: 10px; } }
|
||||
|
||||
.sidebar-tabs {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background-color: #fff; }
|
||||
.sidebar-left .sidebar-tabs {
|
||||
left: 0; }
|
||||
.sidebar-right .sidebar-tabs {
|
||||
right: 0; }
|
||||
.sidebar-tabs, .sidebar-tabs > ul {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
.sidebar-tabs > li, .sidebar-tabs > ul > li {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
font-size: 12pt;
|
||||
overflow: hidden;
|
||||
transition: all 80ms; }
|
||||
.sidebar-tabs > li:hover, .sidebar-tabs > ul > li:hover {
|
||||
color: #000;
|
||||
background-color: #eee; }
|
||||
.sidebar-tabs > li.active, .sidebar-tabs > ul > li.active {
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-tabs > li.disabled, .sidebar-tabs > ul > li.disabled {
|
||||
color: rgba(51, 51, 51, 0.4); }
|
||||
.sidebar-tabs > li.disabled:hover, .sidebar-tabs > ul > li.disabled:hover {
|
||||
background: transparent; }
|
||||
.sidebar-tabs > li.disabled > a, .sidebar-tabs > ul > li.disabled > a {
|
||||
cursor: default; }
|
||||
.sidebar-tabs > li > a, .sidebar-tabs > ul > li > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: center; }
|
||||
.sidebar-tabs > ul + ul {
|
||||
bottom: 0; }
|
||||
|
||||
.sidebar-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; }
|
||||
.sidebar-left .sidebar-content {
|
||||
left: 40px;
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-content {
|
||||
left: 0;
|
||||
right: 40px; }
|
||||
.sidebar.collapsed > .sidebar-content {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.sidebar-pane {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px; }
|
||||
.sidebar-pane.active {
|
||||
display: block; }
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-pane {
|
||||
min-width: 265px; } }
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-pane {
|
||||
min-width: 350px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-pane {
|
||||
min-width: 420px; } }
|
||||
|
||||
.sidebar-header {
|
||||
margin: -10px -20px 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
font-size: 14.4pt;
|
||||
color: #fff;
|
||||
background-color: #0074d9; }
|
||||
.sidebar-right .sidebar-header {
|
||||
padding-left: 40px; }
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer; }
|
||||
.sidebar-left .sidebar-close {
|
||||
right: 0; }
|
||||
.sidebar-right .sidebar-close {
|
||||
left: 0; }
|
||||
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map {
|
||||
margin-left: 0; } }
|
||||
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 40px; }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map {
|
||||
margin-right: 0; } }
|
||||
|
||||
.sidebar {
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); }
|
||||
.sidebar.leaflet-touch {
|
||||
box-shadow: none;
|
||||
border-right: 2px solid rgba(0, 0, 0, 0.2); }
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
border-radius: 4px; }
|
||||
.sidebar.leaflet-touch {
|
||||
border: 2px solid rgba(0, 0, 0, 0.2); } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
transition: left 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 315px; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 400px; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-left ~ .sidebar-map .leaflet-left {
|
||||
left: 470px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-left.collapsed ~ .sidebar-map .leaflet-left {
|
||||
left: 50px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
transition: right 500ms; } }
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 315px; } }
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 400px; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.sidebar-right ~ .sidebar-map .leaflet-right {
|
||||
right: 470px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-right.collapsed ~ .sidebar-map .leaflet-right {
|
||||
right: 50px; } }
|
||||
@@ -1,216 +0,0 @@
|
||||
/* global L */
|
||||
|
||||
/**
|
||||
* @name Sidebar
|
||||
* @class L.Control.Sidebar
|
||||
* @extends L.Control
|
||||
* @param {string} id - The id of the sidebar element (without the # character)
|
||||
* @param {Object} [options] - Optional options object
|
||||
* @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right'
|
||||
* @see L.control.sidebar
|
||||
*/
|
||||
L.Control.Sidebar = L.Control.extend(/** @lends L.Control.Sidebar.prototype */ {
|
||||
includes: (L.Evented.prototype || L.Mixin.Events),
|
||||
|
||||
options: {
|
||||
position: 'left'
|
||||
},
|
||||
|
||||
initialize: function (id, options) {
|
||||
var i, child;
|
||||
|
||||
L.setOptions(this, options);
|
||||
|
||||
// Find sidebar HTMLElement
|
||||
this._sidebar = L.DomUtil.get(id);
|
||||
|
||||
// Attach .sidebar-left/right class
|
||||
L.DomUtil.addClass(this._sidebar, 'sidebar-' + this.options.position);
|
||||
|
||||
// Attach touch styling if necessary
|
||||
if (L.Browser.touch)
|
||||
L.DomUtil.addClass(this._sidebar, 'leaflet-touch');
|
||||
|
||||
// Find sidebar > div.sidebar-content
|
||||
for (i = this._sidebar.children.length - 1; i >= 0; i--) {
|
||||
child = this._sidebar.children[i];
|
||||
if (child.tagName == 'DIV' &&
|
||||
L.DomUtil.hasClass(child, 'sidebar-content'))
|
||||
this._container = child;
|
||||
}
|
||||
|
||||
// Find sidebar ul.sidebar-tabs > li, sidebar .sidebar-tabs > ul > li
|
||||
this._tabitems = this._sidebar.querySelectorAll('ul.sidebar-tabs > li, .sidebar-tabs > ul > li');
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
this._tabitems[i]._sidebar = this;
|
||||
}
|
||||
|
||||
// Find sidebar > div.sidebar-content > div.sidebar-pane
|
||||
this._panes = [];
|
||||
this._closeButtons = [];
|
||||
for (i = this._container.children.length - 1; i >= 0; i--) {
|
||||
child = this._container.children[i];
|
||||
if (child.tagName == 'DIV' &&
|
||||
L.DomUtil.hasClass(child, 'sidebar-pane')) {
|
||||
this._panes.push(child);
|
||||
|
||||
var closeButtons = child.querySelectorAll('.sidebar-close');
|
||||
for (var j = 0, len = closeButtons.length; j < len; j++)
|
||||
this._closeButtons.push(closeButtons[j]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this sidebar to the specified map.
|
||||
*
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
addTo: function (map) {
|
||||
var i, child;
|
||||
|
||||
this._map = map;
|
||||
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
var sub = child.querySelector('a');
|
||||
if (sub.hasAttribute('href') && sub.getAttribute('href').slice(0,1) == '#') {
|
||||
L.DomEvent
|
||||
.on(sub, 'click', L.DomEvent.preventDefault )
|
||||
.on(sub, 'click', this._onClick, child);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = this._closeButtons.length - 1; i >= 0; i--) {
|
||||
child = this._closeButtons[i];
|
||||
L.DomEvent.on(child, 'click', this._onCloseClick, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated - Please use remove() instead of removeFrom(), as of Leaflet 0.8-dev, the removeFrom() has been replaced with remove()
|
||||
* Removes this sidebar from the map.
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
removeFrom: function(map) {
|
||||
console.log('removeFrom() has been deprecated, please use remove() instead as support for this function will be ending soon.');
|
||||
this.remove(map);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove this sidebar from the map.
|
||||
*
|
||||
* @param {L.Map} map
|
||||
* @returns {Sidebar}
|
||||
*/
|
||||
remove: function (map) {
|
||||
var i, child;
|
||||
|
||||
this._map = null;
|
||||
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
L.DomEvent.off(child.querySelector('a'), 'click', this._onClick);
|
||||
}
|
||||
|
||||
for (i = this._closeButtons.length - 1; i >= 0; i--) {
|
||||
child = this._closeButtons[i];
|
||||
L.DomEvent.off(child, 'click', this._onCloseClick, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open sidebar (if necessary) and show the specified tab.
|
||||
*
|
||||
* @param {string} id - The id of the tab to show (without the # character)
|
||||
*/
|
||||
open: function(id) {
|
||||
var i, child;
|
||||
|
||||
// hide old active contents and show new content
|
||||
for (i = this._panes.length - 1; i >= 0; i--) {
|
||||
child = this._panes[i];
|
||||
if (child.id == id)
|
||||
L.DomUtil.addClass(child, 'active');
|
||||
else if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
// remove old active highlights and set new highlight
|
||||
for (i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
child = this._tabitems[i];
|
||||
if (child.querySelector('a').hash == '#' + id)
|
||||
L.DomUtil.addClass(child, 'active');
|
||||
else if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
this.fire('content', { id: id });
|
||||
|
||||
// open sidebar (if necessary)
|
||||
if (L.DomUtil.hasClass(this._sidebar, 'collapsed')) {
|
||||
this.fire('opening');
|
||||
L.DomUtil.removeClass(this._sidebar, 'collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the sidebar (if necessary).
|
||||
*/
|
||||
close: function() {
|
||||
// remove old active highlights
|
||||
for (var i = this._tabitems.length - 1; i >= 0; i--) {
|
||||
var child = this._tabitems[i];
|
||||
if (L.DomUtil.hasClass(child, 'active'))
|
||||
L.DomUtil.removeClass(child, 'active');
|
||||
}
|
||||
|
||||
// close sidebar
|
||||
if (!L.DomUtil.hasClass(this._sidebar, 'collapsed')) {
|
||||
this.fire('closing');
|
||||
L.DomUtil.addClass(this._sidebar, 'collapsed');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClick: function() {
|
||||
if (L.DomUtil.hasClass(this, 'active'))
|
||||
this._sidebar.close();
|
||||
else if (!L.DomUtil.hasClass(this, 'disabled'))
|
||||
this._sidebar.open(this.querySelector('a').hash.slice(1));
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onCloseClick: function () {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new sidebar.
|
||||
*
|
||||
* @example
|
||||
* var sidebar = L.control.sidebar('sidebar').addTo(map);
|
||||
*
|
||||
* @param {string} id - The id of the sidebar element (without the # character)
|
||||
* @param {Object} [options] - Optional options object
|
||||
* @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right'
|
||||
* @returns {Sidebar} A new sidebar instance
|
||||
*/
|
||||
L.control.sidebar = function (id, options) {
|
||||
return new L.Control.Sidebar(id, options);
|
||||
};
|
||||
586
public/admin.php
Normal file
@@ -0,0 +1,586 @@
|
||||
<?php
|
||||
// =====================================================================
|
||||
// Moderation Page
|
||||
// Lists Contributions for Review. Moderators can approve, reject,
|
||||
// edit and delete Contributions. Includes Map Preview and Filtering.
|
||||
// =====================================================================
|
||||
|
||||
// Reads Environment Configfile
|
||||
$envFile = __DIR__ . '/../../.env';
|
||||
if (file_exists($envFile)) {
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
list($key, $value) = array_map('trim', explode('=', $line, 2));
|
||||
putenv("$key=$value");
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/api/db.php';
|
||||
require_once __DIR__ . '/api/auth.php';
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Routing: Login, Logout, or Main Page
|
||||
// -----------------------------------------------------------------
|
||||
$page = $_GET['page'] ?? 'main';
|
||||
|
||||
// Handles Login
|
||||
if ($page === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = $_POST['password'] ?? '';
|
||||
if (admin_login($password)) {
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
} else {
|
||||
$login_error = 'Falsches Passwort.';
|
||||
}
|
||||
}
|
||||
|
||||
// Handles Logout
|
||||
if ($page === 'logout') {
|
||||
admin_logout();
|
||||
header('Location: admin.php?page=login');
|
||||
exit;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Loads Municipality Configuration for Theming
|
||||
// -----------------------------------------------------------------
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
||||
$municipality = $stmt->fetch();
|
||||
|
||||
|
||||
// Loads News for Moderation
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT news_id, title, content, author_name, published_at, created_at
|
||||
FROM news
|
||||
WHERE municipality_id = :mid
|
||||
ORDER BY published_at DESC
|
||||
");
|
||||
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
||||
$news_items = $stmt->fetchAll();
|
||||
|
||||
|
||||
// Loads all Comments with Contribution Titles for Moderation
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT cm.comment_id, cm.contribution_id, cm.author_name, cm.browser_id,
|
||||
cm.content, cm.status, cm.created_at,
|
||||
co.title AS contribution_title, co.category AS contribution_category
|
||||
FROM comments cm
|
||||
JOIN contributions co ON cm.contribution_id = co.contribution_id
|
||||
WHERE co.municipality_id = :mid
|
||||
ORDER BY cm.created_at DESC
|
||||
");
|
||||
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
||||
$all_comments = $stmt->fetchAll();
|
||||
|
||||
// Counts Comments per Status
|
||||
$comment_counts = ['pending' => 0, 'approved' => 0, 'rejected' => 0];
|
||||
foreach ($all_comments as $c) {
|
||||
if (isset($comment_counts[$c['status']])) {
|
||||
$comment_counts[$c['status']]++;
|
||||
}
|
||||
}
|
||||
$comment_counts['total'] = count($all_comments);
|
||||
|
||||
|
||||
// Shows Login Page if not authenticated
|
||||
if ($page === 'login' || !is_admin()) {
|
||||
show_login_page($municipality, $login_error ?? null);
|
||||
exit;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Loads shared Category Definitions
|
||||
// -----------------------------------------------------------------
|
||||
$categories = get_categories();
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Loads Contributions and Statistics
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Loads all Contributions for Municipality
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT contribution_id, title, category, description, author_name, photo_path,
|
||||
geom_type, status, likes_count, dislikes_count, comment_count, created_at, updated_at
|
||||
FROM contributions
|
||||
WHERE municipality_id = :mid
|
||||
ORDER BY created_at DESC
|
||||
");
|
||||
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
||||
$all_contributions = $stmt->fetchAll();
|
||||
|
||||
// Counts per Status
|
||||
$counts = ['pending' => 0, 'approved' => 0, 'rejected' => 0];
|
||||
foreach ($all_contributions as $item) {
|
||||
if (isset($counts[$item['status']])) {
|
||||
$counts[$item['status']]++;
|
||||
}
|
||||
}
|
||||
$counts['total'] = count($all_contributions);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Renders Main Page
|
||||
// -----------------------------------------------------------------
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Moderation — <?= htmlspecialchars($municipality['name']) ?></title>
|
||||
<link rel="icon" href="assets/shield-halved-solid-off-black.png" type="image/png">
|
||||
|
||||
<!-- Loads CSS Dependencies -->
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
<!-- Leaflet -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
|
||||
|
||||
<!-- Application Styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
|
||||
<!-- Loads JavaScript Dependencies -->
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.14.0/dist/sweetalert2.all.min.js"></script>
|
||||
|
||||
|
||||
<!-- Loads Municipality Theme from Database -->
|
||||
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================= -->
|
||||
<div class="page-header">
|
||||
<div class="page-header-inner">
|
||||
<h1><i class="fa-solid fa-shield-halved"></i> Moderationsportal <?= htmlspecialchars($municipality['name']) ?></h1>
|
||||
<div class="page-header-nav">
|
||||
<a href="index.php"><i class="fa-solid fa-map"></i> Bürgerportal</a>
|
||||
<a href="admin.php?page=logout"><i class="fa-solid fa-right-from-bracket"></i> Abmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-container">
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Page Navigation Tabs -->
|
||||
<!-- ========================================================= -->
|
||||
<div class="page-tabs">
|
||||
<button class="page-tab active" onclick="showPageTab('contributions')">
|
||||
<i class="fa-solid fa-list-check"></i> Beiträge
|
||||
</button>
|
||||
<button class="page-tab" onclick="showPageTab('comments')">
|
||||
<i class="fa-solid fa-comments"></i> Kommentare
|
||||
</button>
|
||||
<button class="page-tab" onclick="showPageTab('news')">
|
||||
<i class="fa-solid fa-newspaper"></i> Neuigkeiten
|
||||
</button>
|
||||
<button class="page-tab" onclick="showPageTab('stats')">
|
||||
<i class="fa-solid fa-chart-bar"></i> Statistik
|
||||
</button>
|
||||
<button class="page-tab" onclick="showPageTab('users')">
|
||||
<i class="fa-solid fa-users"></i> Benutzer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Contributions Tab -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-contributions" class="page-tab-content">
|
||||
|
||||
<!-- Status Filter Tabs -->
|
||||
<div class="filter-tabs">
|
||||
<button class="filter-tab active" onclick="filterByStatus('all', this)">
|
||||
Alle <span class="tab-count"><?= $counts['total'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterByStatus('pending', this)">
|
||||
Ausstehend <span class="tab-count"><?= $counts['pending'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterByStatus('approved', this)">
|
||||
Akzeptiert <span class="tab-count"><?= $counts['approved'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterByStatus('rejected', this)">
|
||||
Abgelehnt <span class="tab-count"><?= $counts['rejected'] ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sort Controls -->
|
||||
<div class="sort-controls">
|
||||
<span id="visible-count"><?= $counts['total'] ?> Beiträge</span>
|
||||
<select onchange="sortContributions(this.value)">
|
||||
<option value="date-desc">Neueste zuerst</option>
|
||||
<option value="date-asc">Älteste zuerst</option>
|
||||
<option value="category">Nach Kategorie</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Contribution List -->
|
||||
<div id="contributions-container">
|
||||
<?php if (empty($all_contributions)): ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa-solid fa-inbox" style="font-size:2rem;margin-bottom:8px;display:block;"></i>
|
||||
Noch keine Beiträge vorhanden.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($all_contributions as $item):
|
||||
$cat = $categories[$item['category']] ?? ['label' => $item['category'], 'faIcon' => 'fa-question', 'color' => '#999'];
|
||||
$status_label = ['pending' => 'Ausstehend', 'approved' => 'Akzeptiert', 'rejected' => 'Abgelehnt'];
|
||||
?>
|
||||
<div class="contribution-row"
|
||||
data-status="<?= $item['status'] ?>"
|
||||
data-category="<?= htmlspecialchars($item['category']) ?>"
|
||||
data-date="<?= $item['created_at'] ?>"
|
||||
data-id="<?= $item['contribution_id'] ?>">
|
||||
|
||||
<!-- Collapsed Header: Title + Status -->
|
||||
<div class="contribution-row-header" onclick="toggleRow(this.parentElement)">
|
||||
<div class="contribution-row-summary">
|
||||
<span class="title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
<span class="badge badge-category">
|
||||
<i class="fa-solid <?= $cat['faIcon'] ?>"></i>
|
||||
<?= $cat['label'] ?>
|
||||
</span>
|
||||
<span class="badge badge-<?= $item['status'] ?>"><?= $status_label[$item['status']] ?? $item['status'] ?></span>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down collapse-icon"></i>
|
||||
</div>
|
||||
|
||||
<!-- Expanded Detail -->
|
||||
<div class="contribution-row-detail">
|
||||
<div class="detail-layout">
|
||||
<!-- Map and Photo Slider -->
|
||||
<div class="detail-slider" id="slider-<?= $item['contribution_id'] ?>">
|
||||
<!-- Slide 1: Map -->
|
||||
<div class="detail-slide active" data-slide="map">
|
||||
<div class="detail-map" id="map-<?= $item['contribution_id'] ?>"
|
||||
data-contribution-id="<?= $item['contribution_id'] ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($item['photo_path'])): ?>
|
||||
<!-- Slide 2: Photo -->
|
||||
<div class="detail-slide" data-slide="photo" style="display:none;">
|
||||
<img src="<?= htmlspecialchars($item['photo_path']) ?>" alt="Foto"
|
||||
class="detail-slide-photo" onclick="window.open('<?= htmlspecialchars($item['photo_path']) ?>', '_blank')">
|
||||
</div>
|
||||
<!-- Slider Arrows -->
|
||||
<button class="slider-arrow slider-arrow-left" onclick="slideDetail(<?= $item['contribution_id'] ?>, -1)">
|
||||
<i class="fa-solid fa-chevron-left"></i>
|
||||
</button>
|
||||
<button class="slider-arrow slider-arrow-right" onclick="slideDetail(<?= $item['contribution_id'] ?>, 1)">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="detail-content">
|
||||
<?php if ($item['description']): ?>
|
||||
<div class="description"><?= htmlspecialchars($item['description']) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="description empty">Keine Beschreibung vorhanden.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="detail-meta">
|
||||
<span><i class="fa-solid fa-user"></i> <?= htmlspecialchars($item['author_name']) ?></span>
|
||||
<span><i class="fa-solid fa-calendar"></i> <?= date('d.m.Y, H:i', strtotime($item['created_at'])) ?> Uhr</span>
|
||||
<span>
|
||||
<i class="fa-solid fa-thumbs-up"></i> <?= $item['likes_count'] ?>
|
||||
·
|
||||
<i class="fa-solid fa-thumbs-down"></i> <?= $item['dislikes_count'] ?>
|
||||
·
|
||||
<i class="fa-solid fa-comment"></i> <?= $item['comment_count'] ?? 0 ?>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<?php if ($item['status'] !== 'approved'): ?>
|
||||
<button class="btn btn-approve" onclick="changeStatus(<?= $item['contribution_id'] ?>, 'approved')">
|
||||
<i class="fa-solid fa-check"></i> Akzeptieren
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['status'] !== 'rejected'): ?>
|
||||
<button class="btn btn-reject" onclick="changeStatus(<?= $item['contribution_id'] ?>, 'rejected')">
|
||||
<i class="fa-solid fa-xmark"></i> Ablehnen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['status'] !== 'pending'): ?>
|
||||
<button class="btn btn-reset" onclick="changeStatus(<?= $item['contribution_id'] ?>, 'pending')">
|
||||
<i class="fa-solid fa-rotate-left"></i> Zurücksetzen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="btn btn-edit" onclick="editContribution(<?= $item['contribution_id'] ?>, '<?= htmlspecialchars(addslashes($item['title']), ENT_QUOTES) ?>', '<?= htmlspecialchars(addslashes($item['description'] ?? ''), ENT_QUOTES) ?>')">
|
||||
<i class="fa-solid fa-pen"></i> Bearbeiten
|
||||
</button>
|
||||
|
||||
<button class="btn btn-delete" onclick="deleteContribution(<?= $item['contribution_id'] ?>)">
|
||||
<i class="fa-solid fa-trash"></i> Löschen
|
||||
</button>
|
||||
|
||||
<a class="btn btn-map" href="index.php" target="_blank">
|
||||
<i class="fa-solid fa-map-location-dot"></i> Karte
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Comments Moderation Tab -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-comments" class="page-tab-content" style="display:none;">
|
||||
|
||||
<!-- Status Filter Tabs for Comments -->
|
||||
<div class="filter-tabs" id="comment-filter-tabs">
|
||||
<button class="filter-tab active" onclick="filterCommentsByStatus('all', this)">
|
||||
Alle <span class="tab-count"><?= $comment_counts['total'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterCommentsByStatus('pending', this)">
|
||||
Ausstehend <span class="tab-count"><?= $comment_counts['pending'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterCommentsByStatus('approved', this)">
|
||||
Akzeptiert <span class="tab-count"><?= $comment_counts['approved'] ?></span>
|
||||
</button>
|
||||
<button class="filter-tab" onclick="filterCommentsByStatus('rejected', this)">
|
||||
Abgelehnt <span class="tab-count"><?= $comment_counts['rejected'] ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sort Controls -->
|
||||
<div class="sort-controls">
|
||||
<span id="comment-visible-count"><?= $comment_counts['total'] ?> Kommentare</span>
|
||||
<select onchange="sortCommentRows(this.value)">
|
||||
<option value="date-desc">Neueste zuerst</option>
|
||||
<option value="date-asc">Älteste zuerst</option>
|
||||
<option value="contribution">Nach Beitrag</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Comments List -->
|
||||
<div id="comments-mod-container">
|
||||
<?php if (empty($all_comments)): ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa-solid fa-comments" style="font-size:2rem;margin-bottom:8px;display:block;"></i>
|
||||
Noch keine Kommentare vorhanden.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($all_comments as $comment):
|
||||
$comment_cat = $categories[$comment['contribution_category'] ?? ''] ?? ['label' => 'Unbekannt', 'faIcon' => 'fa-question', 'color' => '#999'];
|
||||
$comment_status_label = ['pending' => 'Ausstehend', 'approved' => 'Akzeptiert', 'rejected' => 'Abgelehnt'];
|
||||
?>
|
||||
<div class="contribution-row comment-mod-row"
|
||||
data-status="<?= $comment['status'] ?>"
|
||||
data-date="<?= $comment['created_at'] ?>"
|
||||
data-contribution="<?= htmlspecialchars($comment['contribution_title']) ?>">
|
||||
|
||||
<!-- Collapsed: Contribution Title + Comment Status + Category -->
|
||||
<div class="contribution-row-header" onclick="toggleRow(this.parentElement)">
|
||||
<div class="contribution-row-summary">
|
||||
<span class="title"><?= htmlspecialchars($comment['contribution_title']) ?></span>
|
||||
<span class="badge badge-<?= $comment['status'] ?>"><?= $comment_status_label[$comment['status']] ?? $comment['status'] ?></span>
|
||||
<span class="badge badge-category">
|
||||
<i class="fa-solid <?= $comment_cat['faIcon'] ?>"></i>
|
||||
<?= $comment_cat['label'] ?>
|
||||
</span>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down collapse-icon"></i>
|
||||
</div>
|
||||
|
||||
<!-- Expanded Detail -->
|
||||
<div class="contribution-row-detail">
|
||||
<div style="padding:12px 0;">
|
||||
<!-- Comment Content -->
|
||||
<div style="font-size:0.9rem;line-height:1.6;color:var(--color-text);margin-bottom:12px;">
|
||||
<?= nl2br(htmlspecialchars($comment['content'])) ?>
|
||||
</div>
|
||||
<!-- Meta -->
|
||||
<div class="detail-meta">
|
||||
<span><i class="fa-solid fa-user"></i> <?= htmlspecialchars($comment['author_name']) ?></span>
|
||||
<span><i class="fa-solid fa-calendar"></i> <?= date('d.m.Y, H:i', strtotime($comment['created_at'])) ?> Uhr</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<?php if ($comment['status'] !== 'approved'): ?>
|
||||
<button class="btn btn-approve" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'approved')">
|
||||
<i class="fa-solid fa-check"></i> Akzeptieren
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($comment['status'] !== 'rejected'): ?>
|
||||
<button class="btn btn-reject" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'rejected')">
|
||||
<i class="fa-solid fa-xmark"></i> Ablehnen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($comment['status'] !== 'pending'): ?>
|
||||
<button class="btn btn-reset" onclick="changeCommentStatus(<?= $comment['comment_id'] ?>, 'pending')">
|
||||
<i class="fa-solid fa-rotate-left"></i> Zurücksetzen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button class="btn btn-edit" onclick="editModComment(<?= $comment['comment_id'] ?>, '<?= htmlspecialchars(addslashes($comment['content']), ENT_QUOTES) ?>')">
|
||||
<i class="fa-solid fa-pen"></i> Bearbeiten
|
||||
</button>
|
||||
<button class="btn btn-delete" onclick="deleteModComment(<?= $comment['comment_id'] ?>)">
|
||||
<i class="fa-solid fa-trash"></i> Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- News Article Tab -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-news" class="page-tab-content" style="display:none;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
|
||||
<h2 style="margin:0;border:none;padding:0;"><i class="fa-solid fa-newspaper"></i> Neuigkeiten</h2>
|
||||
<button class="btn btn-approve" onclick="createNews()">
|
||||
<i class="fa-solid fa-plus"></i> Nachricht hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (empty($news_items)): ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa-solid fa-newspaper" style="font-size:2rem;margin-bottom:8px;display:block;"></i>
|
||||
Noch keine Neuigkeiten veröffentlicht.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($news_items as $news): ?>
|
||||
<div class="contribution-row" data-id="<?= $news['news_id'] ?>">
|
||||
<div class="contribution-row-header" onclick="toggleRow(this.parentElement)">
|
||||
<div class="contribution-row-summary">
|
||||
<span class="title"><?= htmlspecialchars($news['title']) ?></span>
|
||||
<span style="font-size:0.8rem;color:#999;">
|
||||
<?= date('d.m.Y', strtotime($news['published_at'])) ?>
|
||||
· <?= htmlspecialchars($news['author_name']) ?>
|
||||
</span>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down collapse-icon"></i>
|
||||
</div>
|
||||
<div class="contribution-row-detail">
|
||||
<div style="padding:12px 0;font-size:0.9rem;line-height:1.6;color:#5a5a7a;">
|
||||
<?= nl2br(htmlspecialchars($news['content'])) ?>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-edit" onclick="editNews(<?= $news['news_id'] ?>, '<?= htmlspecialchars(addslashes($news['title']), ENT_QUOTES) ?>', '<?= htmlspecialchars(addslashes($news['content']), ENT_QUOTES) ?>', '<?= htmlspecialchars(addslashes($news['author_name']), ENT_QUOTES) ?>')">
|
||||
<i class="fa-solid fa-pen"></i> Bearbeiten
|
||||
</button>
|
||||
<button class="btn btn-delete" onclick="deleteNews(<?= $news['news_id'] ?>)">
|
||||
<i class="fa-solid fa-trash"></i> Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- Placeholder Tabs for future Features -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="tab-stats" class="page-tab-content" style="display:none;">
|
||||
<div class="placeholder-content">
|
||||
<i class="fa-solid fa-chart-bar"></i>
|
||||
<p>Statistiken und Analysen - geplant in zukünftiger Version.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-users" class="page-tab-content" style="display:none;">
|
||||
<div class="placeholder-content">
|
||||
<i class="fa-solid fa-users"></i>
|
||||
<p>Benutzerverwaltung - geplant in zukünftiger Version.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Loads JavaScript Dependencies -->
|
||||
<!-- ============================================================= -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Admin Configuration passed to JavaScript -->
|
||||
<!-- ============================================================= -->
|
||||
<script>
|
||||
const ADMIN_CONFIG = {
|
||||
id: <?= $municipality['municipality_id'] ?>,
|
||||
name: "<?= htmlspecialchars($municipality['name'], ENT_QUOTES) ?>",
|
||||
slug: "<?= htmlspecialchars($municipality['slug'], ENT_QUOTES) ?>",
|
||||
center: [<?= $municipality['center_lat'] ?>, <?= $municipality['center_lng'] ?>],
|
||||
zoom: <?= $municipality['default_zoom'] ?>,
|
||||
primaryColor: "<?= htmlspecialchars($municipality['primary_color'], ENT_QUOTES) ?>"
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Application Logic -->
|
||||
<script src="js/admin.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
<?php
|
||||
// -----------------------------------------------------------------
|
||||
// Login Page
|
||||
// -----------------------------------------------------------------
|
||||
function show_login_page($municipality, $error = null) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Moderation - Anmeldung</title>
|
||||
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-wrapper">
|
||||
<div class="login-box">
|
||||
<h1><i class="fa-solid fa-shield-halved"></i> Moderationsportal</h1>
|
||||
<p>Bitte geben Sie das Moderationspasswort ein.</p>
|
||||
<?php if ($error): ?>
|
||||
<div class="login-error"><i class="fa-solid fa-triangle-exclamation"></i> <?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="POST" action="admin.php?page=login">
|
||||
<input type="password" name="password" placeholder="Passwort" autofocus>
|
||||
<button type="submit"><i class="fa-solid fa-right-to-bracket"></i> Anmelden</button>
|
||||
</form>
|
||||
<div class="back-link"><i class="fa fa-arrow-left"></i> <a href="index.php">Zurück zum Bürgerportal</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
28
public/api/auth.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// =====================================================================
|
||||
// Admin Authentication Helper
|
||||
// Provides simple Password-based Session Authentication for the
|
||||
// Moderation Page. Reads Password from .env File.
|
||||
// ToDo: Replace with full User Authentication in Phase 3-3.
|
||||
// =====================================================================
|
||||
|
||||
// Checks if current Session is authenticated as Admin
|
||||
function is_admin() {
|
||||
return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
}
|
||||
|
||||
// Authenticates with Password, returns true on Success
|
||||
function admin_login($password) {
|
||||
$correct = getenv('ADMIN_PASSWORD');
|
||||
if ($correct && $password === $correct) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Logs out Admin Session
|
||||
function admin_logout() {
|
||||
$_SESSION['is_admin'] = false;
|
||||
session_destroy();
|
||||
}
|
||||
1182
public/api/contributions.php
Normal file
131
public/api/db.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
// =====================================================================
|
||||
// Database Helper Functions
|
||||
// Provides PDO Connection, JSON Response Helpers, Category Definitions
|
||||
// and shared miscellaneous Functions for all API Endpoints.
|
||||
// =====================================================================
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// JSON Response
|
||||
// Creates JSON Response including HTTP Status Code and HTTP Header
|
||||
// for every API Endpoint and terminates the Script.
|
||||
// ---------------------------------------------------------------------
|
||||
function json_response($data, $status_code = 200) {
|
||||
// Defines HTTP Status Code and HTTP Header
|
||||
// 1XX Informational, 2XX Successful, 3XX Redirection,
|
||||
// 4XX Client Error, 5XX Server Error
|
||||
http_response_code($status_code);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
// Converts PHP-Array to JSON-String
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Error Response
|
||||
// Creates standardized Error Responses with Error Message and HTTP Status
|
||||
// Code. Uses json_response() for consistent Formatting.
|
||||
// ---------------------------------------------------------------------
|
||||
function error_response($message, $status_code = 400) {
|
||||
json_response(['error' => $message], $status_code);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Validate Required Fields
|
||||
// Checks if specified Fields exist in the given Data Array and are
|
||||
// non-empty. Returns an Array of missing Field Names, or an empty
|
||||
// Array if all Fields are present.
|
||||
// ---------------------------------------------------------------------
|
||||
function validate_required($data, $fields) {
|
||||
$missing = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
// Checks if Fields exists in Data Array and are not empty
|
||||
if (!isset($data[$field]) || trim($data[$field]) === '') {
|
||||
$missing[] = $field;
|
||||
}
|
||||
}
|
||||
// Returns Array of missing Fields or emty Array
|
||||
return $missing;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Get POST Input
|
||||
// Reads POST Parameters. Returns an associative Array.
|
||||
// Fallback to JSON Request Body if no POST Data is present.
|
||||
// ---------------------------------------------------------------------
|
||||
function get_input() {
|
||||
// Checks for standard POST Requests
|
||||
if (!empty($_POST)) {
|
||||
return array_map('trim', $_POST);
|
||||
}
|
||||
|
||||
// Fall back for JSON POST Requests
|
||||
$json = file_get_contents('php://input');
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (is_array($data)) {
|
||||
return array_map('trim', $data);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Get PDO Connection
|
||||
// Returns PDO Instance wrapped in a Function to prevent global
|
||||
// Variable Dependencies in Endpoint Files.
|
||||
// ---------------------------------------------------------------------
|
||||
function get_db() {
|
||||
global $pdo;
|
||||
|
||||
if (!$pdo) {
|
||||
error_response('Database Connection failed.', 500);
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Category Definitions
|
||||
// Returns associative Array of Category Keys to Labels, Icons,
|
||||
// and Colors. Shared between Citizen Participation Portal and
|
||||
// Moderation Page.
|
||||
// ToDo: Move to Database Table.
|
||||
// ---------------------------------------------------------------------
|
||||
function get_categories() {
|
||||
return [
|
||||
'consumption' => ['label' => 'Geschäfte', 'faIcon' => 'fa-cart-shopping', 'color' => '#C00000'],
|
||||
'building' => ['label' => 'Bauen', 'faIcon' => 'fa-building', 'color' => '#E65100'],
|
||||
'energy' => ['label' => 'Energie', 'faIcon' => 'fa-bolt', 'color' => '#FFC000'],
|
||||
'environment' => ['label' => 'Umwelt', 'faIcon' => 'fa-seedling', 'color' => '#92D050'],
|
||||
'mobility' => ['label' => 'Mobilität', 'faIcon' => 'fa-bus', 'color' => '#0070C0'],
|
||||
'industry' => ['label' => 'Industrie', 'faIcon' => 'fa-industry', 'color' => '#7030A0'],
|
||||
'other' => ['label' => 'Sonstiges', 'faIcon' => 'fa-thumbtack', 'color' => '#7F7F7F'],
|
||||
];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Task Category Definitions
|
||||
// Returns associative Array of Task Category Keys to Labels, Icons,
|
||||
// and Colors. Shared between Citizen Participation Portal and
|
||||
// Moderation Page.
|
||||
// ToDo: Move to Database Table.
|
||||
// ---------------------------------------------------------------------
|
||||
function get_task_categories() {
|
||||
return [
|
||||
'repair' => ['label' => 'Reparatur', 'faIcon' => 'fa-wrench', 'color' => '#C00000'],
|
||||
'social' => ['label' => 'Nachbarschaft', 'faIcon' => 'fa-people-group', 'color' => '#E65100'],
|
||||
'safety' => ['label' => 'Sicherheit', 'faIcon' => 'fa-shield-halved', 'color' => '#FFC000'],
|
||||
'greenery' => ['label' => 'Grünpflege', 'faIcon' => 'fa-leaf', 'color' => '#92D050'],
|
||||
'cleanup' => ['label' => 'Sauberkeit', 'faIcon' => 'fa-broom', 'color' => '#0070C0'],
|
||||
'other_task' => ['label' => 'Sonstiges', 'faIcon' => 'fa-clipboard-check','color' => '#7F7F7F'],
|
||||
];
|
||||
}
|
||||
43
public/api/init.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// =====================================================================
|
||||
// Database Connection
|
||||
// =====================================================================
|
||||
|
||||
|
||||
// Reads Environment Configfile
|
||||
$envFile = __DIR__ . '/../../.env';
|
||||
if (file_exists($envFile)) {
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
list($key, $value) = array_map('trim', explode('=', $line, 2));
|
||||
putenv("$key=$value");
|
||||
}
|
||||
}
|
||||
|
||||
// Defines Environment Variables
|
||||
$host = getenv('POSTGRES_HOSTNAME');
|
||||
$port = getenv('POSTGRES_PORT');
|
||||
$db = getenv('POSTGRES_DB');
|
||||
$user = getenv('POSTGRES_USER');
|
||||
$pass = getenv('POSTGRES_PASSWORD');
|
||||
|
||||
// Output Buffering and Session Start
|
||||
ob_start();
|
||||
session_start();
|
||||
|
||||
// Initializes Database Connection
|
||||
try {
|
||||
$dsn = "pgsql:host=$host;dbname=$db;port=$port";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
]);
|
||||
|
||||
// Creates Error Message
|
||||
} catch (PDOException $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
BIN
public/assets/icon-municipality.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
public/assets/lock-solid-off-black.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/assets/logo-company.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/assets/logo-municipality.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
public/assets/scale-balanced-solid-off-black.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/assets/shield-halved-solid-off-black.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/assets/user-group-solid-off-black.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/assets/user-group-solid-off-white.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
39
public/imprint.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/api/db.php';
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
||||
$municipality = $stmt->fetch();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Impressum — <?= htmlspecialchars($municipality['name']) ?></title>
|
||||
<link rel="icon" href="assets/scale-balanced-solid-off-black.png" type="image/png">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="page-header-inner">
|
||||
<h1><i class="fa-solid fa-scale-balanced"></i> Impressum</h1>
|
||||
<div class="page-header-nav">
|
||||
<a href="index.php"><i class="fa-solid fa-arrow-left"></i> Zurück zur Karte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-container">
|
||||
<div class="page-content-box">
|
||||
<div class="dev-notice">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
Dieses Portal befindet sich in der Entwicklung und wurde nicht offiziell beauftragt. Das Impressum wird mit der offiziellen Inbetriebnahme hier hinzugefügt.
|
||||
</div>
|
||||
<h2>Impressum</h2>
|
||||
<p>Das Impressum wird hier hinzugefügt, sobald das Portal in den Produktivbetrieb geht.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
452
public/index.php
Normal file
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
// =====================================================================
|
||||
// WebGIS Citizen Participation Portal — Main Page
|
||||
// Loads Municipality Configuration from the Database.
|
||||
// Renders Leaflet Map Interface including Leaflet Plugins
|
||||
// =====================================================================
|
||||
|
||||
require_once __DIR__ . '/api/db.php';
|
||||
require_once __DIR__ . '/api/auth.php';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Loads Municipality Configuration
|
||||
// -----------------------------------------------------------------
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
||||
$municipality = $stmt->fetch();
|
||||
|
||||
if (!$municipality) {
|
||||
http_response_code(404);
|
||||
echo "<!DOCTYPE html><html><body><h1>404 — Municipality not listed in Database.</h1></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Loads News for Sidebar
|
||||
$stmt = $pdo->prepare("SELECT * FROM news WHERE municipality_id = :mid ORDER BY published_at DESC LIMIT 10");
|
||||
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
||||
$news_items = $stmt->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></title>
|
||||
<link rel="icon" href="assets/user-group-solid-off-black.png" type="image/png">
|
||||
<meta name="description" content="Bürgerbeteiligungsportal. Hinweise und Vorschläge auf der Karte eintragen.">
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Loads CSS Dependencies -->
|
||||
<!-- ============================================================= -->
|
||||
|
||||
<!-- Leaflet -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
|
||||
|
||||
<!-- Geoman Drawing Tools -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.17.0/dist/leaflet-geoman.css">
|
||||
|
||||
<!-- Leaflet Sidebar -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet-sidebar-v2@3.2.3/css/leaflet-sidebar.min.css">
|
||||
|
||||
<!-- Leaflet Fullscreen -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.fullscreen/3.0.2/Control.FullScreen.css">
|
||||
|
||||
<!-- Leaflet Geocoder for Address Search -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder@2.4.0/dist/Control.Geocoder.css">
|
||||
|
||||
<!-- Leaflet Polyline Measurement Tool -->
|
||||
<!-- <link rel="stylesheet" href="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.css"> -->
|
||||
|
||||
<!-- Font Awesome for Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
<!-- Application Styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Shepherd.js Onboarding Tour -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/css/shepherd.css">
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Municipality Theme loaded from Database -->
|
||||
<!-- ============================================================= -->
|
||||
<style>
|
||||
:root {
|
||||
--color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>;
|
||||
--color-primary-light: <?= htmlspecialchars($municipality['primary_color']) ?>22;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="portal-page">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================================= -->
|
||||
<header id="app-header">
|
||||
<div class="header-left">
|
||||
<?php if (!empty($municipality['logo_path'])): ?>
|
||||
<img src="assets/user-group-solid-off-white.png" alt="user-group-solid-off-white" class="header-logo" onerror="this.style.display='none'">
|
||||
<?php endif; ?>
|
||||
<h1 class="header-title">Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></h1>
|
||||
</div>
|
||||
|
||||
<nav class="header-nav">
|
||||
<button class="nav-btn" onclick="showInfoModal()">
|
||||
<i class="fa-solid fa-circle-info"></i>
|
||||
<span class="nav-label">Informationen</span>
|
||||
</button>
|
||||
<a href="privacy.php" class="nav-btn" target="_blank">
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
<span class="nav-label">Datenschutz</span>
|
||||
</a>
|
||||
<a href="imprint.php" class="nav-btn" target="_blank">
|
||||
<i class="fa-solid fa-scale-balanced"></i>
|
||||
<span class="nav-label">Impressum</span>
|
||||
</a>
|
||||
<a href="admin.php" class="nav-btn nav-btn-admin" title="Moderationsbereich" target="_blank">
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Hamburger Menu -->
|
||||
<button class="header-menu-toggle" onclick="toggleMobileNav()">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Map Container with Sidebar -->
|
||||
<!-- ============================================================= -->
|
||||
<main id="app-main">
|
||||
|
||||
<!-- Leaflet Sidebar -->
|
||||
<div id="sidebar" class="leaflet-sidebar collapsed">
|
||||
|
||||
<!-- Sidebar Tab Icons -->
|
||||
<div class="leaflet-sidebar-tabs">
|
||||
<ul role="tablist">
|
||||
<li><a href="#tab-contributions" role="tab" title="Hinweise"><i class="fa-solid fa-clipboard-list"></i></a></li>
|
||||
<li><a href="#tab-tasks" role="tab" title="Aufgaben"><i class="fa-solid fa-clipboard-check"></i></a></li>
|
||||
<li><a href="#tab-list" role="tab" title="Beiträge"><i class="fa-solid fa-list"></i></a></li>
|
||||
<li><a href="#tab-news" role="tab" title="Neuigkeiten"><i class="fa-solid fa-newspaper"></i></a></li>
|
||||
<li><a href="#tab-help" role="tab" title="Hilfe"><i class="fa-solid fa-circle-question"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Tab Content -->
|
||||
<div class="leaflet-sidebar-content">
|
||||
|
||||
<!-- Contributions Tab -->
|
||||
<div class="leaflet-sidebar-pane" id="tab-contributions">
|
||||
<h2 class="leaflet-sidebar-header">
|
||||
Hinweise
|
||||
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
||||
</h2>
|
||||
<div class="sidebar-body">
|
||||
<p>Verwenden Sie die Karte, um <strong>Hinweise</strong> für die Stadtverwaltung hinzuzufügen oder bestehende Hinweise zu betrachten, bewerten und kommentieren</p>
|
||||
|
||||
<h3>Kategorien</h3>
|
||||
<div id="category-filter">
|
||||
<!-- populated by app.js -->
|
||||
</div>
|
||||
|
||||
<p id="stats-container"></p>
|
||||
<!-- populated by app.js -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tasks Tab -->
|
||||
<div class="leaflet-sidebar-pane" id="tab-tasks">
|
||||
<h2 class="leaflet-sidebar-header">
|
||||
Aufgaben
|
||||
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
||||
</h2>
|
||||
<div class="sidebar-body">
|
||||
<p>Verwenden Sie die Karte, um <strong>Aufgaben</strong> für die Gemeinschaft hinzuzufügen oder bestehende Aufgaben zu betrachten, bewerten und kommentieren.</p>
|
||||
|
||||
<h3>Kategorien</h3>
|
||||
<div id="task-category-filter">
|
||||
<!-- populated by app.js -->
|
||||
</div>
|
||||
<p id="task-stats-container">
|
||||
<!-- populated by app.js -->
|
||||
</p>
|
||||
|
||||
<div class="task-filter-row">
|
||||
<select id="task-status-filter" class="form-input" onchange="updateTasksList()" style="margin-bottom:8px;">
|
||||
<option value="open">Offene Aufgaben</option>
|
||||
<option value="all">Alle Aufgaben</option>
|
||||
<option value="completed">Wartend auf Prüfung</option>
|
||||
<option value="verified">Erledigte Aufgaben</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Leaderboard -->
|
||||
<div id="leaderboard-container" class="leaderboard-box">
|
||||
<h3>Rangliste</h3>
|
||||
<div id="leaderboard-list"></div>
|
||||
<button class="btn btn-secondary leaderboard-more-btn" onclick="showFullLeaderboard()">
|
||||
Vollständige Rangliste
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Tab -->
|
||||
<div class="leaflet-sidebar-pane" id="tab-list">
|
||||
<h2 class="leaflet-sidebar-header">
|
||||
Beiträge
|
||||
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
||||
</h2>
|
||||
<div class="sidebar-body">
|
||||
<div class="list-search">
|
||||
<input type="text" id="list-search-input" placeholder="Beiträge durchsuchen..." class="form-input">
|
||||
</div>
|
||||
<div id="contributions-list">
|
||||
<!-- populated by app.js -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- News Tab -->
|
||||
<div class="leaflet-sidebar-pane" id="tab-news">
|
||||
<h2 class="leaflet-sidebar-header">
|
||||
Neuigkeiten
|
||||
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
||||
</h2>
|
||||
<div class="sidebar-body">
|
||||
<div class="list-search">
|
||||
<input type="text" id="news-search-input" placeholder="Neuigkeiten durchsuchen..." class="form-input" oninput="filterNews()">
|
||||
</div>
|
||||
<div id="news-list">
|
||||
<?php if (empty($news_items)): ?>
|
||||
<p style="text-align:center;color:#999;padding:20px;">Noch keine Neuigkeiten veröffentlicht.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($news_items as $news): ?>
|
||||
<div class="news-item"
|
||||
data-title="<?= htmlspecialchars(strtolower($news['title'])) ?>"
|
||||
data-content="<?= htmlspecialchars(strtolower($news['content'])) ?>"
|
||||
data-author="<?= htmlspecialchars(strtolower($news['author_name'])) ?>">
|
||||
<h3><?= htmlspecialchars($news['title']) ?></h3>
|
||||
<p><?= nl2br(htmlspecialchars($news['content'])) ?></p>
|
||||
<span class="news-date">
|
||||
<?= htmlspecialchars($news['author_name']) ?>
|
||||
· <?= date('d.m.Y', strtotime($news['published_at'])) ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Tab -->
|
||||
<div class="leaflet-sidebar-pane" id="tab-help">
|
||||
<h2 class="leaflet-sidebar-header">
|
||||
Hilfe
|
||||
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
||||
</h2>
|
||||
<div class="sidebar-body">
|
||||
<h3><i class="fa-solid fa-book"></i> Interaktive Anleitung</h3>
|
||||
<p>Klicken Sie unten auf Tutorial starten um Schritt für Schritt durch die Kernfunktionen der Mitmachkarte geführt zu werden.</p>
|
||||
<p>
|
||||
<button class="btn btn-primary" onclick="if(typeof restartOnboarding==='function'){sidebar.close();restartOnboarding()}" style="font-size:0.85rem;">
|
||||
<i class="fa-solid fa-route"></i> Tutorial starten
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<h3><i class="fa-solid fa-map-location-dot"></i> Karte bedienen</h3>
|
||||
<p>Verschieben Sie die Karte per Mausklick und Ziehen. Zoomen Sie mit dem Mausrad oder den Zoom-Buttons.</p>
|
||||
|
||||
<h3><i class="fa-solid fa-location-dot"></i> Beitrag hinzufügen</h3>
|
||||
<p>Verwenden Sie die Zeichenwerkzeuge rechts, um Hinweise, Anregungen und Vorschläge auf der Mitmachkarte als Punkte, Linien oder Flächen hinzuzufügen.</p>
|
||||
|
||||
<h3><i class="fa-solid fa-thumbs-up"></i> Bewerten</h3>
|
||||
<p>Klicken Sie auf bestehende Beiträge und nutzen Sie die Bewertungsfunktion, um Ihre Meinung zu äußern.</p>
|
||||
|
||||
<h3><i class="fa-solid fa-comments"></i> Kommentieren</h3>
|
||||
<p>Gerne können Sie Ihre Meinung zu bestehenden Beiträgen auch durch die Kommentarfunktion äußern.</p>
|
||||
|
||||
<h3><i class="fa-solid fa-clipboard-check"></i> Aufgaben erledigen</h3>
|
||||
<p>Klicken Sie auf eine offene Aufgabe und melden Sie die Erledigung mit einem Foto-Nachweis.</p>
|
||||
|
||||
<h3><i class="fa-solid fa-magnifying-glass"></i> Suchen</h3>
|
||||
<p>Verwenden Sie die Adresssuche rechts, um schnell den richtigen Ort auf der Mitmachkarte zu finden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Leaflet Map -->
|
||||
<div id="map"></div>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Footer -->
|
||||
<!-- ============================================================= -->
|
||||
<footer id="app-footer">
|
||||
<span class="dev-warning">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> Demoversion - nicht in Rücksprache mit der Stadt Lohne entwickelt! Alle Beitrage, Kommentare und Personen sind frei erfunden.
|
||||
</span>
|
||||
<div class="footer-content">
|
||||
<span class="footer-text">© <a href="https://endex-geodaten.de" target="_blank" style="color:inherit;">endex GmbH</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Welcome Modal shown on first Visit -->
|
||||
<!-- ============================================================= -->
|
||||
<div id="welcome-modal" class="modal-overlay" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h2><i class="fa-solid fa-hand-wave"></i> Willkommen!</h2>
|
||||
<p>Herzlich willkommen beim Bürgerbeteiligungsportal <strong><?= htmlspecialchars($municipality['name']) ?></strong>.</p>
|
||||
<p>Hier können Sie:</p>
|
||||
<ul>
|
||||
<li>Hinweise und Verbesserungsvorschläge für die Stadtverwaltung hinzufügen</li>
|
||||
<li>Bestehende Beiträge der Bürgerschaft betrachten und bewerten</li>
|
||||
</ul>
|
||||
<p style="background:#fff3cd;padding:10px;border-radius:6px;border:1px solid #ffc107;font-size:0.85rem;color:#856404;">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> <strong>Hinweis:</strong> Demoversion - nicht in Rücksprache mit der Stadt Lohne entwickelt! Alle Beitrage, Kommentare und Personen sind frei erfunden.
|
||||
</p>
|
||||
<p>Zum Hinzufügen von Beiträgen geben Sie bitte zunächst Ihren Namen ein.</p> <div class="modal-actions">
|
||||
<button class="btn btn-primary" onclick="closeWelcomeAndShowLogin()">Loslegen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Login Modal for Identification -->
|
||||
<!-- ToDo's: User Authentification and Administration -->
|
||||
<!-- ============================================================= -->
|
||||
<div id="login-modal" class="modal-overlay" style="display:none;">
|
||||
<div class="modal-content modal-small">
|
||||
<h2><i class="fa-solid fa-user"></i> Anmelden</h2>
|
||||
<p>Bitte geben Sie Ihren Namen ein, um Beiträge hinzufügen und abstimmen zu können.</p>
|
||||
<div class="form-group">
|
||||
<label for="user-name-input">Ihr Name</label>
|
||||
<input type="text" id="user-name-input" class="form-input" placeholder="Vor- und Nachname">
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="skipLogin()">Gastuser</button>
|
||||
<button class="btn btn-primary" onclick="submitLogin()">Anmelden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Create Contribution Modal -->
|
||||
<!-- ============================================================= -->
|
||||
<div id="create-modal" class="modal-overlay" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h2><i class="fa-solid fa-plus-circle"></i> Beitrag</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="create-category">Kategorie</label>
|
||||
<select id="create-category" class="form-input">
|
||||
<option value="">— Bitte wählen —</option>
|
||||
<!-- Categories populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="create-title">Titel</label>
|
||||
<input type="text" id="create-title" class="form-input" placeholder="Kurze Beschreibung des Anliegens">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="create-description">Beschreibung</label>
|
||||
<textarea id="create-description" class="form-input" rows="4" placeholder="Detaillierte Beschreibung (optional)"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Photo Upload -->
|
||||
<div class="form-group">
|
||||
<label for="create-photo"></i> Foto</label>
|
||||
<input type="file" id="create-photo" class="form-input" accept="image/jpeg,image/png,image/gif,image/webp">
|
||||
<div id="photo-preview" style="margin-top:8px;display:none;">
|
||||
<img id="photo-preview-img" style="max-width:100%;max-height:200px;border-radius:6px;border:1px solid var(--color-border);">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="create-geom">
|
||||
<input type="hidden" id="create-geom-type">
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="cancelCreate()">Abbrechen</button>
|
||||
<button class="btn btn-primary" onclick="submitCreate()">Beitrag einreichen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Loads JavaScript Dependencies -->
|
||||
<!-- ============================================================= -->
|
||||
|
||||
<!-- Leaflet 1.9.4 -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||
|
||||
<!-- Geoman Drawing Tools -->
|
||||
<script src="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.17.0/dist/leaflet-geoman.min.js"></script>
|
||||
|
||||
<!-- Leaflet Sidebar v2 -->
|
||||
<script src="https://unpkg.com/leaflet-sidebar-v2@3.2.3/js/leaflet-sidebar.min.js"></script>
|
||||
|
||||
<!-- Leaflet Fullscreen -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.fullscreen/3.0.2/Control.FullScreen.min.js"></script>
|
||||
|
||||
<!-- Leaflet Geocoder (Address Search) -->
|
||||
<script src="https://unpkg.com/leaflet-control-geocoder@2.4.0/dist/Control.Geocoder.min.js"></script>
|
||||
|
||||
<!-- Leaflet PolylineMeasure -->
|
||||
<!-- <script src="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.js"></script> -->
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.14.0/dist/sweetalert2.all.min.js"></script>
|
||||
|
||||
<!-- Shepherd.js Library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js"></script>
|
||||
|
||||
<!-- Onboarding Logic -->
|
||||
<script src="js/onboarding.js"></script>
|
||||
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Municipality Configuration passed to JavaScript -->
|
||||
<!-- ============================================================= -->
|
||||
<script>
|
||||
// Municipality Configuration from Database
|
||||
const MUNICIPALITY = {
|
||||
id: <?= $municipality['municipality_id'] ?>,
|
||||
name: "<?= htmlspecialchars($municipality['name'], ENT_QUOTES) ?>",
|
||||
slug: "<?= htmlspecialchars($municipality['slug'], ENT_QUOTES) ?>",
|
||||
center: [<?= $municipality['center_lat'] ?>, <?= $municipality['center_lng'] ?>],
|
||||
zoom: <?= $municipality['default_zoom'] ?>,
|
||||
primaryColor: "<?= htmlspecialchars($municipality['primary_color'], ENT_QUOTES) ?>"
|
||||
};
|
||||
|
||||
// Category Definitions from Database
|
||||
const CATEGORIES = <?= json_encode(get_categories(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
const TASK_CATEGORIES = <?= json_encode(get_task_categories(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
// Admin Status from PHP Session
|
||||
const IS_ADMIN = <?= (function_exists('is_admin') && is_admin()) ? 'true' : 'false' ?>;
|
||||
</script>
|
||||
|
||||
<!-- Application Logic -->
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
637
public/js/admin.js
Normal file
@@ -0,0 +1,637 @@
|
||||
// =====================================================================
|
||||
// WebGIS Moderation Portal — Application Logic
|
||||
// Initializes Map Preview, loads Contributions from the API,
|
||||
// handles CRUD Workflow, sorting and filtering for Contributions,
|
||||
// Comments and News, and manages all UI Interactions
|
||||
//
|
||||
// Depends on: ADMIN_CONFIG Object set in Moderation Page
|
||||
// =====================================================================
|
||||
|
||||
// =====================================================================
|
||||
// Block 0: Configuration and Application State
|
||||
// =====================================================================
|
||||
|
||||
// API Endpoint as relative Path
|
||||
const API_URL = 'api/contributions.php';
|
||||
|
||||
// =====================================================================
|
||||
// Block 1: Page Tab Navigation
|
||||
// =====================================================================
|
||||
|
||||
// Restores active Tab after Page Reload
|
||||
const savedTab = sessionStorage.getItem('admin_active_tab');
|
||||
if (savedTab) {
|
||||
// Delays to ensure DOM is ready
|
||||
setTimeout(function () {
|
||||
const tabBtn = document.querySelector('.page-tab[onclick*="' + savedTab + '"]');
|
||||
if (tabBtn) tabBtn.click();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
// Page Tab Navigation
|
||||
function showPageTab(tabName) {
|
||||
// Saves active Tab for Persistence after Reload
|
||||
sessionStorage.setItem('admin_active_tab', tabName);
|
||||
|
||||
document.querySelectorAll('.page-tab-content').forEach(function (el) {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
|
||||
// Deactivates all Tab Buttons
|
||||
document.querySelectorAll('.page-tab').forEach(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// Shows selected Tab and activates Button
|
||||
document.getElementById('tab-' + tabName).style.display = 'block';
|
||||
event.currentTarget.classList.add('active');
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 2: Collapsible Rows for Contributions and Comments
|
||||
// =====================================================================
|
||||
function toggleRow(row) {
|
||||
const wasOpen = row.classList.contains('open');
|
||||
|
||||
// Closes all open Rows
|
||||
document.querySelectorAll('.contribution-row.open').forEach(function (el) {
|
||||
el.classList.remove('open');
|
||||
});
|
||||
|
||||
// Toggles clicked Row
|
||||
if (!wasOpen) {
|
||||
row.classList.add('open');
|
||||
|
||||
// Loads Map Preview if not already loaded
|
||||
const mapDiv = row.querySelector('.detail-map');
|
||||
if (mapDiv && !mapDiv.dataset.loaded) {
|
||||
loadMapPreview(mapDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 3: Details Slider for Maps and Photos
|
||||
// =====================================================================
|
||||
function slideDetail(contributionId, direction) {
|
||||
const slider = document.getElementById('slider-' + contributionId);
|
||||
if (!slider) return;
|
||||
|
||||
const slides = slider.querySelectorAll('.detail-slide');
|
||||
let activeIndex = -1;
|
||||
|
||||
// Finds active Slide
|
||||
slides.forEach(function (slide, i) {
|
||||
if (slide.style.display !== 'none') activeIndex = i;
|
||||
});
|
||||
|
||||
// Calculates next Slide Index
|
||||
const nextIndex = (activeIndex + direction + slides.length) % slides.length;
|
||||
|
||||
// Switches Slides
|
||||
slides.forEach(function (slide) { slide.style.display = 'none'; });
|
||||
slides[nextIndex].style.display = 'block';
|
||||
|
||||
// Loads Map if switching to Map Slide
|
||||
if (slides[nextIndex].dataset.slide === 'map') {
|
||||
const mapDiv = slides[nextIndex].querySelector('.detail-map');
|
||||
if (mapDiv && !mapDiv.dataset.loaded) {
|
||||
loadMapPreview(mapDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 4: Map Preview (Leaflet Mini Map per Contribution)
|
||||
// =====================================================================
|
||||
|
||||
// Erstellt eine Leaflet-Mini-Map in einem Beitrags-Detail-Container.
|
||||
// Lädt alle Beiträge via API und zeigt die Geometrie des entsprechenden Beitrags.
|
||||
// Markiert die Map als geladen (data-loaded="true"), um doppeltes Laden zu verhindern.
|
||||
function loadMapPreview(mapDiv) {
|
||||
const contributionId = mapDiv.dataset.contributionId;
|
||||
|
||||
// Fetches all Contributions to find the Geometry
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'read');
|
||||
formData.append('municipality_id', ADMIN_CONFIG.id);
|
||||
formData.append('status', 'all');
|
||||
|
||||
fetch(API_URL, { method: 'POST', body: formData })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (!data.features) return;
|
||||
|
||||
// Finds specific Contribution
|
||||
const feature = data.features.find(function (f) {
|
||||
return f.properties.contribution_id == contributionId;
|
||||
});
|
||||
|
||||
if (!feature) {
|
||||
mapDiv.innerHTML = '<div style="padding:20px;color:#999;text-align:center;font-size:0.8rem;">Geometrie nicht gefunden.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Creates Leaflet Mini Map
|
||||
const miniMap = L.map(mapDiv, {
|
||||
zoomControl: false,
|
||||
attributionControl: false,
|
||||
dragging: true,
|
||||
scrollWheelZoom: false
|
||||
});
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
maxZoom: 20
|
||||
}).addTo(miniMap);
|
||||
|
||||
// Adds Geometry to Mini Map
|
||||
const geojsonLayer = L.geoJSON(feature, {
|
||||
style: {
|
||||
color: ADMIN_CONFIG.primaryColor,
|
||||
weight: 3,
|
||||
fillOpacity: 0.2
|
||||
},
|
||||
pointToLayer: function (f, latlng) {
|
||||
return L.circleMarker(latlng, {
|
||||
radius: 8,
|
||||
color: '#ffffff',
|
||||
weight: 2,
|
||||
fillColor: ADMIN_CONFIG.primaryColor,
|
||||
fillOpacity: 0.9
|
||||
});
|
||||
}
|
||||
}).addTo(miniMap);
|
||||
|
||||
// Fits Map to Geometry Bounds
|
||||
const bounds = geojsonLayer.getBounds();
|
||||
if (bounds.isValid()) {
|
||||
miniMap.fitBounds(bounds, { padding: [25, 25], maxZoom: 17 });
|
||||
} else {
|
||||
miniMap.setView(ADMIN_CONFIG.center, 15);
|
||||
}
|
||||
|
||||
mapDiv.dataset.loaded = 'true';
|
||||
})
|
||||
.catch(function () {
|
||||
mapDiv.innerHTML = '<div style="padding:20px;color:#999;text-align:center;font-size:0.8rem;">Karte nicht verfügbar.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 5: Contributions Filter and Sorting
|
||||
// =====================================================================
|
||||
|
||||
// Filters Contributions
|
||||
let currentFilter = 'all';
|
||||
function filterByStatus(status, tabButton) {
|
||||
currentFilter = status;
|
||||
|
||||
// Updates active Tab
|
||||
document.querySelectorAll('.filter-tab').forEach(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
tabButton.classList.add('active');
|
||||
|
||||
// Shows or Hides Contribution Rows
|
||||
let visibleCount = 0;
|
||||
document.querySelectorAll('#contributions-container .contribution-row').forEach(function (row) {
|
||||
if (status === 'all' || row.dataset.status === status) {
|
||||
row.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Updates Count Display
|
||||
document.getElementById('visible-count').textContent = visibleCount + ' Beiträge';
|
||||
}
|
||||
|
||||
|
||||
// Sorts Contributions
|
||||
function sortContributions(sortBy) {
|
||||
const container = document.getElementById('contributions-container');
|
||||
const rows = Array.from(container.querySelectorAll('.contribution-row'));
|
||||
|
||||
rows.sort(function (a, b) {
|
||||
if (sortBy === 'date-desc') return new Date(b.dataset.date) - new Date(a.dataset.date);
|
||||
if (sortBy === 'date-asc') return new Date(a.dataset.date) - new Date(b.dataset.date);
|
||||
if (sortBy === 'category') return a.dataset.category.localeCompare(b.dataset.category);
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Reappends sorted Rows
|
||||
rows.forEach(function (row) { container.appendChild(row); });
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 6: Comments Filter and Sorting
|
||||
// =====================================================================
|
||||
|
||||
// Filters Comments
|
||||
function filterCommentsByStatus(status, tabButton) {
|
||||
|
||||
// Updates active Tab
|
||||
document.querySelectorAll('#comment-filter-tabs .filter-tab').forEach(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
tabButton.classList.add('active');
|
||||
|
||||
// Shows or Hides Comments Rows
|
||||
let visibleCount = 0;
|
||||
document.querySelectorAll('.comment-mod-row').forEach(function (row) {
|
||||
if (status === 'all' || row.dataset.status === status) {
|
||||
row.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
// Updates Count Display
|
||||
document.getElementById('comment-visible-count').textContent = visibleCount + ' Kommentare';
|
||||
}
|
||||
|
||||
|
||||
// Sorts Comments
|
||||
function sortCommentRows(sortBy) {
|
||||
const container = document.getElementById('comments-mod-container');
|
||||
const rows = Array.from(container.querySelectorAll('.comment-mod-row'));
|
||||
|
||||
rows.sort(function (a, b) {
|
||||
if (sortBy === 'date-desc') return new Date(b.dataset.date) - new Date(a.dataset.date);
|
||||
if (sortBy === 'date-asc') return new Date(a.dataset.date) - new Date(b.dataset.date);
|
||||
if (sortBy === 'contribution') return a.dataset.contribution.localeCompare(b.dataset.contribution);
|
||||
return 0;
|
||||
});
|
||||
// Reappends sorted Rows
|
||||
rows.forEach(function (row) { container.appendChild(row); });
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 7: Helper Functions
|
||||
// =====================================================================
|
||||
|
||||
// Sends a POST request to API
|
||||
// promise-based instead of callback-based
|
||||
function apiCall(data) {
|
||||
const formData = new FormData();
|
||||
for (const key in data) {
|
||||
formData.append(key, data[key]);
|
||||
}
|
||||
|
||||
return fetch(API_URL, { method: 'POST', body: formData })
|
||||
.then(function (r) { return r.json(); });
|
||||
}
|
||||
|
||||
|
||||
// Escapes HTML to prevent Cross-Site Scripting (XSS) in Popups and Lists
|
||||
function escapeHtml(text) {
|
||||
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(text));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Block 8: CRUD Operations for Contributions
|
||||
// =====================================================================
|
||||
|
||||
// STATUS: Changes Contribution Status
|
||||
function changeStatus(contributionId, newStatus) {
|
||||
const labels = { approved: 'freigeben', rejected: 'ablehnen', pending: 'zurücksetzen' };
|
||||
|
||||
Swal.fire({
|
||||
title: 'Beitrag ' + labels[newStatus] + '?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ja',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'update',
|
||||
contribution_id: contributionId,
|
||||
status: newStatus
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
// Reloads Page to reflect Changes
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// UPDATE: Edits existing Contributions
|
||||
function editContribution(contributionId, currentTitle, currentDescription) {
|
||||
Swal.fire({
|
||||
title: 'Beitrag bearbeiten',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Titel</label>' +
|
||||
'<input id="swal-title" class="swal2-input" style="margin:0;width:100%;" value="' + escapeHtml(currentTitle) + '">' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Beschreibung</label>' +
|
||||
'<textarea id="swal-description" class="swal2-textarea" style="margin:0;width:100%;">' + escapeHtml(currentDescription) + '</textarea>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Speichern',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor,
|
||||
preConfirm: function () {
|
||||
return {
|
||||
title: document.getElementById('swal-title').value.trim(),
|
||||
description: document.getElementById('swal-description').value.trim()
|
||||
};
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'update',
|
||||
contribution_id: contributionId,
|
||||
title: result.value.title,
|
||||
description: result.value.description
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gespeichert!', 'Beitrag wurde aktualisiert.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// DELETE: Deletes existing Contributions
|
||||
function deleteContribution(contributionId) {
|
||||
Swal.fire({
|
||||
title: 'Beitrag löschen?',
|
||||
text: 'Diese Aktion kann nicht rückgängig gemacht werden.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Beitrag löschen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: '#c62828'
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'delete',
|
||||
contribution_id: contributionId
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gelöscht!', 'Beitrag wurde gelöscht.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Block 9: CRUD Operations for Comments
|
||||
// =====================================================================
|
||||
|
||||
// STATUS: Changes Comment Status
|
||||
function changeCommentStatus(commentId, newStatus) {
|
||||
const labels = { approved: 'akzeptieren', rejected: 'ablehnen', pending: 'zurücksetzen' };
|
||||
|
||||
Swal.fire({
|
||||
title: 'Kommentar ' + labels[newStatus] + '?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ja',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'update_comment',
|
||||
comment_id: commentId,
|
||||
status: newStatus
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// UPDATE: Edits existing Comments
|
||||
function editModComment(commentId, currentContent) {
|
||||
Swal.fire({
|
||||
title: 'Kommentar bearbeiten',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
|
||||
'<textarea id="swal-comment-content" class="swal2-textarea" style="margin:0;width:100%;">' + escapeHtml(currentContent) + '</textarea>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Speichern',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor,
|
||||
preConfirm: function () {
|
||||
return { content: document.getElementById('swal-comment-content').value.trim() };
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'update_comment',
|
||||
comment_id: commentId,
|
||||
content: result.value.content
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gespeichert!', 'Kommentar wurde aktualisiert.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// DELETE: Deletes existing Comments
|
||||
function deleteModComment(commentId) {
|
||||
Swal.fire({
|
||||
title: 'Kommentar löschen?',
|
||||
text: 'Diese Aktion kann nicht rückgängig gemacht werden.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Löschen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: '#c62828'
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'delete_comment',
|
||||
comment_id: commentId
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gelöscht!', 'Kommentar wurde entfernt.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// Block 10: CRUD Operations for News
|
||||
// =====================================================================
|
||||
|
||||
// CREATE: Submits new News Article
|
||||
function createNews() {
|
||||
Swal.fire({
|
||||
title: 'Neuigkeit hinzufügen',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Titel</label>' +
|
||||
'<input id="swal-news-title" class="swal2-input" style="margin:0;width:100%;" placeholder="Titel der Neuigkeit">' +
|
||||
'</div>' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
|
||||
'<textarea id="swal-news-content" class="swal2-textarea" style="margin:0;width:100%;" placeholder="Neuigkeit verfassen..."></textarea>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Autor</label>' +
|
||||
'<input id="swal-news-author" class="swal2-input" style="margin:0;width:100%;" value="Stadtverwaltung">' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Veröffentlichen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor,
|
||||
preConfirm: function () {
|
||||
const title = document.getElementById('swal-news-title').value.trim();
|
||||
const content = document.getElementById('swal-news-content').value.trim();
|
||||
const author = document.getElementById('swal-news-author').value.trim() || 'Stadtverwaltung';
|
||||
if (!title || !content) {
|
||||
Swal.showValidationMessage('Titel und Inhalt sind Pflichtfelder.');
|
||||
return false;
|
||||
}
|
||||
return { title, content, author_name: author };
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'create_news',
|
||||
municipality_id: ADMIN_CONFIG.id,
|
||||
title: result.value.title,
|
||||
content: result.value.content,
|
||||
author_name: result.value.author_name
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Veröffentlicht!', 'Neuigkeit wurde veröffentlicht.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// UPDATE: Edits existing News
|
||||
function editNews(newsId, currentTitle, currentContent, currentAuthor) {
|
||||
Swal.fire({
|
||||
title: 'Neuigkeit bearbeiten',
|
||||
html:
|
||||
'<div style="text-align:left;">' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Titel</label>' +
|
||||
'<input id="swal-news-title" class="swal2-input" style="margin:0;width:100%;" value="' + escapeHtml(currentTitle) + '">' +
|
||||
'</div>' +
|
||||
'<div style="margin-bottom:12px;">' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Inhalt</label>' +
|
||||
'<textarea id="swal-news-content" class="swal2-textarea" style="margin:0;width:100%;">' + escapeHtml(currentContent) + '</textarea>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label style="display:block;font-weight:600;font-size:1.15rem;margin-bottom:4px;">Autor</label>' +
|
||||
'<input id="swal-news-author" class="swal2-input" style="margin:0;width:100%;" value="' + escapeHtml(currentAuthor) + '">' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Speichern',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: ADMIN_CONFIG.primaryColor,
|
||||
preConfirm: function () {
|
||||
return {
|
||||
title: document.getElementById('swal-news-title').value.trim(),
|
||||
content: document.getElementById('swal-news-content').value.trim(),
|
||||
author_name: document.getElementById('swal-news-author').value.trim() || 'Stadtverwaltung'
|
||||
};
|
||||
}
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'update_news',
|
||||
news_id: newsId,
|
||||
title: result.value.title,
|
||||
content: result.value.content,
|
||||
author_name: result.value.author_name
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gespeichert!', 'Neuigkeit wurde aktualisiert.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// DELETE: Deletes existing News
|
||||
function deleteNews(newsId) {
|
||||
Swal.fire({
|
||||
title: 'Neuigkeit löschen?',
|
||||
text: 'Diese Aktion kann nicht rückgängig gemacht werden.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Löschen',
|
||||
cancelButtonText: 'Abbrechen',
|
||||
confirmButtonColor: '#c62828'
|
||||
}).then(function (result) {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
apiCall({
|
||||
action: 'delete_news',
|
||||
news_id: newsId
|
||||
}).then(function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
return;
|
||||
}
|
||||
Swal.fire('Gelöscht!', 'Neuigkeit wurde gelöscht.', 'success')
|
||||
.then(function () { location.reload(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
1244
public/js/app.js
Normal file
277
public/js/onboarding.js
Normal file
@@ -0,0 +1,277 @@
|
||||
// =====================================================================
|
||||
// WebGIS Citizen Participation Portal — Onboarding Tour
|
||||
// Guides Users through the Participation Portal
|
||||
// =====================================================================
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Block 1: Onboarding Configuration
|
||||
// =================================================================
|
||||
|
||||
// ONBOARDING_MODE — Controls when the Tutorial is shown:
|
||||
const ONBOARDING_MODE = 'once';
|
||||
// 'once' — Shown on first Visit, stored in localStorage
|
||||
// 'session' — Shown per Browser Session, stored in sessionStorage
|
||||
// 'always' — Shows always, nothing stored
|
||||
|
||||
// Prevents double Initialization
|
||||
let onboardingStarted = false;
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Block 2: Tour Initialization
|
||||
// =================================================================
|
||||
|
||||
function initOnboardingTour() {
|
||||
|
||||
// Checks if Tutorial should be shown based on Onboarding Mode
|
||||
if (ONBOARDING_MODE === 'once' && localStorage.getItem('webgis_onboarding_done')) {
|
||||
return;
|
||||
}
|
||||
if (ONBOARDING_MODE === 'session' && sessionStorage.getItem('webgis_onboarding_done')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Waits for Welcome and Login Modals to be closed
|
||||
waitForModalsToClose(function () {
|
||||
setTimeout(startTour, 600);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Block 3: Modal Watcher — Starts Tour other Welcome and Login Modals closed
|
||||
// =================================================================
|
||||
|
||||
function waitForModalsToClose(callback) {
|
||||
const welcomeModal = document.getElementById('welcome-modal');
|
||||
const loginModal = document.getElementById('login-modal');
|
||||
|
||||
const checkInterval = setInterval(function () {
|
||||
const welcomeHidden = !welcomeModal || welcomeModal.style.display === 'none' || welcomeModal.style.display === '';
|
||||
const loginHidden = !loginModal || loginModal.style.display === 'none' || loginModal.style.display === '';
|
||||
|
||||
if (welcomeHidden && loginHidden) {
|
||||
clearInterval(checkInterval);
|
||||
callback();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// Safety Timeout
|
||||
setTimeout(function () {
|
||||
clearInterval(checkInterval);
|
||||
callback();
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Block 4: Tour Definition
|
||||
// =================================================================
|
||||
|
||||
function startTour() {
|
||||
// Prevents double Start
|
||||
if (onboardingStarted) return;
|
||||
onboardingStarted = true;
|
||||
|
||||
const tour = new Shepherd.Tour({
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
cancelIcon: { enabled: true },
|
||||
scrollTo: false,
|
||||
classes: 'onboarding-step',
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{ name: 'offset', options: { offset: [0, 14] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 1: Welcome
|
||||
// -----------------------------------------------------------------
|
||||
tour.addStep({
|
||||
id: 'welcome',
|
||||
title: '<i class="fa-solid fa-hand-wave"></i> Wilkommen bei der Mitmachkarte!',
|
||||
text: 'Dieses interaktive Tutorial zeigt Ihnen die Kernfunktionen der Mitmachkarte.' +
|
||||
'<br><br><span style="font-size:0.8rem;color:var(--color-text-secondary);">Sie können das Tutorial jederzeit durch den Hilfe-Tab der Seitenleiste wiederholen.</span>',
|
||||
buttons: [
|
||||
{
|
||||
text: 'Überspringen',
|
||||
action: tour.cancel,
|
||||
classes: 'shepherd-button-secondary'
|
||||
},
|
||||
{
|
||||
text: 'Los geht\'s <i class="fa-solid fa-arrow-right"></i>',
|
||||
action: tour.next,
|
||||
classes: 'shepherd-button-primary'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 2: Drawing Tools
|
||||
// -----------------------------------------------------------------
|
||||
tour.addStep({
|
||||
id: 'drawing-tools',
|
||||
title: '<i class="fa-solid fa-pencil"></i> Beitrag hinzufügen',
|
||||
text: 'Verwenden Sie die <strong>Zeichenwerkzeuge</strong>, um Hinweise, Anregungen und Vorschläge auf der Mitmachkarte als Punkte, Linien oder Flächen hinzuzufügen.',
|
||||
attachTo: {
|
||||
element: '.leaflet-pm-toolbar',
|
||||
on: 'left'
|
||||
},
|
||||
beforeShowPromise: function () {
|
||||
return new Promise(function (resolve) {
|
||||
sidebar.close();
|
||||
setTimeout(resolve, 300);
|
||||
});
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
|
||||
action: tour.back,
|
||||
classes: 'shepherd-button-secondary'
|
||||
},
|
||||
{
|
||||
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
|
||||
action: tour.next,
|
||||
classes: 'shepherd-button-primary'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 3: Address Search
|
||||
// -----------------------------------------------------------------
|
||||
tour.addStep({
|
||||
id: 'address-search',
|
||||
title: '<i class="fa-solid fa-magnifying-glass"></i> Adresssuche',
|
||||
text: 'Verwenden Sie die <strong>Adresssuche</strong>, um schnell den richtigen Ort auf der Mitmachkarte zu finden.',
|
||||
attachTo: {
|
||||
element: '.leaflet-control-geocoder',
|
||||
on: 'left'
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
|
||||
action: tour.back,
|
||||
classes: 'shepherd-button-secondary'
|
||||
},
|
||||
{
|
||||
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
|
||||
action: tour.next,
|
||||
classes: 'shepherd-button-primary'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 4: Layer Control
|
||||
// -----------------------------------------------------------------
|
||||
tour.addStep({
|
||||
id: 'layer-control',
|
||||
title: '<i class="fa-solid fa-layer-group"></i> Kartenansicht',
|
||||
text: 'Wechseln Sie zwischen verschiedenen <strong>Hintergrundkarten</strong> und <strong>Satellitenbildern</strong>.',
|
||||
attachTo: {
|
||||
element: '.leaflet-control-layers',
|
||||
on: 'left'
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
|
||||
action: tour.back,
|
||||
classes: 'shepherd-button-secondary'
|
||||
},
|
||||
{
|
||||
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
|
||||
action: tour.next,
|
||||
classes: 'shepherd-button-primary'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 5: Sidebar
|
||||
// -----------------------------------------------------------------
|
||||
tour.addStep({
|
||||
id: 'sidebar',
|
||||
title: '<i class="fa-solid fa-bars"></i> Seitenleiste',
|
||||
text: 'In der Seitenleiste finden Sie <strong>Hilfestellungen</strong>, <strong>Listenansichten</strong> und <strong>Neuigkeiten</strong>.',
|
||||
attachTo: {
|
||||
element: '#sidebar',
|
||||
on: 'right'
|
||||
},
|
||||
beforeShowPromise: function () {
|
||||
return new Promise(function (resolve) {
|
||||
sidebar.open('tab-help');
|
||||
setTimeout(resolve, 400);
|
||||
});
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
|
||||
action: tour.back,
|
||||
classes: 'shepherd-button-secondary'
|
||||
},
|
||||
{
|
||||
text: 'Tutorial abschließen <i class="fa-solid fa-check"></i>',
|
||||
action: tour.next,
|
||||
classes: 'shepherd-button-primary'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Completion and Cancellation
|
||||
// -----------------------------------------------------------------
|
||||
tour.on('complete', function () {
|
||||
markOnboardingDone();
|
||||
onboardingStarted = false;
|
||||
});
|
||||
|
||||
tour.on('cancel', function () {
|
||||
markOnboardingDone();
|
||||
onboardingStarted = false;
|
||||
});
|
||||
|
||||
tour.start();
|
||||
}
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Marks Onboarding as completed
|
||||
// =================================================================
|
||||
|
||||
function markOnboardingDone() {
|
||||
if (ONBOARDING_MODE === 'once') {
|
||||
localStorage.setItem('webgis_onboarding_done', 'true');
|
||||
} else if (ONBOARDING_MODE === 'session') {
|
||||
sessionStorage.setItem('webgis_onboarding_done', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Manual Tour Restart
|
||||
// =================================================================
|
||||
|
||||
function restartOnboarding() {
|
||||
localStorage.removeItem('webgis_onboarding_done');
|
||||
sessionStorage.removeItem('webgis_onboarding_done');
|
||||
onboardingStarted = false;
|
||||
startTour();
|
||||
}
|
||||
|
||||
|
||||
// =================================================================
|
||||
// Auto-Start on Page Load
|
||||
// =================================================================
|
||||
|
||||
initOnboardingTour();
|
||||
39
public/privacy.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/api/db.php';
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
||||
$municipality = $stmt->fetch();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Datenschutz — <?= htmlspecialchars($municipality['name']) ?></title>
|
||||
<link rel="icon" href="assets/lock-solid-off-black.png" type="image/png">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="page-header-inner">
|
||||
<h1><i class="fa-solid fa-lock"></i> Datenschutz</h1>
|
||||
<div class="page-header-nav">
|
||||
<a href="index.php"><i class="fa-solid fa-arrow-left"></i> Zurück zur Karte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-container">
|
||||
<div class="page-content-box">
|
||||
<div class="dev-notice">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
Dieses Portal befindet sich in der Entwicklung und wurde nicht offiziell beauftragt. Die Datenschutzerklärung wird mit der offiziellen Inbetriebnahme hier hinzugefügt.
|
||||
</div>
|
||||
<h2>Datenschutz</h2>
|
||||
<p>Die Datenschutzerklärung wird hier hinzugefügt, sobald das Portal in den Produktivbetrieb geht.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1321
public/styles.css
Normal file
7
public/uploads/.htaccess
Normal file
@@ -0,0 +1,7 @@
|
||||
# Prevents PHP in Upload Directory
|
||||
php_flag engine off
|
||||
|
||||
# Allows Image Files
|
||||
<FilesMatch "\.(?i:jpg|jpeg|png|gif|webp)$">
|
||||
Require all granted
|
||||
</FilesMatch>
|
||||