recup sources
This commit is contained in:
parent
86622a19ea
commit
65fe2a35f9
155 changed files with 50969 additions and 0 deletions
47
.env
Normal file
47
.env
Normal file
|
@ -0,0 +1,47 @@
|
|||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=abcd13223131_CHANGE_THIS_BEARER_TOKEN
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=null://null
|
||||
###< symfony/mailer ###
|
||||
#DEBUG=0
|
||||
|
||||
APP_OSM_BEARER=CHANGE_IT
|
||||
MAPBOX_TOKEN=
|
||||
MAPTILER_TOKEN=
|
||||
USE_PLACES_WITHOUT_EMAIL_TO_REFERENCE=false
|
0
.env.dev
Normal file
0
.env.dev
Normal file
6
.env.test
Normal file
6
.env.test
Normal file
|
@ -0,0 +1,6 @@
|
|||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
/node_modules/
|
||||
/public/build/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
venv
|
||||
|
||||
wiki_compare/.env
|
||||
wiki_compare/*.png
|
||||
wiki_compare/*.json
|
||||
public/*.json
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
5596
.idea/commandlinetools/Symfony_01_09_2025_18_12.xml
generated
Normal file
5596
.idea/commandlinetools/Symfony_01_09_2025_18_12.xml
generated
Normal file
File diff suppressed because one or more lines are too long
47
.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd
generated
Normal file
47
.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd
generated
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="framework" type="frameworkType"/>
|
||||
<xs:complexType name="commandType">
|
||||
<xs:all>
|
||||
<xs:element type="xs:string" name="name" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element type="xs:string" name="params" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element type="optionsBeforeType" name="optionsBefore" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="frameworkType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="extraData" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element type="commandType" name="command" maxOccurs="unbounded" minOccurs="0"/>
|
||||
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="required"/>
|
||||
<xs:attribute type="xs:string" name="invoke" use="required"/>
|
||||
<xs:attribute type="xs:string" name="alias" use="required"/>
|
||||
<xs:attribute type="xs:boolean" name="enabled" use="required"/>
|
||||
<xs:attribute type="xs:integer" name="version" use="required"/>
|
||||
<xs:attribute type="xs:string" name="frameworkId" use="optional"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="optionsBeforeType">
|
||||
<xs:sequence>
|
||||
<xs:element type="optionType" name="option" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="optionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="required"/>
|
||||
<xs:attribute type="xs:string" name="shortcut" use="optional"/>
|
||||
<xs:attribute name="pattern" use="optional">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="space"/>
|
||||
<xs:enumeration value="equals"/>
|
||||
<xs:enumeration value="unknown"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
21
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
21
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="7">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="Placeholder" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/qualiwiki.iml" filepath="$PROJECT_DIR$/.idea/qualiwiki.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
158
.idea/php.xml
generated
Normal file
158
.idea/php.xml
generated
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-messenger" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/expression-language" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/mailer" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-icu" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/log" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/container" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/asset-mapper" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/link" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/clock" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/ux-turbo" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/debug-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/stimulus-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/web-link" />
|
||||
<path value="$PROJECT_DIR$/vendor/monolog/monolog" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/string" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/web-profiler-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/intl" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/notifier" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/console" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/process" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
|
||||
<path value="$PROJECT_DIR$/vendor/twig/twig" />
|
||||
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/form" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/messenger" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/config" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/monolog-bridge" />
|
||||
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
|
||||
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
|
||||
<path value="$PROJECT_DIR$/vendor/egulias/email-validator" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
|
||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
|
||||
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/monolog-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php83" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/mime" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.3">
|
||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpUnit">
|
||||
<phpunit_settings>
|
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
|
||||
</phpunit_settings>
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
140
.idea/qualiwiki.iml
generated
Normal file
140
.idea/qualiwiki.iml
generated
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/public/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/var" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/migrations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/orm" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/sql-formatter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/egulias/email-validator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/masterminds/html5" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpdoc-parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/clock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/link" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/staabm/side-effects-detector" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/asset" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/asset-mapper" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/browser-kit" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/clock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/config" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dependency-injection" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/doctrine-bridge" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/doctrine-messenger" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dotenv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/expression-language" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/filesystem" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/flex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/form" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/framework-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/intl" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mailer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/messenger" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/monolog-bridge" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/monolog-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/notifier" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-icu" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php83" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php84" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/runtime" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-core" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-csrf" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-http" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/serializer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stimulus-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bridge" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/type-info" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/ux-turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/validator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-exporter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/web-link" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/web-profiler-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/yaml" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/extra-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/symfony2.xml
generated
Normal file
6
.idea/symfony2.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Symfony2PluginSettings">
|
||||
<option name="pluginEnabled" value="true" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
34
README.md
34
README.md
|
@ -0,0 +1,34 @@
|
|||
# QualiWiki
|
||||
Outil de mise en qualité du wiki OpenStreetMap.
|
||||
|
||||
plusieurs pages:
|
||||
|
||||
- l’accueil liste les pages par ordre de manque de mise à jour du plus grave au moins grave parmi les pages de tags très utilisés selon taginfo.
|
||||
- on y voit aussi une liste de pages arbitrairement suivies, dont celle proposée par @AdrienHegy , on peut en mettre d’autres.
|
||||
- des pages fr sans traduction en anglais
|
||||
- des pages englishes sans traduction fr
|
||||
- un graphe de répartition de la décrépitude en fin de page.
|
||||
- chaque page peut être comparée entre version fr et englishe. on y trouve une liste des sections, des images, des propositions de correction de la page française par Grammalecte. Exemple ici où on a clairement plus de contenu en version Française qu’en Anglais: Comparaison Wiki OSM - Tag:amenity=charging_station , cas inverse, la page de procédé de proposition Comparaison Wiki OSM - Proposal process qui est bien plus riche en Anglais qu’en Français.
|
||||
- on a une suggestion de page au hasard pour voir à quel point elle manque de fraîcheur
|
||||
- propositions archivées: une étude des 200 propositions de tags, de leurs status et de leurs votes avec quelques stats.
|
||||
- changements récents: une vue globale des dernières modifications de l’année, ce qui permet de voir qui sont les gens qui potassent le wiki régulièrement.
|
||||
- les propositions en cours de vote avec un graphe des votes
|
||||
|
||||
On peut aussi cliquer sur plusieurs trucs dans la page de comparaison pour copier ou rechercher dans le wiki.
|
||||
|
||||
Havez fun.
|
||||
# installation
|
||||
|
||||
npm install
|
||||
npm run build
|
||||
composer install
|
||||
|
||||
pas besoin de base de données, les infos sont stockées dans des fichiers json suite au lancement de scripts de wiki_compare.
|
||||
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# liens
|
||||
sujet sur le forum osm fr: https://forum.openstreetmap.fr/t/fabriquer-un-outil-de-qualite-pour-le-wiki-osm/36814
|
||||
- Tykayn - Cipherbliss.com
|
0
README.md~
Normal file
0
README.md~
Normal file
360
assets/app.js
Normal file
360
assets/app.js
Normal file
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
*
|
||||
* We recommend including the built version of this JavaScript file
|
||||
* (and its CSS file) in your base layout (base.html.twig).
|
||||
*/
|
||||
|
||||
// any CSS you import will output into a single css file (app.css in this case)
|
||||
import './styles/app.css';
|
||||
import jQuery from 'jquery';
|
||||
window.$ = jQuery;
|
||||
window.jQuery = jQuery;
|
||||
import 'tablesort/tablesort.css';
|
||||
|
||||
// start the Stimulus application
|
||||
// import './bootstrap';
|
||||
|
||||
import './utils.js';
|
||||
import './opening_hours.js';
|
||||
import './josm.js';
|
||||
import './edit.js';
|
||||
import './table-map-toggles.js';
|
||||
import './stats-charts.js';
|
||||
import './dashboard-charts.js';
|
||||
|
||||
import Chart from 'chart.js/auto';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import {
|
||||
genererCouleurPastel,
|
||||
setupCitySearch,
|
||||
handleAddCityFormSubmit,
|
||||
enableLabourageForm,
|
||||
getLabourerUrl,
|
||||
adjustListGroupFontSize,
|
||||
toggleCompletionInfo,
|
||||
updateMapHeightForLargeScreens
|
||||
} from './utils.js';
|
||||
import tableSortJs from 'table-sort-js/table-sort.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
console.log('TableSort', tableSortJs)
|
||||
|
||||
// Charger table-sortable (version non minifiée locale)
|
||||
// import '../assets/js/table-sortable.js';
|
||||
|
||||
window.Chart = Chart;
|
||||
window.genererCouleurPastel = genererCouleurPastel;
|
||||
window.setupCitySearch = setupCitySearch;
|
||||
window.handleAddCityFormSubmit = handleAddCityFormSubmit;
|
||||
window.getLabourerUrl = getLabourerUrl;
|
||||
window.ChartDataLabels = ChartDataLabels;
|
||||
window.maplibregl = maplibregl;
|
||||
window.toggleCompletionInfo = toggleCompletionInfo;
|
||||
window.updateMapHeightForLargeScreens = updateMapHeightForLargeScreens;
|
||||
// window.Tablesort = Tablesort;
|
||||
|
||||
Chart.register(ChartDataLabels);
|
||||
|
||||
// Attendre le chargement du DOM
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('DOMContentLoaded');
|
||||
|
||||
if(updateMapHeightForLargeScreens){
|
||||
|
||||
|
||||
window.addEventListener('resize', updateMapHeightForLargeScreens);
|
||||
}
|
||||
|
||||
const randombg = genererCouleurPastel();
|
||||
// Appliquer la couleur au body
|
||||
|
||||
document.querySelectorAll('body, .edit-land, .body-landing').forEach(element => {
|
||||
element.style.backgroundColor = randombg;
|
||||
});
|
||||
|
||||
// Gestion du bouton pour afficher tous les champs
|
||||
const btnShowAllFields = document.querySelector('#showAllFields');
|
||||
if (btnShowAllFields) {
|
||||
console.log('btnShowAllFields détecté');
|
||||
btnShowAllFields.addEventListener('click', () => {
|
||||
// Sélectionner tous les inputs dans le formulaire
|
||||
const form = document.querySelector('form');
|
||||
if (form) {
|
||||
// Sélectionner tous les inputs sauf #validation_messages
|
||||
const hiddenInputs = form.querySelectorAll('#advanced_tags');
|
||||
|
||||
hiddenInputs.forEach(input => {
|
||||
input.classList.toggle('d-none');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
const btnClosedCommerce = document.querySelector('#closedCommerce');
|
||||
if (btnClosedCommerce) {
|
||||
btnClosedCommerce.addEventListener('click', () => {
|
||||
|
||||
if (!confirm('Êtes-vous sûr de vouloir signaler ce commerce comme fermé ?')) {
|
||||
return;
|
||||
}
|
||||
window.location.href = '/closed_commerce/' + document.getElementById('app_public_closed_commerce').value;
|
||||
});
|
||||
}
|
||||
|
||||
openingHoursFormManager.init();
|
||||
|
||||
|
||||
// Vérifier si l'élément avec l'ID 'userChangesHistory' existe avant d'appeler la fonction
|
||||
if (document.getElementById('userChangesHistory')) {
|
||||
listChangesets();
|
||||
} else {
|
||||
console.log('userChangesHistory non trouvé');
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[type="text"]').forEach(input => {
|
||||
input.addEventListener('blur', updateCompletionProgress);
|
||||
});
|
||||
const form = document.querySelector('form')
|
||||
if (form) {
|
||||
form.addEventListener('submit', check_validity);
|
||||
updateCompletionProgress()
|
||||
}
|
||||
updateCompletionProgress();
|
||||
|
||||
// Focus sur le premier champ texte au chargement
|
||||
// const firstTextInput = document.querySelector('input.form-control');
|
||||
// if (firstTextInput) {
|
||||
// firstTextInput.focus();
|
||||
// console.log('focus sur le premier champ texte', firstTextInput);
|
||||
// } else {
|
||||
// console.log('pas de champ texte trouvé');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
parseCuisine();
|
||||
|
||||
// Modifier la fonction de recherche existante
|
||||
const searchInput = document.getElementById('app_admin_labourer');
|
||||
const suggestionList = document.getElementById('suggestionList');
|
||||
|
||||
if (searchInput && suggestionList) {
|
||||
let timeoutId;
|
||||
|
||||
searchInput.addEventListener('input', () => {
|
||||
clearTimeout(timeoutId);
|
||||
const query = searchInput.value.trim();
|
||||
|
||||
if (query.length < 2) {
|
||||
suggestionList.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(async () => {
|
||||
const suggestions = await searchInseeCode(query);
|
||||
suggestionList.innerHTML = '';
|
||||
|
||||
if (suggestions.length === 0) {
|
||||
const li = document.createElement('li');
|
||||
li.style.cssText = `
|
||||
padding: 8px 12px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
`;
|
||||
li.textContent = 'Aucun résultat trouvé';
|
||||
suggestionList.appendChild(li);
|
||||
return;
|
||||
}
|
||||
|
||||
suggestions.forEach(suggestion => {
|
||||
const li = document.createElement('li');
|
||||
li.style.cssText = `
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
`;
|
||||
li.textContent = suggestion.label;
|
||||
|
||||
li.addEventListener('mouseenter', () => {
|
||||
li.style.backgroundColor = '#f0f0f0';
|
||||
});
|
||||
|
||||
li.addEventListener('mouseleave', () => {
|
||||
li.style.backgroundColor = 'white';
|
||||
});
|
||||
|
||||
li.addEventListener('click', () => {
|
||||
searchInput.value = suggestion.insee;
|
||||
suggestionList.innerHTML = '';
|
||||
labourer();
|
||||
});
|
||||
|
||||
suggestionList.appendChild(li);
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
if(enableLabourageForm){
|
||||
|
||||
enableLabourageForm();
|
||||
}
|
||||
adjustListGroupFontSize('.list-group-item');
|
||||
|
||||
// Activer le tri naturel sur tous les tableaux avec la classe table-sort
|
||||
if (tableSortJs) {
|
||||
tableSortJs();
|
||||
}else{
|
||||
console.log('pas de tablesort')
|
||||
}
|
||||
|
||||
// Initialisation du tri et filtrage sur les tableaux du dashboard et de la page stats
|
||||
// if (document.querySelector('#dashboard-table')) {
|
||||
// $('#dashboard-table').tableSortable({
|
||||
// pagination: false,
|
||||
// showPaginationLabel: true,
|
||||
// searchField: '#dashboard-table-search',
|
||||
// responsive: false
|
||||
// });
|
||||
// }
|
||||
// if (document.querySelector('#stats-table')) {
|
||||
// $('#stats-table').tableSortable({
|
||||
// pagination: false,
|
||||
// showPaginationLabel: true,
|
||||
// searchField: '#stats-table-search',
|
||||
// responsive: false
|
||||
// });
|
||||
// }
|
||||
|
||||
// Correction pour le formulaire de labourage
|
||||
const labourerForm = document.getElementById('labourerForm');
|
||||
if (labourerForm) {
|
||||
labourerForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const zipInput = document.getElementById('selectedZipCode');
|
||||
const cityInput = document.getElementById('citySearch');
|
||||
let insee = zipInput.value;
|
||||
if (!insee && cityInput && cityInput.value.trim().length > 0) {
|
||||
// Recherche du code INSEE via l'API
|
||||
const response = await fetch(`https://geo.api.gouv.fr/communes?nom=${encodeURIComponent(cityInput.value.trim())}&fields=nom,code&limit=1`);
|
||||
const data = await response.json();
|
||||
if (data.length > 0) {
|
||||
insee = data[0].code;
|
||||
}
|
||||
}
|
||||
if (insee) {
|
||||
window.location.href = `/add-city-without-labourage/${insee}`;
|
||||
} else {
|
||||
alert('Veuillez sélectionner une ville valide.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ajouter un écouteur pour l'événement 'load' de MapLibre afin d'ajuster la hauteur de la carte
|
||||
if (window.maplibregl && document.getElementById('map')) {
|
||||
// On suppose que la carte est initialisée ailleurs et accessible via window.mapInstance
|
||||
// Sinon, on peut essayer de détecter l'instance automatiquement
|
||||
let mapInstance = window.mapInstance;
|
||||
if (!mapInstance && window.maplibreMap) {
|
||||
mapInstance = window.maplibreMap;
|
||||
}
|
||||
// Si l'instance n'est pas trouvée, essayer de la récupérer via une variable globale courante
|
||||
if (!mapInstance && window.map) {
|
||||
mapInstance = window.map;
|
||||
}
|
||||
if (mapInstance && typeof mapInstance.on === 'function') {
|
||||
mapInstance.on('load', function() {
|
||||
updateMapHeightForLargeScreens();
|
||||
});
|
||||
}
|
||||
}
|
||||
//updateMapHeightForLargeScreens();
|
||||
|
||||
console.log('window.followupSeries',window.followupSeries)
|
||||
if (!window.followupSeries) return;
|
||||
|
||||
const series = window.followupSeries;
|
||||
|
||||
// Données bornes de recharge
|
||||
const chargingStationCount = (series['charging_station_count'] || []).map(point => ({ x: point.date, y: point.value }));
|
||||
const chargingStationCompletion = (series['charging_station_completion'] || []).map(point => ({ x: point.date, y: point.value }));
|
||||
|
||||
// Données bornes incendie
|
||||
const fireHydrantCount = (series['fire_hydrant_count'] || []).map(point => ({ x: point.date, y: point.value }));
|
||||
const fireHydrantCompletion = (series['fire_hydrant_completion'] || []).map(point => ({ x: point.date, y: point.value }));
|
||||
|
||||
// Graphique bornes de recharge
|
||||
const chargingStationChart = document.getElementById('chargingStationChart');
|
||||
if (chargingStationChart) {
|
||||
new Chart(chargingStationChart, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Nombre de bornes de recharge',
|
||||
data: chargingStationCount,
|
||||
borderColor: 'blue',
|
||||
backgroundColor: 'rgba(0,0,255,0.1)',
|
||||
fill: false,
|
||||
yAxisID: 'y',
|
||||
},
|
||||
{
|
||||
label: 'Complétion (%)',
|
||||
data: chargingStationCompletion,
|
||||
borderColor: 'green',
|
||||
backgroundColor: 'rgba(0,255,0,0.1)',
|
||||
fill: false,
|
||||
yAxisID: 'y1',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
parsing: false,
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
|
||||
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
|
||||
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Graphique bornes incendie
|
||||
const fireHydrantChart = document.getElementById('fireHydrantChart');
|
||||
if (fireHydrantChart) {
|
||||
new Chart(fireHydrantChart, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Nombre de bornes incendie',
|
||||
data: fireHydrantCount,
|
||||
borderColor: 'red',
|
||||
backgroundColor: 'rgba(255,0,0,0.1)',
|
||||
fill: false,
|
||||
yAxisID: 'y',
|
||||
},
|
||||
{
|
||||
label: 'Complétion (%)',
|
||||
data: fireHydrantCompletion,
|
||||
borderColor: 'orange',
|
||||
backgroundColor: 'rgba(255,165,0,0.1)',
|
||||
fill: false,
|
||||
yAxisID: 'y1',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
parsing: false,
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
|
||||
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
|
||||
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
5
assets/bootstrap.js
vendored
Normal file
5
assets/bootstrap.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||
|
||||
const app = startStimulusApp();
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
15
assets/controllers.json
Normal file
15
assets/controllers.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"controllers": {
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
"enabled": false,
|
||||
"fetch": "eager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoints": []
|
||||
}
|
79
assets/controllers/csrf_protection_controller.js
Normal file
79
assets/controllers/csrf_protection_controller.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
|
||||
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
|
||||
|
||||
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
|
||||
document.addEventListener('submit', function (event) {
|
||||
generateCsrfToken(event.target);
|
||||
}, true);
|
||||
|
||||
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
|
||||
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
|
||||
document.addEventListener('turbo:submit-start', function (event) {
|
||||
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
|
||||
Object.keys(h).map(function (k) {
|
||||
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
|
||||
});
|
||||
});
|
||||
|
||||
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
|
||||
document.addEventListener('turbo:submit-end', function (event) {
|
||||
removeCsrfToken(event.detail.formSubmission.formElement);
|
||||
});
|
||||
|
||||
export function generateCsrfToken (formElement) {
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return;
|
||||
}
|
||||
|
||||
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
let csrfToken = csrfField.value;
|
||||
|
||||
if (!csrfCookie && nameCheck.test(csrfToken)) {
|
||||
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
|
||||
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
|
||||
}
|
||||
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
if (csrfCookie && tokenCheck.test(csrfToken)) {
|
||||
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
|
||||
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||
}
|
||||
}
|
||||
|
||||
export function generateCsrfHeaders (formElement) {
|
||||
const headers = {};
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
|
||||
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||
headers[csrfCookie] = csrfField.value;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function removeCsrfToken (formElement) {
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return;
|
||||
}
|
||||
|
||||
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
|
||||
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
|
||||
|
||||
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||
}
|
||||
}
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default 'csrf-protection-controller';
|
16
assets/controllers/hello_controller.js
Normal file
16
assets/controllers/hello_controller.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* This is an example Stimulus controller!
|
||||
*
|
||||
* Any element with a data-controller="hello" attribute will cause
|
||||
* this controller to be executed. The name "hello" comes from the filename:
|
||||
* hello_controller.js -> "hello"
|
||||
*
|
||||
* Delete this file or adapt it for your use!
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
|
||||
}
|
||||
}
|
229
assets/dashboard-charts.js
Normal file
229
assets/dashboard-charts.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
// Bubble chart du dashboard avec option de taille de bulle proportionnelle ou égale
|
||||
|
||||
let bubbleChart = null; // Déclaré en dehors pour garder la référence
|
||||
function waitForChartAndDrawBubble() {
|
||||
if (!window.Chart || !window.ChartDataLabels) {
|
||||
setTimeout(waitForChartAndDrawBubble, 50);
|
||||
return;
|
||||
}
|
||||
const chartCanvas = document.getElementById('bubbleChart');
|
||||
const toggle = document.getElementById('toggleBubbleSize');
|
||||
|
||||
|
||||
|
||||
|
||||
function drawBubbleChart(proportional) {
|
||||
// Détruire toute instance Chart.js existante sur ce canvas (Chart.js v3+)
|
||||
const existing = window.Chart.getChart(chartCanvas);
|
||||
if (existing) {
|
||||
try { existing.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); }
|
||||
}
|
||||
if (bubbleChart) {
|
||||
try { bubbleChart.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); }
|
||||
bubbleChart = null;
|
||||
}
|
||||
|
||||
// Forcer le canvas à occuper toute la largeur/hauteur du conteneur en pixels
|
||||
if (chartCanvas && chartCanvas.parentElement) {
|
||||
const parentRect = chartCanvas.parentElement.getBoundingClientRect();
|
||||
console.log('parentRect', parentRect)
|
||||
|
||||
chartCanvas.width = (parentRect.width);
|
||||
chartCanvas.height = (parentRect.height);
|
||||
chartCanvas.style.width = parentRect.width + 'px';
|
||||
chartCanvas.style.height = parentRect.height + 'px';
|
||||
}
|
||||
|
||||
|
||||
if(!getBubbleData){
|
||||
console.log('pas de getBubbleData')
|
||||
return ;
|
||||
}
|
||||
const bubbleChartData = getBubbleData(proportional);
|
||||
|
||||
if(!bubbleChartData){
|
||||
console.log('pas de bubbleChartData')
|
||||
return ;
|
||||
}
|
||||
|
||||
// Calcul de la régression linéaire (moindres carrés)
|
||||
// On ne fait la régression que si on veut, mais l'axe X = fraicheur, Y = complétion
|
||||
const validPoints = bubbleChartData.filter(d => d.x !== null && d.y !== null);
|
||||
const n = validPoints.length;
|
||||
let regressionLine = null, slope = 0, intercept = 0;
|
||||
if (n >= 2) {
|
||||
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
|
||||
validPoints.forEach(d => {
|
||||
sumX += d.x;
|
||||
sumY += d.y;
|
||||
sumXY += d.x * d.y;
|
||||
sumXX += d.x * d.x;
|
||||
});
|
||||
const meanX = sumX / n;
|
||||
const meanY = sumY / n;
|
||||
slope = (sumXY - n * meanX * meanY) / (sumXX - n * meanX * meanX);
|
||||
intercept = meanY - slope * meanX;
|
||||
const xMin = Math.min(...validPoints.map(d => d.x));
|
||||
const xMax = Math.max(...validPoints.map(d => d.x));
|
||||
regressionLine = [
|
||||
{ x: xMin, y: slope * xMin + intercept },
|
||||
{ x: xMax, y: slope * xMax + intercept }
|
||||
];
|
||||
}
|
||||
window.Chart.register(window.ChartDataLabels);
|
||||
bubbleChart = new window.Chart(chartCanvas.getContext('2d'), {
|
||||
type: 'bubble',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Villes',
|
||||
data: bubbleChartData,
|
||||
backgroundColor: bubbleChartData.map(d => `rgba(94, 255, 121, ${d.completion / 100})`),
|
||||
borderColor: 'rgb(94, 255, 121)',
|
||||
datalabels: {
|
||||
anchor: 'center',
|
||||
align: 'center',
|
||||
color: '#000',
|
||||
display: true,
|
||||
font: { weight: '400', size : "12px" },
|
||||
formatter: (value, context) => {
|
||||
return context.dataset.data[context.dataIndex].label;
|
||||
}
|
||||
}
|
||||
},
|
||||
regressionLine ? {
|
||||
label: 'Régression linéaire',
|
||||
type: 'line',
|
||||
data: regressionLine,
|
||||
borderColor: 'rgba(95, 168, 0, 0.7)',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
fill: false,
|
||||
order: 0,
|
||||
tension: 0,
|
||||
datalabels: { display: false }
|
||||
} : null
|
||||
].filter(Boolean)
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false
|
||||
},
|
||||
legend: { display: true },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
const d = context.raw;
|
||||
if (context.dataset.type === 'line') {
|
||||
return `Régression: y = ${slope.toFixed(2)} × x + ${intercept.toFixed(2)}`;
|
||||
}
|
||||
return [
|
||||
`${d.label}`,
|
||||
`Fraîcheur moyenne: ${d.freshnessDays ? d.freshnessDays.toLocaleString() + ' jours' : 'N/A'}`,
|
||||
`Complétion: ${d.y.toFixed(2)}%`,
|
||||
`Population: ${d.population ? d.population.toLocaleString() : 'N/A'}`,
|
||||
`Nombre de lieux: ${d.r.toFixed(2)}`,
|
||||
`Budget: ${d.budget ? d.budget.toLocaleString() + ' €' : 'N/A'}`,
|
||||
`Budget/habitant: ${d.budgetParHabitant ? d.budgetParHabitant.toFixed(2) + ' €' : 'N/A'}`,
|
||||
`Budget/lieu: ${d.budgetParLieu ? d.budgetParLieu.toFixed(2) + ' €' : 'N/A'}`
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
title: { display: true, text: 'Fraîcheur moyenne (jours, plus petit = plus récent)' }
|
||||
},
|
||||
y: {
|
||||
title: { display: true, text: 'Taux de complétion (%)' },
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Ajout du clic sur une bulle
|
||||
chartCanvas.onclick = function(evt) {
|
||||
const points = bubbleChart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true);
|
||||
if (points.length > 0) {
|
||||
const firstPoint = points[0];
|
||||
const dataIndex = firstPoint.index;
|
||||
const stat = window.statsDataForBubble[dataIndex];
|
||||
if (stat && stat.zone) {
|
||||
window.location.href = '/admin/stats/' + stat.zone;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initial draw
|
||||
console.log('[bubble chart] Initialisation avec taille proportionnelle ?', toggle?.checked);
|
||||
if(drawBubbleChart){
|
||||
|
||||
drawBubbleChart(toggle && toggle.checked);
|
||||
// Listener
|
||||
toggle?.addEventListener('change', function() {
|
||||
console.log('[bubble chart] Toggle changé, taille proportionnelle ?', toggle?.checked);
|
||||
drawBubbleChart(toggle?.checked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getBubbleData(proportional) {
|
||||
// Générer les données puis trier par rayon décroissant
|
||||
const data = window.statsDataForBubble?.map(stat => {
|
||||
const population = parseInt(stat.population, 10);
|
||||
const placesCount = parseInt(stat.placesCount, 10);
|
||||
const completion = parseInt(stat.completionPercent, 10);
|
||||
// Fraîcheur moyenne : âge moyen en jours (plus récent à droite)
|
||||
let freshnessDays = null;
|
||||
if (stat.osmDataDateAvg) {
|
||||
const now = new Date();
|
||||
const avgDate = new Date(stat.osmDataDateAvg);
|
||||
freshnessDays = Math.round((now - avgDate) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
// Pour l'axe X, on veut que les plus récents soient à droite (donc X = -freshnessDays)
|
||||
const x = freshnessDays !== null ? -freshnessDays : 0;
|
||||
// Budget
|
||||
const budget = stat.budgetAnnuel ? parseFloat(stat.budgetAnnuel) : null;
|
||||
const budgetParHabitant = (budget && population) ? budget / population : null;
|
||||
const budgetParLieu = (budget && placesCount) ? budget / placesCount : null;
|
||||
return {
|
||||
x: x,
|
||||
y: completion,
|
||||
r: proportional ? Math.sqrt(placesCount) * 2 : 12,
|
||||
label: stat.name,
|
||||
completion: stat.completionPercent || 0,
|
||||
zone: stat.zone,
|
||||
budget,
|
||||
budgetParHabitant,
|
||||
budgetParLieu,
|
||||
population,
|
||||
placesCount,
|
||||
freshnessDays
|
||||
};
|
||||
});
|
||||
// Trier du plus gros au plus petit rayon
|
||||
if(data){
|
||||
data.sort((a, b) => b.r - a.r);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
waitForChartAndDrawBubble();
|
||||
|
||||
// Forcer deleteMissing=1 sur le formulaire de labourage
|
||||
const labourerForm = document.getElementById('labourerForm');
|
||||
if (labourerForm) {
|
||||
labourerForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const zipCode = document.getElementById('selectedZipCode').value;
|
||||
if (zipCode) {
|
||||
window.location.href = '/admin/labourer/' + zipCode + '?deleteMissing=1';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
126
assets/edit.js
Normal file
126
assets/edit.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
|
||||
/**
|
||||
* mettre à jour la barre de progression
|
||||
* pour le formulaire de modification
|
||||
*/
|
||||
function updateCompletionProgress() {
|
||||
const inputs = document.querySelectorAll('input[data-important]');
|
||||
let filledInputs = 0;
|
||||
let totalInputs = inputs.length;
|
||||
let missingFields = [];
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input.value.trim() !== '') {
|
||||
filledInputs++;
|
||||
} else {
|
||||
// Get the field label or name for display in the popup
|
||||
let fieldName = '';
|
||||
const label = input.closest('.row')?.querySelector('.form-label, .label-translated');
|
||||
if (label) {
|
||||
fieldName = label.textContent.trim();
|
||||
} else {
|
||||
// If no label found, try to get a meaningful name from the input
|
||||
const name = input.getAttribute('name');
|
||||
if (name) {
|
||||
// Extract field name from the attribute (e.g., commerce_tag_value__contact:email -> Email)
|
||||
const parts = name.split('__');
|
||||
if (parts.length > 1) {
|
||||
fieldName = parts[1].replace('commerce_tag_value_', '').replace('contact:', '');
|
||||
// Capitalize first letter
|
||||
fieldName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
||||
} else {
|
||||
fieldName = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
missingFields.push(fieldName || input.getAttribute('name') || 'Champ inconnu');
|
||||
}
|
||||
});
|
||||
|
||||
const completionPercentage = (filledInputs / totalInputs) * 100;
|
||||
const progressBar = document.querySelector('#completion_progress .progress-bar');
|
||||
if (progressBar) {
|
||||
progressBar.style.width = completionPercentage + '%';
|
||||
progressBar.setAttribute('aria-valuenow', completionPercentage);
|
||||
|
||||
// Create the completion display with a clickable question mark
|
||||
const displayElement = document.querySelector('#completion_display');
|
||||
|
||||
// Format missing fields as an HTML list for better readability
|
||||
let missingFieldsContent = '';
|
||||
if (missingFields.length > 0) {
|
||||
missingFieldsContent = '<ul class="list-unstyled mb-0">';
|
||||
// Filter out empty or undefined field names and sort them alphabetically
|
||||
missingFields
|
||||
.filter(field => field && field.trim() !== '')
|
||||
.sort()
|
||||
.forEach(field => {
|
||||
missingFieldsContent += `<li><i class="bi bi-exclamation-circle text-warning"></i> ${field}</li>`;
|
||||
});
|
||||
missingFieldsContent += '</ul>';
|
||||
} else {
|
||||
missingFieldsContent = 'Tous les champs importants sont remplis !';
|
||||
}
|
||||
|
||||
displayElement.innerHTML = `Votre commerce est complété à ${Math.round(completionPercentage)}% <a href="#" class="missing-fields-info badge rounded-pill bg-warning text-dark ms-1" style="text-decoration: none; font-weight: bold;" data-bs-toggle="popover" data-bs-placement="bottom" title="Champs manquants" data-bs-html="true" data-bs-content="${missingFieldsContent.replace(/"/g, '"')}">?</a>`;
|
||||
|
||||
// Initialize the Bootstrap popover
|
||||
const popoverTrigger = displayElement.querySelector('.missing-fields-info');
|
||||
if (popoverTrigger) {
|
||||
// Add click handler to focus on the first missing field
|
||||
popoverTrigger.addEventListener('click', function(e) {
|
||||
e.preventDefault(); // Prevent scrolling to top
|
||||
|
||||
// Find the first missing field
|
||||
const missingInput = document.querySelector('input[data-important]:not(.good_filled)');
|
||||
if (missingInput) {
|
||||
// Focus on the first missing field
|
||||
missingInput.focus();
|
||||
// Scroll to the field if needed
|
||||
missingInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
});
|
||||
|
||||
// Use setTimeout to ensure this runs after the current execution context
|
||||
setTimeout(() => {
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Popover) {
|
||||
// Destroy existing popover if any
|
||||
const existingPopover = bootstrap.Popover.getInstance(popoverTrigger);
|
||||
if (existingPopover) {
|
||||
existingPopover.dispose();
|
||||
}
|
||||
// Initialize new popover
|
||||
new bootstrap.Popover(popoverTrigger, {
|
||||
html: true,
|
||||
trigger: 'click',
|
||||
container: 'body'
|
||||
});
|
||||
} else {
|
||||
console.warn('Bootstrap popover not available');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseCuisine() {
|
||||
const cuisineInput = document.querySelector('input[name="commerce_tag_value__cuisine"]');
|
||||
|
||||
// Récupérer tous les checkboxes de type de cuisine
|
||||
const cuisineCheckboxes = document.querySelectorAll('input[name="cuisine_type"]');
|
||||
|
||||
// Ajouter un écouteur d'événement sur chaque checkbox
|
||||
cuisineCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
// Récupérer toutes les checkboxes cochées
|
||||
const checkedCuisines = Array.from(document.querySelectorAll('input[name="cuisine_type"]:checked'))
|
||||
.map(input => input.value);
|
||||
|
||||
// Mettre à jour l'input avec les valeurs séparées par des points-virgules
|
||||
cuisineInput.value = checkedCuisines.join(';');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.updateCompletionProgress = updateCompletionProgress;
|
||||
window.parseCuisine = parseCuisine;
|
77
assets/josm.js
Normal file
77
assets/josm.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
|
||||
|
||||
/**
|
||||
* Ouvre les éléments sélectionnés dans JOSM
|
||||
* @param {Object} map - L'instance de la carte MapLibre
|
||||
* @param {boolean} map_is_loaded - État de chargement de la carte
|
||||
* @param {Array|Object} osmElements - Élément(s) OSM à ouvrir (peut être un tableau d'objets ou un objet unique)
|
||||
* @returns {void}
|
||||
*/
|
||||
function openInJOSM(map, map_is_loaded, osmElements) {
|
||||
if (!map_is_loaded) {
|
||||
alert('Veuillez attendre que la carte soit chargée');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!osmElements || (Array.isArray(osmElements) && osmElements.length === 0)) {
|
||||
alert('Aucun élément à ouvrir dans JOSM');
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = map.getBounds();
|
||||
|
||||
// Convertir en tableau si c'est un seul élément
|
||||
const elements = Array.isArray(osmElements) ? osmElements : [osmElements];
|
||||
|
||||
// Séparer les éléments par type
|
||||
const nodeIds = [];
|
||||
const wayIds = [];
|
||||
const relationIds = [];
|
||||
|
||||
elements.forEach(element => {
|
||||
if (typeof element === 'string') {
|
||||
// Si c'est juste un ID, on le traite comme un node par défaut
|
||||
nodeIds.push(element);
|
||||
} else {
|
||||
// Sinon on utilise le type spécifié
|
||||
switch (element.osm_type) {
|
||||
case 'node':
|
||||
nodeIds.push(element.osm_id);
|
||||
break;
|
||||
case 'way':
|
||||
wayIds.push(element.osm_id);
|
||||
break;
|
||||
case 'relation':
|
||||
relationIds.push(element.osm_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Construire les paramètres de sélection
|
||||
const selectParams = [];
|
||||
if (nodeIds.length > 0) selectParams.push(`node${nodeIds.join(',')}`);
|
||||
if (wayIds.length > 0) selectParams.push(`way${wayIds.join(',')}`);
|
||||
if (relationIds.length > 0) selectParams.push(`relation${relationIds.join(',')}`);
|
||||
|
||||
// Construire l'URL JOSM avec les paramètres de la boîte englobante
|
||||
const josmUrl = `http://localhost:8111/load_and_zoom?` +
|
||||
`left=${bounds.getWest()}&` +
|
||||
`right=${bounds.getEast()}&` +
|
||||
`top=${bounds.getNorth()}&` +
|
||||
`bottom=${bounds.getSouth()}&` +
|
||||
`select=${selectParams.join(',')}`;
|
||||
|
||||
// Utiliser le bouton caché pour ouvrir JOSM
|
||||
// Créer un élément <a> temporaire
|
||||
const tempLink = document.createElement('a');
|
||||
tempLink.style.display = 'none';
|
||||
document.body.appendChild(tempLink);
|
||||
tempLink.href = josmUrl;
|
||||
|
||||
console.log('josmUrl', josmUrl);
|
||||
tempLink.click();
|
||||
document.body.removeChild(tempLink);
|
||||
|
||||
}
|
||||
window.openInJOSM = openInJOSM;
|
0
assets/js/map-utils.js
Normal file
0
assets/js/map-utils.js
Normal file
12
assets/js/table-sortable.js
Normal file
12
assets/js/table-sortable.js
Normal file
File diff suppressed because one or more lines are too long
0
assets/js/table-sortable.min.js
vendored
Normal file
0
assets/js/table-sortable.min.js
vendored
Normal file
683
assets/opening_hours.js
Normal file
683
assets/opening_hours.js
Normal file
|
@ -0,0 +1,683 @@
|
|||
/**
|
||||
* Gestion du formulaire d'horaires d'ouverture
|
||||
*
|
||||
*/
|
||||
const openingHoursFormManager = {
|
||||
defaultOpeningHours: '',
|
||||
inputSelector: '',
|
||||
init: function (inputSelector = 'input[name="custom__opening_hours"]') {
|
||||
// Rechercher l'élément par son attribut name plutôt que par son id
|
||||
this.setInputSelector(inputSelector);
|
||||
const openingHoursInput = document.querySelector(inputSelector);
|
||||
if (!openingHoursInput) {
|
||||
console.warn('Élément ', inputSelector, ' non trouvé');
|
||||
return;
|
||||
}
|
||||
this.defaultOpeningHours = openingHoursInput.value;
|
||||
|
||||
// Créer la div de rendu si elle n'existe pas
|
||||
let renderDiv = document.getElementById('opening_hours_render');
|
||||
if (!renderDiv) {
|
||||
renderDiv = document.createElement('div');
|
||||
renderDiv.id = 'opening_hours_render';
|
||||
renderDiv.classList.add('mt-4');
|
||||
openingHoursInput.parentNode.insertBefore(renderDiv, openingHoursInput.nextSibling);
|
||||
}
|
||||
|
||||
this.makeForm(inputSelector);
|
||||
|
||||
if (this.defaultOpeningHours !== '') {
|
||||
this.parseOpeningHoursValue(inputSelector);
|
||||
}
|
||||
|
||||
// Ajouter un écouteur d'événement keyup sur l'input des horaires
|
||||
openingHoursInput.addEventListener('keyup', () => {
|
||||
this.defaultOpeningHours = openingHoursInput.value;
|
||||
this.parseOpeningHoursValue(inputSelector);
|
||||
});
|
||||
},
|
||||
setInputSelector: function (inputSelector) {
|
||||
this.inputSelector = inputSelector;
|
||||
},
|
||||
/**
|
||||
* convertir les checkboxes et inputs en horaires OSM dans l'input de référence
|
||||
* @param {string} inputSelector
|
||||
*/
|
||||
parseOpeningHoursValue: function (inputSelector = 'input[name="custom__opening_hours"]') {
|
||||
// Analyser la chaîne d'horaires d'ouverture
|
||||
const parsedOpeningHours = [];
|
||||
|
||||
// Masquer toutes les plages horaires par défaut
|
||||
const allDayContainers = document.querySelectorAll('.jour-container');
|
||||
allDayContainers.forEach(container => {
|
||||
const checkbox = container.querySelector('input[type="checkbox"]');
|
||||
const horairesContainer = container.querySelector('.horaires-container');
|
||||
checkbox.checked = false;
|
||||
horairesContainer.classList.add('d-none');
|
||||
});
|
||||
|
||||
if (this.defaultOpeningHours) {
|
||||
// Diviser les différentes règles (séparées par des points-virgules)
|
||||
const rules = this.defaultOpeningHours.split(';').map(r => r.trim());
|
||||
|
||||
rules.forEach(rule => {
|
||||
// Extraire les jours et les heures
|
||||
const parts = rule.split(' ').filter(Boolean);
|
||||
const days = parts[0];
|
||||
const hours = parts.slice(1).join(' ');
|
||||
|
||||
// Convertir les jours en français
|
||||
const daysMap = {
|
||||
'Mo': 'lundi',
|
||||
'Tu': 'mardi',
|
||||
'We': 'mercredi',
|
||||
'Th': 'jeudi',
|
||||
'Fr': 'vendredi',
|
||||
'Sa': 'samedi',
|
||||
'Su': 'dimanche'
|
||||
};
|
||||
|
||||
// Gérer les plages de jours (ex: Mo-Fr)
|
||||
if (days.includes('-')) {
|
||||
const [start, end] = days.split('-');
|
||||
const startIndex = Object.keys(daysMap).indexOf(start);
|
||||
const endIndex = Object.keys(daysMap).indexOf(end);
|
||||
|
||||
const dayRange = [];
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const day = Object.keys(daysMap)[i];
|
||||
dayRange.push(day);
|
||||
// Cocher la case du jour
|
||||
const checkbox = document.querySelector(`#jour-${daysMap[day]}`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
const horairesContainer = checkbox.closest('.jour-container').querySelector('.horaires-container');
|
||||
horairesContainer.classList.remove('d-none');
|
||||
|
||||
// Gérer le cas "fermé"
|
||||
if (hours === 'off') {
|
||||
const fermeCheckbox = horairesContainer.querySelector(`input[name="${daysMap[day]}-ferme"]`);
|
||||
if (fermeCheckbox) {
|
||||
fermeCheckbox.checked = true;
|
||||
// Décocher les plages horaires
|
||||
const plageInputs = horairesContainer.querySelectorAll('.time-range input[type="checkbox"]');
|
||||
plageInputs.forEach(plageInput => {
|
||||
plageInput.checked = false;
|
||||
plageInput.dispatchEvent(new Event('change'));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Décocher la deuxième plage si elle n'est pas présente dans l'input
|
||||
if (hours && !hours.includes(',')) {
|
||||
const plage2Checkbox = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage2-active"]`);
|
||||
if (plage2Checkbox) {
|
||||
plage2Checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remplir la première plage horaire si spécifiée
|
||||
if (hours) {
|
||||
const [startTime, endTime] = hours.split('-');
|
||||
if (startTime && endTime) {
|
||||
const [startHour, startMinute] = startTime.split(':');
|
||||
const [endHour, endMinute] = endTime.split(':');
|
||||
|
||||
const startHourInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-start-hour"]`);
|
||||
const startMinuteInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-start-minute"]`);
|
||||
const endHourInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-end-hour"]`);
|
||||
const endMinuteInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-end-minute"]`);
|
||||
|
||||
if (startHourInput) startHourInput.value = startHour;
|
||||
if (startMinuteInput) startMinuteInput.value = startMinute;
|
||||
if (endHourInput) endHourInput.value = endHour;
|
||||
if (endMinuteInput) endMinuteInput.value = endMinute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedOpeningHours.push({
|
||||
days: dayRange,
|
||||
hours: hours
|
||||
});
|
||||
} else {
|
||||
// Jour unique
|
||||
const day = daysMap[days];
|
||||
const checkbox = document.querySelector(`#jour-${day}`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
const horairesContainer = checkbox.closest('.jour-container').querySelector('.horaires-container');
|
||||
horairesContainer.classList.remove('d-none');
|
||||
|
||||
// Gérer le cas "fermé"
|
||||
if (hours === 'off') {
|
||||
const fermeCheckbox = horairesContainer.querySelector(`input[name="${day}-ferme"]`);
|
||||
if (fermeCheckbox) {
|
||||
fermeCheckbox.checked = true;
|
||||
// Décocher les plages horaires
|
||||
const plageInputs = horairesContainer.querySelectorAll('.time-range input[type="checkbox"]');
|
||||
plageInputs.forEach(plageInput => {
|
||||
plageInput.checked = false;
|
||||
plageInput.dispatchEvent(new Event('change'));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Décocher la deuxième plage si elle n'est pas présente dans l'input
|
||||
if (hours && !hours.includes(',')) {
|
||||
const plage2Checkbox = horairesContainer.querySelector(`input[name="${day}-plage2-active"]`);
|
||||
if (plage2Checkbox) {
|
||||
plage2Checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remplir la première plage horaire si spécifiée
|
||||
if (hours) {
|
||||
const [startTime, endTime] = hours.split('-');
|
||||
if (startTime && endTime) {
|
||||
const [startHour, startMinute] = startTime.split(':');
|
||||
const [endHour, endMinute] = endTime.split(':');
|
||||
|
||||
const startHourInput = horairesContainer.querySelector(`input[name="${day}-plage1-start-hour"]`);
|
||||
const startMinuteInput = horairesContainer.querySelector(`input[name="${day}-plage1-start-minute"]`);
|
||||
const endHourInput = horairesContainer.querySelector(`input[name="${day}-plage1-end-hour"]`);
|
||||
const endMinuteInput = horairesContainer.querySelector(`input[name="${day}-plage1-end-minute"]`);
|
||||
|
||||
if (startHourInput) startHourInput.value = startHour;
|
||||
if (startMinuteInput) startMinuteInput.value = startMinute;
|
||||
if (endHourInput) endHourInput.value = endHour;
|
||||
if (endMinuteInput) endMinuteInput.value = endMinute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedOpeningHours.push({
|
||||
days: [days],
|
||||
hours: hours
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.renderOpeningHours(parsedOpeningHours);
|
||||
console.log(parsedOpeningHours);
|
||||
},
|
||||
makeForm: function (inputSelector = 'input[name="custom__opening_hours"]') {
|
||||
const customOpeningHours = document.querySelector(inputSelector);
|
||||
console.log('makeForm customOpeningHours', customOpeningHours);
|
||||
|
||||
if (customOpeningHours) {
|
||||
// Créer un conteneur flex pour aligner l'input et le formulaire
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('d-flex', 'flex-column', 'flex-md-row', 'align-items-start', 'gap-3', 'w-100');
|
||||
|
||||
// Créer le formulaire
|
||||
const form = document.createElement('form');
|
||||
form.id = 'app_public_opening_hours';
|
||||
form.classList.add('mt-3', 'flex-grow-1');
|
||||
|
||||
// Créer les cases à cocher pour chaque jour
|
||||
const jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'];
|
||||
const joursDiv = document.createElement('div');
|
||||
joursDiv.classList.add('jours-ouverture', 'mb-4', 'row', 'mx-4');
|
||||
|
||||
jours.forEach(jour => {
|
||||
const jourContainer = document.createElement('div');
|
||||
jourContainer.classList.add('jour-container', 'col-12');
|
||||
|
||||
// Checkbox pour le jour
|
||||
const divCheck = document.createElement('div');
|
||||
divCheck.classList.add('form-check', 'mb-2');
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.id = `jour-${jour.toLowerCase()}`;
|
||||
input.name = `jour-${jour.toLowerCase()}`;
|
||||
input.classList.add('form-check-input');
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `jour-${jour.toLowerCase()}`;
|
||||
label.classList.add('form-check-label');
|
||||
label.textContent = jour;
|
||||
|
||||
divCheck.appendChild(input);
|
||||
divCheck.appendChild(label);
|
||||
jourContainer.appendChild(divCheck);
|
||||
|
||||
// Conteneur pour les plages horaires
|
||||
const horairesContainer = document.createElement('div');
|
||||
horairesContainer.classList.add('horaires-container', 'ml-2', 'd-none', 'row');
|
||||
|
||||
// Option "fermé"
|
||||
const fermeContainer = document.createElement('div');
|
||||
fermeContainer.classList.add('col-12', 'mb-2');
|
||||
const fermeCheck = document.createElement('div');
|
||||
fermeCheck.classList.add('form-check');
|
||||
const fermeInput = document.createElement('input');
|
||||
fermeInput.type = 'checkbox';
|
||||
fermeInput.id = `${jour.toLowerCase()}-ferme`;
|
||||
fermeInput.name = `${jour.toLowerCase()}-ferme`;
|
||||
fermeInput.classList.add('form-check-input');
|
||||
const fermeLabel = document.createElement('label');
|
||||
fermeLabel.htmlFor = `${jour.toLowerCase()}-ferme`;
|
||||
fermeLabel.classList.add('form-check-label');
|
||||
fermeLabel.textContent = 'Fermé';
|
||||
fermeCheck.appendChild(fermeInput);
|
||||
fermeCheck.appendChild(fermeLabel);
|
||||
fermeContainer.appendChild(fermeCheck);
|
||||
horairesContainer.appendChild(fermeContainer);
|
||||
|
||||
// Première plage horaire
|
||||
const plage1 = this.createTimeRangeInputs(`${jour.toLowerCase()}-plage1`);
|
||||
horairesContainer.appendChild(plage1);
|
||||
|
||||
// Deuxième plage horaire
|
||||
const plage2 = this.createTimeRangeInputs(`${jour.toLowerCase()}-plage2`);
|
||||
horairesContainer.appendChild(plage2);
|
||||
|
||||
jourContainer.appendChild(horairesContainer);
|
||||
joursDiv.appendChild(jourContainer);
|
||||
|
||||
// Ajouter l'événement pour afficher/masquer les plages horaires
|
||||
input.addEventListener('change', (e) => {
|
||||
horairesContainer.classList.toggle('d-none', !e.target.checked);
|
||||
this.convertToOSMOpeningHours(inputSelector);
|
||||
});
|
||||
|
||||
// Ajouter l'événement pour gérer l'option "fermé"
|
||||
fermeInput.addEventListener('change', (e) => {
|
||||
const plageInputs = horairesContainer.querySelectorAll('.time-range input[type="checkbox"]');
|
||||
plageInputs.forEach(plageInput => {
|
||||
plageInput.checked = !e.target.checked;
|
||||
plageInput.dispatchEvent(new Event('change'));
|
||||
});
|
||||
this.convertToOSMOpeningHours(inputSelector);
|
||||
});
|
||||
});
|
||||
|
||||
form.appendChild(joursDiv);
|
||||
|
||||
// Ajouter le formulaire au conteneur
|
||||
container.appendChild(form);
|
||||
|
||||
// Insérer le conteneur après l'input original
|
||||
const parent = customOpeningHours.parentNode;
|
||||
if (parent) {
|
||||
parent.insertBefore(container, customOpeningHours.nextSibling);
|
||||
}
|
||||
|
||||
// Ajouter un debounce pour limiter les appels lors des modifications
|
||||
const debounce = (func, wait) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// Appliquer le debounce à la fonction de conversion
|
||||
const debouncedConvert = debounce(() => {
|
||||
this.convertToOSMOpeningHours();
|
||||
}, 300);
|
||||
|
||||
// Ajouter les listeners sur tous les inputs
|
||||
const allInputs = form.querySelectorAll('input');
|
||||
allInputs.forEach(input => {
|
||||
input.addEventListener('change', debouncedConvert);
|
||||
input.addEventListener('input', debouncedConvert);
|
||||
});
|
||||
} else {
|
||||
console.log('pas d input opening hours détecté')
|
||||
}
|
||||
},
|
||||
|
||||
createTimeRangeInputs: function (prefix) {
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('time-range', 'mb-2', 'col-12', 'col-md-6');
|
||||
|
||||
// Case à cocher pour activer la plage
|
||||
const checkboxContainer = document.createElement('div');
|
||||
checkboxContainer.classList.add('form-check', 'mb-2');
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = `${prefix}-active`;
|
||||
checkbox.name = `${prefix}-active`;
|
||||
checkbox.classList.add('form-check-input');
|
||||
checkbox.checked = true;
|
||||
|
||||
const checkboxLabel = document.createElement('label');
|
||||
checkboxLabel.htmlFor = `${prefix}-active`;
|
||||
checkboxLabel.classList.add('form-check-label');
|
||||
checkboxLabel.textContent = '';
|
||||
|
||||
checkboxContainer.appendChild(checkbox);
|
||||
checkboxContainer.appendChild(checkboxLabel);
|
||||
container.appendChild(checkboxContainer);
|
||||
|
||||
// Conteneur pour les inputs d'horaires
|
||||
const timeContainer = document.createElement('div');
|
||||
timeContainer.classList.add('ms-4', 'row', 'g-2');
|
||||
|
||||
// Heure de début
|
||||
const startContainer = document.createElement('div');
|
||||
startContainer.classList.add('col-6', 'd-flex', 'align-items-center', 'gap-2', 'start-hour');
|
||||
|
||||
const startHour = document.createElement('input');
|
||||
startHour.type = 'number';
|
||||
startHour.min = '0';
|
||||
startHour.max = '23';
|
||||
startHour.classList.add('form-control', 'form-control-sm');
|
||||
startHour.style.width = '60px';
|
||||
startHour.placeholder = 'HH';
|
||||
startHour.name = `${prefix}-start-hour`;
|
||||
// Définir les horaires par défaut selon la plage
|
||||
startHour.value = prefix.includes('plage1') ? '08' : '14';
|
||||
|
||||
const startMinute = document.createElement('input');
|
||||
startMinute.type = 'number';
|
||||
startMinute.min = '0';
|
||||
startMinute.max = '59';
|
||||
startMinute.classList.add('form-control', 'form-control-sm');
|
||||
startMinute.style.width = '60px';
|
||||
startMinute.placeholder = 'MM';
|
||||
startMinute.name = `${prefix}-start-minute`;
|
||||
startMinute.value = '00';
|
||||
|
||||
// Heure de fin
|
||||
const endContainer = document.createElement('div');
|
||||
endContainer.classList.add('col-6', 'd-flex', 'align-items-center', 'gap-2', 'end-hour');
|
||||
|
||||
const endHour = document.createElement('input');
|
||||
endHour.type = 'number';
|
||||
endHour.min = '0';
|
||||
endHour.max = '23';
|
||||
endHour.classList.add('form-control', 'form-control-sm');
|
||||
endHour.style.width = '60px';
|
||||
endHour.placeholder = 'HH';
|
||||
endHour.name = `${prefix}-end-hour`;
|
||||
// Définir les horaires par défaut selon la plage
|
||||
endHour.value = prefix.includes('plage1') ? '12' : '18';
|
||||
|
||||
const endMinute = document.createElement('input');
|
||||
endMinute.type = 'number';
|
||||
endMinute.min = '0';
|
||||
endMinute.max = '59';
|
||||
endMinute.classList.add('form-control', 'form-control-sm');
|
||||
endMinute.style.width = '60px';
|
||||
endMinute.placeholder = 'MM';
|
||||
endMinute.name = `${prefix}-end-minute`;
|
||||
endMinute.value = '00';
|
||||
|
||||
// Créer le texte avec les horaires
|
||||
const timeText = document.createElement('div');
|
||||
timeText.classList.add('d-flex', 'align-items-center', 'gap-2', 'flex-wrap');
|
||||
|
||||
// Texte de début
|
||||
const startText = document.createElement('span');
|
||||
startText.textContent = 'de';
|
||||
timeText.appendChild(startText);
|
||||
timeText.appendChild(startHour);
|
||||
timeText.appendChild(document.createTextNode(':'));
|
||||
timeText.appendChild(startMinute);
|
||||
|
||||
// Texte du milieu
|
||||
const middleText = document.createElement('span');
|
||||
middleText.textContent = 'à';
|
||||
timeText.appendChild(middleText);
|
||||
|
||||
// Texte de fin
|
||||
timeText.appendChild(endHour);
|
||||
timeText.appendChild(document.createTextNode(':'));
|
||||
timeText.appendChild(endMinute);
|
||||
|
||||
timeContainer.appendChild(timeText);
|
||||
container.appendChild(timeContainer);
|
||||
|
||||
// Fonction pour mettre à jour le style des inputs
|
||||
const updateInputsStyle = (isActive) => {
|
||||
const inputs = timeContainer.querySelectorAll('input');
|
||||
inputs.forEach(input => {
|
||||
if (isActive) {
|
||||
input.classList.remove('bg-light', 'text-muted');
|
||||
input.disabled = false;
|
||||
} else {
|
||||
input.classList.add('bg-light', 'text-muted');
|
||||
input.disabled = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Ajouter l'événement sur la checkbox
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
updateInputsStyle(e.target.checked);
|
||||
});
|
||||
|
||||
// Appliquer le style initial
|
||||
updateInputsStyle(checkbox.checked);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
convertToOSMOpeningHours: function (inputSelector = 'input[name="custom__opening_hours"]') {
|
||||
const jours = {
|
||||
'Lundi': 'Mo',
|
||||
'Mardi': 'Tu',
|
||||
'Mercredi': 'We',
|
||||
'Jeudi': 'Th',
|
||||
'Vendredi': 'Fr',
|
||||
'Samedi': 'Sa',
|
||||
'Dimanche': 'Su'
|
||||
};
|
||||
|
||||
let joursSelectionnes = [];
|
||||
let horairesParJour = {};
|
||||
|
||||
// Parcourir les checkboxes des jours
|
||||
Object.keys(jours).forEach(jour => {
|
||||
const checkbox = document.getElementById(`jour-${jour.toLowerCase()}`);
|
||||
if (checkbox && checkbox.checked) {
|
||||
joursSelectionnes.push(jours[jour]);
|
||||
|
||||
// Vérifier si le jour est marqué comme fermé
|
||||
const fermeCheckbox = document.getElementById(`${jour.toLowerCase()}-ferme`);
|
||||
if (fermeCheckbox && fermeCheckbox.checked) {
|
||||
horairesParJour[jours[jour]] = 'off';
|
||||
} else {
|
||||
// Récupérer les horaires pour ce jour
|
||||
const prefix = jour.toLowerCase();
|
||||
const plage1 = this.getTimeRange(`${prefix}-plage1`);
|
||||
const plage2 = this.getTimeRange(`${prefix}-plage2`);
|
||||
|
||||
let horaires = [];
|
||||
if (plage1) horaires.push(plage1);
|
||||
if (plage2) horaires.push(plage2);
|
||||
|
||||
if (horaires.length > 0) {
|
||||
horairesParJour[jours[jour]] = horaires.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Optimiser les plages horaires identiques consécutives
|
||||
let optimizedRules = [];
|
||||
let currentRule = null;
|
||||
let currentDays = [];
|
||||
let currentHours = null;
|
||||
|
||||
joursSelectionnes.forEach((jour, index) => {
|
||||
const hours = horairesParJour[jour];
|
||||
|
||||
if (currentHours === null) {
|
||||
// Première règle
|
||||
currentHours = hours;
|
||||
currentDays = [jour];
|
||||
} else if (hours === currentHours) {
|
||||
// Mêmes horaires que la règle en cours
|
||||
currentDays.push(jour);
|
||||
} else {
|
||||
// Horaires différents, on finalise la règle en cours
|
||||
if (currentDays.length > 0) {
|
||||
optimizedRules.push({
|
||||
days: currentDays,
|
||||
hours: currentHours
|
||||
});
|
||||
}
|
||||
// On commence une nouvelle règle
|
||||
currentDays = [jour];
|
||||
currentHours = hours;
|
||||
}
|
||||
|
||||
// Si c'est le dernier jour, on finalise la règle en cours
|
||||
if (index === joursSelectionnes.length - 1 && currentDays.length > 0) {
|
||||
optimizedRules.push({
|
||||
days: currentDays,
|
||||
hours: currentHours
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Construire la chaîne au format OSM
|
||||
let osmFormat = optimizedRules.map(rule => {
|
||||
const days = rule.days.length > 1 ? `${rule.days[0]}-${rule.days[rule.days.length - 1]}` : rule.days[0];
|
||||
return rule.hours ? `${days} ${rule.hours}` : days;
|
||||
}).join('; ');
|
||||
|
||||
// Mettre à jour l'input custom__opening_hours
|
||||
const customOpeningHours = document.querySelector(inputSelector);
|
||||
if (customOpeningHours) {
|
||||
customOpeningHours.value = osmFormat;
|
||||
}
|
||||
|
||||
// Mettre à jour le rendu visuel
|
||||
this.renderOpeningHours(optimizedRules);
|
||||
},
|
||||
|
||||
renderOpeningHours: function (rules) {
|
||||
const container = document.getElementById('opening_hours_render');
|
||||
console.log('renderOpeningHours', rules);
|
||||
if (!container) return;
|
||||
|
||||
// Vider le conteneur
|
||||
container.innerHTML = '';
|
||||
|
||||
// Créer le style pour les sections d'ouverture
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.opening-hours-day {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
}
|
||||
.opening-hours-time {
|
||||
background-color: #d4edda;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
height: 24px;
|
||||
top: 8px;
|
||||
}
|
||||
.opening-hours-label {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
.opening-hours-closed {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Mapping des jours OSM vers français
|
||||
const joursMap = {
|
||||
'Mo': 'Lundi',
|
||||
'Tu': 'Mardi',
|
||||
'We': 'Mercredi',
|
||||
'Th': 'Jeudi',
|
||||
'Fr': 'Vendredi',
|
||||
'Sa': 'Samedi',
|
||||
'Su': 'Dimanche'
|
||||
};
|
||||
|
||||
// Créer les lignes pour chaque jour
|
||||
Object.entries(joursMap).forEach(([osmDay, frenchDay]) => {
|
||||
const dayDiv = document.createElement('div');
|
||||
dayDiv.classList.add('opening-hours-day');
|
||||
|
||||
// Ajouter le label du jour
|
||||
const label = document.createElement('span');
|
||||
label.classList.add('opening-hours-label');
|
||||
label.textContent = frenchDay;
|
||||
dayDiv.appendChild(label);
|
||||
|
||||
// Trouver les horaires pour ce jour
|
||||
const rule = rules.find(r => r.days.includes(osmDay));
|
||||
if (rule) {
|
||||
if (rule.hours === 'off') {
|
||||
// Jour fermé
|
||||
dayDiv.classList.add('opening-hours-closed');
|
||||
} else if (rule.hours) {
|
||||
// Plages horaires spécifiques
|
||||
const timeRanges = rule.hours.split(',');
|
||||
timeRanges.forEach((timeRange, index) => {
|
||||
const [start, end] = timeRange.split('-');
|
||||
const [startHour, startMinute] = start.split(':');
|
||||
const [endHour, endMinute] = end.split(':');
|
||||
|
||||
// Calculer la position et la largeur
|
||||
const startMinutes = parseInt(startHour) * 60 + parseInt(startMinute);
|
||||
const endMinutes = parseInt(endHour) * 60 + parseInt(endMinute);
|
||||
const totalMinutes = 24 * 60;
|
||||
|
||||
const left = (startMinutes / totalMinutes) * 100;
|
||||
const width = ((endMinutes - startMinutes) / totalMinutes) * 100;
|
||||
|
||||
const timeDiv = document.createElement('div');
|
||||
timeDiv.classList.add('opening-hours-time');
|
||||
timeDiv.style.left = `${left}%`;
|
||||
timeDiv.style.width = `${width}%`;
|
||||
timeDiv.title = `${start}-${end}`;
|
||||
|
||||
dayDiv.appendChild(timeDiv);
|
||||
});
|
||||
} else {
|
||||
// Toute la journée
|
||||
const timeDiv = document.createElement('div');
|
||||
timeDiv.classList.add('opening-hours-time');
|
||||
timeDiv.style.left = '0%';
|
||||
timeDiv.style.width = '100%';
|
||||
timeDiv.title = 'Toute la journée';
|
||||
dayDiv.appendChild(timeDiv);
|
||||
}
|
||||
}
|
||||
|
||||
container.appendChild(dayDiv);
|
||||
});
|
||||
},
|
||||
|
||||
getTimeRange: function (prefix) {
|
||||
const isActive = document.querySelector(`input[name="${prefix}-active"]`).checked;
|
||||
if (!isActive) return null;
|
||||
|
||||
const startHour = document.querySelector(`input[name="${prefix}-start-hour"]`).value;
|
||||
const startMinute = document.querySelector(`input[name="${prefix}-start-minute"]`).value;
|
||||
const endHour = document.querySelector(`input[name="${prefix}-end-hour"]`).value;
|
||||
const endMinute = document.querySelector(`input[name="${prefix}-end-minute"]`).value;
|
||||
|
||||
if (startHour && startMinute && endHour && endMinute) {
|
||||
const start = `${startHour.padStart(2, '0')}:${startMinute.padStart(2, '0')}`;
|
||||
const end = `${endHour.padStart(2, '0')}:${endMinute.padStart(2, '0')}`;
|
||||
return `${start}-${end}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
window.openingHoursFormManager = openingHoursFormManager;
|
44
assets/stats-charts.js
Normal file
44
assets/stats-charts.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Affichage du graphique de fréquence des mises à jour par trimestre sur la page stats d'une ville
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
function drawModificationsByQuarterChart() {
|
||||
if (!window.Chart) {
|
||||
setTimeout(drawModificationsByQuarterChart, 50);
|
||||
return;
|
||||
}
|
||||
if (typeof window.modificationsByQuarter === 'undefined') return;
|
||||
const modifData = window.modificationsByQuarter;
|
||||
const modifLabels = Object.keys(modifData);
|
||||
const modifCounts = Object.values(modifData);
|
||||
const modifCanvas = document.getElementById('modificationsByQuarterChart');
|
||||
if (modifCanvas && modifLabels.length > 0) {
|
||||
new window.Chart(modifCanvas.getContext('2d'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: modifLabels,
|
||||
datasets: [{
|
||||
label: 'Nombre de lieux modifiés',
|
||||
data: modifCounts,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.7)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: true, text: 'Fréquence des mises à jour par trimestre' }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, title: { display: true, text: 'Nombre de lieux' } },
|
||||
x: { title: { display: true, text: 'Trimestre' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (modifCanvas) {
|
||||
modifCanvas.parentNode.innerHTML = '<div class="alert alert-info">Aucune donnée de modification disponible pour cette ville.</div>';
|
||||
}
|
||||
}
|
||||
drawModificationsByQuarterChart();
|
||||
});
|
192
assets/styles/app.css
Normal file
192
assets/styles/app.css
Normal file
|
@ -0,0 +1,192 @@
|
|||
body {
|
||||
background-color: rgb(236, 236, 236);
|
||||
transition: background-color ease 0.5s;
|
||||
}
|
||||
|
||||
.body-landing .container {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.main-footer {
|
||||
padding-bottom: 20rem;
|
||||
background-color: #dfe5eb;
|
||||
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
margin-bottom: 8rem;
|
||||
}
|
||||
|
||||
input[data-important] {
|
||||
border-color: #7a8fbb;
|
||||
border-left-width: 5px;
|
||||
}
|
||||
|
||||
input[data-important]:before {
|
||||
content: ">" !important;
|
||||
}
|
||||
|
||||
.filled, .good_filled {
|
||||
border-color: rgba(0, 255, 0, 0.8) !important;
|
||||
color: #082b0a !important;
|
||||
}
|
||||
|
||||
.filled:hover, .good_filled:hover {
|
||||
background-color: #d9ffd1 !important;
|
||||
}
|
||||
|
||||
.no-name {
|
||||
color: #df5a0d;
|
||||
}
|
||||
|
||||
table {
|
||||
max-height: 100vh;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
table.js-sort-table th {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table.js-sort-table th:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
table.js-sort-table th:active {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
/*// formulaire de temps d'ouverture */
|
||||
.form-check-input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.jour-container {
|
||||
border: solid #dedede 1px;
|
||||
}
|
||||
|
||||
#maploader {
|
||||
position: relative;
|
||||
top: 200px;
|
||||
left: 50%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.maplibregl-popup-content {
|
||||
overflow: auto;
|
||||
min-width: 300px;
|
||||
max-height: 11rem !important;
|
||||
}
|
||||
|
||||
.maplibregl-popup-content strong {
|
||||
min-width: 10rem;
|
||||
}
|
||||
|
||||
.maplibregl-popup-content table {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.maplibregl-popup-content h1,
|
||||
.maplibregl-popup-content h2,
|
||||
.maplibregl-popup-contenth3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#attribution {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
#restaurant .form-check-label {
|
||||
margin-top: 0;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#restaurant .form-check-label:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: #1e40c6;
|
||||
}
|
||||
|
||||
#advanced_tags {
|
||||
border-left: solid 3px #ccc;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.start-hour {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.end-hour {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.good_filled {
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.is-invalid {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.is-invalid #validation_messages {
|
||||
color: red;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
|
||||
#completionHistoryChart {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
table tbody {
|
||||
max-height: 700px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#table_container, .table-container, #table-container {
|
||||
max-height: 700px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
border: solid 3px rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
#citySuggestions {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
body .card:hover {
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
19
assets/table-map-toggles.js
Normal file
19
assets/table-map-toggles.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Gestion du tri des tableaux
|
||||
// import Tablesort from 'tablesort';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Gestion du toggle gouttes/ronds sur la carte
|
||||
const toggle = document.getElementById('toggleMarkers');
|
||||
if (toggle && window.updateMarkers) {
|
||||
toggle.addEventListener('change', (e) => {
|
||||
window.updateMarkers(e.target.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Exposer une fonction pour (ré)appliquer le tri si besoin
|
||||
export function applyTableSort() {
|
||||
document.querySelectorAll('.js-sort-table').forEach(table => {
|
||||
new window.Tablesort(table);
|
||||
});
|
||||
}
|
384
assets/utils.js
Normal file
384
assets/utils.js
Normal file
|
@ -0,0 +1,384 @@
|
|||
function colorHeadingTable() {
|
||||
const headers = document.querySelectorAll('th');
|
||||
headers.forEach(header => {
|
||||
const text = header.textContent;
|
||||
const match = text.match(/\((\d+)\s*\/\s*(\d+)\)/);
|
||||
if (match) {
|
||||
const [_, completed, total] = match;
|
||||
const ratio = completed / total;
|
||||
const alpha = ratio.toFixed(2);
|
||||
header.style.backgroundColor = `rgba(154, 205, 50, ${alpha})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function check_validity(e) {
|
||||
list_inputs_good_to_fill = [
|
||||
'input[name="commerce_tag_value__contact:email"]',
|
||||
'input[name="commerce_tag_value__contact:phone"]',
|
||||
'input[name="commerce_tag_value__contact:website"]',
|
||||
'input[name="commerce_tag_value__contact:mastodon"]',
|
||||
'input[name="commerce_tag_value__address"]',
|
||||
'input[name="custom_opening_hours"]',
|
||||
'input[name="commerce_tag_value__contact:street"]',
|
||||
'input[name="commerce_tag_value__contact:housenumber"]',
|
||||
'input[name="custom__cuisine"]',
|
||||
]
|
||||
|
||||
list_inputs_good_to_fill.forEach(selector => {
|
||||
const input = document.querySelector(selector);
|
||||
if (input) {
|
||||
if (input.value.trim() !== '') {
|
||||
input.classList.add('good_filled');
|
||||
} else {
|
||||
input.classList.remove('good_filled');
|
||||
}
|
||||
}
|
||||
});
|
||||
let errors = [];
|
||||
document.querySelectorAll('.is-invalid').forEach(input => {
|
||||
input.classList.remove('is-invalid');
|
||||
});
|
||||
|
||||
const nameInput = document.querySelector('input[name="commerce_tag_value__name"]');
|
||||
if (!nameInput.value.trim()) {
|
||||
errors.push("Le nom de l'établissement est obligatoire");
|
||||
nameInput.classList.add('is-invalid');
|
||||
}
|
||||
|
||||
const emailInput = document.querySelector('input[name="commerce_tag_value__contact:email"]');
|
||||
if (emailInput && emailInput.value) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(emailInput.value)) {
|
||||
errors.push("L'adresse email n'est pas valide");
|
||||
emailInput.classList.add('is-invalid');
|
||||
}
|
||||
}
|
||||
|
||||
const phoneInput = document.querySelector('input[name="commerce_tag_value__contact:phone"]');
|
||||
if (phoneInput && phoneInput.value) {
|
||||
const phoneRegex = /^(\+33|0)[1-9](\d{2}){4}$/;
|
||||
if (!phoneRegex.test(phoneInput.value.replace(/\s/g, ''))) {
|
||||
errors.push("Le numéro de téléphone n'est pas valide");
|
||||
phoneInput.classList.add('is-invalid');
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
e.preventDefault();
|
||||
document.querySelector('#validation_messages').innerHTML = errors.join('<br>');
|
||||
document.querySelector('#validation_messages').classList.add('is-invalid');
|
||||
}
|
||||
}
|
||||
|
||||
export const genererCouleurPastel = () => {
|
||||
const r = Math.floor(Math.random() * 75 + 180);
|
||||
const g = Math.floor(Math.random() * 75 + 180);
|
||||
const b = Math.floor(Math.random() * 75 + 180);
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
};
|
||||
|
||||
async function searchInseeCode(query) {
|
||||
try {
|
||||
document.querySelector('#loading_search_insee').classList.remove('d-none');
|
||||
|
||||
const response = await fetch(`https://geo.api.gouv.fr/communes?nom=${query}&fields=nom,code,codesPostaux&limit=10`);
|
||||
const data = await response.json();
|
||||
document.querySelector('#loading_search_insee').classList.add('d-none');
|
||||
|
||||
return data.map(commune => ({
|
||||
label: `${commune.nom} (${commune.codesPostaux.join(', ')}, code insee ${commune.code})`,
|
||||
insee: commune.code,
|
||||
postcodes: commune.codesPostaux
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche du code INSEE:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function updateMapHeightForLargeScreens() {
|
||||
const mapFound = document.querySelector('#map');
|
||||
const canvasFound = document.querySelector('#map canvas');
|
||||
const newHeight = window.innerHeight * 0.5 + 'px'
|
||||
if (mapFound && window.innerHeight > 800 && window.innerWidth > 800) {
|
||||
mapFound.style.height = newHeight;
|
||||
} else {
|
||||
console.log('window.innerHeight', window.innerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
async function listChangesets() {
|
||||
const options = {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
};
|
||||
const changesets = await fetch('https://api.openstreetmap.org/api/0.6/changesets?display_name=osm-commerce-fr', options);
|
||||
const data = await changesets.json();
|
||||
console.log(data.changesets.length);
|
||||
|
||||
const now = new Date();
|
||||
const last24h = new Date(now - 24 * 60 * 60 * 1000);
|
||||
const last7days = new Date(now - 7 * 24 * 60 * 60 * 1000);
|
||||
const last30days = new Date(now - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const stats = {
|
||||
last24h: 0,
|
||||
last7days: 0,
|
||||
last30days: 0
|
||||
};
|
||||
|
||||
data.changesets.forEach(changeset => {
|
||||
const changesetDate = new Date(changeset.closed_at);
|
||||
|
||||
if (changesetDate >= last24h) {
|
||||
stats.last24h++;
|
||||
}
|
||||
if (changesetDate >= last7days) {
|
||||
stats.last7days++;
|
||||
}
|
||||
if (changesetDate >= last30days) {
|
||||
stats.last30days++;
|
||||
}
|
||||
});
|
||||
|
||||
const historyDiv = document.getElementById('userChangesHistory');
|
||||
if (historyDiv) {
|
||||
historyDiv.innerHTML = `
|
||||
<div id="changesets_history">
|
||||
<p>Changesets créés :</p>
|
||||
<div class="row">
|
||||
<div class="col-6">Dernières 24h :</div> <div class="col-6 text-right">${stats.last24h}</div>
|
||||
<div class="col-6">7 derniers jours :</div> <div class="col-6 text-right">${stats.last7days}</div>
|
||||
<div class="col-6">30 derniers jours :</div> <div class="col-6 text-right">${stats.last30days}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function openInPanoramax() {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
const panoramaxUrl = `https://api.panoramax.xyz/?focus=map&map=${zoom}/${center.lat}/${center.lng}`;
|
||||
window.open(panoramaxUrl);
|
||||
}
|
||||
|
||||
export function enableLabourageForm() {
|
||||
const citySearchInput = document.getElementById('citySearch');
|
||||
const citySuggestionsList = document.getElementById('citySuggestions');
|
||||
|
||||
if (citySearchInput && citySuggestionsList) {
|
||||
const form = citySearchInput.closest('form');
|
||||
setupCitySearch('citySearch', 'citySuggestions', function (result_search) {
|
||||
if (form) {
|
||||
const labourageBtn = form.querySelector('button[type="submit"]');
|
||||
if (labourageBtn) {
|
||||
// Remplir le champ caché avec le code INSEE
|
||||
const inseeInput = form.querySelector('#selectedZipCode');
|
||||
if (inseeInput) {
|
||||
inseeInput.value = result_search.insee;
|
||||
}
|
||||
// Changer l'action du formulaire pour pointer vers la bonne URL
|
||||
form.action = getLabourerUrl(result_search);
|
||||
|
||||
// Changer le texte du bouton et le désactiver
|
||||
labourageBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Ajout de ' + result_search.name + '...';
|
||||
labourageBtn.disabled = true;
|
||||
|
||||
// Soumettre le formulaire
|
||||
form.submit();
|
||||
}
|
||||
}else{
|
||||
console.warn('pas de form pour labourage trouvé')
|
||||
}
|
||||
});
|
||||
}else{
|
||||
console.warn('pas de labourage citySearchInput citySuggestionsList trouvé', citySearchInput,citySuggestionsList )
|
||||
}
|
||||
}
|
||||
|
||||
export function setupCitySearch(inputId, suggestionListId, onSelect) {
|
||||
const searchInput = document.getElementById(inputId);
|
||||
const suggestionList = document.getElementById(suggestionListId);
|
||||
|
||||
if (!searchInput || !suggestionList) return;
|
||||
|
||||
let timeoutId = null;
|
||||
let searchOngoing = false;
|
||||
|
||||
searchInput.addEventListener('keyup', function () {
|
||||
clearTimeout(timeoutId);
|
||||
const query = this.value.trim();
|
||||
if (query.length < 3) {
|
||||
clearSuggestions();
|
||||
return;
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
if (!searchOngoing) {
|
||||
searchOngoing = true;
|
||||
performSearch(query).then(() => {
|
||||
searchOngoing = false;
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
async function performSearch(query) {
|
||||
try {
|
||||
const response = await fetch(`https://geo.api.gouv.fr/communes?nom=${encodeURIComponent(query)}&fields=nom,code,codesPostaux&limit=5`);
|
||||
const data = await response.json();
|
||||
const citySuggestions = data.map(city => ({
|
||||
name: city.nom,
|
||||
insee: city.code,
|
||||
postcodes: city.codesPostaux,
|
||||
postcode: city.codesPostaux && city.codesPostaux.length > 0 ? city.codesPostaux[0] : '',
|
||||
display_name: `${city.nom} (${city.codesPostaux && city.codesPostaux.length > 0 ? city.codesPostaux[0] : ''})`
|
||||
}));
|
||||
displaySuggestions(citySuggestions);
|
||||
} catch (error) {
|
||||
console.error("Erreur de recherche:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function displaySuggestions(suggestions) {
|
||||
clearSuggestions();
|
||||
suggestions.forEach(suggestion => {
|
||||
const item = document.createElement('div');
|
||||
item.classList.add('suggestion-item');
|
||||
// Nouveau rendu : nom en gras, INSEE et CP en petit/gris
|
||||
item.innerHTML = `
|
||||
<span class="suggestion-name" style="font-weight:bold;">${suggestion.name}</span><br>
|
||||
<span class="suggestion-details" style="font-size:0.95em;color:#666;">
|
||||
<span>INSEE : <b>${suggestion.insee}</b></span>
|
||||
<span style="margin-left:12px;">CP : <b>${Array.isArray(suggestion.postcodes) ? suggestion.postcodes.join(', ') : suggestion.postcode}</b></span>
|
||||
</span>
|
||||
`;
|
||||
item.addEventListener('click', () => {
|
||||
searchInput.value = suggestion.name;
|
||||
clearSuggestions();
|
||||
if (onSelect) {
|
||||
onSelect(suggestion);
|
||||
}
|
||||
});
|
||||
suggestionList.appendChild(item);
|
||||
});
|
||||
suggestionList.style.display = 'block';
|
||||
}
|
||||
|
||||
function clearSuggestions() {
|
||||
suggestionList.innerHTML = '';
|
||||
suggestionList.style.display = 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!searchInput.contains(e.target) && !suggestionList.contains(e.target)) {
|
||||
clearSuggestions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getLabourerUrl(obj) {
|
||||
if (obj && obj.insee) {
|
||||
return `/add-city-without-labourage/${obj.insee}`;
|
||||
}
|
||||
return '#';
|
||||
}
|
||||
|
||||
export function handleAddCityFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
const zipCode = document.getElementById('selectedZipCode').value;
|
||||
if (zipCode && zipCode.match(/^\d{5}$/)) {
|
||||
window.location.href = `/add-city-without-labourage/${zipCode}`;
|
||||
} else {
|
||||
alert('Veuillez sélectionner une ville valide avec un code postal.');
|
||||
}
|
||||
}
|
||||
|
||||
export function colorizePercentageCells(selector, color = '154, 205, 50') {
|
||||
document.querySelectorAll(selector).forEach(cell => {
|
||||
const percentage = parseInt(cell.textContent.replace('%', ''), 10);
|
||||
if (!isNaN(percentage)) {
|
||||
const alpha = percentage / 100;
|
||||
cell.style.backgroundColor = `rgba(${color}, ${alpha})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function colorizePercentageCellsRelative(selector, color = '154, 205, 50') {
|
||||
let min = Infinity;
|
||||
let max = -Infinity;
|
||||
const cells = document.querySelectorAll(selector);
|
||||
|
||||
cells.forEach(cell => {
|
||||
const value = parseInt(cell.textContent.replace('%', ''), 10);
|
||||
if (!isNaN(value)) {
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (max > min) {
|
||||
cells.forEach(cell => {
|
||||
const value = parseInt(cell.textContent.replace('%', ''), 10);
|
||||
if (!isNaN(value)) {
|
||||
const ratio = (value - min) / (max - min);
|
||||
cell.style.backgroundColor = `rgba(${color}, ${ratio.toFixed(2)})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function adjustListGroupFontSize(selector, minFont = 0.8, maxFont = 1.2) {
|
||||
const listItems = document.querySelectorAll(selector);
|
||||
if (listItems.length === 0) return;
|
||||
|
||||
let fontSize = maxFont;
|
||||
const count = listItems.length;
|
||||
if (count > 0) {
|
||||
fontSize = Math.max(minFont, maxFont - (count - 5) * 0.05);
|
||||
}
|
||||
listItems.forEach(item => {
|
||||
item.style.fontSize = fontSize + 'rem';
|
||||
});
|
||||
}
|
||||
|
||||
export function calculateCompletion(properties) {
|
||||
let completed = 0;
|
||||
const total = 7; // Nombre de critères
|
||||
|
||||
if (properties.name) completed++;
|
||||
if (properties['addr:housenumber'] && properties['addr:street']) completed++;
|
||||
if (properties.opening_hours) completed++;
|
||||
if (properties.website || properties['contact:website']) completed++;
|
||||
if (properties.phone || properties['contact:phone']) completed++;
|
||||
if (properties.wheelchair) completed++;
|
||||
|
||||
return {
|
||||
percentage: total > 0 ? (completed / total) * 100 : 0,
|
||||
completed: completed,
|
||||
total: total
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleCompletionInfo() {
|
||||
const content = document.getElementById('completionInfoContent');
|
||||
const icon = document.getElementById('completionInfoIcon');
|
||||
if (content) {
|
||||
const isVisible = content.style.display === 'block';
|
||||
content.style.display = isVisible ? 'none' : 'block';
|
||||
if (icon) {
|
||||
icon.classList.toggle('bi-chevron-down', isVisible);
|
||||
icon.classList.toggle('bi-chevron-up', !isVisible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.check_validity = check_validity;
|
||||
window.colorHeadingTable = colorHeadingTable;
|
||||
window.openInPanoramax = openInPanoramax;
|
||||
window.listChangesets = listChangesets;
|
||||
window.adjustListGroupFontSize = adjustListGroupFontSize;
|
||||
window.calculateCompletion = calculateCompletion;
|
||||
window.toggleCompletionInfo = toggleCompletionInfo;
|
7
assets/vendor/@hotwired/stimulus/stimulus.index.js
vendored
Normal file
7
assets/vendor/@hotwired/stimulus/stimulus.index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
30
assets/vendor/@hotwired/turbo/turbo.index.js
vendored
Normal file
30
assets/vendor/@hotwired/turbo/turbo.index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
22
assets/vendor/installed.php
vendored
Normal file
22
assets/vendor/installed.php
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php return array (
|
||||
'@hotwired/stimulus' =>
|
||||
array (
|
||||
'version' => '3.2.2',
|
||||
'dependencies' =>
|
||||
array (
|
||||
),
|
||||
'extraFiles' =>
|
||||
array (
|
||||
),
|
||||
),
|
||||
'@hotwired/turbo' =>
|
||||
array (
|
||||
'version' => '7.3.0',
|
||||
'dependencies' =>
|
||||
array (
|
||||
),
|
||||
'extraFiles' =>
|
||||
array (
|
||||
),
|
||||
),
|
||||
);
|
21
bin/console
Executable file
21
bin/console
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
|
||||
return new Application($kernel);
|
||||
};
|
23
bin/phpunit
Executable file
23
bin/phpunit
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
24
cipherbliss-osm-commerce.config.caddy
Normal file
24
cipherbliss-osm-commerce.config.caddy
Normal file
|
@ -0,0 +1,24 @@
|
|||
qualiwiki.cipherbliss.com {
|
||||
root * /home/poule/encrypted/stockage-syncable/www/development/html/qualiwiki/public
|
||||
|
||||
# serve files directly if they can be found (e.g. CSS or JS files in public/)
|
||||
encode zstd gzip
|
||||
file_server
|
||||
|
||||
# otherwise, use PHP-FPM (replace "unix//var/..." with "127.0.0.1:9000" when using TCP)
|
||||
php_fastcgi unix//var/run/php/php8.4-fpm.sock {
|
||||
# only fall back to root index.php aka front controller.
|
||||
try_files {path} index.php
|
||||
}
|
||||
|
||||
# Logs
|
||||
log {
|
||||
output file /var/log/caddy/access-qualiwiki.log
|
||||
format console
|
||||
}
|
||||
|
||||
@phpFile {
|
||||
path *.php*
|
||||
}
|
||||
error @phpFile "Not found" 404
|
||||
}
|
18
compose.override.yaml
Normal file
18
compose.override.yaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
mailer:
|
||||
image: axllent/mailpit
|
||||
ports:
|
||||
- "1025"
|
||||
- "8025"
|
||||
environment:
|
||||
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
###< symfony/mailer ###
|
25
compose.yaml
Normal file
25
compose.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
volumes:
|
||||
- database_data:/var/lib/postgresql/data:rw
|
||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
108
composer.json
Normal file
108
composer.json
Normal file
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.15",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.5",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/asset-mapper": "7.3.*",
|
||||
"symfony/console": "7.3.*",
|
||||
"symfony/doctrine-messenger": "7.3.*",
|
||||
"symfony/dotenv": "7.3.*",
|
||||
"symfony/expression-language": "7.3.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.3.*",
|
||||
"symfony/framework-bundle": "7.3.*",
|
||||
"symfony/http-client": "7.3.*",
|
||||
"symfony/intl": "7.3.*",
|
||||
"symfony/mailer": "7.3.*",
|
||||
"symfony/mime": "7.3.*",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/notifier": "7.3.*",
|
||||
"symfony/process": "7.3.*",
|
||||
"symfony/property-access": "7.3.*",
|
||||
"symfony/property-info": "7.3.*",
|
||||
"symfony/runtime": "7.3.*",
|
||||
"symfony/security-bundle": "7.3.*",
|
||||
"symfony/serializer": "7.3.*",
|
||||
"symfony/stimulus-bundle": "^2.30",
|
||||
"symfony/string": "7.3.*",
|
||||
"symfony/translation": "7.3.*",
|
||||
"symfony/twig-bundle": "7.3.*",
|
||||
"symfony/ux-turbo": "^2.30",
|
||||
"symfony/validator": "7.3.*",
|
||||
"symfony/web-link": "7.3.*",
|
||||
"symfony/yaml": "7.3.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"bump-after-update": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
||||
"importmap:install": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.3.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.3",
|
||||
"symfony/browser-kit": "7.3.*",
|
||||
"symfony/css-selector": "7.3.*",
|
||||
"symfony/debug-bundle": "7.3.*",
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/stopwatch": "7.3.*",
|
||||
"symfony/web-profiler-bundle": "7.3.*"
|
||||
}
|
||||
}
|
9964
composer.lock
generated
Normal file
9964
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
config/bundles.php
Normal file
16
config/bundles.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
];
|
11
config/packages/asset_mapper.yaml
Normal file
11
config/packages/asset_mapper.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
framework:
|
||||
asset_mapper:
|
||||
# The paths to make available to the asset mapper.
|
||||
paths:
|
||||
- assets/
|
||||
missing_import_mode: strict
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
asset_mapper:
|
||||
missing_import_mode: warn
|
19
config/packages/cache.yaml
Normal file
19
config/packages/cache.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
11
config/packages/csrf.yaml
Normal file
11
config/packages/csrf.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Enable stateless CSRF protection for forms and logins/logouts
|
||||
framework:
|
||||
form:
|
||||
csrf_protection:
|
||||
token_id: submit
|
||||
|
||||
csrf_protection:
|
||||
stateless_token_ids:
|
||||
- submit
|
||||
- authenticate
|
||||
- logout
|
5
config/packages/debug.yaml
Normal file
5
config/packages/debug.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
when@dev:
|
||||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
54
config/packages/doctrine.yaml
Normal file
54
config/packages/doctrine.yaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
6
config/packages/doctrine_migrations.yaml
Normal file
6
config/packages/doctrine_migrations.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
15
config/packages/framework.yaml
Normal file
15
config/packages/framework.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
3
config/packages/mailer.yaml
Normal file
3
config/packages/mailer.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
29
config/packages/messenger.yaml
Normal file
29
config/packages/messenger.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
framework:
|
||||
messenger:
|
||||
failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
async:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
use_notify: true
|
||||
check_delayed_interval: 60000
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
multiplier: 2
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
# sync: 'sync://'
|
||||
|
||||
default_bus: messenger.bus.default
|
||||
|
||||
buses:
|
||||
messenger.bus.default: []
|
||||
|
||||
routing:
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
62
config/packages/monolog.yaml
Normal file
62
config/packages/monolog.yaml
Normal file
|
@ -0,0 +1,62 @@
|
|||
monolog:
|
||||
channels:
|
||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
|
||||
when@prod:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: [deprecation]
|
||||
path: php://stderr
|
||||
formatter: monolog.formatter.json
|
12
config/packages/notifier.yaml
Normal file
12
config/packages/notifier.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
3
config/packages/property_info.yaml
Normal file
3
config/packages/property_info.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
10
config/packages/routing.yaml
Normal file
10
config/packages/routing.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
39
config/packages/security.yaml
Normal file
39
config/packages/security.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: users_in_memory
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# By default, password hashers are resource intensive and take time. This is
|
||||
# important to generate secure password hashes. In tests however, secure hashes
|
||||
# are not important, waste resources and increase test times. The following
|
||||
# reduces the work factor to the lowest possible values.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
5
config/packages/translation.yaml
Normal file
5
config/packages/translation.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
providers:
|
6
config/packages/twig.yaml
Normal file
6
config/packages/twig.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
4
config/packages/ux_turbo.yaml
Normal file
4
config/packages/ux_turbo.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Enable stateless CSRF protection for forms and logins/logouts
|
||||
framework:
|
||||
csrf_protection:
|
||||
check_header: true
|
11
config/packages/validator.yaml
Normal file
11
config/packages/validator.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
framework:
|
||||
validation:
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
13
config/packages/web_profiler.yaml
Normal file
13
config/packages/web_profiler.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
profiler:
|
||||
collect: false
|
||||
collect_serializer_data: true
|
5
config/preload.php
Normal file
5
config/preload.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||
}
|
5
config/routes.yaml
Normal file
5
config/routes.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
controllers:
|
||||
resource:
|
||||
path: ../src/Controller/
|
||||
namespace: App\Controller
|
||||
type: attribute
|
4
config/routes/framework.yaml
Normal file
4
config/routes/framework.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
20
config/services.yaml
Normal file
20
config/services.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
28
importmap.php
Normal file
28
importmap.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Returns the importmap for this application.
|
||||
*
|
||||
* - "path" is a path inside the asset mapper system. Use the
|
||||
* "debug:asset-map" command to see the full list of paths.
|
||||
*
|
||||
* - "entrypoint" (JavaScript only) set to true for any module that will
|
||||
* be used as an "entrypoint" (and passed to the importmap() Twig function).
|
||||
*
|
||||
* The "importmap:require" command can be used to add new entries to this file.
|
||||
*/
|
||||
return [
|
||||
'app' => [
|
||||
'path' => './assets/app.js',
|
||||
'entrypoint' => true,
|
||||
],
|
||||
'@hotwired/stimulus' => [
|
||||
'version' => '3.2.2',
|
||||
],
|
||||
'@symfony/stimulus-bundle' => [
|
||||
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
|
||||
],
|
||||
'@hotwired/turbo' => [
|
||||
'version' => '7.3.0',
|
||||
],
|
||||
];
|
0
migrations/.gitignore
vendored
Normal file
0
migrations/.gitignore
vendored
Normal file
5989
package-lock.json
generated
Normal file
5989
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
package.json
Normal file
30
package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.0",
|
||||
"@symfony/webpack-encore": "^5.0.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"core-js": "^3.38.0",
|
||||
"maplibre-gl": "^5.6.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"table-sort-js": "^1.22.2",
|
||||
"tablesort": "^5.6.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^5.1.0"
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev-server": "encore dev-server",
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"charjs": "^0.0.1-security",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"jquery": "^3.7.1",
|
||||
"table-sort": "^1.0.16"
|
||||
}
|
||||
}
|
44
phpunit.dist.xml
Normal file
44
phpunit.dist.xml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
failOnDeprecation="true"
|
||||
failOnNotice="true"
|
||||
failOnWarning="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreSuppressionOfDeprecations="true"
|
||||
ignoreIndirectDeprecations="true"
|
||||
restrictNotices="true"
|
||||
restrictWarnings="true"
|
||||
>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
|
||||
<deprecationTrigger>
|
||||
<method>Doctrine\Deprecations\Deprecation::trigger</method>
|
||||
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
|
||||
<function>trigger_deprecation</function>
|
||||
</deprecationTrigger>
|
||||
</source>
|
||||
|
||||
<extensions>
|
||||
</extensions>
|
||||
</phpunit>
|
13
public/assets/img/Panoramax.svg
Normal file
13
public/assets/img/Panoramax.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 19 KiB |
BIN
public/assets/img/josm.png
Normal file
BIN
public/assets/img/josm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
public/assets/img/logo-osm.png
Normal file
BIN
public/assets/img/logo-osm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/img/osm-id.png
Normal file
BIN
public/assets/img/osm-id.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
1
public/assets/js/main.js
Normal file
1
public/assets/js/main.js
Normal file
|
@ -0,0 +1 @@
|
|||
console.log('Hello World');
|
6
public/css/bootstrap-icons.css
vendored
Normal file
6
public/css/bootstrap-icons.css
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
@font-face {
|
||||
font-display: block;
|
||||
font-family: "bootstrap-icons";
|
||||
src: url("../fonts/bootstrap-icons/bootstrap-icons.woff2") format("woff2"),
|
||||
url("../fonts/bootstrap-icons/bootstrap-icons.woff") format("woff");
|
||||
}
|
92
public/css/city-sidebar.css
Normal file
92
public/css/city-sidebar.css
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* Styles pour la sidebar */
|
||||
.city-sidebar {
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Desktop styles */
|
||||
@media (min-width: 768px) {
|
||||
.city-sidebar {
|
||||
width: 25%;
|
||||
max-width: 280px;
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 25%;
|
||||
width: 75%;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.main-header {
|
||||
margin-left: 25%;
|
||||
width: 75%;
|
||||
z-index: 1001;
|
||||
}
|
||||
.main-footer {
|
||||
margin-left: 25%;
|
||||
width: 75%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 767px) {
|
||||
.city-sidebar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.main-header {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.main-footer {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.city-sidebar .nav-link {
|
||||
color: #495057;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.city-sidebar .nav-link:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.city-sidebar .nav-link.active {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.city-sidebar .nav-link i {
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.city-sidebar .sidebar-heading {
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1rem;
|
||||
color: #6c757d;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.section-anchor {
|
||||
scroll-margin-top: 2rem;
|
||||
}
|
328
public/css/global.css
Normal file
328
public/css/global.css
Normal file
|
@ -0,0 +1,328 @@
|
|||
:root {
|
||||
--primary-color: #2c3e50;
|
||||
--secondary-color: #3498db;
|
||||
--accent-color: #e74c3c;
|
||||
--text-color: #333;
|
||||
--light-gray: #f5f6fa;
|
||||
--border-color: #dcdde1;
|
||||
--success-color: #27ae60;
|
||||
--warning-color: #f1c40f;
|
||||
--error-color: #c0392b;
|
||||
}
|
||||
|
||||
/* Reset et styles de base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-gray);
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.navbar {
|
||||
background-color: var(--primary-color);
|
||||
padding: 1rem 0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.navbar-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.navbar-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2980b9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #2c3e50;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: var(--success-color);
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: #fff3cd;
|
||||
color: var(--warning-color);
|
||||
border: 1px solid #ffeeba;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #f8d7da;
|
||||
color: var(--error-color);
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.grid-2,
|
||||
.grid-3,
|
||||
.grid-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-menu.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--primary-color);
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.p-1 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Map container */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Tags display */
|
||||
.tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: var(--light-gray);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 2rem 0;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.footer-link:hover {
|
||||
opacity: 1;
|
||||
}
|
123
public/css/main.css
Normal file
123
public/css/main.css
Normal file
|
@ -0,0 +1,123 @@
|
|||
/* Layout général */
|
||||
.body-landing {
|
||||
background-color: rgb(255, 255, 255);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.main-header {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.main-header h1 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-header a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.main-footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2rem 0;
|
||||
margin-top: 3rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.main-footer p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.main-footer a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.main-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Barre de progression */
|
||||
#completion_progress {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background-color: white;
|
||||
border-top: 1px solid #e9ecef;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 -2px 4px rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
#completion_progress .container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Home page specific styles */
|
||||
.hero-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2rem;
|
||||
color: #fff;
|
||||
border-radius: 0.75rem;
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Media queries */
|
||||
@media (max-width: 768px) {
|
||||
.main-header h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.main-footer {
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.display-4 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
max-height: 200px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
BIN
public/fonts/bootstrap-icons/bootstrap-icons.woff
Normal file
BIN
public/fonts/bootstrap-icons/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
public/fonts/bootstrap-icons/bootstrap-icons.woff2
Normal file
BIN
public/fonts/bootstrap-icons/bootstrap-icons.woff2
Normal file
Binary file not shown.
9
public/index.php
Normal file
9
public/index.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
};
|
2
public/js/bootstrap/Sortable.min.js
vendored
Normal file
2
public/js/bootstrap/Sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8337
public/js/bootstrap/bootstrap-icons.min.css
vendored
Normal file
8337
public/js/bootstrap/bootstrap-icons.min.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
7
public/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
public/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
public/js/bootstrap/bootstrap.min.css
vendored
Normal file
6
public/js/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
public/js/chartjs/chart.umd.js
Normal file
14
public/js/chartjs/chart.umd.js
Normal file
File diff suppressed because one or more lines are too long
7
public/js/chartjs/chartjs-adapter-date-fns.js
Normal file
7
public/js/chartjs/chartjs-adapter-date-fns.js
Normal file
File diff suppressed because one or more lines are too long
780
public/js/mapbox/mapbox-gl.css
Normal file
780
public/js/mapbox/mapbox-gl.css
Normal file
|
@ -0,0 +1,780 @@
|
|||
.mapboxgl-map {
|
||||
-webkit-tap-highlight-color: rgb(0 0 0/0);
|
||||
font: 12px/20px Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.mapboxgl-canvas {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-map:-webkit-full-screen {
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.mapboxgl-canary {
|
||||
background-color: salmon
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-interactive,
|
||||
.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass {
|
||||
cursor: grab;
|
||||
-webkit-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-interactive:active,
|
||||
.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active {
|
||||
cursor: grabbing
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas {
|
||||
touch-action: pan-x pan-y
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas {
|
||||
touch-action: pinch-zoom
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas {
|
||||
touch-action: none
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left,
|
||||
.mapboxgl-ctrl-bottom-right,
|
||||
.mapboxgl-ctrl-top-left,
|
||||
.mapboxgl-ctrl-top-right {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: 2
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-left {
|
||||
left: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-right {
|
||||
right: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-right {
|
||||
bottom: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl {
|
||||
clear: both;
|
||||
pointer-events: auto;
|
||||
transform: translate(0)
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-left .mapboxgl-ctrl {
|
||||
float: left;
|
||||
margin: 10px 0 0 10px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-right .mapboxgl-ctrl {
|
||||
float: right;
|
||||
margin: 10px 10px 0 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl {
|
||||
float: left;
|
||||
margin: 0 0 10px 10px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl {
|
||||
float: right;
|
||||
margin: 0 10px 10px 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group {
|
||||
background: #fff;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group:not(:empty) {
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, .1)
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl-group:not(:empty) {
|
||||
box-shadow: 0 0 0 2px ButtonText
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 29px;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 29px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button+button {
|
||||
border-top: 1px solid #ddd
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button .mapboxgl-ctrl-icon {
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl-icon {
|
||||
background-color: transparent
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button+button {
|
||||
border-top: 1px solid ButtonText
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib-button:focus,
|
||||
.mapboxgl-ctrl-group button:focus {
|
||||
box-shadow: 0 0 2px 2px #0096ff
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button:disabled {
|
||||
cursor: not-allowed
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon {
|
||||
opacity: .25
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button:first-child {
|
||||
border-radius: 4px 4px 0 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button:last-child {
|
||||
border-radius: 0 0 4px 4px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button:only-child {
|
||||
border-radius: inherit
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button:not(:disabled):hover {
|
||||
background-color: rgb(0 0 0/5%)
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button:focus:focus-visible {
|
||||
box-shadow: 0 0 2px 2px #0096ff
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button:focus:not(:focus-visible) {
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:black-on-white) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 29 29'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:black-on-white) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 29 29'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 29 29'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath id='south' d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath id='south' d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:black-on-white) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 29 29'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath id='south' d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' fill='%23f00'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2' display='none'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2' display='none'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon {
|
||||
animation: mapboxgl-spin 2s linear infinite
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' fill='%23f00'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2' display='none'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2' display='none'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:black-on-white) {
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23000'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' display='none'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7z'/%3E%3Ccircle id='dot' cx='10' cy='10' r='2'/%3E%3Cpath id='stroke' d='M14 5l1 1-9 9-1-1 9-9z' fill='%23f00'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mapboxgl-spin {
|
||||
0% {
|
||||
transform: rotate(0deg)
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn)
|
||||
}
|
||||
}
|
||||
|
||||
a.mapboxgl-ctrl-logo {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd' viewBox='0 0 88 23'%3E%3Cdefs%3E%3Cpath id='logo' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='text' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='clip'%3E%3Crect x='0' y='0' width='100%25' height='100%25' fill='white'/%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/mask%3E%3Cg id='outline' opacity='0.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23clip)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23text' mask='url(%23clip)'/%3E%3C/g%3E%3Cg id='fill' opacity='0.9' fill='%23fff'%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/g%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 23px;
|
||||
margin: 0 0 -4px -4px;
|
||||
overflow: hidden;
|
||||
width: 88px
|
||||
}
|
||||
|
||||
a.mapboxgl-ctrl-logo.mapboxgl-compact {
|
||||
width: 23px
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:active) {
|
||||
a.mapboxgl-ctrl-logo {
|
||||
background-color: transparent;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd' viewBox='0 0 88 23'%3E%3Cdefs%3E%3Cpath id='logo' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='text' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='clip'%3E%3Crect x='0' y='0' width='100%25' height='100%25' fill='white'/%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/mask%3E%3Cg id='outline' opacity='1' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23clip)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23text' mask='url(%23clip)'/%3E%3C/g%3E%3Cg id='fill' opacity='1' fill='%23fff'%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/g%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media (-ms-high-contrast:black-on-white) {
|
||||
a.mapboxgl-ctrl-logo {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd' viewBox='0 0 88 23'%3E%3Cdefs%3E%3Cpath id='logo' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='text' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='clip'%3E%3Crect x='0' y='0' width='100%25' height='100%25' fill='white'/%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/mask%3E%3Cg id='outline' opacity='1' stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23clip)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23text' mask='url(%23clip)'/%3E%3C/g%3E%3Cg id='fill' opacity='1' fill='%23000'%3E%3Cuse xlink:href='%23logo'/%3E%3Cuse xlink:href='%23text'/%3E%3C/g%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
|
||||
background-color: hsla(0, 0%, 100%, .5);
|
||||
margin: 0;
|
||||
padding: 0 5px
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
margin: 10px;
|
||||
min-height: 20px;
|
||||
padding: 2px 24px 2px 0;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact-show {
|
||||
padding: 2px 28px 2px 8px;
|
||||
visibility: visible
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show,
|
||||
.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show {
|
||||
border-radius: 12px;
|
||||
padding: 2px 8px 2px 28px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner {
|
||||
display: none
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib-button {
|
||||
background-color: hsla(0, 0%, 100%, .5);
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E");
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
height: 24px;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 24px
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-attrib-button,
|
||||
.mapboxgl-ctrl-top-left .mapboxgl-ctrl-attrib-button {
|
||||
left: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-button,
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-inner {
|
||||
display: block
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button {
|
||||
background-color: rgb(0 0 0/5%)
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
bottom: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
right: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
left: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
bottom: 0;
|
||||
left: 0
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (-ms-high-contrast:active) {
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (-ms-high-contrast:black-on-white) {
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib a {
|
||||
color: rgba(0, 0, 0, .75);
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib a:hover {
|
||||
color: inherit;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib .mapbox-improve-map {
|
||||
font-weight: 700;
|
||||
margin-left: 2px
|
||||
}
|
||||
|
||||
.mapboxgl-attrib-empty {
|
||||
display: none
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-scale {
|
||||
background-color: hsla(0, 0%, 100%, .75);
|
||||
border: 2px solid #333;
|
||||
border-top: #333;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
font-size: 10px;
|
||||
padding: 0 5px;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.mapboxgl-popup {
|
||||
display: flex;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
will-change: transform
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top,
|
||||
.mapboxgl-popup-anchor-top-left,
|
||||
.mapboxgl-popup-anchor-top-right {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom,
|
||||
.mapboxgl-popup-anchor-bottom-left,
|
||||
.mapboxgl-popup-anchor-bottom-right {
|
||||
flex-direction: column-reverse
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-left {
|
||||
flex-direction: row
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-right {
|
||||
flex-direction: row-reverse
|
||||
}
|
||||
|
||||
.mapboxgl-popup-tip {
|
||||
border: 10px solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
|
||||
align-self: center;
|
||||
border-bottom-color: #fff;
|
||||
border-top: none
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
|
||||
align-self: flex-start;
|
||||
border-bottom-color: #fff;
|
||||
border-left: none;
|
||||
border-top: none
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
|
||||
align-self: flex-end;
|
||||
border-bottom-color: #fff;
|
||||
border-right: none;
|
||||
border-top: none
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
|
||||
align-self: center;
|
||||
border-bottom: none;
|
||||
border-top-color: #fff
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
|
||||
align-self: flex-start;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-top-color: #fff
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
|
||||
align-self: flex-end;
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
border-top-color: #fff
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
|
||||
align-self: center;
|
||||
border-left: none;
|
||||
border-right-color: #fff
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
|
||||
align-self: center;
|
||||
border-left-color: #fff;
|
||||
border-right: none
|
||||
}
|
||||
|
||||
.mapboxgl-popup-close-button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 0 3px 0 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.mapboxgl-popup-close-button:hover {
|
||||
background-color: rgb(0 0 0/5%)
|
||||
}
|
||||
|
||||
.mapboxgl-popup-content {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1);
|
||||
padding: 10px 10px 15px;
|
||||
pointer-events: auto;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content {
|
||||
border-top-left-radius: 0
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content {
|
||||
border-top-right-radius: 0
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content {
|
||||
border-bottom-left-radius: 0
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content {
|
||||
border-bottom-right-radius: 0
|
||||
}
|
||||
|
||||
.mapboxgl-popup-track-pointer {
|
||||
display: none
|
||||
}
|
||||
|
||||
.mapboxgl-popup-track-pointer * {
|
||||
pointer-events: none;
|
||||
user-select: none
|
||||
}
|
||||
|
||||
.mapboxgl-map:hover .mapboxgl-popup-track-pointer {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.mapboxgl-map:active .mapboxgl-popup-track-pointer {
|
||||
display: none
|
||||
}
|
||||
|
||||
.mapboxgl-marker {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity .2s;
|
||||
will-change: transform
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-dot,
|
||||
.mapboxgl-user-location-dot:before {
|
||||
background-color: #1da1f2;
|
||||
border-radius: 50%;
|
||||
height: 15px;
|
||||
width: 15px
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-dot:before {
|
||||
animation: mapboxgl-user-location-dot-pulse 2s infinite;
|
||||
content: "";
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-dot:after {
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .35);
|
||||
box-sizing: border-box;
|
||||
content: "";
|
||||
height: 19px;
|
||||
left: -2px;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 19px
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading {
|
||||
height: 0;
|
||||
width: 0
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading:after,
|
||||
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading:before {
|
||||
border-bottom: 7.5px solid #4aa1eb;
|
||||
content: "";
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading:before {
|
||||
border-left: 7.5px solid transparent;
|
||||
transform: translateY(-28px) skewY(-20deg)
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading:after {
|
||||
border-right: 7.5px solid transparent;
|
||||
transform: translate(7.5px, -28px) skewY(20deg)
|
||||
}
|
||||
|
||||
@keyframes mapboxgl-user-location-dot-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1)
|
||||
}
|
||||
|
||||
70% {
|
||||
opacity: 0;
|
||||
transform: scale(3)
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-dot-stale {
|
||||
background-color: #aaa
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-dot-stale:after {
|
||||
display: none
|
||||
}
|
||||
|
||||
.mapboxgl-user-location-accuracy-circle {
|
||||
background-color: #1da1f233;
|
||||
border-radius: 100%;
|
||||
height: 1px;
|
||||
width: 1px
|
||||
}
|
||||
|
||||
.mapboxgl-crosshair,
|
||||
.mapboxgl-crosshair .mapboxgl-interactive,
|
||||
.mapboxgl-crosshair .mapboxgl-interactive:active {
|
||||
cursor: crosshair
|
||||
}
|
||||
|
||||
.mapboxgl-boxzoom {
|
||||
background: #fff;
|
||||
border: 2px dotted #202020;
|
||||
height: 0;
|
||||
left: 0;
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0
|
||||
}
|
||||
|
||||
@media print {
|
||||
.mapbox-improve-map {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
.mapboxgl-scroll-zoom-blocker,
|
||||
.mapboxgl-touch-pan-blocker {
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, .7);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
transition: opacity .75s ease-in-out;
|
||||
transition-delay: 1s;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.mapboxgl-scroll-zoom-blocker-show,
|
||||
.mapboxgl-touch-pan-blocker-show {
|
||||
opacity: 1;
|
||||
transition: opacity .1s ease-in-out
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-pan-blocker-override.mapboxgl-scrollable-page,
|
||||
.mapboxgl-canvas-container.mapboxgl-touch-pan-blocker-override.mapboxgl-scrollable-page .mapboxgl-canvas {
|
||||
touch-action: pan-x pan-y
|
||||
}
|
49
public/js/mapbox/mapbox-gl.js
Normal file
49
public/js/mapbox/mapbox-gl.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/maplibre/maplibre-gl.css
Normal file
1
public/js/maplibre/maplibre-gl.css
Normal file
File diff suppressed because one or more lines are too long
48
public/js/maplibre/maplibre-gl.js
Normal file
48
public/js/maplibre/maplibre-gl.js
Normal file
File diff suppressed because one or more lines are too long
1
public/js/qrcode/qrcode.min.js
vendored
Normal file
1
public/js/qrcode/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
92
public/js/turf/turf.min.js
vendored
Normal file
92
public/js/turf/turf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
beautifulsoup4==4.13.5
|
||||
docutils==0.22
|
||||
matplotlib==3.10.6
|
||||
pandas==2.3.2
|
||||
plotly==6.3.0
|
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
1439
src/Controller/WikiController.php
Normal file
1439
src/Controller/WikiController.php
Normal file
File diff suppressed because it is too large
Load diff
0
src/Entity/.gitignore
vendored
Normal file
0
src/Entity/.gitignore
vendored
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue