mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-21 17:22:28 +01:00
Compare commits
1 Commits
copilot/wr
...
stura_impo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
407d78b151 |
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
### Taken from here: https://github.com/docker-library/php/blob/master/apache2-foreground
|
||||
|
||||
set -e
|
||||
|
||||
# Note: we don't just use "apache2ctl" here because it itself is just a shell-script wrapper around apache2 which provides extra functionality like "apache2ctl start" for launching apache2 in the background.
|
||||
# (also, when run as "apache2ctl <apache args>", it does not use "exec", which leaves an undesirable resident shell process)
|
||||
|
||||
: "${APACHE_CONFDIR:=/etc/apache2}"
|
||||
: "${APACHE_ENVVARS:=$APACHE_CONFDIR/envvars}"
|
||||
if test -f "$APACHE_ENVVARS"; then
|
||||
. "$APACHE_ENVVARS"
|
||||
fi
|
||||
|
||||
# Apache gets grumpy about PID files pre-existing
|
||||
: "${APACHE_RUN_DIR:=/var/run/apache2}"
|
||||
: "${APACHE_PID_FILE:=$APACHE_RUN_DIR/apache2.pid}"
|
||||
rm -f "$APACHE_PID_FILE"
|
||||
|
||||
# create missing directories
|
||||
# (especially APACHE_RUN_DIR, APACHE_LOCK_DIR, and APACHE_LOG_DIR)
|
||||
for e in "${!APACHE_@}"; do
|
||||
if [[ "$e" == *_DIR ]] && [[ "${!e}" == /* ]]; then
|
||||
# handle "/var/lock" being a symlink to "/run/lock", but "/run/lock" not existing beforehand, so "/var/lock/something" fails to mkdir
|
||||
# mkdir: cannot create directory '/var/lock': File exists
|
||||
dir="${!e}"
|
||||
while [ "$dir" != "$(dirname "$dir")" ]; do
|
||||
dir="$(dirname "$dir")"
|
||||
if [ -d "$dir" ]; then
|
||||
break
|
||||
fi
|
||||
absDir="$(readlink -f "$dir" 2>/dev/null || :)"
|
||||
if [ -n "$absDir" ]; then
|
||||
mkdir -p "$absDir"
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "${!e}"
|
||||
fi
|
||||
done
|
||||
|
||||
exec apache2 -DFOREGROUND "$@"
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
{$CADDY_GLOBAL_OPTIONS}
|
||||
|
||||
frankenphp {
|
||||
{$FRANKENPHP_CONFIG}
|
||||
}
|
||||
|
||||
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
|
||||
order mercure after encode
|
||||
order vulcain after reverse_proxy
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
{$CADDY_EXTRA_CONFIG}
|
||||
|
||||
{$SERVER_NAME:localhost} {
|
||||
log {
|
||||
# Redact the authorization query parameter that can be set by Mercure
|
||||
format filter {
|
||||
wrap console
|
||||
fields {
|
||||
uri query {
|
||||
replace authorization REDACTED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root * /app/public
|
||||
encode zstd br gzip
|
||||
|
||||
mercure {
|
||||
# Transport to use (default to Bolt)
|
||||
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||
# Publisher JWT key
|
||||
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
||||
# Subscriber JWT key
|
||||
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
|
||||
# Allow anonymous subscribers (double-check that it's what you want)
|
||||
anonymous
|
||||
# Enable the subscription API (double-check that it's what you want)
|
||||
subscriptions
|
||||
# Extra directives
|
||||
{$MERCURE_EXTRA_DIRECTIVES}
|
||||
}
|
||||
|
||||
vulcain
|
||||
|
||||
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
||||
|
||||
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
||||
header ?Permissions-Policy "browsing-topics=()"
|
||||
|
||||
php_server
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host
|
||||
; See https://github.com/docker/for-linux/issues/264
|
||||
; The `client_host` below may optionally be replaced with `discover_client_host=yes`
|
||||
; Add `start_with_request=yes` to start debug session on each request
|
||||
xdebug.client_host = host.docker.internal
|
||||
@@ -1,18 +0,0 @@
|
||||
expose_php = 0
|
||||
date.timezone = UTC
|
||||
apc.enable_cli = 1
|
||||
session.use_strict_mode = 1
|
||||
zend.detect_unicode = 0
|
||||
|
||||
; https://symfony.com/doc/current/performance.html
|
||||
realpath_cache_size = 4096K
|
||||
realpath_cache_ttl = 600
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.memory_consumption = 256
|
||||
opcache.enable_file_override = 1
|
||||
|
||||
memory_limit = 256M
|
||||
|
||||
upload_max_filesize=256M
|
||||
post_max_size=300M
|
||||
@@ -1,2 +0,0 @@
|
||||
opcache.preload_user = root
|
||||
opcache.preload = /app/config/preload.php
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
||||
# Install the project the first time PHP is started
|
||||
# After the installation, the following block can be deleted
|
||||
if [ ! -f composer.json ]; then
|
||||
rm -Rf tmp/
|
||||
composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install
|
||||
|
||||
cd tmp
|
||||
cp -Rp . ..
|
||||
cd -
|
||||
rm -Rf tmp/
|
||||
|
||||
composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony
|
||||
composer config --json extra.symfony.docker 'true'
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait"
|
||||
sleep infinity
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
|
||||
composer install --prefer-dist --no-progress --no-interaction
|
||||
fi
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "Waiting for database to be ready..."
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
|
||||
if [ $? -eq 255 ]; then
|
||||
# If the Doctrine command exits with 255, an unrecoverable error occurred
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
|
||||
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
|
||||
done
|
||||
|
||||
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
|
||||
echo "The database is not up or not reachable:"
|
||||
echo "$DATABASE_ERROR"
|
||||
exit 1
|
||||
else
|
||||
echo "The database is now ready and reachable"
|
||||
fi
|
||||
|
||||
if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
|
||||
php bin/console doctrine:migrations:migrate --no-interaction
|
||||
fi
|
||||
fi
|
||||
|
||||
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||
fi
|
||||
|
||||
exec docker-php-entrypoint "$@"
|
||||
@@ -1,4 +0,0 @@
|
||||
worker {
|
||||
file ./public/index.php
|
||||
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
#
|
||||
# Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# recursive chowns can take a while, so we'll just do it if the owner is wrong
|
||||
|
||||
# Chown uploads/ folder if it does not belong to www-data
|
||||
if [ "$(stat -c '%u' /var/www/html/uploads)" != "$(id -u www-data)" ]; then
|
||||
chown -R www-data:www-data /var/www/html/uploads
|
||||
fi
|
||||
|
||||
# Do the same for the public/media folder
|
||||
if [ "$(stat -c '%u' /var/www/html/public/media)" != "$(id -u www-data)" ]; then
|
||||
chown -R www-data:www-data /var/www/html/public/media
|
||||
fi
|
||||
|
||||
# If var/db/ folder exists, do the same for it
|
||||
if [ -d /var/www/html/var/db ]; then
|
||||
if [ "$(stat -c '%u' /var/www/html/var/db)" != "$(id -u www-data)" ]; then
|
||||
chown -R www-data:www-data /var/www/html/var/db
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
|
||||
php-fpmPHP_VERSION -F &
|
||||
|
||||
|
||||
# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE
|
||||
if [ "$DB_AUTOMIGRATE" = "true" ]; then
|
||||
echo "Waiting for database to be ready..."
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(sudo -E -u www-data php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
|
||||
if [ $? -eq 255 ]; then
|
||||
# If the Doctrine command exits with 255, an unrecoverable error occurred
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
|
||||
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
|
||||
done
|
||||
|
||||
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
|
||||
echo "The database is not up or not reachable:"
|
||||
echo "$DATABASE_ERROR"
|
||||
exit 1
|
||||
else
|
||||
echo "The database is now ready and reachable"
|
||||
fi
|
||||
|
||||
# Check if there are any available migrations to do, by executing doctrine:migrations:up-to-date
|
||||
# and checking if the exit code is 0 (up to date) or 1 (not up to date)
|
||||
if sudo -E -u www-data php bin/console doctrine:migrations:up-to-date --no-interaction; then
|
||||
echo "Database is up to date, no migrations necessary."
|
||||
else
|
||||
echo "Migrations available..."
|
||||
echo "Do backup of database..."
|
||||
|
||||
sudo -E -u www-data mkdir -p /var/www/html/uploads/.automigration-backup/
|
||||
# Backup the database
|
||||
sudo -E -u www-data php bin/console partdb:backup -n --database /var/www/html/uploads/.automigration-backup/backup-$(date +%Y-%m-%d_%H-%M-%S).zip
|
||||
|
||||
# Check if there are any migration files
|
||||
sudo -E -u www-data php bin/console doctrine:migrations:migrate --no-interaction
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
|
||||
if [ "${1#-}" != "$1" ]; then
|
||||
set -- apache2-foreground "$@"
|
||||
fi
|
||||
|
||||
# Pass to the original entrypoint
|
||||
exec "$@"
|
||||
@@ -30,4 +30,4 @@
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
</VirtualHost>
|
||||
</VirtualHost>
|
||||
@@ -5,8 +5,6 @@ tests/
|
||||
docs/
|
||||
.git
|
||||
|
||||
/public/media/*
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
@@ -44,39 +42,3 @@ yarn-error.log
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
|
||||
### From frankenphp
|
||||
|
||||
**/*.log
|
||||
**/*.php~
|
||||
**/*.dist.php
|
||||
**/*.dist
|
||||
**/*.cache
|
||||
**/._*
|
||||
**/.dockerignore
|
||||
**/.DS_Store
|
||||
**/.git/
|
||||
**/.gitattributes
|
||||
**/.gitignore
|
||||
**/.gitmodules
|
||||
**/compose.*.yaml
|
||||
**/compose.*.yml
|
||||
**/compose.yaml
|
||||
**/compose.yml
|
||||
**/docker-compose.*.yaml
|
||||
**/docker-compose.*.yml
|
||||
**/docker-compose.yaml
|
||||
**/docker-compose.yml
|
||||
**/Dockerfile
|
||||
**/Thumbs.db
|
||||
.github/
|
||||
public/bundles/
|
||||
var/
|
||||
vendor/
|
||||
.editorconfig
|
||||
.env.*.local
|
||||
.env.local
|
||||
.env.local.php
|
||||
.env.test
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{compose.yaml,compose.*.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
155
.env
155
.env
@@ -1,10 +1,26 @@
|
||||
#### Part-DB Configuration
|
||||
# See https://docs.part-db.de/configuration.html for documentation of available options
|
||||
# In all environments, the following files are loaded if they exist,
|
||||
# the later 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.
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
|
||||
|
||||
###################################################################################
|
||||
# Database settings
|
||||
###################################################################################
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_HOSTS='^(localhost|example\.com)$'
|
||||
###< 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
|
||||
|
||||
@@ -14,28 +30,29 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
|
||||
# Uncomment this line (and comment the line above to use a MySQL database
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db?serverVersion=5.7
|
||||
|
||||
# Set this value to 1, if you want to use SSL to connect to the MySQL server. It will be tried to use the CA certificate
|
||||
# otherwise a CA bundle shipped with PHP will be used.
|
||||
# Leave it at 0, if you do not want to use SSL or if your server does not support it
|
||||
DATABASE_MYSQL_USE_SSL_CA=0
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
# Set this value to 0, if you don't want to verify the CA certificate of the MySQL server
|
||||
# Only do this, if you know what you are doing!
|
||||
DATABASE_MYSQL_SSL_VERIFY_CERT=1
|
||||
### Custom vars
|
||||
|
||||
# Emulate natural sorting of strings even on databases that do not support it (like SQLite, MySQL or MariaDB < 10.7)
|
||||
# This can be slow on big databases and might have some problems and quirks, so use it with caution
|
||||
DATABASE_EMULATE_NATURAL_SORT=0
|
||||
# Allow users to download attachments to the server by providing an URL
|
||||
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
|
||||
ALLOW_ATTACHMENT_DOWNLOADS=0
|
||||
|
||||
FIXER_API_KEY=CHANGEME
|
||||
|
||||
# Change this to true, if no url rewriting (like mod_rewrite for Apache) is available
|
||||
# In that case all URL contains the index.php front controller in URL
|
||||
NO_URL_REWRITE_AVAILABLE=false
|
||||
|
||||
# Override value if you want to show to show a given text on homepage.
|
||||
# When this is empty the content of config/banner.md is used as banner
|
||||
BANNER=""
|
||||
|
||||
# In demo mode things it is not possible for a user to change his password and his settings.
|
||||
DEMO_MODE=0
|
||||
|
||||
###################################################################################
|
||||
# General settings
|
||||
###################################################################################
|
||||
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available.
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
###################################################################################
|
||||
# Email settings
|
||||
# Email related settings
|
||||
###################################################################################
|
||||
|
||||
# The DSN of the email server that should be used for sending emails (disabled by default)
|
||||
@@ -45,93 +62,25 @@ MAILER_DSN=null://null
|
||||
|
||||
# The email address from which all Part-DB emails should be sent. Change this when you configure email!
|
||||
EMAIL_SENDER_EMAIL=noreply@partdb.changeme
|
||||
# The sender name which should be used for all Part-DB emails
|
||||
EMAIL_SENDER_NAME="Part-DB Mailer"
|
||||
|
||||
# Set this to 1 to allow reset of a password per email
|
||||
ALLOW_EMAIL_PW_RESET=0
|
||||
|
||||
###################################################################################
|
||||
# Error pages settings
|
||||
###################################################################################
|
||||
|
||||
# You can set an email address here, which is shown on an error page, how to contact an administrator
|
||||
ERROR_PAGE_ADMIN_EMAIL=''
|
||||
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
||||
ERROR_PAGE_SHOW_HELP=1
|
||||
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
###################################################################################
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
# Be also sure to set the correct values for DEFAULT_URI
|
||||
SAML_ENABLED=0
|
||||
|
||||
# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML
|
||||
SAML_BEHIND_PROXY=0
|
||||
|
||||
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
|
||||
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
|
||||
# Please not to only use single quotes to enclose the JSON string
|
||||
SAML_ROLE_MAPPING='{}'
|
||||
# A mapping could look like the following
|
||||
#SAML_ROLE_MAPPING='{ "*": 2, "admin": 1, "editor": 3}'
|
||||
|
||||
# When this is set to 1, the group of SAML users will be updated everytime they login based on their SAML roles
|
||||
SAML_UPDATE_GROUP_ON_LOGIN=1
|
||||
|
||||
# The entity ID of your SAML IDP (e.g. the realm name of your Keycloak server)
|
||||
SAML_IDP_ENTITY_ID="https://idp.changeme.invalid/realms/master"
|
||||
# The URL of your SAML IDP SingleSignOnService (e.g. the endpoint of your Keycloak server)
|
||||
SAML_IDP_SINGLE_SIGN_ON_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
|
||||
# The URL of your SAML IDP SingleLogoutService (e.g. the endpoint of your Keycloak server)
|
||||
SAML_IDP_SINGLE_LOGOUT_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
|
||||
# The public certificate of the SAML IDP (e.g. the certificate of your Keycloak server)
|
||||
SAML_IDP_X509_CERT="MIIC..."
|
||||
|
||||
# The entity of your SAML SP, must match the SP entityID configured in your SAML IDP (e.g. Keycloak).
|
||||
# This should be a the domain name of your Part-DB installation, followed by "/sp"
|
||||
SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
|
||||
|
||||
# The public certificate of the SAML SP
|
||||
SAML_SP_X509_CERT="MIIC..."
|
||||
# The private key of the SAML SP
|
||||
SAML_SP_PRIVATE_KEY="MIIE..."
|
||||
|
||||
|
||||
######################################################################################
|
||||
# Other settings
|
||||
# History/Eventlog related settings
|
||||
######################################################################################
|
||||
# In demo mode things it is not possible for a user to change his password and his settings.
|
||||
DEMO_MODE=0
|
||||
|
||||
# Change this to true, if no url rewriting (like mod_rewrite for Apache) is available
|
||||
# In that case all URL contains the index.php front controller in URL
|
||||
NO_URL_REWRITE_AVAILABLE=0
|
||||
# If you want to use full timetrave functionality
|
||||
|
||||
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
|
||||
REDIRECT_TO_HTTPS=0
|
||||
|
||||
# Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions)
|
||||
DISABLE_YEAR2038_BUG_CHECK=0
|
||||
|
||||
# Set the trusted IPs here, when using an reverse proxy
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_HOSTS='^(localhost|example\.com)$'
|
||||
# Save which fields were changed in a ElementEdited log entry
|
||||
HISTORY_SAVE_CHANGED_FIELDS=1
|
||||
# Save the old data in the ElementEdited log entry (warning this could increase the database size in short time)
|
||||
HISTORY_SAVE_CHANGED_DATA=0
|
||||
# Save the data of an element that gets removed into log entry. This allows to undelete an element
|
||||
HISTORY_SAVE_REMOVED_DATA=0
|
||||
|
||||
|
||||
###> symfony/lock ###
|
||||
# Choose one of the stores below
|
||||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||
LOCK_DSN=flock
|
||||
###< symfony/lock ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
###> symfony/mailer ###
|
||||
# MAILER_DSN=smtp://localhost
|
||||
###< symfony/mailer ###
|
||||
|
||||
4
.env.dev
4
.env.dev
@@ -1,4 +0,0 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_SECRET=318b5d659e07a0b3f96d9b3a83b254ca
|
||||
###< symfony/framework-bundle ###
|
||||
@@ -1 +0,0 @@
|
||||
APP_DEBUG=0
|
||||
@@ -5,11 +5,5 @@ SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
||||
# Doctrine automatically adds an _test suffix to database name in test env
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
|
||||
# Disable update checks, as tests would fail, when github is not reachable
|
||||
CHECK_FOR_UPDATES=0
|
||||
|
||||
INSTANCE_NAME="Part-DB"
|
||||
DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# For sh files, always use LF line endings
|
||||
*.sh text eol=lf
|
||||
752
.github/assets/legacy_import/db_jbtronics.sql
vendored
752
.github/assets/legacy_import/db_jbtronics.sql
vendored
@@ -1,752 +0,0 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.3
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Host: 127.0.0.1
|
||||
-- Erstellungszeit: 07. Mai 2023 um 01:58
|
||||
-- Server-Version: 10.6.5-MariaDB-log
|
||||
-- PHP-Version: 8.1.2
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Datenbank: `partdb_demo`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `attachements`
|
||||
--
|
||||
|
||||
CREATE TABLE `attachements` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`class_name` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`element_id` int(11) NOT NULL,
|
||||
`type_id` int(11) NOT NULL,
|
||||
`filename` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`show_in_table` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `attachements`
|
||||
--
|
||||
|
||||
INSERT INTO `attachements` (`id`, `name`, `class_name`, `element_id`, `type_id`, `filename`, `show_in_table`, `last_modified`) VALUES
|
||||
(1, 'BC547', 'Part', 2, 2, '%BASE%/data/media/bc547.pdf', 1, '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `attachement_types`
|
||||
--
|
||||
|
||||
CREATE TABLE `attachement_types` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `attachement_types`
|
||||
--
|
||||
|
||||
INSERT INTO `attachement_types` (`id`, `name`, `parent_id`, `comment`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'Bilder', NULL, NULL, '2017-10-21 17:58:48', '0000-00-00 00:00:00'),
|
||||
(2, 'Datenblätter', NULL, NULL, '2017-10-21 17:58:48', '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `categories`
|
||||
--
|
||||
|
||||
CREATE TABLE `categories` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`disable_footprints` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_manufacturers` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_autodatasheets` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_properties` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`partname_regex` text COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`partname_hint` text COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`default_description` text COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`default_comment` text COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `categories`
|
||||
--
|
||||
|
||||
INSERT INTO `categories` (`id`, `name`, `parent_id`, `disable_footprints`, `disable_manufacturers`, `disable_autodatasheets`, `disable_properties`, `partname_regex`, `partname_hint`, `default_description`, `default_comment`, `comment`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'aktive Bauteile', NULL, 0, 0, 0, 0, '', '', '', '', NULL, '2017-10-21 17:58:49', '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `devices`
|
||||
--
|
||||
|
||||
CREATE TABLE `devices` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`order_quantity` int(11) NOT NULL DEFAULT 0,
|
||||
`order_only_missing_parts` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `devices`
|
||||
--
|
||||
|
||||
INSERT INTO `devices` (`id`, `name`, `parent_id`, `order_quantity`, `order_only_missing_parts`, `datetime_added`, `last_modified`, `comment`) VALUES
|
||||
(1, 'Test', NULL, 0, 0, '2015-04-16 15:08:56', '0000-00-00 00:00:00', NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `device_parts`
|
||||
--
|
||||
|
||||
CREATE TABLE `device_parts` (
|
||||
`id` int(11) NOT NULL,
|
||||
`id_part` int(11) NOT NULL DEFAULT 0,
|
||||
`id_device` int(11) NOT NULL DEFAULT 0,
|
||||
`quantity` int(11) NOT NULL DEFAULT 0,
|
||||
`mountnames` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `device_parts`
|
||||
--
|
||||
|
||||
INSERT INTO `device_parts` (`id`, `id_part`, `id_device`, `quantity`, `mountnames`) VALUES
|
||||
(1, 2, 1, 1, '');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `footprints`
|
||||
--
|
||||
|
||||
CREATE TABLE `footprints` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`filename` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`filename_3d` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `footprints`
|
||||
--
|
||||
|
||||
INSERT INTO `footprints` (`id`, `name`, `filename`, `filename_3d`, `parent_id`, `comment`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'LEDs', '%BASE%/img/footprints/Optik/LEDs/Bedrahtet/LED-GELB_3MM.png', '', NULL, NULL, '2017-10-21 17:58:49', '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `groups`
|
||||
--
|
||||
|
||||
CREATE TABLE `groups` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(32) NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` mediumtext DEFAULT NULL,
|
||||
`perms_system` int(11) NOT NULL,
|
||||
`perms_groups` int(11) NOT NULL,
|
||||
`perms_users` int(11) NOT NULL,
|
||||
`perms_self` int(11) NOT NULL,
|
||||
`perms_system_config` int(11) NOT NULL,
|
||||
`perms_system_database` int(11) NOT NULL,
|
||||
`perms_parts` bigint(11) NOT NULL,
|
||||
`perms_parts_name` smallint(6) NOT NULL,
|
||||
`perms_parts_description` smallint(6) NOT NULL,
|
||||
`perms_parts_instock` smallint(6) NOT NULL,
|
||||
`perms_parts_mininstock` smallint(6) NOT NULL,
|
||||
`perms_parts_footprint` smallint(6) NOT NULL,
|
||||
`perms_parts_storelocation` smallint(6) NOT NULL,
|
||||
`perms_parts_manufacturer` smallint(6) NOT NULL,
|
||||
`perms_parts_comment` smallint(6) NOT NULL,
|
||||
`perms_parts_order` smallint(6) NOT NULL,
|
||||
`perms_parts_orderdetails` smallint(6) NOT NULL,
|
||||
`perms_parts_prices` smallint(6) NOT NULL,
|
||||
`perms_parts_attachements` smallint(6) NOT NULL,
|
||||
`perms_devices` int(11) NOT NULL,
|
||||
`perms_devices_parts` int(11) NOT NULL,
|
||||
`perms_storelocations` int(11) NOT NULL,
|
||||
`perms_footprints` int(11) NOT NULL,
|
||||
`perms_categories` int(11) NOT NULL,
|
||||
`perms_suppliers` int(11) NOT NULL,
|
||||
`perms_manufacturers` int(11) NOT NULL,
|
||||
`perms_attachement_types` int(11) NOT NULL,
|
||||
`perms_tools` int(11) NOT NULL,
|
||||
`perms_labels` smallint(6) NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `groups`
|
||||
--
|
||||
|
||||
INSERT INTO `groups` (`id`, `name`, `parent_id`, `comment`, `perms_system`, `perms_groups`, `perms_users`, `perms_self`, `perms_system_config`, `perms_system_database`, `perms_parts`, `perms_parts_name`, `perms_parts_description`, `perms_parts_instock`, `perms_parts_mininstock`, `perms_parts_footprint`, `perms_parts_storelocation`, `perms_parts_manufacturer`, `perms_parts_comment`, `perms_parts_order`, `perms_parts_orderdetails`, `perms_parts_prices`, `perms_parts_attachements`, `perms_devices`, `perms_devices_parts`, `perms_storelocations`, `perms_footprints`, `perms_categories`, `perms_suppliers`, `perms_manufacturers`, `perms_attachement_types`, `perms_tools`, `perms_labels`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'admins', NULL, 'Users of this group can do everything: Read, Write and Administrative actions.', 21, 1365, 87381, 85, 85, 21, 1431655765, 5, 5, 5, 5, 5, 5, 5, 5, 5, 325, 325, 325, 5461, 325, 5461, 5461, 5461, 5461, 5461, 1365, 1365, 85, '2017-10-21 17:58:46', '2018-10-08 17:27:41'),
|
||||
(2, 'readonly', NULL, 'Users of this group can only read informations, use tools, and don\'t have access to administrative tools.', 2, 2730, 43690, 25, 170, 42, 2778027689, 9, 9, 9, 9, 9, 9, 9, 9, 9, 649, 649, 649, 1705, 649, 1705, 1705, 1705, 1705, 1705, 681, 1366, 165, '2017-10-21 17:58:46', '2018-10-08 17:28:35'),
|
||||
(3, 'users', NULL, 'Users of this group, can edit part informations, create new ones, etc. but are not allowed to use administrative tools. (But can read current configuration, and see Server status)', 42, 2730, 43689, 89, 105, 41, 1431655765, 5, 5, 5, 5, 5, 5, 5, 5, 5, 325, 325, 325, 5461, 325, 5461, 5461, 5461, 5461, 5461, 1365, 1365, 85, '2017-10-21 17:58:46', '2018-10-08 17:28:17');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `internal`
|
||||
--
|
||||
|
||||
CREATE TABLE `internal` (
|
||||
`keyName` char(30) CHARACTER SET ascii NOT NULL,
|
||||
`keyValue` varchar(255) COLLATE utf8mb3_unicode_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `internal`
|
||||
--
|
||||
|
||||
INSERT INTO `internal` (`keyName`, `keyValue`) VALUES
|
||||
('dbVersion', '26');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `log`
|
||||
--
|
||||
|
||||
CREATE TABLE `log` (
|
||||
`id` int(11) NOT NULL,
|
||||
`datetime` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`id_user` int(11) NOT NULL,
|
||||
`level` tinyint(4) NOT NULL,
|
||||
`type` smallint(6) NOT NULL,
|
||||
`target_id` int(11) NOT NULL,
|
||||
`target_type` smallint(6) NOT NULL,
|
||||
`extra` mediumtext NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `manufacturers`
|
||||
--
|
||||
|
||||
CREATE TABLE `manufacturers` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`address` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`phone_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`fax_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`email_address` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`website` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`auto_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `manufacturers`
|
||||
--
|
||||
|
||||
INSERT INTO `manufacturers` (`id`, `name`, `parent_id`, `address`, `phone_number`, `fax_number`, `email_address`, `website`, `auto_product_url`, `datetime_added`, `comment`, `last_modified`) VALUES
|
||||
(1, 'Atmel', NULL, '', '', '', '', '', '', '2015-03-01 11:27:10', NULL, '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `orderdetails`
|
||||
--
|
||||
|
||||
CREATE TABLE `orderdetails` (
|
||||
`id` int(11) NOT NULL,
|
||||
`part_id` int(11) NOT NULL,
|
||||
`id_supplier` int(11) NOT NULL DEFAULT 0,
|
||||
`supplierpartnr` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`obsolete` tinyint(1) DEFAULT 0,
|
||||
`supplier_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `parts`
|
||||
--
|
||||
|
||||
CREATE TABLE `parts` (
|
||||
`id` int(11) NOT NULL,
|
||||
`id_category` int(11) NOT NULL DEFAULT 0,
|
||||
`name` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`description` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`instock` int(11) NOT NULL DEFAULT 0,
|
||||
`mininstock` int(11) NOT NULL DEFAULT 0,
|
||||
`comment` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`visible` tinyint(1) NOT NULL,
|
||||
`id_footprint` int(11) DEFAULT NULL,
|
||||
`id_storelocation` int(11) DEFAULT NULL,
|
||||
`order_orderdetails_id` int(11) DEFAULT NULL,
|
||||
`order_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`manual_order` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`id_manufacturer` int(11) DEFAULT NULL,
|
||||
`id_master_picture_attachement` int(11) DEFAULT NULL,
|
||||
`manufacturer_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`favorite` tinyint(1) NOT NULL DEFAULT 0
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `parts`
|
||||
--
|
||||
|
||||
INSERT INTO `parts` (`id`, `id_category`, `name`, `description`, `instock`, `mininstock`, `comment`, `visible`, `id_footprint`, `id_storelocation`, `order_orderdetails_id`, `order_quantity`, `manual_order`, `id_manufacturer`, `id_master_picture_attachement`, `manufacturer_product_url`, `datetime_added`, `last_modified`, `favorite`) VALUES
|
||||
(2, 1, 'BC547C', 'NPN 45V 0,1A 0,5W', 59, 0, '', 0, 1, 1, NULL, 1, 0, NULL, NULL, '', '2015-03-01 10:40:31', '2016-12-26 10:48:49', 0);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `pricedetails`
|
||||
--
|
||||
|
||||
CREATE TABLE `pricedetails` (
|
||||
`id` int(11) NOT NULL,
|
||||
`orderdetails_id` int(11) NOT NULL,
|
||||
`price` decimal(11,5) DEFAULT NULL,
|
||||
`price_related_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`min_discount_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`manual_input` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`last_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `storelocations`
|
||||
--
|
||||
|
||||
CREATE TABLE `storelocations` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`is_full` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `storelocations`
|
||||
--
|
||||
|
||||
INSERT INTO `storelocations` (`id`, `name`, `parent_id`, `is_full`, `datetime_added`, `comment`, `last_modified`) VALUES
|
||||
(1, 'Halbleiter I', NULL, 0, '2015-03-01 11:26:37', NULL, '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `suppliers`
|
||||
--
|
||||
|
||||
CREATE TABLE `suppliers` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`address` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`phone_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`fax_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`email_address` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`website` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`auto_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `suppliers`
|
||||
--
|
||||
|
||||
INSERT INTO `suppliers` (`id`, `name`, `parent_id`, `address`, `phone_number`, `fax_number`, `email_address`, `website`, `auto_product_url`, `datetime_added`, `comment`, `last_modified`) VALUES
|
||||
(1, 'Test', NULL, '', '', '', '', '', 'Test', '2015-03-01 10:37:23', NULL, '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `users`
|
||||
--
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(32) NOT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`first_name` tinytext DEFAULT NULL,
|
||||
`last_name` tinytext DEFAULT NULL,
|
||||
`department` tinytext DEFAULT NULL,
|
||||
`email` tinytext DEFAULT NULL,
|
||||
`need_pw_change` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`group_id` int(11) DEFAULT NULL,
|
||||
`config_language` tinytext DEFAULT NULL,
|
||||
`config_timezone` tinytext DEFAULT NULL,
|
||||
`config_theme` tinytext DEFAULT NULL,
|
||||
`config_currency` tinytext DEFAULT NULL,
|
||||
`config_image_path` text NOT NULL,
|
||||
`config_instock_comment_w` text NOT NULL,
|
||||
`config_instock_comment_a` text NOT NULL,
|
||||
`perms_system` int(11) NOT NULL,
|
||||
`perms_groups` int(11) NOT NULL,
|
||||
`perms_users` int(11) NOT NULL,
|
||||
`perms_self` int(11) NOT NULL,
|
||||
`perms_system_config` int(11) NOT NULL,
|
||||
`perms_system_database` int(11) NOT NULL,
|
||||
`perms_parts` bigint(11) NOT NULL,
|
||||
`perms_parts_name` smallint(6) NOT NULL,
|
||||
`perms_parts_description` smallint(6) NOT NULL,
|
||||
`perms_parts_instock` smallint(6) NOT NULL,
|
||||
`perms_parts_mininstock` smallint(6) NOT NULL,
|
||||
`perms_parts_footprint` smallint(6) NOT NULL,
|
||||
`perms_parts_storelocation` smallint(6) NOT NULL,
|
||||
`perms_parts_manufacturer` smallint(6) NOT NULL,
|
||||
`perms_parts_comment` smallint(6) NOT NULL,
|
||||
`perms_parts_order` smallint(6) NOT NULL,
|
||||
`perms_parts_orderdetails` smallint(6) NOT NULL,
|
||||
`perms_parts_prices` smallint(6) NOT NULL,
|
||||
`perms_parts_attachements` smallint(6) NOT NULL,
|
||||
`perms_devices` int(11) NOT NULL,
|
||||
`perms_devices_parts` int(11) NOT NULL,
|
||||
`perms_storelocations` int(11) NOT NULL,
|
||||
`perms_footprints` int(11) NOT NULL,
|
||||
`perms_categories` int(11) NOT NULL,
|
||||
`perms_suppliers` int(11) NOT NULL,
|
||||
`perms_manufacturers` int(11) NOT NULL,
|
||||
`perms_attachement_types` int(11) NOT NULL,
|
||||
`perms_tools` int(11) NOT NULL,
|
||||
`perms_labels` smallint(6) NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `users`
|
||||
--
|
||||
|
||||
INSERT INTO `users` (`id`, `name`, `password`, `first_name`, `last_name`, `department`, `email`, `need_pw_change`, `group_id`, `config_language`, `config_timezone`, `config_theme`, `config_currency`, `config_image_path`, `config_instock_comment_w`, `config_instock_comment_a`, `perms_system`, `perms_groups`, `perms_users`, `perms_self`, `perms_system_config`, `perms_system_database`, `perms_parts`, `perms_parts_name`, `perms_parts_description`, `perms_parts_instock`, `perms_parts_mininstock`, `perms_parts_footprint`, `perms_parts_storelocation`, `perms_parts_manufacturer`, `perms_parts_comment`, `perms_parts_order`, `perms_parts_orderdetails`, `perms_parts_prices`, `perms_parts_attachements`, `perms_devices`, `perms_devices_parts`, `perms_storelocations`, `perms_footprints`, `perms_categories`, `perms_suppliers`, `perms_manufacturers`, `perms_attachement_types`, `perms_tools`, `perms_labels`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'anonymous', '', '', '', '', '', 0, 2, '', '', '', NULL, '', '', '', 21848, 20480, 0, 0, 0, 0, 0, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21520, 21520, 21520, 20480, 21520, 20480, 20480, 20480, 20480, 20480, 21504, 20480, 0, '2017-10-21 17:58:46', '2018-02-18 12:46:58'),
|
||||
(2, 'admin', '$2a$12$j0RKrKlx60bzX1DWMyXwjeaW.pe3bFjAK8ByIGnvjrRnET2JtsFoe', 'Admin', 'Ad', NULL, 'admin@ras.pi', 0, 1, '', '', '', NULL, '', '', '', 21845, 21845, 21845, 21, 85, 21, 349525, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 0, '2017-10-21 17:58:46', '2017-12-23 11:04:48');
|
||||
|
||||
--
|
||||
-- Indizes der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `attachements_class_name_k` (`class_name`),
|
||||
ADD KEY `attachements_element_id_k` (`element_id`),
|
||||
ADD KEY `attachements_type_id_fk` (`type_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `attachement_types_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `categories_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `devices_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `device_parts`
|
||||
--
|
||||
ALTER TABLE `device_parts`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `device_parts_combination_uk` (`id_part`,`id_device`),
|
||||
ADD KEY `device_parts_id_part_k` (`id_part`),
|
||||
ADD KEY `device_parts_id_device_k` (`id_device`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `footprints_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `groups`
|
||||
--
|
||||
ALTER TABLE `groups`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `internal`
|
||||
--
|
||||
ALTER TABLE `internal`
|
||||
ADD UNIQUE KEY `keyName` (`keyName`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `log`
|
||||
--
|
||||
ALTER TABLE `log`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `id_user` (`id_user`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `manufacturers_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `orderdetails`
|
||||
--
|
||||
ALTER TABLE `orderdetails`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `orderdetails_part_id_k` (`part_id`),
|
||||
ADD KEY `orderdetails_id_supplier_k` (`id_supplier`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `parts_id_category_k` (`id_category`),
|
||||
ADD KEY `parts_id_footprint_k` (`id_footprint`),
|
||||
ADD KEY `parts_id_storelocation_k` (`id_storelocation`),
|
||||
ADD KEY `parts_order_orderdetails_id_k` (`order_orderdetails_id`),
|
||||
ADD KEY `parts_id_manufacturer_k` (`id_manufacturer`),
|
||||
ADD KEY `favorite` (`favorite`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `pricedetails`
|
||||
--
|
||||
ALTER TABLE `pricedetails`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `pricedetails_combination_uk` (`orderdetails_id`,`min_discount_quantity`),
|
||||
ADD KEY `pricedetails_orderdetails_id_k` (`orderdetails_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `storelocations_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `suppliers_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für exportierte Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=201;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=123;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `device_parts`
|
||||
--
|
||||
ALTER TABLE `device_parts`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=82;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `groups`
|
||||
--
|
||||
ALTER TABLE `groups`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `log`
|
||||
--
|
||||
ALTER TABLE `log`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=218;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `orderdetails`
|
||||
--
|
||||
ALTER TABLE `orderdetails`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=650;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1171;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `pricedetails`
|
||||
--
|
||||
ALTER TABLE `pricedetails`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=437;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=340;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
|
||||
|
||||
--
|
||||
-- Constraints der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
ADD CONSTRAINT `attachements_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `attachement_types` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
ADD CONSTRAINT `attachement_types_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `attachement_types` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
ADD CONSTRAINT `categories_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
ADD CONSTRAINT `devices_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `devices` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
ADD CONSTRAINT `footprints_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `footprints` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
ADD CONSTRAINT `manufacturers_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `manufacturers` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
ADD CONSTRAINT `parts_id_footprint_fk` FOREIGN KEY (`id_footprint`) REFERENCES `footprints` (`id`),
|
||||
ADD CONSTRAINT `parts_id_manufacturer_fk` FOREIGN KEY (`id_manufacturer`) REFERENCES `manufacturers` (`id`),
|
||||
ADD CONSTRAINT `parts_id_storelocation_fk` FOREIGN KEY (`id_storelocation`) REFERENCES `storelocations` (`id`),
|
||||
ADD CONSTRAINT `parts_order_orderdetails_id_fk` FOREIGN KEY (`order_orderdetails_id`) REFERENCES `orderdetails` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
ADD CONSTRAINT `storelocations_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `storelocations` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
ADD CONSTRAINT `suppliers_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `suppliers` (`id`);
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
736
.github/assets/legacy_import/db_minimal.sql
vendored
736
.github/assets/legacy_import/db_minimal.sql
vendored
@@ -1,736 +0,0 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.3
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Host: 127.0.0.1
|
||||
-- Erstellungszeit: 07. Mai 2023 um 01:48
|
||||
-- Server-Version: 10.6.5-MariaDB-log
|
||||
-- PHP-Version: 8.1.2
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Datenbank: `partdb-legacy`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `attachements`
|
||||
--
|
||||
|
||||
CREATE TABLE `attachements` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`class_name` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`element_id` int(11) NOT NULL,
|
||||
`type_id` int(11) NOT NULL,
|
||||
`filename` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`show_in_table` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `attachement_types`
|
||||
--
|
||||
|
||||
CREATE TABLE `attachement_types` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `attachement_types`
|
||||
--
|
||||
|
||||
INSERT INTO `attachement_types` (`id`, `name`, `parent_id`, `comment`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'Bilder', NULL, NULL, '2023-01-07 18:31:48', '0000-00-00 00:00:00'),
|
||||
(2, 'Datenblätter', NULL, NULL, '2023-01-07 18:31:48', '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `categories`
|
||||
--
|
||||
|
||||
CREATE TABLE `categories` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`disable_footprints` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_manufacturers` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_autodatasheets` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`disable_properties` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`partname_regex` text COLLATE utf8mb3_unicode_ci NOT NULL DEFAULT '',
|
||||
`partname_hint` text COLLATE utf8mb3_unicode_ci NOT NULL DEFAULT '',
|
||||
`default_description` text COLLATE utf8mb3_unicode_ci NOT NULL DEFAULT '',
|
||||
`default_comment` text COLLATE utf8mb3_unicode_ci NOT NULL DEFAULT '',
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `categories`
|
||||
--
|
||||
|
||||
INSERT INTO `categories` (`id`, `name`, `parent_id`, `disable_footprints`, `disable_manufacturers`, `disable_autodatasheets`, `disable_properties`, `partname_regex`, `partname_hint`, `default_description`, `default_comment`, `comment`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'Test', NULL, 0, 0, 0, 0, '', '', '', '', '', '2023-01-07 18:32:29', '2023-01-07 18:32:29');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `devices`
|
||||
--
|
||||
|
||||
CREATE TABLE `devices` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`order_quantity` int(11) NOT NULL DEFAULT 0,
|
||||
`order_only_missing_parts` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `device_parts`
|
||||
--
|
||||
|
||||
CREATE TABLE `device_parts` (
|
||||
`id` int(11) NOT NULL,
|
||||
`id_part` int(11) NOT NULL DEFAULT 0,
|
||||
`id_device` int(11) NOT NULL DEFAULT 0,
|
||||
`quantity` int(11) NOT NULL DEFAULT 0,
|
||||
`mountnames` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `footprints`
|
||||
--
|
||||
|
||||
CREATE TABLE `footprints` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`filename` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`filename_3d` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `groups`
|
||||
--
|
||||
|
||||
CREATE TABLE `groups` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(32) NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`comment` mediumtext DEFAULT NULL,
|
||||
`perms_system` int(11) NOT NULL,
|
||||
`perms_groups` int(11) NOT NULL,
|
||||
`perms_users` int(11) NOT NULL,
|
||||
`perms_self` int(11) NOT NULL,
|
||||
`perms_system_config` int(11) NOT NULL,
|
||||
`perms_system_database` int(11) NOT NULL,
|
||||
`perms_parts` bigint(11) NOT NULL,
|
||||
`perms_parts_name` smallint(6) NOT NULL,
|
||||
`perms_parts_description` smallint(6) NOT NULL,
|
||||
`perms_parts_instock` smallint(6) NOT NULL,
|
||||
`perms_parts_mininstock` smallint(6) NOT NULL,
|
||||
`perms_parts_footprint` smallint(6) NOT NULL,
|
||||
`perms_parts_storelocation` smallint(6) NOT NULL,
|
||||
`perms_parts_manufacturer` smallint(6) NOT NULL,
|
||||
`perms_parts_comment` smallint(6) NOT NULL,
|
||||
`perms_parts_order` smallint(6) NOT NULL,
|
||||
`perms_parts_orderdetails` smallint(6) NOT NULL,
|
||||
`perms_parts_prices` smallint(6) NOT NULL,
|
||||
`perms_parts_attachements` smallint(6) NOT NULL,
|
||||
`perms_devices` int(11) NOT NULL,
|
||||
`perms_devices_parts` int(11) NOT NULL,
|
||||
`perms_storelocations` int(11) NOT NULL,
|
||||
`perms_footprints` int(11) NOT NULL,
|
||||
`perms_categories` int(11) NOT NULL,
|
||||
`perms_suppliers` int(11) NOT NULL,
|
||||
`perms_manufacturers` int(11) NOT NULL,
|
||||
`perms_attachement_types` int(11) NOT NULL,
|
||||
`perms_tools` int(11) NOT NULL,
|
||||
`perms_labels` smallint(6) NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `groups`
|
||||
--
|
||||
|
||||
INSERT INTO `groups` (`id`, `name`, `parent_id`, `comment`, `perms_system`, `perms_groups`, `perms_users`, `perms_self`, `perms_system_config`, `perms_system_database`, `perms_parts`, `perms_parts_name`, `perms_parts_description`, `perms_parts_instock`, `perms_parts_mininstock`, `perms_parts_footprint`, `perms_parts_storelocation`, `perms_parts_manufacturer`, `perms_parts_comment`, `perms_parts_order`, `perms_parts_orderdetails`, `perms_parts_prices`, `perms_parts_attachements`, `perms_devices`, `perms_devices_parts`, `perms_storelocations`, `perms_footprints`, `perms_categories`, `perms_suppliers`, `perms_manufacturers`, `perms_attachement_types`, `perms_tools`, `perms_labels`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'admins', NULL, 'Users of this group can do everything: Read, Write and Administrative actions.', 21, 1365, 87381, 85, 85, 21, 1431655765, 5, 5, 5, 5, 5, 5, 5, 5, 5, 325, 325, 325, 5461, 325, 5461, 5461, 5461, 5461, 5461, 1365, 1365, 85, '2023-01-07 18:31:48', '0000-00-00 00:00:00'),
|
||||
(2, 'readonly', NULL, 'Users of this group can only read informations, use tools, and don\'t have access to administrative tools.', 42, 2730, 174762, 154, 170, 42, -1516939607, 9, 9, 9, 9, 9, 9, 9, 9, 9, 649, 649, 649, 1705, 649, 1705, 1705, 1705, 1705, 1705, 681, 1366, 165, '2023-01-07 18:31:48', '0000-00-00 00:00:00'),
|
||||
(3, 'users', NULL, 'Users of this group, can edit part informations, create new ones, etc. but are not allowed to use administrative tools. (But can read current configuration, and see Server status)', 42, 2730, 109226, 89, 105, 41, 1431655765, 5, 5, 5, 5, 5, 5, 5, 5, 5, 325, 325, 325, 5461, 325, 5461, 5461, 5461, 5461, 5461, 1365, 1365, 85, '2023-01-07 18:31:48', '0000-00-00 00:00:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `internal`
|
||||
--
|
||||
|
||||
CREATE TABLE `internal` (
|
||||
`keyName` char(30) CHARACTER SET ascii NOT NULL,
|
||||
`keyValue` varchar(255) COLLATE utf8mb3_unicode_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `internal`
|
||||
--
|
||||
|
||||
INSERT INTO `internal` (`keyName`, `keyValue`) VALUES
|
||||
('dbVersion', '26');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `log`
|
||||
--
|
||||
|
||||
CREATE TABLE `log` (
|
||||
`id` int(11) NOT NULL,
|
||||
`datetime` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`id_user` int(11) NOT NULL,
|
||||
`level` tinyint(4) NOT NULL,
|
||||
`type` smallint(6) NOT NULL,
|
||||
`target_id` int(11) NOT NULL,
|
||||
`target_type` smallint(6) NOT NULL,
|
||||
`extra` mediumtext NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `log`
|
||||
--
|
||||
|
||||
INSERT INTO `log` (`id`, `datetime`, `id_user`, `level`, `type`, `target_id`, `target_type`, `extra`) VALUES
|
||||
(1, '2023-01-07 18:31:48', 1, 4, 10, 0, 0, '{\"o\":0,\"n\":26,\"s\":true}'),
|
||||
(2, '2023-01-07 18:32:13', 2, 6, 1, 2, 1, '{\"i\":\"::\"}'),
|
||||
(3, '2023-01-07 18:32:29', 2, 6, 6, 1, 4, '[]'),
|
||||
(4, '2023-01-07 18:32:53', 2, 6, 6, 1, 12, '[]'),
|
||||
(5, '2023-01-07 18:33:26', 2, 6, 6, 1, 10, '{\"i\":0}');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `manufacturers`
|
||||
--
|
||||
|
||||
CREATE TABLE `manufacturers` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`address` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`phone_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`fax_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`email_address` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`website` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`auto_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `orderdetails`
|
||||
--
|
||||
|
||||
CREATE TABLE `orderdetails` (
|
||||
`id` int(11) NOT NULL,
|
||||
`part_id` int(11) NOT NULL,
|
||||
`id_supplier` int(11) NOT NULL DEFAULT 0,
|
||||
`supplierpartnr` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`obsolete` tinyint(1) DEFAULT 0,
|
||||
`supplier_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `orderdetails`
|
||||
--
|
||||
|
||||
INSERT INTO `orderdetails` (`id`, `part_id`, `id_supplier`, `supplierpartnr`, `obsolete`, `supplier_product_url`, `datetime_added`) VALUES
|
||||
(1, 1, 1, 'BC547', 0, '', '2023-01-07 18:45:59'),
|
||||
(2, 1, 1, 'Test', 0, '', '2023-01-07 18:46:09');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `parts`
|
||||
--
|
||||
|
||||
CREATE TABLE `parts` (
|
||||
`id` int(11) NOT NULL,
|
||||
`id_category` int(11) NOT NULL DEFAULT 0,
|
||||
`name` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`description` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`instock` int(11) NOT NULL DEFAULT 0,
|
||||
`mininstock` int(11) NOT NULL DEFAULT 0,
|
||||
`comment` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`visible` tinyint(1) NOT NULL,
|
||||
`id_footprint` int(11) DEFAULT NULL,
|
||||
`id_storelocation` int(11) DEFAULT NULL,
|
||||
`order_orderdetails_id` int(11) DEFAULT NULL,
|
||||
`order_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`manual_order` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`id_manufacturer` int(11) DEFAULT NULL,
|
||||
`id_master_picture_attachement` int(11) DEFAULT NULL,
|
||||
`manufacturer_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`favorite` tinyint(1) NOT NULL DEFAULT 0
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `parts`
|
||||
--
|
||||
|
||||
INSERT INTO `parts` (`id`, `id_category`, `name`, `description`, `instock`, `mininstock`, `comment`, `visible`, `id_footprint`, `id_storelocation`, `order_orderdetails_id`, `order_quantity`, `manual_order`, `id_manufacturer`, `id_master_picture_attachement`, `manufacturer_product_url`, `datetime_added`, `last_modified`, `favorite`) VALUES
|
||||
(1, 1, 'BC547', '', 0, 0, '', 0, NULL, NULL, NULL, 1, 0, NULL, NULL, '', '2023-01-07 18:33:26', '2023-01-07 18:33:26', 0);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `pricedetails`
|
||||
--
|
||||
|
||||
CREATE TABLE `pricedetails` (
|
||||
`id` int(11) NOT NULL,
|
||||
`orderdetails_id` int(11) NOT NULL,
|
||||
`price` decimal(11,5) DEFAULT NULL,
|
||||
`price_related_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`min_discount_quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`manual_input` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`last_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `pricedetails`
|
||||
--
|
||||
|
||||
INSERT INTO `pricedetails` (`id`, `orderdetails_id`, `price`, `price_related_quantity`, `min_discount_quantity`, `manual_input`, `last_modified`) VALUES
|
||||
(1, 2, '3.55000', 1, 1, 1, '2023-01-07 18:46:19');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `storelocations`
|
||||
--
|
||||
|
||||
CREATE TABLE `storelocations` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`is_full` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `suppliers`
|
||||
--
|
||||
|
||||
CREATE TABLE `suppliers` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`parent_id` int(11) DEFAULT NULL,
|
||||
`address` mediumtext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`phone_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`fax_number` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`email_address` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`website` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`auto_product_url` tinytext COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`comment` text COLLATE utf8mb3_unicode_ci DEFAULT NULL,
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `suppliers`
|
||||
--
|
||||
|
||||
INSERT INTO `suppliers` (`id`, `name`, `parent_id`, `address`, `phone_number`, `fax_number`, `email_address`, `website`, `auto_product_url`, `datetime_added`, `comment`, `last_modified`) VALUES
|
||||
(1, 'Reichelt', NULL, '', '', '', '', '', '', '2023-01-07 18:32:53', '', '2023-01-07 18:32:53');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabellenstruktur für Tabelle `users`
|
||||
--
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(32) NOT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`first_name` tinytext DEFAULT NULL,
|
||||
`last_name` tinytext DEFAULT NULL,
|
||||
`department` tinytext DEFAULT NULL,
|
||||
`email` tinytext DEFAULT NULL,
|
||||
`need_pw_change` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`group_id` int(11) DEFAULT NULL,
|
||||
`config_language` tinytext DEFAULT NULL,
|
||||
`config_timezone` tinytext DEFAULT NULL,
|
||||
`config_theme` tinytext DEFAULT NULL,
|
||||
`config_currency` tinytext DEFAULT NULL,
|
||||
`config_image_path` text NOT NULL,
|
||||
`config_instock_comment_w` text NOT NULL,
|
||||
`config_instock_comment_a` text NOT NULL,
|
||||
`perms_system` int(11) NOT NULL,
|
||||
`perms_groups` int(11) NOT NULL,
|
||||
`perms_users` int(11) NOT NULL,
|
||||
`perms_self` int(11) NOT NULL,
|
||||
`perms_system_config` int(11) NOT NULL,
|
||||
`perms_system_database` int(11) NOT NULL,
|
||||
`perms_parts` bigint(11) NOT NULL,
|
||||
`perms_parts_name` smallint(6) NOT NULL,
|
||||
`perms_parts_description` smallint(6) NOT NULL,
|
||||
`perms_parts_instock` smallint(6) NOT NULL,
|
||||
`perms_parts_mininstock` smallint(6) NOT NULL,
|
||||
`perms_parts_footprint` smallint(6) NOT NULL,
|
||||
`perms_parts_storelocation` smallint(6) NOT NULL,
|
||||
`perms_parts_manufacturer` smallint(6) NOT NULL,
|
||||
`perms_parts_comment` smallint(6) NOT NULL,
|
||||
`perms_parts_order` smallint(6) NOT NULL,
|
||||
`perms_parts_orderdetails` smallint(6) NOT NULL,
|
||||
`perms_parts_prices` smallint(6) NOT NULL,
|
||||
`perms_parts_attachements` smallint(6) NOT NULL,
|
||||
`perms_devices` int(11) NOT NULL,
|
||||
`perms_devices_parts` int(11) NOT NULL,
|
||||
`perms_storelocations` int(11) NOT NULL,
|
||||
`perms_footprints` int(11) NOT NULL,
|
||||
`perms_categories` int(11) NOT NULL,
|
||||
`perms_suppliers` int(11) NOT NULL,
|
||||
`perms_manufacturers` int(11) NOT NULL,
|
||||
`perms_attachement_types` int(11) NOT NULL,
|
||||
`perms_tools` int(11) NOT NULL,
|
||||
`perms_labels` smallint(6) NOT NULL,
|
||||
`datetime_added` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`last_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Daten für Tabelle `users`
|
||||
--
|
||||
|
||||
INSERT INTO `users` (`id`, `name`, `password`, `first_name`, `last_name`, `department`, `email`, `need_pw_change`, `group_id`, `config_language`, `config_timezone`, `config_theme`, `config_currency`, `config_image_path`, `config_instock_comment_w`, `config_instock_comment_a`, `perms_system`, `perms_groups`, `perms_users`, `perms_self`, `perms_system_config`, `perms_system_database`, `perms_parts`, `perms_parts_name`, `perms_parts_description`, `perms_parts_instock`, `perms_parts_mininstock`, `perms_parts_footprint`, `perms_parts_storelocation`, `perms_parts_manufacturer`, `perms_parts_comment`, `perms_parts_order`, `perms_parts_orderdetails`, `perms_parts_prices`, `perms_parts_attachements`, `perms_devices`, `perms_devices_parts`, `perms_storelocations`, `perms_footprints`, `perms_categories`, `perms_suppliers`, `perms_manufacturers`, `perms_attachement_types`, `perms_tools`, `perms_labels`, `datetime_added`, `last_modified`) VALUES
|
||||
(1, 'anonymous', '', '', '', '', '', 0, 2, NULL, NULL, NULL, NULL, '', '', '', 21844, 20480, 0, 0, 0, 0, 0, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21840, 21520, 21520, 21520, 20480, 21520, 20480, 20480, 20480, 20480, 20480, 21504, 20480, 0, '2023-01-07 18:31:48', '0000-00-00 00:00:00'),
|
||||
(2, 'admin', '$2a$12$j0RKrKlx60bzX1DWMyXwjeaW.pe3bFjAK8ByIGnvjrRnET2JtsFoe$2a$12$j0RKrKlx60bzX1DWMyXwjeaW.pe3bFjAK8ByIGnvjrRnET2JtsFoe', '', '', '', '', 1, 1, NULL, NULL, NULL, NULL, '', '', '', 21845, 21845, 21845, 21, 85, 21, 349525, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 21845, 0, '2023-01-07 18:31:48', '0000-00-00 00:00:00');
|
||||
|
||||
--
|
||||
-- Indizes der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `attachements_class_name_k` (`class_name`),
|
||||
ADD KEY `attachements_element_id_k` (`element_id`),
|
||||
ADD KEY `attachements_type_id_fk` (`type_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `attachement_types_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `categories_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `devices_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `device_parts`
|
||||
--
|
||||
ALTER TABLE `device_parts`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `device_parts_combination_uk` (`id_part`,`id_device`),
|
||||
ADD KEY `device_parts_id_part_k` (`id_part`),
|
||||
ADD KEY `device_parts_id_device_k` (`id_device`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `footprints_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `groups`
|
||||
--
|
||||
ALTER TABLE `groups`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `internal`
|
||||
--
|
||||
ALTER TABLE `internal`
|
||||
ADD UNIQUE KEY `keyName` (`keyName`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `log`
|
||||
--
|
||||
ALTER TABLE `log`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `id_user` (`id_user`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `manufacturers_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `orderdetails`
|
||||
--
|
||||
ALTER TABLE `orderdetails`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `orderdetails_part_id_k` (`part_id`),
|
||||
ADD KEY `orderdetails_id_supplier_k` (`id_supplier`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `parts_id_category_k` (`id_category`),
|
||||
ADD KEY `parts_id_footprint_k` (`id_footprint`),
|
||||
ADD KEY `parts_id_storelocation_k` (`id_storelocation`),
|
||||
ADD KEY `parts_order_orderdetails_id_k` (`order_orderdetails_id`),
|
||||
ADD KEY `parts_id_manufacturer_k` (`id_manufacturer`),
|
||||
ADD KEY `favorite` (`favorite`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `pricedetails`
|
||||
--
|
||||
ALTER TABLE `pricedetails`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `pricedetails_combination_uk` (`orderdetails_id`,`min_discount_quantity`),
|
||||
ADD KEY `pricedetails_orderdetails_id_k` (`orderdetails_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `storelocations_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `suppliers_parent_id_k` (`parent_id`);
|
||||
|
||||
--
|
||||
-- Indizes für die Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für exportierte Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `device_parts`
|
||||
--
|
||||
ALTER TABLE `device_parts`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `groups`
|
||||
--
|
||||
ALTER TABLE `groups`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `log`
|
||||
--
|
||||
ALTER TABLE `log`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `orderdetails`
|
||||
--
|
||||
ALTER TABLE `orderdetails`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `pricedetails`
|
||||
--
|
||||
ALTER TABLE `pricedetails`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT für Tabelle `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||
|
||||
--
|
||||
-- Constraints der exportierten Tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `attachements`
|
||||
--
|
||||
ALTER TABLE `attachements`
|
||||
ADD CONSTRAINT `attachements_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `attachement_types` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `attachement_types`
|
||||
--
|
||||
ALTER TABLE `attachement_types`
|
||||
ADD CONSTRAINT `attachement_types_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `attachement_types` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `categories`
|
||||
--
|
||||
ALTER TABLE `categories`
|
||||
ADD CONSTRAINT `categories_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `devices`
|
||||
--
|
||||
ALTER TABLE `devices`
|
||||
ADD CONSTRAINT `devices_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `devices` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `footprints`
|
||||
--
|
||||
ALTER TABLE `footprints`
|
||||
ADD CONSTRAINT `footprints_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `footprints` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `manufacturers`
|
||||
--
|
||||
ALTER TABLE `manufacturers`
|
||||
ADD CONSTRAINT `manufacturers_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `manufacturers` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `parts`
|
||||
--
|
||||
ALTER TABLE `parts`
|
||||
ADD CONSTRAINT `parts_id_footprint_fk` FOREIGN KEY (`id_footprint`) REFERENCES `footprints` (`id`),
|
||||
ADD CONSTRAINT `parts_id_manufacturer_fk` FOREIGN KEY (`id_manufacturer`) REFERENCES `manufacturers` (`id`),
|
||||
ADD CONSTRAINT `parts_id_storelocation_fk` FOREIGN KEY (`id_storelocation`) REFERENCES `storelocations` (`id`),
|
||||
ADD CONSTRAINT `parts_order_orderdetails_id_fk` FOREIGN KEY (`order_orderdetails_id`) REFERENCES `orderdetails` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `storelocations`
|
||||
--
|
||||
ALTER TABLE `storelocations`
|
||||
ADD CONSTRAINT `storelocations_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `storelocations` (`id`);
|
||||
|
||||
--
|
||||
-- Constraints der Tabelle `suppliers`
|
||||
--
|
||||
ALTER TABLE `suppliers`
|
||||
ADD CONSTRAINT `suppliers_parent_id_fk` FOREIGN KEY (`parent_id`) REFERENCES `suppliers` (`id`);
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
@@ -1,54 +0,0 @@
|
||||
#
|
||||
# This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
#
|
||||
# Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# This script is used to test the legacy import of Part-DB
|
||||
|
||||
SQL_FILES_TO_TEST=("db_minimal.sql" "db_jbtronics.sql")
|
||||
|
||||
DB_NAME="legacy_db_test"
|
||||
DB_USER="root"
|
||||
DB_PASSWORD="root"
|
||||
|
||||
# Iterate over all given SQL files and import them into the mysql database with the given name, drop the database if it already exists before
|
||||
for SQL_FILE in "${SQL_FILES_TO_TEST[@]}"
|
||||
do
|
||||
echo "Testing for $SQL_FILE"
|
||||
mysql -u $DB_USER --password=$DB_PASSWORD -e "DROP DATABASE IF EXISTS $DB_NAME; CREATE DATABASE $DB_NAME;"
|
||||
# If the last command failed, exit the script
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create database $DB_NAME"
|
||||
exit 1
|
||||
fi
|
||||
# Import the SQL file into the database. The file pathes are relative to the current script location
|
||||
mysql -u $DB_USER --password=$DB_PASSWORD $DB_NAME < .github/assets/legacy_import/$SQL_FILE
|
||||
# If the last command failed, exit the script
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to import $SQL_FILE into database $DB_NAME"
|
||||
exit 1
|
||||
fi
|
||||
# Run doctrine migrations, this will migrate the database to the current version. This process should not fail
|
||||
php bin/console doctrine:migrations:migrate -n
|
||||
# If the last command failed, exit the script
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to migrate database $DB_NAME"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
186
.github/copilot-instructions.md
vendored
186
.github/copilot-instructions.md
vendored
@@ -1,186 +0,0 @@
|
||||
# Copilot Instructions for Part-DB
|
||||
|
||||
Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM
|
||||
- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore
|
||||
- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite
|
||||
- **Testing**: PHPUnit with DAMA Doctrine Test Bundle
|
||||
- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5)
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.)
|
||||
- `assets/`: Frontend TypeScript/JavaScript and CSS files
|
||||
- `templates/`: Twig templates for views
|
||||
- `tests/`: PHPUnit tests mirroring the `src/` structure
|
||||
- `config/`: Symfony configuration files
|
||||
- `public/`: Web-accessible files
|
||||
- `translations/`: Translation files for multi-language support
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### PHP Code
|
||||
|
||||
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html)
|
||||
- Use type hints for all parameters and return types
|
||||
- Always declare strict types: `declare(strict_types=1);` at the top of PHP files
|
||||
- Use PHPDoc blocks for complex logic or when type information is needed
|
||||
|
||||
### TypeScript/JavaScript
|
||||
|
||||
- Use TypeScript for new frontend code
|
||||
- Follow existing Stimulus controller patterns in `assets/controllers/`
|
||||
- Use Bootstrap 5 components and utilities
|
||||
- Leverage Hotwire Turbo for dynamic page updates
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`)
|
||||
- Controllers: Suffix with `Controller` (e.g., `PartController`)
|
||||
- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`)
|
||||
- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Install PHP dependencies: `composer install`
|
||||
- Install JS dependencies: `yarn install`
|
||||
- Build frontend assets: `yarn build` (production) or `yarn watch` (development)
|
||||
|
||||
### Database
|
||||
|
||||
- Create database: `php bin/console doctrine:database:create --env=dev`
|
||||
- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev`
|
||||
- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev`
|
||||
|
||||
Or use Makefile shortcuts:
|
||||
- `make dev-setup`: Complete development environment setup
|
||||
- `make dev-reset`: Reset development environment (cache clear + migrate)
|
||||
|
||||
### Testing
|
||||
|
||||
- Set up test environment: `make test-setup`
|
||||
- Run all tests: `php bin/phpunit`
|
||||
- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php`
|
||||
- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage`
|
||||
- Test environment uses SQLite by default for speed
|
||||
|
||||
### Static Analysis
|
||||
|
||||
- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5`
|
||||
- PHPStan configuration is in `phpstan.dist.neon`
|
||||
|
||||
### Running the Application
|
||||
|
||||
- Development server: `symfony serve` (requires Symfony CLI)
|
||||
- Or configure Apache/nginx to serve from `public/` directory
|
||||
- Set `APP_ENV=dev` in `.env.local` for development mode
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
- Always sanitize user input
|
||||
- Use Symfony's security component for authentication/authorization
|
||||
- Check permissions using the permission system before allowing actions
|
||||
- Never expose sensitive data in logs or error messages
|
||||
- Use parameterized queries (Doctrine handles this automatically)
|
||||
|
||||
### Performance
|
||||
|
||||
- Use Doctrine query builder for complex queries instead of DQL when possible
|
||||
- Lazy load relationships to avoid N+1 queries
|
||||
- Cache results when appropriate using Symfony's cache component
|
||||
- Use pagination for large result sets (DataTables integration available)
|
||||
|
||||
### Database
|
||||
|
||||
- Always create migrations for schema changes: `php bin/console make:migration`
|
||||
- Review migration files before running them
|
||||
- Use Doctrine annotations or attributes for entity mapping
|
||||
- Follow existing entity patterns for relationships and lifecycle callbacks
|
||||
|
||||
### Frontend
|
||||
|
||||
- Use Stimulus controllers for interactive components
|
||||
- Leverage Turbo for dynamic page updates without full page reloads
|
||||
- Use Bootstrap 5 classes for styling
|
||||
- Keep JavaScript modular and organized in controllers
|
||||
- Use the translation system for user-facing strings
|
||||
|
||||
### Translations
|
||||
|
||||
- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}`
|
||||
- Add new translation keys to `translations/` files
|
||||
- Primary language is English (en)
|
||||
- Translations are managed via Crowdin, but can be edited locally if needed
|
||||
|
||||
### Testing
|
||||
|
||||
- Write unit tests for services and helpers
|
||||
- Write functional tests for controllers
|
||||
- Use fixtures for test data
|
||||
- Tests should be isolated and not depend on execution order
|
||||
- Mock external dependencies when appropriate
|
||||
- Follow existing test patterns in the repository
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating an Entity
|
||||
|
||||
1. Create entity class in `src/Entity/` with Doctrine attributes
|
||||
2. Generate migration: `php bin/console make:migration`
|
||||
3. Review and run migration: `php bin/console doctrine:migrations:migrate`
|
||||
4. Create repository if needed in `src/Repository/`
|
||||
5. Add fixtures in `src/DataFixtures/` for testing
|
||||
|
||||
### Adding a Form
|
||||
|
||||
1. Create form type in `src/Form/`
|
||||
2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()`
|
||||
3. Use in controller and render in Twig template
|
||||
4. Follow existing form patterns for consistency
|
||||
|
||||
### Creating a Controller Action
|
||||
|
||||
1. Add method to appropriate controller in `src/Controller/`
|
||||
2. Use route attributes for routing
|
||||
3. Check permissions using security voters
|
||||
4. Return Response or render Twig template
|
||||
5. Add corresponding template in `templates/`
|
||||
|
||||
### Adding a Service
|
||||
|
||||
1. Create service class in `src/Services/`
|
||||
2. Use dependency injection via constructor
|
||||
3. Tag service in `config/services.yaml` if needed
|
||||
4. Services are autowired by default
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Part-DB uses fine-grained permissions - always check user permissions before actions
|
||||
- Multi-language support is critical - use translation keys everywhere
|
||||
- The application supports multiple database backends - write portable code
|
||||
- Responsive design is important - test on mobile/tablet viewports
|
||||
- Event system is used for logging changes - emit events when appropriate
|
||||
- API Platform is integrated for REST API endpoints
|
||||
|
||||
## Multi-tenancy Considerations
|
||||
|
||||
- Part-DB is designed as a single-tenant application with multiple users
|
||||
- User groups have different permission levels
|
||||
- Always scope queries to respect user permissions
|
||||
- Use the security context to get current user information
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.part-db.de/)
|
||||
- [Contributing Guide](CONTRIBUTING.md)
|
||||
- [Symfony Documentation](https://symfony.com/doc/current/index.html)
|
||||
- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/)
|
||||
- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/)
|
||||
- [Hotwire Documentation](https://hotwired.dev/)
|
||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
92
.github/workflows/assets_artifact_build.yml
vendored
92
.github/workflows/assets_artifact_build.yml
vendored
@@ -1,92 +0,0 @@
|
||||
name: Build assets artifact
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*" # Dont test localization branches
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*"
|
||||
|
||||
jobs:
|
||||
assets_artifact_build:
|
||||
name: Build assets artifact
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
env:
|
||||
APP_ENV: prod
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-dev -a
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install yarn dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build frontend
|
||||
run: yarn build
|
||||
|
||||
- name: Remove node_modules/ folder
|
||||
run: rm -rf node_modules/
|
||||
|
||||
- name: Zip the current folder
|
||||
run: zip -r /tmp/partdb_with_assets.zip . -x .git/\* -x var/\* -x node_modules/\*
|
||||
|
||||
- name: Zip only the assets folder (include public/build/ and vendor/)
|
||||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||
|
||||
- name: Upload assets artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: Only dependencies and built assets
|
||||
path: /tmp/partdb_assets.zip
|
||||
|
||||
- name: Upload full artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: Full Part-DB including dependencies and built assets
|
||||
path: /tmp/partdb_with_assets.zip
|
||||
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 14 * * 3'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
52
.github/workflows/docker_build.yml
vendored
52
.github/workflows/docker_build.yml
vendored
@@ -1,18 +1,16 @@
|
||||
name: Docker Image Build
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: '0 10 * * *' # everyday at 10am
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # everyday at 10am
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!l10n_**'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*.*.*-**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
@@ -20,60 +18,34 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
jbtronics/part-db1
|
||||
# Mark the image build from master as latest (as we dont have really releases yet)
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch,
|
||||
type=ref,event=tag,
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.title=Part-DB
|
||||
org.opencontainers.image.description=Part-DB is a web application for managing electronic components and your inventory.
|
||||
org.opencontainers.image.url=https://github.com/Part-DB/Part-DB-symfony
|
||||
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-symfony
|
||||
org.opencontainers.image.authors=Jan Böhmer
|
||||
org.opencontainers.licenses=AGPL-3.0-or-later
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
80
.github/workflows/docker_frankenphp.yml
vendored
80
.github/workflows/docker_frankenphp.yml
vendored
@@ -1,80 +0,0 @@
|
||||
name: Docker Image Build (FrankenPHP)
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: '0 10 * * *' # everyday at 10am
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!l10n_**'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*.*.*-**'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
partdborg/part-db
|
||||
# Mark the image build from master as latest (as we dont have really releases yet)
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch,
|
||||
type=ref,event=tag,
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.title=Part-DB
|
||||
org.opencontainers.image.description=Part-DB is a web application for managing electronic components and your inventory.
|
||||
org.opencontainers.image.url=https://github.com/Part-DB/Part-DB-server
|
||||
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-server
|
||||
org.opencontainers.image.authors=Jan Böhmer
|
||||
org.opencontainers.licenses=AGPL-3.0-or-later
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile-frankenphp
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
49
.github/workflows/static_analysis.yml
vendored
49
.github/workflows/static_analysis.yml
vendored
@@ -1,68 +1,51 @@
|
||||
name: Static analysis
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*" # Dont test localization branches
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*"
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
phpstan:
|
||||
name: Static analysis
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Lint config files
|
||||
run: ./bin/console lint:yaml config --parse-tags
|
||||
|
||||
|
||||
- name: Lint twig templates
|
||||
run: ./bin/console lint:twig templates --env=prod
|
||||
|
||||
# This causes problems with emtpy language files
|
||||
#- name: Lint translations
|
||||
# run: ./bin/console lint:xliff translations
|
||||
|
||||
- name: Lint translations
|
||||
run: ./bin/console lint:xliff translations
|
||||
|
||||
- name: Check dependencies for security
|
||||
uses: symfonycorp/security-checker-action@v5
|
||||
|
||||
uses: symfonycorp/security-checker-action@v2
|
||||
|
||||
- name: Check doctrine mapping
|
||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||
|
||||
# Use the -d option to raise the max nesting level
|
||||
|
||||
- name: Generate dev container
|
||||
run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev
|
||||
|
||||
run: ./bin/console cache:clear --env dev
|
||||
|
||||
- name: Run PHPstan
|
||||
run: composer phpstan
|
||||
|
||||
130
.github/workflows/tests.yml
vendored
130
.github/workflows/tests.yml
vendored
@@ -1,157 +1,113 @@
|
||||
name: PHPUnit Tests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*" # Dont test localization branches
|
||||
- "!i10n_*" # Dont test localization branches
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
- "!l10n_*"
|
||||
|
||||
- "!i10n_*"
|
||||
|
||||
jobs:
|
||||
phpunit:
|
||||
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.2', '8.3', '8.4', '8.5' ]
|
||||
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
||||
|
||||
name: PHPUnit and coverage Test (${{ matrix.php-versions }})
|
||||
# Ubuntu 20.04 ships MySQL 8.0 which causes problems with login, so we just use ubuntu 18.04 for now...
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
env:
|
||||
# Note that we set DATABASE URL later based on our db-type matrix value
|
||||
APP_ENV: test
|
||||
SYMFONY_DEPRECATIONS_HELPER: disabled
|
||||
DATABASE_URL: 'mysql://root:root@127.0.0.1:3306/test'
|
||||
PHP_VERSION: ${{ matrix.php-versions }}
|
||||
DB_TYPE: ${{ matrix.db-type }}
|
||||
CHECK_FOR_UPDATES: false # Disable update checks for tests
|
||||
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4', '8.0']
|
||||
steps:
|
||||
- name: Set Database env for MySQL
|
||||
run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0.35" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'mysql'
|
||||
|
||||
- name: Set Database env for SQLite
|
||||
run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'sqlite'
|
||||
|
||||
- name: Set Database env for PostgreSQL
|
||||
run: echo "DATABASE_URL=postgresql://postgres:postgres @127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV
|
||||
if: matrix.db-type == 'postgres'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: pcov
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
|
||||
- name: Start MySQL
|
||||
run: sudo systemctl start mysql.service
|
||||
if: matrix.db-type == 'mysql'
|
||||
|
||||
# Replace the scram-sha-256 with trust for host connections, to avoid password authentication
|
||||
- name: Start PostgreSQL
|
||||
run: |
|
||||
sudo sed -i 's/^\(host.*all.*all.*\)scram-sha-256/\1trust/' /etc/postgresql/14/main/pg_hba.conf
|
||||
sudo systemctl start postgresql.service
|
||||
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';"
|
||||
if: matrix.db-type == 'postgres'
|
||||
|
||||
#- name: Setup MySQL
|
||||
#- name: Setup MySQL
|
||||
# uses: mirromutth/mysql-action@v1.1
|
||||
# with:
|
||||
# mysql version: 5.7
|
||||
# mysql database: 'part-db'
|
||||
# mysql root password: '1234'
|
||||
|
||||
|
||||
## Setup caches
|
||||
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v1
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
node-version: '12'
|
||||
|
||||
- name: Install yarn dependencies
|
||||
run: yarn install
|
||||
|
||||
|
||||
- name: Build frontend
|
||||
run: yarn build
|
||||
|
||||
run: yarn dev
|
||||
|
||||
- name: Create DB
|
||||
run: php bin/console --env test doctrine:database:create --if-not-exists -n
|
||||
if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres'
|
||||
|
||||
|
||||
- name: Do migrations
|
||||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
||||
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test partdb:fixtures:load -n
|
||||
|
||||
run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
env_vars: PHP_VERSION,DB_TYPE
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
|
||||
env_vars: PHP_VERSION
|
||||
|
||||
- name: Test app:clean-attachments
|
||||
run: php bin/console partdb:attachments:clean-unused -n
|
||||
|
||||
run: php bin/console app:clean-attachments -n
|
||||
|
||||
- name: Test app:convert-bbcode
|
||||
run: php bin/console app:convert-bbcode -n
|
||||
|
||||
|
||||
- name: Test app:show-logs
|
||||
run: php bin/console app:show-logs -n
|
||||
|
||||
- name: Test check-requirements command
|
||||
run: php bin/console partdb:check-requirements -n
|
||||
|
||||
- name: Test partdb:backup command
|
||||
run: php bin/console partdb:backup -n --full /tmp/test_backup.zip
|
||||
|
||||
- name: Test legacy Part-DB import
|
||||
run: bash .github/assets/legacy_import/test_legacy_import.sh
|
||||
if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2'
|
||||
env:
|
||||
DATABASE_URL: mysql://root:root@localhost:3306/legacy_db
|
||||
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/.env.local.bak
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
@@ -24,11 +23,6 @@
|
||||
.idea/*
|
||||
uploads/*
|
||||
!uploads/.keep
|
||||
|
||||
# Do not keep cache files
|
||||
.php_cs.cache
|
||||
.phpcs-cache
|
||||
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
/node_modules/
|
||||
/public/build/
|
||||
@@ -42,12 +36,5 @@ yarn-error.log
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
/.phpunit.cache/
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
1
.php_cs.cache
Normal file
1
.php_cs.cache
Normal file
File diff suppressed because one or more lines are too long
1
.phpcs-cache
Normal file
1
.phpcs-cache
Normal file
File diff suppressed because one or more lines are too long
58
.travis.yml
Normal file
58
.travis.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
language: php
|
||||
dist: bionic
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
env:
|
||||
- SYMFONY_DEPRECATIONS_HELPER=disabled APP_ENV=test # Disable deprecation warnings
|
||||
|
||||
# Test for all supported PHP versions
|
||||
php:
|
||||
#- '7.1'
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
- '7.4'
|
||||
|
||||
before_install:
|
||||
# Use a more modern nodejs version, than provided by default
|
||||
- nvm install --lts
|
||||
|
||||
install:
|
||||
- composer install -o
|
||||
# Compile assets
|
||||
- yarn install
|
||||
- yarn build
|
||||
- php bin/console --env test doctrine:database:create --if-not-exists -n
|
||||
- php bin/console --env test doctrine:migrations:migrate -n
|
||||
# Load fixtures (We have to disable foreign key checks or we get errors)
|
||||
- mysql -e "SET GLOBAL FOREIGN_KEY_CHECKS=0;"
|
||||
- php bin/console --env test doctrine:fixtures:load -n
|
||||
- mysql -e "SET GLOBAL FOREIGN_KEY_CHECKS=1;"
|
||||
|
||||
before_script:
|
||||
# Disable Xdebug on non coverage builds
|
||||
- if [[ $(phpenv version-name) != '7.2' ]]; then phpenv config-rm xdebug.ini ; fi
|
||||
|
||||
script:
|
||||
# Only run code coverage on PHP 7.2 to improve performance (code coverage is slower than normal testing)
|
||||
- if [[ $(phpenv version-name) == '7.2' ]]; then php bin/phpunit --coverage-clover=coverage.xml ; fi
|
||||
- if [[ $(phpenv version-name) != '7.2' ]]; then php bin/phpunit ; fi
|
||||
|
||||
# Perform some simple linting if everything is correct
|
||||
- ./bin/console lint:yaml config --parse-tags
|
||||
- ./bin/console lint:twig templates --env=prod
|
||||
- ./bin/console lint:xliff translations
|
||||
# Check if we dont use dependencies with known vulnerabilities
|
||||
- ./bin/console security:check
|
||||
# Check doctrine mapping
|
||||
- ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||
|
||||
# Static analysis
|
||||
- ./bin/console cache:clear --env dev
|
||||
- composer phpstan
|
||||
|
||||
after_success:
|
||||
- if [[ $(phpenv version-name) == '7.2' ]]; then bash <(curl -s https://codecov.io/bash) ; fi
|
||||
matrix:
|
||||
fast_finish: true
|
||||
@@ -1,55 +1,57 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for considering contributing to Part-DB!
|
||||
Please read the text below, so your contributed content can be incorporated into Part-DB easily.
|
||||
Thank you for consider to contribute to Part-DB!
|
||||
Please read the text below, so your contributed content can be contributed easily to Part-DB.
|
||||
|
||||
You can contribute to Part-DB in various ways:
|
||||
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues)
|
||||
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-symfony/issues)
|
||||
* Improve translations (via https://part-db.crowdin.com/part-db)
|
||||
* Improve code (either PHP, Javascript or HTML templates) by creating a [pull request](https://github.com/Part-DB/Part-DB-server/pulls)
|
||||
* Improve code (either PHP, Javascript or HTML templates) by creating a [pull request](https://github.com/Part-DB/Part-DB-symfony/pulls)
|
||||
|
||||
## Translations
|
||||
The recommended way to create/improve translations is to use the online platform [Crowdin](https://part-db.crowdin.com/part-db).
|
||||
Register an account there and join the Part-DB team.
|
||||
|
||||
If you want to start translation for a new language that does not have an entry on Crowdin yet, send a message to `@jbtronics`.
|
||||
If you want to start translation for a new language that does not have an entry on Crowdin yet, send an message to `@jbtronics`.
|
||||
|
||||
Part-DB uses translation keys (e.g. part.info.title) that are sorted by their usage, so you will most likely have to lookup, how the key
|
||||
was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor).
|
||||
|
||||
Translation keys can be extracted from templates and PHP codes by running `bin/console translation:extract`.
|
||||
|
||||
## Project structure
|
||||
Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
||||
Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
||||
Interesting folders are:
|
||||
* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images.
|
||||
* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here.
|
||||
* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.)
|
||||
* `translations`: The translations used in Part-DB are saved here.
|
||||
* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images.
|
||||
* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here.
|
||||
* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.)
|
||||
* `translations`: The translations used in Part-DB are saved here
|
||||
* `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here.
|
||||
* `tests/`: Tests that can be run by PHPUnit.
|
||||
* `tests/`: Tests that can be run by PHPunit.
|
||||
|
||||
## Development environment
|
||||
For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment).
|
||||
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that
|
||||
For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment).
|
||||
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that
|
||||
will simplify development.
|
||||
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies.
|
||||
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files.
|
||||
* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download).
|
||||
That way you can run a correctly configured webserver with `symfony serve`.
|
||||
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies
|
||||
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files
|
||||
* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download).
|
||||
That way you can run a correct configured webserver with `symfony serve`
|
||||
|
||||
## Coding style
|
||||
Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
||||
Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
||||
|
||||
Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations:
|
||||
* To check your code for valid code style, run `vendor/bin/ecs check src/`
|
||||
* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid)
|
||||
* To check your code for valid code style run `vendor/bin/ecs check src/`
|
||||
* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards)
|
||||
|
||||
## GitHub actions
|
||||
Part-DB uses GitHub Actions to run various tests and checks on the code:
|
||||
## TravisCI
|
||||
Part-DB has a [Travis-CI](https://travis-ci.com/Part-DB/Part-DB-symfony) instance running, that checks for every commit and contribute if the following things are working:
|
||||
* Yarn dependencies can compile
|
||||
* PHPUnit tests run successfully
|
||||
* Config files, translations, and templates have valid syntax
|
||||
* Doctrine schema is valid
|
||||
* No known vulnerable dependencies are used
|
||||
* Static analysis is successful (phpstan with `--level=2`)
|
||||
* PHPunit tests run successful
|
||||
* Config files, translations and templates has valid syntax
|
||||
* Doctrine schema valid
|
||||
* No known vulnerable dependecies are used
|
||||
* Static analysis successful (phpstan with `--level=2`)
|
||||
|
||||
Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
|
||||
Further the code coverage of the PHPunit tests is determined.
|
||||
|
||||
182
Dockerfile
182
Dockerfile
@@ -1,180 +1,44 @@
|
||||
ARG BASE_IMAGE=debian:bookworm-slim
|
||||
ARG PHP_VERSION=8.4
|
||||
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
ARG PHP_VERSION
|
||||
FROM php:7-apache
|
||||
|
||||
# Install needed dependencies for PHP build
|
||||
#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
|
||||
# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
|
||||
# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
apt-transport-https \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
curl \
|
||||
zip \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
|
||||
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
|
||||
&& apt-get update && apt-get upgrade -y \
|
||||
&& apt-get install -y \
|
||||
apache2 \
|
||||
php${PHP_VERSION} \
|
||||
php${PHP_VERSION}-fpm \
|
||||
php${PHP_VERSION}-opcache \
|
||||
php${PHP_VERSION}-curl \
|
||||
php${PHP_VERSION}-gd \
|
||||
php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-xml \
|
||||
php${PHP_VERSION}-bcmath \
|
||||
php${PHP_VERSION}-intl \
|
||||
php${PHP_VERSION}-zip \
|
||||
php${PHP_VERSION}-xsl \
|
||||
php${PHP_VERSION}-sqlite3 \
|
||||
php${PHP_VERSION}-mysql \
|
||||
php${PHP_VERSION}-pgsql \
|
||||
gpg \
|
||||
sudo \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \
|
||||
# Create workdir and set permissions if directory does not exists
|
||||
&& mkdir -p /var/www/html \
|
||||
&& chown -R www-data:www-data /var/www/html \
|
||||
# delete the "index.html" that installing Apache drops in here
|
||||
&& rm -rvf /var/www/html/*
|
||||
|
||||
# Install node and yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
|
||||
apt-get update && apt-get install -y \
|
||||
nodejs \
|
||||
yarn \
|
||||
RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
|
||||
libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libonig-dev libxslt-dev vim \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install GD (we have to configure GD first before installing it, see issue #115)
|
||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install gd
|
||||
# Install other needed PHP extensions
|
||||
RUN docker-php-ext-install pdo_mysql curl intl mbstring bcmath gd zip xml xsl
|
||||
|
||||
# Install yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
||||
RUN apt-get update && apt-get install -y yarn && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
ENV APACHE_CONFDIR=/etc/apache2
|
||||
ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
|
||||
|
||||
# Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile)
|
||||
# generically convert lines like
|
||||
# export APACHE_RUN_USER=www-data
|
||||
# into
|
||||
# : ${APACHE_RUN_USER:=www-data}
|
||||
# export APACHE_RUN_USER
|
||||
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
|
||||
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
|
||||
set -eux; . "$APACHE_ENVVARS"; \
|
||||
\
|
||||
# logs should go to stdout / stderr
|
||||
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
|
||||
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
|
||||
|
||||
# ---
|
||||
|
||||
FROM scratch AS apache-config
|
||||
ARG PHP_VERSION
|
||||
# Configure php-fpm to log to stdout of the container (stdout of PID 1)
|
||||
# We have to use /proc/1/fd/1 because /dev/stdout or /proc/self/fd/1 does not point to the container stdout (because we use apache as entrypoint)
|
||||
# We also disable the clear_env option to allow the use of environment variables in php-fpm
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/pool.d/zz-docker.conf
|
||||
[global]
|
||||
error_log = /proc/1/fd/1
|
||||
|
||||
[www]
|
||||
access.log = /proc/1/fd/1
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
||||
clear_env = no
|
||||
EOF
|
||||
|
||||
# PHP files should be handled by PHP, and should be preferred over any other file type
|
||||
COPY <<EOF /etc/apache2/conf-available/docker-php.conf
|
||||
<FilesMatch \\.php$>
|
||||
SetHandler application/x-httpd-php
|
||||
</FilesMatch>
|
||||
|
||||
DirectoryIndex disabled
|
||||
DirectoryIndex index.php index.html
|
||||
|
||||
<Directory /var/www/>
|
||||
Options -Indexes
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
EOF
|
||||
|
||||
# Enable opcache and configure it recommended for symfony (see https://symfony.com/doc/current/performance.html)
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/symfony-recommended.ini
|
||||
opcache.memory_consumption=256
|
||||
opcache.max_accelerated_files=20000
|
||||
opcache.validate_timestamp=0
|
||||
# Configure Realpath cache for performance
|
||||
realpath_cache_size=4096K
|
||||
realpath_cache_ttl=600
|
||||
EOF
|
||||
|
||||
# Increase upload limit and enable preloading (disabled for now, as it does not seem to work properly, and require prod env anyway)
|
||||
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
|
||||
upload_max_filesize=256M
|
||||
post_max_size=300M
|
||||
;opcache.preload_user=www-data
|
||||
;opcache.preload=/var/www/html/config/preload.php
|
||||
log_limit=8096
|
||||
EOF
|
||||
|
||||
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
||||
|
||||
# ---
|
||||
|
||||
FROM base
|
||||
ARG PHP_VERSION
|
||||
|
||||
# Set working dir
|
||||
WORKDIR /var/www/html
|
||||
COPY --from=apache-config / /
|
||||
COPY --chown=www-data:www-data . .
|
||||
|
||||
# Setup apache2
|
||||
RUN a2dissite 000-default.conf && \
|
||||
a2ensite symfony.conf && \
|
||||
# Enable php-fpm
|
||||
a2enmod proxy_fcgi setenvif && \
|
||||
a2enconf php${PHP_VERSION}-fpm && \
|
||||
a2enconf docker-php && \
|
||||
a2enmod rewrite
|
||||
RUN a2dissite 000-default.conf
|
||||
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
||||
RUN a2ensite symfony.conf
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
USER www-data
|
||||
RUN composer install -a --no-dev && \
|
||||
composer clear-cache
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
RUN composer install -a --no-dev && composer clear-cache
|
||||
RUN yarn install && yarn build && yarn cache clean
|
||||
RUN php bin/console --env=prod ckeditor:install --clear=skip
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
# Use demo env to output logs to stdout
|
||||
ENV APP_ENV=demo
|
||||
ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
|
||||
|
||||
USER root
|
||||
|
||||
# Replace the php version placeholder in the entry point, with our php version
|
||||
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh
|
||||
|
||||
# Copy entrypoint and apache2-foreground to /usr/local/bin and make it executable
|
||||
RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
|
||||
install ./.docker/apache2-foreground /usr/local/bin
|
||||
ENTRYPOINT ["partdb-entrypoint.sh"]
|
||||
CMD ["apache2-foreground"]
|
||||
|
||||
# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
|
||||
STOPSIGNAL SIGWINCH
|
||||
|
||||
EXPOSE 80
|
||||
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
||||
EXPOSE 80
|
||||
@@ -1,121 +0,0 @@
|
||||
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl \
|
||||
ca-certificates \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
file \
|
||||
acl \
|
||||
git \
|
||||
gettext \
|
||||
gnupg \
|
||||
zip \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
RUN set -eux; \
|
||||
# Prepare keyrings directory
|
||||
mkdir -p /etc/apt/keyrings; \
|
||||
\
|
||||
# Import Yarn GPG key
|
||||
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg \
|
||||
| tee /etc/apt/keyrings/yarn.gpg >/dev/null; \
|
||||
chmod 644 /etc/apt/keyrings/yarn.gpg; \
|
||||
\
|
||||
# Add Yarn repo with signed-by
|
||||
echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \
|
||||
| tee /etc/apt/sources.list.d/yarn.list; \
|
||||
\
|
||||
# Run NodeSource setup script (unchanged)
|
||||
curl -sL https://deb.nodesource.com/setup_22.x | bash -; \
|
||||
\
|
||||
# Install Node.js + Yarn
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
nodejs \
|
||||
yarn; \
|
||||
\
|
||||
# Cleanup
|
||||
apt-get -y autoremove; \
|
||||
apt-get clean autoclean; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Install PHP
|
||||
RUN set -eux; \
|
||||
install-php-extensions \
|
||||
@composer \
|
||||
apcu \
|
||||
intl \
|
||||
opcache \
|
||||
zip \
|
||||
pdo_mysql \
|
||||
pdo_sqlite \
|
||||
pdo_pgsql \
|
||||
gd \
|
||||
bcmath \
|
||||
xsl \
|
||||
;
|
||||
|
||||
# Copy config files for php and caddy
|
||||
COPY --link .docker/frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
|
||||
COPY --chmod=755 .docker/frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
|
||||
COPY --link .docker/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --link .docker/frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
|
||||
COPY --link .docker/frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
|
||||
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Install composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||
#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Create workdir and set permissions if directory does not exists
|
||||
WORKDIR /app
|
||||
|
||||
# prevent the reinstallation of vendors at every changes in the source code
|
||||
COPY --link composer.* symfony.* ./
|
||||
RUN set -eux; \
|
||||
composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress
|
||||
|
||||
# copy sources
|
||||
COPY --link . ./
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
RUN set -eux; \
|
||||
mkdir -p var/cache var/log; \
|
||||
composer dump-autoload --classmap-authoritative --no-dev; \
|
||||
composer dump-env prod; \
|
||||
composer run-script --no-dev post-install-cmd; \
|
||||
chmod +x bin/console; sync;
|
||||
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
|
||||
|
||||
USER root
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint"]
|
||||
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||
|
||||
# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
|
||||
STOPSIGNAL SIGWINCH
|
||||
|
||||
VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
EXPOSE 443/udp
|
||||
EXPOSE 2019
|
||||
91
Makefile
91
Makefile
@@ -1,91 +0,0 @@
|
||||
# PartDB Makefile for Test Environment Management
|
||||
|
||||
.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \
|
||||
test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \
|
||||
section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset
|
||||
|
||||
# Default target
|
||||
help: ## Show this help
|
||||
@awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
# Dependencies
|
||||
deps-install: ## Install PHP dependencies with unlimited memory
|
||||
@echo "📦 Installing PHP dependencies..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 composer install
|
||||
yarn install
|
||||
@echo "✅ Dependencies installed"
|
||||
|
||||
# Complete test environment setup
|
||||
test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures)
|
||||
@echo "✅ Test environment setup complete!"
|
||||
|
||||
# Clean test environment
|
||||
test-clean: ## Clean test cache and database files
|
||||
@echo "🧹 Cleaning test environment..."
|
||||
rm -rf var/cache/test
|
||||
rm -f var/app_test.db
|
||||
@echo "✅ Test environment cleaned"
|
||||
|
||||
# Create test database
|
||||
test-db-create: ## Create test database (if not exists)
|
||||
@echo "🗄️ Creating test database..."
|
||||
-php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||
|
||||
# Run database migrations for test environment
|
||||
test-db-migrate: ## Run database migrations for test environment
|
||||
@echo "🔄 Running database migrations..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test
|
||||
|
||||
# Clear test cache
|
||||
test-cache-clear: ## Clear test cache
|
||||
@echo "🗑️ Clearing test cache..."
|
||||
rm -rf var/cache/test
|
||||
@echo "✅ Test cache cleared"
|
||||
|
||||
# Load test fixtures
|
||||
test-fixtures: ## Load test fixtures
|
||||
@echo "📦 Loading test fixtures..."
|
||||
php bin/console partdb:fixtures:load -n --env test
|
||||
|
||||
# Run PHPUnit tests
|
||||
test-run: ## Run PHPUnit tests
|
||||
@echo "🧪 Running tests..."
|
||||
php bin/phpunit
|
||||
|
||||
# Quick test reset (clean + migrate + fixtures, skip DB creation)
|
||||
test-reset: test-cache-clear test-db-migrate test-fixtures
|
||||
@echo "✅ Test environment reset complete!"
|
||||
|
||||
test-typecheck: ## Run static analysis (PHPStan)
|
||||
@echo "🧪 Running type checks..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 composer phpstan
|
||||
|
||||
# Development helpers
|
||||
dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup)
|
||||
@echo "✅ Development environment setup complete!"
|
||||
|
||||
dev-clean: ## Clean development cache and database files
|
||||
@echo "🧹 Cleaning development environment..."
|
||||
rm -rf var/cache/dev
|
||||
rm -f var/app_dev.db
|
||||
@echo "✅ Development environment cleaned"
|
||||
|
||||
dev-db-create: ## Create development database (if not exists)
|
||||
@echo "🗄️ Creating development database..."
|
||||
-php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||
|
||||
dev-db-migrate: ## Run database migrations for development environment
|
||||
@echo "🔄 Running database migrations..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev
|
||||
|
||||
dev-cache-clear: ## Clear development cache
|
||||
@echo "🗑️ Clearing development cache..."
|
||||
rm -rf var/cache/dev
|
||||
@echo "✅ Development cache cleared"
|
||||
|
||||
dev-warmup: ## Warm up development cache
|
||||
@echo "🔥 Warming up development cache..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n
|
||||
|
||||
dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate)
|
||||
@echo "✅ Development environment reset complete!"
|
||||
188
README.md
188
README.md
@@ -1,166 +1,126 @@
|
||||
[](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master)
|
||||

|
||||

|
||||
[](https://codecov.io/gh/Part-DB/Part-DB-server)
|
||||
[](https://codecov.io/gh/Part-DB/Part-DB-symfony)
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
[](https://part-db.crowdin.com/part-db)
|
||||
|
||||
**[Documentation](https://docs.part-db.de/)** | **[Demo](https://demo.part-db.de/)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)**
|
||||
|
||||
# Part-DB
|
||||
|
||||
Part-DB is an Open-Source inventory management system for your electronic components.
|
||||
Part-DB is an Open-Source inventory managment system for your electronic components.
|
||||
It is installed on a web server and so can be accessed with any browser without the need to install additional software.
|
||||
|
||||
The version in this repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB)
|
||||
(Version < 1.0) based on a modern framework and is the recommended version to use.
|
||||
The version in this Repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB) (Version < 1.0) based on a modern framework.
|
||||
In the moment it lacks some features from the old Part-DB and the testing and documentation is not finished.
|
||||
Part-DB is in undergoing development, that means features and appeareance can change, and the software can contain bugs.
|
||||
|
||||
If you find a bug, please open an [Issue on GitHub,](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed
|
||||
for everybody.
|
||||
If you find a bug, please open an [Issue on Github](https://github.com/Part-DB/Part-DB-symfony/issues) so it can be fixed for everybody.
|
||||
|
||||
## Demo
|
||||
|
||||
If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance.
|
||||
(Or this link for the [German Version](https://demo.part-db.de/de/)).
|
||||
If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance.
|
||||
(Or this link for the [German Version](https://part-db.herokuapp.com/de/)).
|
||||
|
||||
You can log in with username: *user* and password: *user*.
|
||||
|
||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and
|
||||
may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
the page
|
||||
Every change to the master branch gets automatically deployed, so it represents the currenct development progress and is
|
||||
maybe not completly stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page
|
||||
for the first time.
|
||||
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/part_info.png">
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/parts_list.png">
|
||||
|
||||
## Features
|
||||
As this version of Part-DB is under development, some of the features listed below is not existing yet and the
|
||||
list of the features could change in the future. Features that are not working yet are marked with a star (*).
|
||||
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer,
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
|
||||
like datasheets or pictures with the parts.
|
||||
* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese)
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
|
||||
Password reset via email can be set up.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service
|
||||
like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system for parts and data structure. BOM import for projects from KiCAD is supported.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build
|
||||
this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older
|
||||
versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface.
|
||||
* MySQL, SQLite and PostgreSQL are supported as database backends
|
||||
* Support for rich text descriptions and comments in parts
|
||||
* Support for multiple currencies and automatic update of exchange rates supported
|
||||
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
|
||||
* Automatic thumbnail generation for pictures
|
||||
* Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and
|
||||
prices for parts
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
* Inventory managment of your electronic parts. Each part can be assigned to a category, footprint, manufacturer
|
||||
and multiple store locations and price informations. Parts can be grouped using tags. Support for file attachments like datasheets.
|
||||
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental))
|
||||
* Barcodes/Labels generator for parts and storage locations
|
||||
* User system with groups and detailed permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and U2F keys) and can be enforced. Password reset via email can be setuped.
|
||||
* Import/Export system (*)
|
||||
* Project managment: Parts can be assigned to projects to manage how often a project can be build. (*)
|
||||
* Order managment: Collect parts that should be ordered during the next order on your distributor and automatically add
|
||||
it to your instock, when they arrive. (*)
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
* PartKeepr import (*)
|
||||
* MySQL and SQLite (experimental) supported as database backends
|
||||
|
||||
With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or maker spaces, where many users should have (controlled) access to the shared inventory.
|
||||
With this features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
|
||||
Part-DB is also used by small companies and universities for managing their inventory.
|
||||
|
||||
## Requirements
|
||||
|
||||
* A **web server** (like Apache2 or nginx) that is capable of
|
||||
running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 8.2**
|
||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
|
||||
* Shell access to your server is highly recommended!
|
||||
* For building the client-side assets **yarn** and **nodejs** (>= 20.0) is needed.
|
||||
|
||||
* A **web server** (like Apache2 or nginx) that is capable of running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 7.2.5**
|
||||
* A **MySQL** (at least 5.6.5) /**MariaDB** (at least 10.0.1) database server if you do not want to use SQLite.
|
||||
* Shell access to your server is highly suggested!
|
||||
* For building the client side assets **yarn** and **nodejs** is needed.
|
||||
|
||||
## Installation
|
||||
**Caution:** It is possible to upgrade the old Part-DB databases.
|
||||
Anyhow, the migrations that will be made, are not compatible with the old Part-DB versions, so you must not use the old Part-DB versions with the new database, or the DB could become corrupted.
|
||||
Also after the migration it is not possible to go back to the old database scheme, so make sure to make a backup of your database beforehand.
|
||||
See [UPGRADE](UPGRADE.md) for more infos.
|
||||
|
||||
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please
|
||||
read [this](https://docs.part-db.de/upgrade_legacy.html) first.
|
||||
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set
|
||||
up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
|
||||
|
||||
**Below you find a very rough outline of the installation process, see [here](https://docs.part-db.de/installation/)
|
||||
for a detailed guide on how to install Part-DB.**
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to setup Part-DB via docker is described [here](https://github.com/Part-DB/Part-DB-symfony/blob/master/docs/docker/docker-install.md).
|
||||
|
||||
1. Copy or clone this repository into a folder on your server.
|
||||
2. Configure your webserver to serve from the `public/` folder.
|
||||
See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional information.
|
||||
2. Configure your webserver to serve from the `public/` folder. See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional informations.
|
||||
3. Copy the global config file `cp .env .env.local` and edit `.env.local`:
|
||||
* Change the line `APP_ENV=dev` to `APP_ENV=prod`
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (
|
||||
see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url))
|
||||
for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so
|
||||
choose wisely.
|
||||
4. Install composer dependencies and generate autoload files: `composer install -o --no-dev`
|
||||
5. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and
|
||||
follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**:
|
||||
These steps tamper with your database and could potentially destroy it. So make sure to make a backup of your
|
||||
database.
|
||||
8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations after
|
||||
you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be
|
||||
changed after creating price information).
|
||||
Run `php bin/console cache:clear` when you change something.
|
||||
9. Access Part-DB in your browser (under the URL you put it) and log in with user *admin*. Password is the one outputted
|
||||
during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create
|
||||
new users with the admin user and start using Part-DB.
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so choose wisely.
|
||||
4. Install composer dependencies and generate autoload files: `composer install --no-dev`
|
||||
5. If you have put Part-DB into a sub-directory on your server (like `part-db/`), you have to edit the file
|
||||
`webpack.config.js` and uncomment the lines (remove the `//` before the lines) `.setPublicPath('/part-db/build')` (line 43) and
|
||||
`.setManifestKeyPrefix('build/')` (line 44). You have to replace `/part-db` with your own path on line 44.
|
||||
6. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
7. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
8. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database.
|
||||
9. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be changed after creating price informations).
|
||||
Run `php bin/console cache:clear` when you changed something.
|
||||
10. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create new users with the admin user and start using Part-DB.
|
||||
|
||||
When you want to upgrade to a newer version, then just copy the new files into the folder
|
||||
and repeat the steps 4. to 7.
|
||||
|
||||
Normally a random password is generated when the admin user is created during initial database creation,
|
||||
however, you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
|
||||
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
|
||||
See [here](https://docs.part-db.de/configuration.html) for more information.
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
If you are using a reverse proxy, you have to ensure that the proxies set the `X-Forwarded-*` headers correctly, or you
|
||||
will get HTTP/HTTPS mixup and wrong hostnames.
|
||||
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set
|
||||
the `TRUSTED_PROXIES` env variable to match your reverse proxy's IP address (or IP block). You can do this in
|
||||
your `.env.local` or (when using docker) in your `docker-compose.yml` file.
|
||||
## Useful console commands
|
||||
Part-DB provides some command consoles which can be invoked by `php bin/console [command]`. You can get help for every command with the parameter `--help`.
|
||||
Useful commands are:
|
||||
* `php bin/console app:set-password [username]`: Sets a new password for the user with the given username. Useful if you forget the password to your Part-DB instance.
|
||||
* `php bin/console app:show-logs`: Show last activty log on console. Use `-x` options, to include extra column.
|
||||
* `php bin/console app:update-exchange-rates`: Update the exchange rates of your currencies from internet. Setup this to be run in a cronjob to always get up-to-date exchange rates.
|
||||
If you dont use Euro as base currency, you have to setup an fixer.io API key in `.env.local`.
|
||||
* `php bin/console app:clean-attachments`: Removes all unused files (files without an associated attachment) in attachments folder.
|
||||
Normally Part-DB should be able to delete the attachment file, if you delete the attachment, but if you have some obsolete files left over from legacy Part-DB you can remove them safely with this command.
|
||||
* `php bin/console cache:clear`: Remove and rebuild all caches. If you encounter some weird issues in Part-DB, it maybe helps to run this command.
|
||||
* `php bin/console doctrine:migrations:up-to-date`: Check if your database is up to date.
|
||||
|
||||
* Normally a random password is generated when the admin user is created during inital database creation.
|
||||
You can set the inital admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
## Donate for development
|
||||
|
||||
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
|
||||
There you will find various methods to support development on a monthly or a one-time base.
|
||||
There you will find various methods to support development on a monthly or a one time base.
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
Javascript
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 4](https://getbootstrap.com/) and [Fontawesome](https://fontawesome.com/) : Used for the webpages
|
||||
|
||||
## Authors
|
||||
* **Jan Böhmer** - *Inital work* - [Github](https://github.com/jbtronics/)
|
||||
|
||||
* **Jan Böhmer** - *Initial work* - [GitHub](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in
|
||||
this project.
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-symfony/graphs/contributors) who participated in this project.
|
||||
|
||||
Based on the original Part-DB by Christoph Lechner and K. Jacobs
|
||||
|
||||
## License
|
||||
|
||||
Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later).
|
||||
This mostly means that you can use Part-DB for whatever you want (even use it commercially)
|
||||
as long as you publish the source code for every change you make under the AGPL, too.
|
||||
|
||||
See [LICENSE](https://github.com/Part-DB/Part-DB-server/blob/master/LICENSE) for more information.
|
||||
See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) for more informations.
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the most recent release of Part-DB is supported.
|
||||
|
||||
(Unreleased) development versions are not supported and might contain security vulnerabilities, which might not be
|
||||
fixed before the next release. However, if you find a security vulnerability in a development version, please report it
|
||||
This is an alpha version without releases yet. Only the newest version from the master branch is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a security vulnerability, report a vulnerability in the [security section of GitHub](https://github.com/Part-DB/Part-DB-server/security/advisories) or contact the maintainer directly (Email: security@part-db.de)
|
||||
If you find an vulnerability contact the maintainer directly (Email: security@part-db.de).
|
||||
|
||||
35
UPGRADE.md
Normal file
35
UPGRADE.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Upgrade from legacy Versions (Part-DB 0.5/0.6)
|
||||
|
||||
This document describes how to upgrade from an old Part-DB version (Part-DB 0.6 or older) to Part-DB 1.0.
|
||||
The instructions on how to install the new version or upgrade from Part-DB 1.0 to a newer version, see
|
||||
[README](README.md).
|
||||
|
||||
## Breaking Changes
|
||||
Please note that there are some breaking changes with the new version.
|
||||
It is tried to keep the breaking changes as small as possible, so they should not have much impact for the most users:
|
||||
* PHP 7.2.5 is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0).
|
||||
PHP 7.2.5 (or newer) is shipped by all current major Linux distros now (and can be installed by third party sources on others),
|
||||
Releases are available for Windows too, so almost everybody should be able to use PHP 7.2.5
|
||||
* Console access highly required. The installation of composer and frontend dependencies require console access, also
|
||||
the managment commands are using CLI, so you should have console access on your server.
|
||||
* Markdown/HTML is now used instead of BBCode for rich text in description and command fields.
|
||||
It is possible to migrate your existing BBCode to Markdown via `php bin/console php bin/console app:convert-bbcode`.
|
||||
* Server exceptions are not logged to Event log anymore. For security reasons (exceptions can contain sensitive informations)
|
||||
exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it.
|
||||
* Profile labels are now saved in Database (before they were saved in a seperate JSON file). The profiles of legacy Part-DB versions can not be imported into new Part-DB 1.0
|
||||
* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also some placeholders has changed.
|
||||
|
||||
## Upgrade process
|
||||
1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (in the moment Part-DB 0.5.8), like
|
||||
described in the old Part-DB's repository.
|
||||
2. Make a backup of your database. If somethings goes wrong during migration, you can use this backup to start over.
|
||||
3. Setup the new Part-DB like described on [README](README.md) in section Installation. In `.env.local` enter the URL
|
||||
to your old Part-DB database.
|
||||
4. Run `php bin/console app:convert-bbcode` to convert the BBCode used in comments and part description to the newly used markdown.
|
||||
5. Copy the content of `data/media` from the old Part-DB version into `public/media` in the new version.
|
||||
6. Run 'php bin/console cache:clear'
|
||||
|
||||
You should now be able to access Part-DB and log in using your old credentials.
|
||||
|
||||
**It is not possible to access the database using the old Part-DB version.
|
||||
If you do so, this could damage your database.** Therefore it is recommended to remove the old Part-DB version.
|
||||
12
assets/app.js
Normal file
12
assets/app.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
// start the Stimulus application
|
||||
import './bootstrap';
|
||||
@@ -4,7 +4,8 @@ import { startStimulusApp } from '@symfony/stimulus-bridge';
|
||||
export const app = startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.[jt]sx?$/
|
||||
/\.(j|t)sx?$/
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
File diff suppressed because one or more lines are too long
@@ -1,250 +0,0 @@
|
||||
import {ClassicEditor} from 'ckeditor5'
|
||||
import {Alignment} from 'ckeditor5';
|
||||
import {Autoformat} from 'ckeditor5';
|
||||
import {Base64UploadAdapter} from 'ckeditor5';
|
||||
import {BlockQuote} from 'ckeditor5';
|
||||
import {Bold} from 'ckeditor5';
|
||||
import {Code} from 'ckeditor5';
|
||||
import {CodeBlock} from 'ckeditor5';
|
||||
import {Essentials} from 'ckeditor5';
|
||||
import {FindAndReplace} from 'ckeditor5';
|
||||
import {FontBackgroundColor} from 'ckeditor5';
|
||||
import {FontColor} from 'ckeditor5';
|
||||
import {FontFamily} from 'ckeditor5';
|
||||
import {FontSize} from 'ckeditor5';
|
||||
import {GeneralHtmlSupport} from 'ckeditor5';
|
||||
import {Heading} from 'ckeditor5';
|
||||
import {Highlight} from 'ckeditor5';
|
||||
import {HorizontalLine} from 'ckeditor5';
|
||||
import {HtmlComment} from 'ckeditor5';
|
||||
import {HtmlEmbed} from 'ckeditor5';
|
||||
import {Image} from 'ckeditor5';
|
||||
import {ImageResize} from 'ckeditor5';
|
||||
import {ImageStyle} from 'ckeditor5';
|
||||
import {ImageToolbar} from 'ckeditor5';
|
||||
import {ImageUpload} from 'ckeditor5';
|
||||
import {Indent} from 'ckeditor5';
|
||||
import {IndentBlock} from 'ckeditor5';
|
||||
import {Italic} from 'ckeditor5';
|
||||
import {Link} from 'ckeditor5';
|
||||
import {LinkImage} from 'ckeditor5';
|
||||
import {List} from 'ckeditor5';
|
||||
import {ListProperties} from 'ckeditor5';
|
||||
import {Markdown} from 'ckeditor5';
|
||||
import {MediaEmbed} from 'ckeditor5';
|
||||
import {MediaEmbedToolbar} from 'ckeditor5';
|
||||
import {Paragraph} from 'ckeditor5';
|
||||
import {PasteFromOffice} from 'ckeditor5';
|
||||
import {RemoveFormat} from 'ckeditor5';
|
||||
import {SourceEditing} from 'ckeditor5';
|
||||
import {SpecialCharacters} from 'ckeditor5';
|
||||
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||
import {SpecialCharactersText} from 'ckeditor5';
|
||||
import {Strikethrough} from 'ckeditor5';
|
||||
import {Subscript} from 'ckeditor5';
|
||||
import {Superscript} from 'ckeditor5';
|
||||
import {Table} from 'ckeditor5';
|
||||
import {TableCaption} from 'ckeditor5';
|
||||
import {TableCellProperties} from 'ckeditor5';
|
||||
import {TableColumnResize} from 'ckeditor5';
|
||||
import {TableProperties} from 'ckeditor5';
|
||||
import {TableToolbar} from 'ckeditor5';
|
||||
import {Underline} from 'ckeditor5';
|
||||
import {WordCount} from 'ckeditor5';
|
||||
import {EditorWatchdog} from 'ckeditor5';
|
||||
import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel";
|
||||
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||
|
||||
class Editor extends ClassicEditor {}
|
||||
|
||||
// Plugins to include in the build.
|
||||
Editor.builtinPlugins = [
|
||||
Alignment,
|
||||
Autoformat,
|
||||
Base64UploadAdapter,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Code,
|
||||
CodeBlock,
|
||||
Essentials,
|
||||
FindAndReplace,
|
||||
FontBackgroundColor,
|
||||
FontColor,
|
||||
FontFamily,
|
||||
FontSize,
|
||||
GeneralHtmlSupport,
|
||||
Heading,
|
||||
Highlight,
|
||||
HorizontalLine,
|
||||
HtmlComment,
|
||||
HtmlEmbed,
|
||||
Image,
|
||||
ImageResize,
|
||||
ImageStyle,
|
||||
ImageToolbar,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
IndentBlock,
|
||||
Italic,
|
||||
Link,
|
||||
LinkImage,
|
||||
List,
|
||||
ListProperties,
|
||||
MediaEmbed,
|
||||
MediaEmbedToolbar,
|
||||
Paragraph,
|
||||
PasteFromOffice,
|
||||
RemoveFormat,
|
||||
SourceEditing,
|
||||
SpecialCharacters,
|
||||
SpecialCharactersArrows,
|
||||
SpecialCharactersCurrency,
|
||||
SpecialCharactersEssentials,
|
||||
SpecialCharactersLatin,
|
||||
SpecialCharactersMathematical,
|
||||
SpecialCharactersText,
|
||||
Strikethrough,
|
||||
Subscript,
|
||||
Superscript,
|
||||
Table,
|
||||
TableCaption,
|
||||
TableCellProperties,
|
||||
TableColumnResize,
|
||||
TableProperties,
|
||||
TableToolbar,
|
||||
Underline,
|
||||
WordCount,
|
||||
|
||||
PartDBLabel,
|
||||
SpecialCharactersGreek
|
||||
];
|
||||
|
||||
// Editor configuration.
|
||||
Editor.defaultConfig = {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading',
|
||||
'alignment',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'subscript',
|
||||
'superscript',
|
||||
'removeFormat',
|
||||
'highlight',
|
||||
'|',
|
||||
'fontBackgroundColor',
|
||||
'fontColor',
|
||||
'fontSize',
|
||||
'|',
|
||||
'fontFamily',
|
||||
'link',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'outdent',
|
||||
'indent',
|
||||
'|',
|
||||
'specialCharacters',
|
||||
'horizontalLine',
|
||||
'|',
|
||||
'imageUpload',
|
||||
'blockQuote',
|
||||
'insertTable',
|
||||
'mediaEmbed',
|
||||
'code',
|
||||
'codeBlock',
|
||||
'htmlEmbed',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'findAndReplace',
|
||||
'sourceEditing',
|
||||
'|',
|
||||
'partdb_label',
|
||||
],
|
||||
shouldNotGroupWhenFull: true
|
||||
},
|
||||
language: 'en',
|
||||
fontFamily: {
|
||||
options: [
|
||||
'default',
|
||||
'DejaVu Sans Mono, monospace',
|
||||
'DejaVu Sans, sans-serif',
|
||||
'DejaVu Serif, serif',
|
||||
'Helvetica, Arial, sans-serif',
|
||||
'Times New Roman, Times, serif',
|
||||
'Courier New, Courier, monospace',
|
||||
'Unifont, monospace',
|
||||
],
|
||||
supportAllValues: true
|
||||
},
|
||||
'fontSize': {
|
||||
options: [
|
||||
'default',
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
],
|
||||
supportAllValues: true
|
||||
},
|
||||
// Allow all HTML features for our labels
|
||||
htmlSupport: {
|
||||
allow: [
|
||||
{
|
||||
name: /.*/,
|
||||
attributes: true,
|
||||
classes: true,
|
||||
styles: true
|
||||
}
|
||||
],
|
||||
disallow: [
|
||||
//Some rudimentary protection against XSS, even if it is not really needed as this is only parsed by DOMHTML which does not support any kind of script execution.
|
||||
{
|
||||
name: /^(head|body|html|script)$/i,
|
||||
},
|
||||
{
|
||||
name: /.*/,
|
||||
attributes: /^on.*/i
|
||||
}
|
||||
]
|
||||
},
|
||||
image: {
|
||||
toolbar: [
|
||||
'imageTextAlternative',
|
||||
'imageStyle:inline',
|
||||
'imageStyle:block',
|
||||
'imageStyle:side',
|
||||
'linkImage'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells',
|
||||
'tableCellProperties',
|
||||
'tableProperties'
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
export default { Editor, EditorWatchdog };
|
||||
@@ -1,198 +0,0 @@
|
||||
/**
|
||||
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
import {ClassicEditor} from 'ckeditor5';
|
||||
import {Alignment} from 'ckeditor5';
|
||||
import {Autoformat} from 'ckeditor5';
|
||||
import {Base64UploadAdapter} from 'ckeditor5';
|
||||
import {BlockQuote} from 'ckeditor5';
|
||||
import {Bold} from 'ckeditor5';
|
||||
import {Code} from 'ckeditor5';
|
||||
import {CodeBlock} from 'ckeditor5';
|
||||
import {Essentials} from 'ckeditor5';
|
||||
import {FindAndReplace} from 'ckeditor5';
|
||||
import {FontBackgroundColor} from 'ckeditor5';
|
||||
import {FontColor} from 'ckeditor5';
|
||||
import {FontFamily} from 'ckeditor5';
|
||||
import {FontSize} from 'ckeditor5';
|
||||
import {GeneralHtmlSupport} from 'ckeditor5';
|
||||
import {Heading} from 'ckeditor5';
|
||||
import {Highlight} from 'ckeditor5';
|
||||
import {HorizontalLine} from 'ckeditor5';
|
||||
import {HtmlComment} from 'ckeditor5';
|
||||
import {HtmlEmbed} from 'ckeditor5';
|
||||
import {Image} from 'ckeditor5';
|
||||
import {ImageResize} from 'ckeditor5';
|
||||
import {ImageStyle} from 'ckeditor5';
|
||||
import {ImageToolbar} from 'ckeditor5';
|
||||
import {ImageUpload} from 'ckeditor5';
|
||||
import {Indent} from 'ckeditor5';
|
||||
import {IndentBlock} from 'ckeditor5';
|
||||
import {Italic} from 'ckeditor5';
|
||||
import {Link} from 'ckeditor5';
|
||||
import {LinkImage} from 'ckeditor5';
|
||||
import {List} from 'ckeditor5';
|
||||
import {ListProperties} from 'ckeditor5';
|
||||
import {Markdown} from 'ckeditor5';
|
||||
import {MediaEmbed} from 'ckeditor5';
|
||||
import {MediaEmbedToolbar} from 'ckeditor5';
|
||||
import {Paragraph} from 'ckeditor5';
|
||||
import {PasteFromOffice} from 'ckeditor5';
|
||||
import {RemoveFormat} from 'ckeditor5';
|
||||
import {SourceEditing} from 'ckeditor5';
|
||||
import {SpecialCharacters} from 'ckeditor5';
|
||||
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||
import {SpecialCharactersText} from 'ckeditor5';
|
||||
import {Strikethrough} from 'ckeditor5';
|
||||
import {Subscript} from 'ckeditor5';
|
||||
import {Superscript} from 'ckeditor5';
|
||||
import {Table} from 'ckeditor5';
|
||||
import {TableCaption} from 'ckeditor5';
|
||||
import {TableCellProperties} from 'ckeditor5';
|
||||
import {TableColumnResize} from 'ckeditor5';
|
||||
import {TableProperties} from 'ckeditor5';
|
||||
import {TableToolbar} from 'ckeditor5';
|
||||
import {Underline} from 'ckeditor5';
|
||||
import {WordCount} from 'ckeditor5';
|
||||
import {EditorWatchdog} from 'ckeditor5';
|
||||
import {TodoList} from 'ckeditor5';
|
||||
|
||||
import ExtendedMarkdown from "./plugins/extendedMarkdown.js";
|
||||
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||
import {Mention, Emoji} from "ckeditor5";
|
||||
|
||||
class Editor extends ClassicEditor {}
|
||||
|
||||
// Plugins to include in the build.
|
||||
Editor.builtinPlugins = [
|
||||
Autoformat,
|
||||
Base64UploadAdapter,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Code,
|
||||
CodeBlock,
|
||||
Essentials,
|
||||
FindAndReplace,
|
||||
FontBackgroundColor,
|
||||
FontColor,
|
||||
FontSize,
|
||||
GeneralHtmlSupport,
|
||||
Heading,
|
||||
Highlight,
|
||||
HorizontalLine,
|
||||
Image,
|
||||
ImageResize,
|
||||
ImageStyle,
|
||||
ImageToolbar,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
IndentBlock,
|
||||
Italic,
|
||||
Link,
|
||||
LinkImage,
|
||||
List,
|
||||
//MediaEmbed,
|
||||
//MediaEmbedToolbar,
|
||||
Paragraph,
|
||||
PasteFromOffice,
|
||||
RemoveFormat,
|
||||
SourceEditing,
|
||||
SpecialCharacters,
|
||||
SpecialCharactersArrows,
|
||||
SpecialCharactersCurrency,
|
||||
SpecialCharactersEssentials,
|
||||
SpecialCharactersLatin,
|
||||
SpecialCharactersMathematical,
|
||||
SpecialCharactersText,
|
||||
Strikethrough,
|
||||
Subscript,
|
||||
Superscript,
|
||||
Table,
|
||||
TableProperties,
|
||||
TableToolbar,
|
||||
Underline,
|
||||
TodoList,
|
||||
|
||||
Mention, Emoji,
|
||||
|
||||
//Our own extensions
|
||||
ExtendedMarkdown,
|
||||
SpecialCharactersGreek
|
||||
];
|
||||
|
||||
// Editor configuration.
|
||||
Editor.defaultConfig = {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'subscript',
|
||||
'superscript',
|
||||
'removeFormat',
|
||||
'highlight',
|
||||
'|',
|
||||
'fontBackgroundColor',
|
||||
'fontColor',
|
||||
'fontSize',
|
||||
'|',
|
||||
'link',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'outdent',
|
||||
'indent',
|
||||
'|',
|
||||
'specialCharacters',
|
||||
"emoji",
|
||||
'horizontalLine',
|
||||
'|',
|
||||
'imageUpload',
|
||||
'blockQuote',
|
||||
'insertTable',
|
||||
//'mediaEmbed',
|
||||
'code',
|
||||
'codeBlock',
|
||||
'todoList',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'findAndReplace',
|
||||
'sourceEditing',
|
||||
],
|
||||
shouldNotGroupWhenFull: true
|
||||
},
|
||||
language: 'en',
|
||||
image: {
|
||||
toolbar: [
|
||||
'imageTextAlternative',
|
||||
'imageStyle:inline',
|
||||
'imageStyle:block',
|
||||
'imageStyle:side',
|
||||
'linkImage'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells',
|
||||
'tableProperties'
|
||||
]
|
||||
},
|
||||
list: {
|
||||
properties: {
|
||||
styles: false,
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default { Editor, EditorWatchdog };
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
import {ClassicEditor} from 'ckeditor5';
|
||||
import {Autoformat} from 'ckeditor5';
|
||||
import {AutoLink} from 'ckeditor5';
|
||||
import {Bold} from 'ckeditor5';
|
||||
import {Code} from 'ckeditor5';
|
||||
import {Essentials} from 'ckeditor5';
|
||||
import {FindAndReplace} from 'ckeditor5';
|
||||
import {Highlight} from 'ckeditor5';
|
||||
import {Italic} from 'ckeditor5';
|
||||
import {Link} from 'ckeditor5';
|
||||
import {Paragraph} from 'ckeditor5';
|
||||
import {RemoveFormat} from 'ckeditor5';
|
||||
import {SourceEditing} from 'ckeditor5';
|
||||
import {SpecialCharacters} from 'ckeditor5';
|
||||
import {SpecialCharactersArrows} from 'ckeditor5';
|
||||
import {SpecialCharactersCurrency} from 'ckeditor5';
|
||||
import {SpecialCharactersEssentials} from 'ckeditor5';
|
||||
import {SpecialCharactersLatin} from 'ckeditor5';
|
||||
import {SpecialCharactersMathematical} from 'ckeditor5';
|
||||
import {SpecialCharactersText} from 'ckeditor5';
|
||||
import {Strikethrough} from 'ckeditor5';
|
||||
import {Subscript} from 'ckeditor5';
|
||||
import {Superscript} from 'ckeditor5';
|
||||
import {Underline} from 'ckeditor5';
|
||||
import {EditorWatchdog} from 'ckeditor5';
|
||||
import {Mention, Emoji} from "ckeditor5";
|
||||
|
||||
import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline";
|
||||
import SingleLinePlugin from "./plugins/singleLine";
|
||||
import SpecialCharactersGreek from "./plugins/special_characters_emoji";
|
||||
|
||||
class Editor extends ClassicEditor {}
|
||||
|
||||
// Plugins to include in the build.
|
||||
Editor.builtinPlugins = [
|
||||
Autoformat,
|
||||
AutoLink,
|
||||
Bold,
|
||||
Code,
|
||||
FindAndReplace,
|
||||
Highlight,
|
||||
Italic,
|
||||
Link,
|
||||
Paragraph,
|
||||
RemoveFormat,
|
||||
SourceEditing,
|
||||
SpecialCharacters,
|
||||
SpecialCharactersArrows,
|
||||
SpecialCharactersCurrency,
|
||||
SpecialCharactersEssentials,
|
||||
SpecialCharactersLatin,
|
||||
SpecialCharactersMathematical,
|
||||
SpecialCharactersText,
|
||||
Strikethrough,
|
||||
Subscript,
|
||||
Superscript,
|
||||
Underline,
|
||||
Essentials,
|
||||
|
||||
ExtendedMarkdownInline,
|
||||
SingleLinePlugin,
|
||||
SpecialCharactersGreek,
|
||||
Mention, Emoji
|
||||
];
|
||||
|
||||
// Editor configuration.
|
||||
Editor.defaultConfig = {
|
||||
toolbar: {
|
||||
items: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'subscript',
|
||||
'superscript',
|
||||
'removeFormat',
|
||||
'highlight',
|
||||
'|',
|
||||
'link',
|
||||
'code',
|
||||
'specialCharacters',
|
||||
'emoji',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'findAndReplace',
|
||||
'sourceEditing'
|
||||
]
|
||||
},
|
||||
language: 'en'
|
||||
};
|
||||
|
||||
export default { Editor, EditorWatchdog };
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.cke_placeholder {
|
||||
background: #ffff00;
|
||||
padding: 1px 2px;
|
||||
outline-offset: -2px;
|
||||
line-height: 1em;
|
||||
margin: 0 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.cke_placeholder::selection {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*.cke_overflow_dropdown {
|
||||
overflow: auto;
|
||||
height: 40vh;
|
||||
}*/
|
||||
|
||||
/** Make long editor dropdown panels scrollable */
|
||||
.ck-dropdown__panel {
|
||||
overflow: auto;
|
||||
max-height: 40vh;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import PartDBLabelUI from "./PartDBLabelUI";
|
||||
import PartDBLabelEditing from "./PartDBLabelEditing";
|
||||
|
||||
import "./PartDBLabel.css";
|
||||
|
||||
import {Plugin} from "ckeditor5";
|
||||
|
||||
export default class PartDBLabel extends Plugin {
|
||||
static get requires() {
|
||||
return [PartDBLabelUI, PartDBLabelEditing];
|
||||
}
|
||||
|
||||
static get pluginName() {
|
||||
return 'PartDBLabel';
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Command} from 'ckeditor5';
|
||||
|
||||
export default class PartDBLabelCommand extends Command {
|
||||
execute( { value } ) {
|
||||
const editor = this.editor;
|
||||
const selection = editor.model.document.selection;
|
||||
|
||||
editor.model.change( writer => {
|
||||
// Create a <placeholder> elment with the "name" attribute (and all the selection attributes)...
|
||||
const placeholder = writer.createElement( 'partdb_label', {
|
||||
...Object.fromEntries( selection.getAttributes() ),
|
||||
name: value
|
||||
} );
|
||||
|
||||
// ... and insert it into the document.
|
||||
editor.model.insertContent( placeholder );
|
||||
|
||||
// Put the selection on the inserted element.
|
||||
writer.setSelection( placeholder, 'on' );
|
||||
} );
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const model = this.editor.model;
|
||||
const selection = model.document.selection;
|
||||
|
||||
const isAllowed = model.schema.checkChild( selection.focus.parent, 'partdb_label' );
|
||||
|
||||
this.isEnabled = isAllowed;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Plugin} from 'ckeditor5';
|
||||
import PartDBLabelCommand from "./PartDBLabelCommand";
|
||||
|
||||
import { toWidget } from 'ckeditor5';
|
||||
import {Widget} from 'ckeditor5';
|
||||
|
||||
export default class PartDBLabelEditing extends Plugin {
|
||||
static get requires() { // ADDED
|
||||
return [ Widget ];
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
this._defineSchema();
|
||||
this._defineConverters();
|
||||
|
||||
this.editor.commands.add('partdb_label', new PartDBLabelCommand( this.editor ) );
|
||||
}
|
||||
|
||||
_defineSchema() {
|
||||
const schema = this.editor.model.schema;
|
||||
|
||||
schema.register('partdb_label', {
|
||||
// Allow wherever text is allowed:
|
||||
allowWhere: '$text',
|
||||
|
||||
// The placeholder will act as an inline node:
|
||||
isInline: true,
|
||||
|
||||
// The inline widget is self-contained so it cannot be split by the caret and can be selected:
|
||||
isObject: true,
|
||||
|
||||
allowAttributesOf: '$text',
|
||||
|
||||
allowAttributes: [ 'name' ]
|
||||
});
|
||||
}
|
||||
|
||||
_defineConverters() {
|
||||
const conversion = this.editor.conversion;
|
||||
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
name: 'span',
|
||||
classes: [ 'cke_placeholder' ]
|
||||
},
|
||||
model: ( viewElement, { writer: modelWriter } ) => {
|
||||
// Extract the "name" from "{name}".
|
||||
const name = viewElement.getChild( 0 ).data;
|
||||
|
||||
return modelWriter.createElement( 'partdb_label', { name } );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: 'partdb_label',
|
||||
view: ( modelItem, { writer: viewWriter } ) => {
|
||||
const widgetElement = createPlaceholderView( modelItem, viewWriter );
|
||||
|
||||
// Enable widget handling on a placeholder element inside the editing view.
|
||||
return toWidget( widgetElement, viewWriter );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: 'partdb_label',
|
||||
view: ( modelItem, { writer: viewWriter } ) => createPlaceholderView( modelItem, viewWriter )
|
||||
} );
|
||||
|
||||
// Helper method for both downcast converters.
|
||||
function createPlaceholderView( modelItem, viewWriter ) {
|
||||
const name = modelItem.getAttribute( 'name' );
|
||||
|
||||
const placeholderView = viewWriter.createContainerElement( 'span', {
|
||||
class: 'cke_placeholder'
|
||||
} );
|
||||
|
||||
// Insert the placeholder name (as a text).
|
||||
const innerText = viewWriter.createText( name );
|
||||
viewWriter.insert( viewWriter.createPositionAt( placeholderView, 0 ), innerText );
|
||||
|
||||
return placeholderView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Plugin} from 'ckeditor5';
|
||||
|
||||
require('./lang/de.js');
|
||||
require('./lang/en.js');
|
||||
|
||||
import { addListToDropdown, createDropdown } from 'ckeditor5';
|
||||
|
||||
import {Collection} from 'ckeditor5';
|
||||
import {UIModel} from 'ckeditor5';
|
||||
|
||||
export default class PartDBLabelUI extends Plugin {
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
const t = editor.t;
|
||||
|
||||
// The "placeholder" dropdown must be registered among the UI components of the editor
|
||||
// to be displayed in the toolbar.
|
||||
editor.ui.componentFactory.add( 'partdb_label', locale => {
|
||||
const dropdownView = createDropdown( locale );
|
||||
|
||||
// Populate the list in the dropdown with items.
|
||||
addListToDropdown( dropdownView, getDropdownItemsDefinitions(t) );
|
||||
|
||||
dropdownView.buttonView.set( {
|
||||
// The t() function helps localize the editor. All strings enclosed in t() can be
|
||||
// translated and change when the language of the editor changes.
|
||||
label: t( 'Label Placeholder' ),
|
||||
tooltip: true,
|
||||
withText: true
|
||||
} );
|
||||
|
||||
// Disable the placeholder button when the command is disabled.
|
||||
const command = editor.commands.get( 'partdb_label' );
|
||||
dropdownView.bind( 'isEnabled' ).to( command );
|
||||
|
||||
// Execute the command when the dropdown item is clicked (executed).
|
||||
this.listenTo( dropdownView, 'execute', evt => {
|
||||
editor.execute( 'partdb_label', { value: evt.source.commandParam } );
|
||||
editor.editing.view.focus();
|
||||
} );
|
||||
|
||||
return dropdownView;
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
const PLACEHOLDERS = [
|
||||
{
|
||||
label: 'Part',
|
||||
entries: [
|
||||
['[[ID]]', 'Database ID'],
|
||||
['[[NAME]]', 'Part name'],
|
||||
['[[CATEGORY]]', 'Category'],
|
||||
['[[CATEGORY_FULL]]', 'Category (Full path)'],
|
||||
['[[MANUFACTURER]]', 'Manufacturer'],
|
||||
['[[MANUFACTURER_FULL]]', 'Manufacturer (Full path)'],
|
||||
['[[FOOTPRINT]]', 'Footprint'],
|
||||
['[[FOOTPRINT_FULL]]', 'Footprint (Full path)'],
|
||||
['[[MASS]]', 'Mass'],
|
||||
['[[MPN]]', 'Manufacturer Product Number (MPN)'],
|
||||
['[[IPN]]', 'Internal Part Number (IPN)'],
|
||||
['[[TAGS]]', 'Tags'],
|
||||
['[[M_STATUS]]', 'Manufacturing status'],
|
||||
['[[DESCRIPTION]]', 'Description'],
|
||||
['[[DESCRIPTION_T]]', 'Description (plain text)'],
|
||||
['[[COMMENT]]', 'Comment'],
|
||||
['[[COMMENT_T]]', 'Comment (plain text)'],
|
||||
['[[LAST_MODIFIED]]', 'Last modified datetime'],
|
||||
['[[CREATION_DATE]]', 'Creation datetime'],
|
||||
['[[IPN_BARCODE_QR]]', 'IPN as QR code'],
|
||||
['[[IPN_BARCODE_C128]]', 'IPN as Code 128 barcode'],
|
||||
['[[IPN_BARCODE_C39]]', 'IPN as Code 39 barcode'],
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Part lot',
|
||||
entries: [
|
||||
['[[LOT_ID]]', 'Lot ID'],
|
||||
['[[LOT_NAME]]', 'Lot name'],
|
||||
['[[LOT_COMMENT]]', 'Lot comment'],
|
||||
['[[EXPIRATION_DATE]]', 'Lot expiration date'],
|
||||
['[[AMOUNT]]', 'Lot amount'],
|
||||
['[[LOCATION]]', 'Storage location'],
|
||||
['[[LOCATION_FULL]]', 'Storage location (Full path)'],
|
||||
['[[OWNER]]', 'Full name of the lot owner'],
|
||||
['[[OWNER_USERNAME]]', 'Username of the lot owner'],
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Storage location',
|
||||
entries: [
|
||||
['[[ID]]', 'Location ID'],
|
||||
['[[NAME]]', 'Name'],
|
||||
['[[FULL_PATH]]', 'Full path'],
|
||||
['[[PARENT]]', 'Parent name'],
|
||||
['[[PARENT_FULL_PATH]]', 'Parent full path'],
|
||||
['[[COMMENT]]', 'Comment'],
|
||||
['[[COMMENT_T]]', 'Comment (plain text)'],
|
||||
['[[LAST_MODIFIED]]', 'Last modified datetime'],
|
||||
['[[CREATION_DATE]]', 'Creation datetime'],
|
||||
['[[OWNER]]', 'Full name of the location owner'],
|
||||
['[[OWNER_USERNAME]]', 'Username of the location owner'],
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Barcodes',
|
||||
entries: [
|
||||
['[[1D_CONTENT]]', 'Content of the 1D barcodes (like Code 39)'],
|
||||
['[[2D_CONTENT]]', 'Content of the 2D barcodes (QR codes)'],
|
||||
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
||||
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
|
||||
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
|
||||
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
|
||||
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Globals',
|
||||
entries: [
|
||||
['[[USERNAME]]', 'Username'],
|
||||
['[[USERNAME_FULL]]', 'Username (including name)'],
|
||||
['[[DATETIME]]', 'Current datetime'],
|
||||
['[[DATE]]', 'Current date'],
|
||||
['[[TIME]]', 'Current time'],
|
||||
['[[INSTALL_NAME]]', 'Instance name'],
|
||||
['[[TYPE]]', 'Target type'],
|
||||
['[[INSTANCE_URL]]', 'URL of this Part-DB instance']
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
function getDropdownItemsDefinitions(t) {
|
||||
const itemDefinitions = new Collection();
|
||||
|
||||
let first = true;
|
||||
|
||||
for ( const group of PLACEHOLDERS) {
|
||||
|
||||
//Add group header
|
||||
|
||||
//Skip separator for first group
|
||||
if (!first) {
|
||||
|
||||
itemDefinitions.add({
|
||||
'type': 'separator',
|
||||
model: new UIModel( {
|
||||
withText: true,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
itemDefinitions.add({
|
||||
type: 'button',
|
||||
model: new UIModel( {
|
||||
label: t(group.label),
|
||||
withText: true,
|
||||
isEnabled: false,
|
||||
} )
|
||||
});
|
||||
|
||||
//Add group entries
|
||||
for ( const entry of group.entries) {
|
||||
const definition = {
|
||||
type: 'button',
|
||||
model: new UIModel( {
|
||||
commandParam: entry[0],
|
||||
label: t(entry[1]),
|
||||
tooltip: entry[0],
|
||||
withText: true
|
||||
} ),
|
||||
};
|
||||
|
||||
// Add the item definition to the collection.
|
||||
itemDefinitions.add( definition );
|
||||
}
|
||||
}
|
||||
|
||||
return itemDefinitions;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {add} from "ckeditor5";
|
||||
|
||||
add( "de", {
|
||||
'Label Placeholder': 'Label Platzhalter',
|
||||
'Part': 'Bauteil',
|
||||
|
||||
'Database ID': 'Datenbank ID',
|
||||
'Part name': 'Bauteilname',
|
||||
'Category': 'Kategorie',
|
||||
'Category (Full path)': 'Kategorie (Vollständiger Pfad)',
|
||||
'Manufacturer': 'Hersteller',
|
||||
'Manufacturer (Full path)': 'Hersteller (Vollständiger Pfad)',
|
||||
'Footprint': 'Footprint',
|
||||
'Footprint (Full path)': 'Footprint (Vollständiger Pfad)',
|
||||
'Mass': 'Gewicht',
|
||||
'Manufacturer Product Number (MPN)': 'Hersteller Produktnummer (MPN)',
|
||||
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
|
||||
'Tags': 'Tags',
|
||||
'Manufacturing status': 'Herstellungsstatus',
|
||||
'Description': 'Beschreibung',
|
||||
'Description (plain text)': 'Beschreibung (Nur-Text)',
|
||||
'Comment': 'Kommentar',
|
||||
'Comment (plain text)': 'Kommentar (Nur-Text)',
|
||||
'Last modified datetime': 'Zuletzt geändert',
|
||||
'Creation datetime': 'Erstellt',
|
||||
'IPN as QR code': 'IPN als QR Code',
|
||||
'IPN as Code 128 barcode': 'IPN als Code 128 Barcode',
|
||||
'IPN as Code 39 barcode': 'IPN als Code 39 Barcode',
|
||||
|
||||
'Lot ID': 'Lot ID',
|
||||
'Lot name': 'Lot Name',
|
||||
'Lot comment': 'Lot Kommentar',
|
||||
'Lot expiration date': 'Lot Ablaufdatum',
|
||||
'Lot amount': 'Lot Menge',
|
||||
'Storage location': 'Lagerort',
|
||||
'Storage location (Full path)': 'Lagerort (Vollständiger Pfad)',
|
||||
'Full name of the lot owner': 'Name des Besitzers des Lots',
|
||||
'Username of the lot owner': 'Benutzername des Besitzers des Lots',
|
||||
|
||||
|
||||
'Barcodes': 'Barcodes',
|
||||
'Content of the 1D barcodes (like Code 39)': 'Inhalt der 1D Barcodes (z.B. Code 39)',
|
||||
'Content of the 2D barcodes (QR codes)': 'Inhalt der 2D Barcodes (QR Codes)',
|
||||
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
|
||||
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
|
||||
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
|
||||
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
|
||||
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
|
||||
|
||||
'Location ID': 'Lagerort ID',
|
||||
'Name': 'Name',
|
||||
'Full path': 'Vollständiger Pfad',
|
||||
'Parent name': 'Name des Übergeordneten Elements',
|
||||
'Parent full path': 'Ganzer Pfad des Übergeordneten Elements',
|
||||
'Full name of the location owner': 'Name des Besitzers des Lagerorts',
|
||||
'Username of the location owner': 'Benutzername des Besitzers des Lagerorts',
|
||||
|
||||
'Username': 'Benutzername',
|
||||
'Username (including name)': 'Benutzername (inklusive Name)',
|
||||
'Current datetime': 'Aktuelle Datum/Zeit',
|
||||
'Current date': 'Aktuelles Datum',
|
||||
'Current time': 'Aktuelle Zeit',
|
||||
'Instance name': 'Instanzname',
|
||||
'Target type': 'Zieltyp',
|
||||
'URL of this Part-DB instance': 'URL dieser Part-DB Instanz',
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {add} from "ckeditor5";
|
||||
|
||||
add( "en", {
|
||||
'Label Placeholder': 'Label placeholder',
|
||||
'Part': 'Part',
|
||||
|
||||
'Database ID': 'Database ID',
|
||||
'Part name': 'Part name',
|
||||
'Category': 'Category',
|
||||
'Category (Full path)': 'Category (full path)',
|
||||
'Manufacturer': 'Manufacturer',
|
||||
'Manufacturer (Full path)': 'Manufacturer (full path)',
|
||||
'Footprint': 'Footprint',
|
||||
'Footprint (Full path)': 'Footprint (full path)',
|
||||
'Mass': 'Mass',
|
||||
'Manufacturer Product Number (MPN)': 'Manufacturer Product Number (MPN)',
|
||||
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
|
||||
'Tags': 'Tags',
|
||||
'Manufacturing status': 'Manufacturing status',
|
||||
'Description': 'Description',
|
||||
'Description (plain text)': 'Description (plain text)',
|
||||
'Comment': 'Comment',
|
||||
'Comment (plain text)': 'Comment (plain text)',
|
||||
'Last modified datetime': 'Last modified datetime',
|
||||
'Creation datetime': 'Creation datetime',
|
||||
'IPN as QR code': 'IPN as QR code',
|
||||
'IPN as Code 128 barcode': 'IPN as Code 128 barcode',
|
||||
'IPN as Code 39 barcode': 'IPN as Code 39 barcode',
|
||||
|
||||
'Lot ID': 'Lot ID',
|
||||
'Lot name': 'Lot name',
|
||||
'Lot comment': 'Lot comment',
|
||||
'Lot expiration date': 'Lot expiration date',
|
||||
'Lot amount': 'Lot amount',
|
||||
'Storage location': 'Storage location',
|
||||
'Storage location (Full path)': 'Storage location (full path)',
|
||||
'Full name of the lot owner': 'Full name of the lot owner',
|
||||
'Username of the lot owner': 'Username of the lot owner',
|
||||
|
||||
'Barcodes': 'Barcodes',
|
||||
'Content of the 1D barcodes (like Code 39)': 'Content of the 1D barcodes (like Code 39)',
|
||||
'Content of the 2D barcodes (QR codes)': 'Content of the 2D barcodes (QR codes)',
|
||||
'QR code linking to this element': 'QR code linking to this element',
|
||||
'Code 128 barcode linking to this element': 'Code 128 barcode linking to this element',
|
||||
'Code 39 barcode linking to this element': 'Code 39 barcode linking to this element',
|
||||
'Code 93 barcode linking to this element': 'Code 93 barcode linking to this element',
|
||||
'Datamatrix code linking to this element': 'Datamatrix code linking to this element',
|
||||
|
||||
'Location ID': 'Location ID',
|
||||
'Name': 'Name',
|
||||
'Full path': 'Full path',
|
||||
'Parent name': 'Parent name',
|
||||
'Parent full path': 'Parent full path',
|
||||
'Full name of the location owner': 'Full name of the location owner',
|
||||
'Username of the location owner': 'Username of the location owner',
|
||||
|
||||
'Username': 'Username',
|
||||
'Username (including name)': 'Username (including name)',
|
||||
'Current datetime': 'Current datetime',
|
||||
'Current date': 'Current date',
|
||||
'Current time': 'Current time',
|
||||
'Instance name': 'Instance name',
|
||||
'Target type': 'Target type',
|
||||
'URL of this Part-DB instance': 'URL of this Part-DB instance',
|
||||
} );
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Plugin, MarkdownGfmDataProcessor } from 'ckeditor5';
|
||||
|
||||
const ALLOWED_TAGS = [
|
||||
//Common elements
|
||||
'sup',
|
||||
'sub',
|
||||
'u',
|
||||
'kbd',
|
||||
'mark',
|
||||
'ins',
|
||||
'small',
|
||||
'abbr',
|
||||
'br',
|
||||
|
||||
//Block elements
|
||||
'span',
|
||||
'img',
|
||||
|
||||
|
||||
|
||||
//These commands are somehow ignored: TODO
|
||||
'left',
|
||||
'center',
|
||||
'right',
|
||||
];
|
||||
|
||||
/**
|
||||
* The GitHub Flavored Markdown (GFM) plugin with added HTML tags, which are kept in the output. (inline mode)
|
||||
*
|
||||
*/
|
||||
export default class ExtendedMarkdown extends Plugin {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
constructor( editor ) {
|
||||
super( editor );
|
||||
|
||||
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument );
|
||||
for (const tag of ALLOWED_TAGS) {
|
||||
editor.data.processor.keepHtml(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
static get pluginName() {
|
||||
return 'Markdown';
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Plugin} from 'ckeditor5';
|
||||
import {MarkdownGfmDataProcessor} from 'ckeditor5';
|
||||
|
||||
const ALLOWED_TAGS = [
|
||||
//Common elements
|
||||
'sup',
|
||||
'sub',
|
||||
'u',
|
||||
'kbd',
|
||||
'mark',
|
||||
'ins',
|
||||
'small',
|
||||
'abbr',
|
||||
'br',
|
||||
'span',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The GitHub Flavored Markdown (GFM) plugin with added HTML tags, which are kept in the output. (inline mode)
|
||||
*
|
||||
*/
|
||||
export default class ExtendedMarkdownInline extends Plugin {
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
constructor( editor ) {
|
||||
super( editor );
|
||||
|
||||
editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument );
|
||||
for (const tag of ALLOWED_TAGS) {
|
||||
editor.data.processor.keepHtml(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
static get pluginName() {
|
||||
return 'Markdown';
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Plugin} from 'ckeditor5';
|
||||
|
||||
export default class SingleLinePlugin extends Plugin {
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
const view = editor.editing.view;
|
||||
const viewDocument = view.document;
|
||||
|
||||
//Listen to enter presses
|
||||
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
|
||||
//If user presses enter, prevent the enter action
|
||||
evt.stop();
|
||||
}, { priority: 'high' } );
|
||||
|
||||
//And clipboard pastes
|
||||
this.listenTo( viewDocument, 'clipboardInput', ( evt, data ) => {
|
||||
let dataTransfer = data.dataTransfer;
|
||||
|
||||
//Clean text input (replace newlines with spaces)
|
||||
let input = dataTransfer.getData("text");
|
||||
let cleaned = input.replace(/\r?\n/g, ' ');
|
||||
|
||||
//We can not use the dataTransfer.setData method because the old object is somehow protected
|
||||
data.dataTransfer = new DataTransfer();
|
||||
data.dataTransfer.setData("text", cleaned);
|
||||
|
||||
}, { priority: 'high' } );
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SpecialCharacters from 'ckeditor5';
|
||||
import SpecialCharactersEssentials from 'ckeditor5';
|
||||
|
||||
import {Plugin} from 'ckeditor5';
|
||||
|
||||
export default class SpecialCharactersGreek extends Plugin {
|
||||
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
const specialCharsPlugin = editor.plugins.get('SpecialCharacters');
|
||||
|
||||
//Add greek characters to special characters
|
||||
specialCharsPlugin.addItems('Greek', this.getGreek());
|
||||
}
|
||||
|
||||
getGreek() {
|
||||
return [
|
||||
{ title: 'Alpha', character: 'Α' },
|
||||
{ title: 'Beta', character: 'Β' },
|
||||
{ title: 'Gamma', character: 'Γ' },
|
||||
{ title: 'Delta', character: 'Δ' },
|
||||
{ title: 'Epsilon', character: 'Ε' },
|
||||
{ title: 'Zeta', character: 'Ζ' },
|
||||
{ title: 'Eta', character: 'Η' },
|
||||
{ title: 'Theta', character: 'Θ' },
|
||||
{ title: 'Iota', character: 'Ι' },
|
||||
{ title: 'Kappa', character: 'Κ' },
|
||||
{ title: 'Lambda', character: 'Λ' },
|
||||
{ title: 'Mu', character: 'Μ' },
|
||||
{ title: 'Nu', character: 'Ν' },
|
||||
{ title: 'Xi', character: 'Ξ' },
|
||||
{ title: 'Omicron', character: 'Ο' },
|
||||
{ title: 'Pi', character: 'Π' },
|
||||
{ title: 'Rho', character: 'Ρ' },
|
||||
{ title: 'Sigma', character: 'Σ' },
|
||||
{ title: 'Tau', character: 'Τ' },
|
||||
{ title: 'Upsilon', character: 'Υ' },
|
||||
{ title: 'Phi', character: 'Φ' },
|
||||
{ title: 'Chi', character: 'Χ' },
|
||||
{ title: 'Psi', character: 'Ψ' },
|
||||
{ title: 'Omega', character: 'Ω' },
|
||||
{ title: 'alpha', character: 'α' },
|
||||
{ title: 'beta', character: 'β' },
|
||||
{ title: 'gamma', character: 'γ' },
|
||||
{ title: 'delta', character: 'δ' },
|
||||
{ title: 'epsilon', character: 'ε' },
|
||||
{ title: 'zeta', character: 'ζ' },
|
||||
{ title: 'eta', character: 'η' },
|
||||
{ title: 'theta', character: 'θ' },
|
||||
{ title: 'alternate theta', character: 'ϑ' },
|
||||
{ title: 'iota', character: 'ι' },
|
||||
{ title: 'kappa', character: 'κ' },
|
||||
{ title: 'lambda', character: 'λ' },
|
||||
{ title: 'mu', character: 'μ' },
|
||||
{ title: 'nu', character: 'ν' },
|
||||
{ title: 'xi', character: 'ξ' },
|
||||
{ title: 'omicron', character: 'ο' },
|
||||
{ title: 'pi', character: 'π' },
|
||||
{ title: 'rho', character: 'ρ' },
|
||||
{ title: 'sigma', character: 'σ' },
|
||||
{ title: 'tau', character: 'τ' },
|
||||
{ title: 'upsilon', character: 'υ' },
|
||||
{ title: 'phi', character: 'φ' },
|
||||
{ title: 'chi', character: 'χ' },
|
||||
{ title: 'psi', character: 'ψ' },
|
||||
{ title: 'omega', character: 'ω' },
|
||||
{ title: 'digamma', character: 'Ϝ' },
|
||||
{ title: 'stigma', character: 'Ϛ' },
|
||||
{ title: 'heta', character: 'Ͱ' },
|
||||
{ title: 'sampi', character: 'Ϡ' },
|
||||
{ title: 'koppa', character: 'Ϟ' },
|
||||
{ title: 'san', character: 'Ϻ' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,4 @@
|
||||
{
|
||||
"controllers": {
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
"enabled": false,
|
||||
"fetch": "eager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controllers": [],
|
||||
"entrypoints": []
|
||||
}
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { generateCsrfHeaders } from "./csrf_protection_controller"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["progressBar", "progressText"]
|
||||
static values = {
|
||||
jobId: Number,
|
||||
partId: Number,
|
||||
researchUrl: String,
|
||||
researchAllUrl: String,
|
||||
markCompletedUrl: String,
|
||||
markSkippedUrl: String,
|
||||
markPendingUrl: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Auto-refresh progress if job is in progress
|
||||
if (this.hasProgressBarTarget) {
|
||||
this.startProgressUpdates()
|
||||
}
|
||||
|
||||
// Restore scroll position after page reload (if any)
|
||||
this.restoreScrollPosition()
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
|
||||
// Add CSRF headers if available
|
||||
const form = document.querySelector('form')
|
||||
if (form) {
|
||||
const csrfHeaders = generateCsrfHeaders(form)
|
||||
Object.assign(headers, csrfHeaders)
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
async fetchWithErrorHandling(url, options = {}, timeout = 30000) {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: { ...this.getHeaders(), ...options.headers },
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('Request timed out. Please try again.')
|
||||
} else if (error.message.includes('Failed to fetch')) {
|
||||
throw new Error('Network error. Please check your connection and try again.')
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.progressInterval) {
|
||||
clearInterval(this.progressInterval)
|
||||
}
|
||||
}
|
||||
|
||||
startProgressUpdates() {
|
||||
// Progress updates are handled via page reload for better reliability
|
||||
// No need for periodic updates since state changes trigger page refresh
|
||||
}
|
||||
|
||||
restoreScrollPosition() {
|
||||
const savedPosition = sessionStorage.getItem('bulkImportScrollPosition')
|
||||
if (savedPosition) {
|
||||
// Restore scroll position after a small delay to ensure page is fully loaded
|
||||
setTimeout(() => {
|
||||
window.scrollTo(0, parseInt(savedPosition))
|
||||
// Clear the saved position so it doesn't interfere with normal navigation
|
||||
sessionStorage.removeItem('bulkImportScrollPosition')
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
async markCompleted(event) {
|
||||
const partId = event.currentTarget.dataset.partId
|
||||
|
||||
try {
|
||||
const url = this.markCompletedUrlValue.replace('__PART_ID__', partId)
|
||||
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
|
||||
|
||||
if (data.success) {
|
||||
this.updateProgressDisplay(data)
|
||||
this.markRowAsCompleted(partId)
|
||||
|
||||
if (data.job_completed) {
|
||||
this.showJobCompletedMessage()
|
||||
}
|
||||
} else {
|
||||
this.showErrorMessage(data.error || 'Failed to mark part as completed')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking part as completed:', error)
|
||||
this.showErrorMessage(error.message || 'Failed to mark part as completed')
|
||||
}
|
||||
}
|
||||
|
||||
async markSkipped(event) {
|
||||
const partId = event.currentTarget.dataset.partId
|
||||
const reason = prompt('Reason for skipping (optional):') || ''
|
||||
|
||||
try {
|
||||
const url = this.markSkippedUrlValue.replace('__PART_ID__', partId)
|
||||
const data = await this.fetchWithErrorHandling(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ reason })
|
||||
})
|
||||
|
||||
if (data.success) {
|
||||
this.updateProgressDisplay(data)
|
||||
this.markRowAsSkipped(partId)
|
||||
} else {
|
||||
this.showErrorMessage(data.error || 'Failed to mark part as skipped')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking part as skipped:', error)
|
||||
this.showErrorMessage(error.message || 'Failed to mark part as skipped')
|
||||
}
|
||||
}
|
||||
|
||||
async markPending(event) {
|
||||
const partId = event.currentTarget.dataset.partId
|
||||
|
||||
try {
|
||||
const url = this.markPendingUrlValue.replace('__PART_ID__', partId)
|
||||
const data = await this.fetchWithErrorHandling(url, { method: 'POST' })
|
||||
|
||||
if (data.success) {
|
||||
this.updateProgressDisplay(data)
|
||||
this.markRowAsPending(partId)
|
||||
} else {
|
||||
this.showErrorMessage(data.error || 'Failed to mark part as pending')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking part as pending:', error)
|
||||
this.showErrorMessage(error.message || 'Failed to mark part as pending')
|
||||
}
|
||||
}
|
||||
|
||||
updateProgressDisplay(data) {
|
||||
if (this.hasProgressBarTarget) {
|
||||
this.progressBarTarget.style.width = `${data.progress}%`
|
||||
this.progressBarTarget.setAttribute('aria-valuenow', data.progress)
|
||||
}
|
||||
|
||||
if (this.hasProgressTextTarget) {
|
||||
this.progressTextTarget.textContent = `${data.completed_count} / ${data.total_count} completed`
|
||||
}
|
||||
}
|
||||
|
||||
markRowAsCompleted(partId) {
|
||||
// Save scroll position and refresh page to show updated state
|
||||
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
markRowAsSkipped(partId) {
|
||||
// Save scroll position and refresh page to show updated state
|
||||
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
markRowAsPending(partId) {
|
||||
// Save scroll position and refresh page to show updated state
|
||||
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
showJobCompletedMessage() {
|
||||
const alert = document.createElement('div')
|
||||
alert.className = 'alert alert-success alert-dismissible fade show'
|
||||
alert.innerHTML = `
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Job completed! All parts have been processed.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`
|
||||
|
||||
const container = document.querySelector('.card-body')
|
||||
container.insertBefore(alert, container.firstChild)
|
||||
}
|
||||
|
||||
async researchPart(event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const partId = event.currentTarget.dataset.partId
|
||||
const spinner = event.currentTarget.querySelector(`[data-research-spinner="${partId}"]`)
|
||||
const button = event.currentTarget
|
||||
|
||||
// Show loading state
|
||||
if (spinner) {
|
||||
spinner.style.display = 'inline-block'
|
||||
}
|
||||
button.disabled = true
|
||||
|
||||
try {
|
||||
const url = this.researchUrlValue.replace('__PART_ID__', partId)
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 second timeout
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
this.showSuccessMessage(`Research completed for part. Found ${data.results_count} results.`)
|
||||
// Save scroll position and reload to show updated results
|
||||
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||
window.location.reload()
|
||||
} else {
|
||||
this.showErrorMessage(data.error || 'Research failed')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error researching part:', error)
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
this.showErrorMessage('Research timed out. Please try again.')
|
||||
} else if (error.message.includes('Failed to fetch')) {
|
||||
this.showErrorMessage('Network error. Please check your connection and try again.')
|
||||
} else {
|
||||
this.showErrorMessage(error.message || 'Research failed due to an unexpected error')
|
||||
}
|
||||
} finally {
|
||||
// Hide loading state
|
||||
if (spinner) {
|
||||
spinner.style.display = 'none'
|
||||
}
|
||||
button.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
async researchAllParts(event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const spinner = document.getElementById('research-all-spinner')
|
||||
const button = event.currentTarget
|
||||
|
||||
// Show loading state
|
||||
if (spinner) {
|
||||
spinner.style.display = 'inline-block'
|
||||
}
|
||||
button.disabled = true
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout for bulk operations
|
||||
|
||||
const response = await fetch(this.researchAllUrlValue, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Server error (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
this.showSuccessMessage(`Research completed for ${data.researched_count} parts.`)
|
||||
// Save scroll position and reload to show updated results
|
||||
sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString())
|
||||
window.location.reload()
|
||||
} else {
|
||||
this.showErrorMessage(data.error || 'Bulk research failed')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error researching all parts:', error)
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
this.showErrorMessage('Bulk research timed out. This may happen with large batches. Please try again or process smaller batches.')
|
||||
} else if (error.message.includes('Failed to fetch')) {
|
||||
this.showErrorMessage('Network error. Please check your connection and try again.')
|
||||
} else {
|
||||
this.showErrorMessage(error.message || 'Bulk research failed due to an unexpected error')
|
||||
}
|
||||
} finally {
|
||||
// Hide loading state
|
||||
if (spinner) {
|
||||
spinner.style.display = 'none'
|
||||
}
|
||||
button.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
showSuccessMessage(message) {
|
||||
this.showToast('success', message)
|
||||
}
|
||||
|
||||
showErrorMessage(message) {
|
||||
this.showToast('error', message)
|
||||
}
|
||||
|
||||
showToast(type, message) {
|
||||
// Create a simple alert that doesn't disrupt layout
|
||||
const alertId = 'alert-' + Date.now()
|
||||
const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle'
|
||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger'
|
||||
|
||||
const alertHTML = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show position-fixed"
|
||||
style="top: 20px; right: 20px; z-index: 9999; max-width: 400px;"
|
||||
id="${alertId}">
|
||||
<i class="fas ${iconClass} me-2"></i>
|
||||
${message}
|
||||
<button type="button" class="btn-close" onclick="this.parentElement.remove()" aria-label="Close"></button>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Add alert to body
|
||||
document.body.insertAdjacentHTML('beforeend', alertHTML)
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
const alertElement = document.getElementById(alertId)
|
||||
if (alertElement) {
|
||||
alertElement.remove()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { generateCsrfHeaders } from "./csrf_protection_controller"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
deleteUrl: String,
|
||||
stopUrl: String,
|
||||
deleteConfirmMessage: String,
|
||||
stopConfirmMessage: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Controller initialized
|
||||
}
|
||||
getHeaders() {
|
||||
const headers = {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
|
||||
// Add CSRF headers if available
|
||||
const form = document.querySelector('form')
|
||||
if (form) {
|
||||
const csrfHeaders = generateCsrfHeaders(form)
|
||||
Object.assign(headers, csrfHeaders)
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
async deleteJob(event) {
|
||||
const jobId = event.currentTarget.dataset.jobId
|
||||
const confirmMessage = this.deleteConfirmMessageValue || 'Are you sure you want to delete this job?'
|
||||
|
||||
if (confirm(confirmMessage)) {
|
||||
try {
|
||||
const deleteUrl = this.deleteUrlValue.replace('__JOB_ID__', jobId)
|
||||
|
||||
const response = await fetch(deleteUrl, {
|
||||
method: 'DELETE',
|
||||
headers: this.getHeaders()
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
location.reload()
|
||||
} else {
|
||||
alert('Error deleting job: ' + (data.error || 'Unknown error'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting job:', error)
|
||||
alert('Error deleting job: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async stopJob(event) {
|
||||
const jobId = event.currentTarget.dataset.jobId
|
||||
const confirmMessage = this.stopConfirmMessageValue || 'Are you sure you want to stop this job?'
|
||||
|
||||
if (confirm(confirmMessage)) {
|
||||
try {
|
||||
const stopUrl = this.stopUrlValue.replace('__JOB_ID__', jobId)
|
||||
|
||||
const response = await fetch(stopUrl, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders()
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
location.reload()
|
||||
} else {
|
||||
alert('Error stopping job: ' + (data.error || 'Unknown error'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping job:', error)
|
||||
alert('Error stopping job: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import {Tooltip} from "bootstrap";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
connect() {
|
||||
window.addEventListener('scroll', this._onscroll.bind(this));
|
||||
}
|
||||
|
||||
_onscroll() {
|
||||
const button = this.element;
|
||||
|
||||
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
||||
button.style.display = "block";
|
||||
} else {
|
||||
button.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
backToTop() {
|
||||
//Hide button tooltip to prevent ugly tooltip on scroll
|
||||
Tooltip.getInstance(this.element)?.hide();
|
||||
|
||||
document.body.scrollTop = 0; // For Safari
|
||||
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
connect() {
|
||||
this.setMode(this.getMode());
|
||||
document.querySelectorAll('input[name="darkmode"]').forEach((radio) => {
|
||||
radio.addEventListener('change', this._radioChanged.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for the change of radio buttons
|
||||
* @private
|
||||
*/
|
||||
_radioChanged(event) {
|
||||
const new_mode = this.getSelectedMode();
|
||||
this.setMode(new_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode from the local storage
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getMode() {
|
||||
return localStorage.getItem('darkmode') ?? 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode in the local storage and apply it and change the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (mode !== 'dark' && mode !== 'light' && mode !== 'auto') {
|
||||
console.warn('Invalid darkmode mode: ' + mode);
|
||||
mode = 'auto';
|
||||
}
|
||||
|
||||
localStorage.setItem('darkmode', mode);
|
||||
|
||||
this.setButtonMode(mode);
|
||||
|
||||
if (mode === 'auto') {
|
||||
this._setDarkmodeAuto();
|
||||
} else if (mode === 'dark') {
|
||||
this._enableDarkmode();
|
||||
} else if (mode === 'light') {
|
||||
this._disableDarkmode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected mode via the radio buttons
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getSelectedMode() {
|
||||
return document.querySelector('input[name="darkmode"]:checked').value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setButtonMode(mode) {
|
||||
document.querySelector('input[name="darkmode"][value="' + mode + '"]').checked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable darkmode by adding the data-bs-theme="dark" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_enableDarkmode() {
|
||||
//Add data-bs-theme="dark" to the html tag
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable darkmode by adding the data-bs-theme="light" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_disableDarkmode() {
|
||||
//Set data-bs-theme to light
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the darkmode to auto and enable/disable it depending on the system settings, also add
|
||||
* an event listener to change the darkmode if the system settings change
|
||||
* @private
|
||||
*/
|
||||
_setDarkmodeAuto() {
|
||||
if (this.getMode() !== 'auto') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
this._enableDarkmode();
|
||||
} else {
|
||||
this._disableDarkmode();
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
console.log('Prefered color scheme changed to ' + event.matches ? 'dark' : 'light');
|
||||
this._setDarkmodeAuto();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if darkmode is activated
|
||||
* @return {boolean}
|
||||
*/
|
||||
isDarkmodeActivated() {
|
||||
return document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
const STORAGE_KEY = 'hide_sidebar';
|
||||
|
||||
export default class extends Controller {
|
||||
/**
|
||||
* The element representing the sidebar which can be hidden.
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
_sidebar;
|
||||
|
||||
/**
|
||||
* The element of the container which is expanded to the full width.
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
_container;
|
||||
|
||||
/**
|
||||
* The button which toggles the sidebar.
|
||||
* @private
|
||||
*/
|
||||
_toggle_button;
|
||||
|
||||
_hidden = false;
|
||||
|
||||
connect() {
|
||||
this._sidebar = document.getElementById('fixed-sidebar');
|
||||
this._container = document.getElementById('main');
|
||||
this._toggle_button = this.element;
|
||||
|
||||
//Make the state persistent over reloads
|
||||
if(localStorage.getItem(STORAGE_KEY) === 'true') {
|
||||
sidebarHide();
|
||||
}
|
||||
}
|
||||
|
||||
hideSidebar() {
|
||||
this._sidebar.classList.add('d-none');
|
||||
|
||||
this._container.classList.remove(...['col-md-9', 'col-lg-10', 'offset-md-3', 'offset-lg-2']);
|
||||
this._container.classList.add('col-12');
|
||||
|
||||
//Change button icon
|
||||
this._toggle_button.innerHTML = '<i class="fas fa-angle-right"></i>';
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, 'true');
|
||||
this._hidden = true;
|
||||
}
|
||||
|
||||
showSidebar() {
|
||||
this._sidebar.classList.remove('d-none');
|
||||
|
||||
this._container.classList.remove('col-12');
|
||||
this._container.classList.add(...['col-md-9', 'col-lg-10', 'offset-md-3', 'offset-lg-2']);
|
||||
|
||||
|
||||
//Change button icon
|
||||
this._toggle_button.innerHTML = '<i class="fas fa-angle-left"></i>';
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, 'false');
|
||||
this._hidden = false;
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
if(this._hidden) {
|
||||
this.showSidebar();
|
||||
} else {
|
||||
this.hideSidebar();
|
||||
}
|
||||
|
||||
//Hide the tootip on the button
|
||||
this._toggle_button.blur();
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
//import "katex";
|
||||
import 'katex/dist/katex.css';
|
||||
import {auto} from "@popperjs/core";
|
||||
//import renderMathInElement from "katex/dist/contrib/auto-render";
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.applyLatex();
|
||||
this.element.addEventListener('markdown:finished', () => this.applyLatex());
|
||||
}
|
||||
|
||||
applyLatex() {
|
||||
//Only import the katex library, if we have an delimiter string in our element text
|
||||
let str = this.element.textContent;
|
||||
if(str.match(/(\$|\\\(|\\\[).+(\$|\\\)|\\\])/)) {
|
||||
import('katex/dist/contrib/auto-render').then((autorender) => {
|
||||
//This calls renderMathInElement()
|
||||
autorender.default(this.element, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
]
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mutate() {
|
||||
this.applyLatex();
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { Marked } from "marked";
|
||||
import { mangle } from "marked-mangle";
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import "../../css/app/markdown.css";
|
||||
|
||||
export default class MarkdownController extends Controller {
|
||||
|
||||
static _marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
|
||||
connect()
|
||||
{
|
||||
this.render();
|
||||
|
||||
//Dispatch an event that we are now finished
|
||||
const event = new CustomEvent('markdown:finished', {
|
||||
bubbles: true
|
||||
});
|
||||
this.element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
let raw = this.element.dataset['markdown'];
|
||||
|
||||
//Apply purified parsed markdown
|
||||
this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw)));
|
||||
|
||||
for(let a of this.element.querySelectorAll('a')) {
|
||||
// test if link is absolute
|
||||
var r = new RegExp('^(?:[a-z+]+:)?//', 'i');
|
||||
if (r.test(a.getAttribute('href'))) {
|
||||
//Mark all links as external
|
||||
a.classList.add('link-external');
|
||||
//Open links in new tag
|
||||
a.setAttribute('target', '_blank');
|
||||
//Dont track
|
||||
a.setAttribute('rel', 'noopener');
|
||||
}
|
||||
}
|
||||
|
||||
//Apply bootstrap styles to tables
|
||||
for(let table of this.element.querySelectorAll('table')) {
|
||||
table.classList.add('table', 'table-hover', 'table-striped', 'table-bordered', 'table-sm');
|
||||
}
|
||||
|
||||
//Make header line dark
|
||||
for(let head of this.element.querySelectorAll('thead')) {
|
||||
head.classList.add('table-dark');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape the given HTML
|
||||
* @param {string} html
|
||||
* @returns {string}
|
||||
*/
|
||||
unescapeHTML(html) {
|
||||
var txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the marked parser
|
||||
*/
|
||||
/*static newMarked()
|
||||
{
|
||||
const marked = new Marked([
|
||||
{
|
||||
gfm: true,
|
||||
},
|
||||
gfmHeadingId(),
|
||||
mangle(),
|
||||
])
|
||||
;
|
||||
|
||||
marked.use(mangle());
|
||||
marked.use(gfmHeadingId({
|
||||
}));
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
});
|
||||
}*/
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { Toast } from 'bootstrap';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
//Move all toasts from the page into our toast container and show them
|
||||
|
||||
const toastContainer = document.getElementById('toast-container');
|
||||
if (this.element.parentNode !== toastContainer) {
|
||||
toastContainer.appendChild(this.element);
|
||||
return;
|
||||
}
|
||||
|
||||
const toast = new Toast(this.element);
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
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
|
||||
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
|
||||
// and thus this event-listener will not be executed.
|
||||
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';
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
maxItems: 1,
|
||||
createOnBlur: true,
|
||||
selectOnTab: true,
|
||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
|
||||
dropdownParent: dropdownParent,
|
||||
render: {
|
||||
item: (data, escape) => {
|
||||
return '<span>' + escape(data.label) + '</span>';
|
||||
},
|
||||
option: (data, escape) => {
|
||||
if (data.image) {
|
||||
return "<div class='row m-0'><div class='col-2 pl-0 pr-1'><img class='typeahead-image' src='" + data.image + "'/></div><div class='col-10'>" + data.label + "</div></div>"
|
||||
}
|
||||
return '<div>' + escape(data.label) + '</div>';
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
"restore_on_backspace": {}
|
||||
}
|
||||
};
|
||||
|
||||
if(this.element.dataset.autocomplete) {
|
||||
const base_url = this.element.dataset.autocomplete;
|
||||
settings.searchField = "label";
|
||||
settings.sortField = "label";
|
||||
settings.valueField = "label";
|
||||
settings.load = (query, callback) => {
|
||||
if(query.length < 2){
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
const data = json.map(x => {
|
||||
return {
|
||||
"label": x.name,
|
||||
"image": x.image,
|
||||
}
|
||||
});
|
||||
callback(data);
|
||||
}).catch(()=>{
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
/**
|
||||
* This controller synchronizes the filetype filters of the file input type with our selected attachment type
|
||||
*/
|
||||
export default class extends Controller
|
||||
{
|
||||
_selectInput;
|
||||
_fileInput;
|
||||
|
||||
connect() {
|
||||
//Find the select input for our attachment form
|
||||
this._selectInput = this.element.querySelector('select');
|
||||
//Find the file input for our attachment form
|
||||
this._fileInput = this.element.querySelector('input[type="file"]');
|
||||
|
||||
this._selectInput.addEventListener('change', this.updateAllowedFiletypes.bind(this));
|
||||
|
||||
//Update file file on load
|
||||
this.updateAllowedFiletypes();
|
||||
}
|
||||
|
||||
updateAllowedFiletypes() {
|
||||
let selected_option = this._selectInput.options[this._selectInput.selectedIndex];
|
||||
let filetype_filter = selected_option.dataset.filetype_filter;
|
||||
//Apply filetype filter to file input
|
||||
this._fileInput.setAttribute('accept', filetype_filter);
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import { default as FullEditor } from "../../ckeditor/markdown_full";
|
||||
import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line";
|
||||
import { default as HTMLLabelEditor } from "../../ckeditor/html_label";
|
||||
|
||||
import {EditorWatchdog} from 'ckeditor5';
|
||||
|
||||
import "ckeditor5/ckeditor5.css";;
|
||||
import "../../css/components/ckeditor.css";
|
||||
|
||||
const translationContext = require.context(
|
||||
'ckeditor5/translations',
|
||||
false,
|
||||
//Only load the translation files we will really need
|
||||
/(de|it|fr|ru|ja|cs|da|zh|pl|hu)\.js$/
|
||||
);
|
||||
|
||||
function loadTranslation(language) {
|
||||
if (!language || language === 'en') {
|
||||
return null;
|
||||
}
|
||||
const lang = language.slice(0, 2);
|
||||
const path = `./${lang}.js`;
|
||||
if (translationContext.keys().includes(path)) {
|
||||
const module = translationContext(path);
|
||||
return module.default;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
const mode = this.element.dataset.mode;
|
||||
|
||||
let EDITOR_TYPE = "Invalid";
|
||||
|
||||
switch (mode) {
|
||||
case "markdown-full":
|
||||
EDITOR_TYPE = FullEditor['Editor'];
|
||||
break;
|
||||
case "markdown-single_line":
|
||||
EDITOR_TYPE = SingleLineEditor['Editor'];
|
||||
break;
|
||||
case "html-label":
|
||||
EDITOR_TYPE = HTMLLabelEditor['Editor'];
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown mode: " + mode);
|
||||
return;
|
||||
}
|
||||
|
||||
const language = document.body.dataset.locale ?? "en";
|
||||
|
||||
const emojiURL = new URL('../../ckeditor/emojis.json', import.meta.url).href;
|
||||
|
||||
const config = {
|
||||
language: language,
|
||||
licenseKey: "GPL",
|
||||
|
||||
emoji: {
|
||||
definitionsUrl: emojiURL
|
||||
}
|
||||
}
|
||||
|
||||
//Load translations if not english
|
||||
let translations = loadTranslation(language);
|
||||
if (translations) {
|
||||
//Keep existing translations (e.g. from other plugins), if any
|
||||
config.translations = [window.CKEDITOR_TRANSLATIONS, translations];
|
||||
}
|
||||
|
||||
const watchdog = new EditorWatchdog();
|
||||
watchdog.setCreator((elementOrData, editorConfig) => {
|
||||
return EDITOR_TYPE.create(elementOrData, editorConfig)
|
||||
.then(editor => {
|
||||
if(this.element.disabled) {
|
||||
editor.enableReadOnlyMode("readonly");
|
||||
}
|
||||
|
||||
//Apply additional styles
|
||||
const editor_div = editor.ui.view.element;
|
||||
const new_classes = this.element.dataset.ckClass;
|
||||
if (editor_div && new_classes) {
|
||||
editor_div.classList.add(...new_classes.split(","));
|
||||
}
|
||||
|
||||
// Automatic synchronization of source input
|
||||
editor.model.document.on("change:data", () => {
|
||||
editor.updateSourceElement();
|
||||
|
||||
// Dispatch the input event for further treatment
|
||||
const event = new Event("input");
|
||||
this.element.dispatchEvent(event);
|
||||
});
|
||||
|
||||
//This return is important! Otherwise we get mysterious errors in the console
|
||||
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
||||
return editor;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
watchdog.create(this.element, config).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import * as bootbox from "bootbox";
|
||||
import "../../css/components/bootbox_extensions.css";
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
deleteMessage: String,
|
||||
prototype: String,
|
||||
rowsToDelete: Number, //How many rows (including the current one) shall be deleted after the current row
|
||||
fieldPlaceholder: String
|
||||
}
|
||||
|
||||
static targets = ["target"];
|
||||
|
||||
/**
|
||||
* Decodes escaped HTML entities
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
htmlDecode(input) {
|
||||
const doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique ID to be used for the new element
|
||||
* @returns {string}
|
||||
*/
|
||||
generateUID() {
|
||||
const long = (performance.now().toString(36)+Math.random().toString(36)).replace(/\./g,"");
|
||||
return long.slice(0, 6); // 6 characters is enough for our unique IDs here
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new entry in the target using the given prototype value
|
||||
*/
|
||||
createElement(event) {
|
||||
const targetTable = this.targetTarget;
|
||||
const prototype = this.prototypeValue
|
||||
|
||||
if(!prototype) {
|
||||
console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
|
||||
bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const regexString = this.fieldPlaceholderValue || "__name__";
|
||||
const regex = new RegExp(regexString, "g");
|
||||
|
||||
//Apply the index to prototype to create our element to insert
|
||||
const newElementStr = this.htmlDecode(prototype.replace(regex, this.generateUID()));
|
||||
|
||||
|
||||
//Insert new html after the last child element
|
||||
//If the table has a tbody, insert it there
|
||||
//Afterwards return the newly created row
|
||||
if(targetTable.tBodies[0]) {
|
||||
targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.tBodies[0].lastElementChild;
|
||||
} else { //Otherwise just insert it
|
||||
targetTable.insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.lastElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This action opens a file dialog to select multiple files and then creates a new element for each file, where
|
||||
* the file is assigned to the input field.
|
||||
* This should only be used for attachments collection types
|
||||
* @param event
|
||||
*/
|
||||
uploadMultipleFiles(event) {
|
||||
//Open a file dialog to select multiple files
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
input.click();
|
||||
|
||||
input.addEventListener('change', (event) => {
|
||||
//Create a element for each file
|
||||
|
||||
for (let i = 0; i < input.files.length; i++) {
|
||||
const file = input.files[i];
|
||||
|
||||
const newElement = this.createElement(event);
|
||||
const rowInput = newElement.querySelector("input[type='file']");
|
||||
|
||||
//We can not directly assign the file to the input, so we have to create a new DataTransfer object
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
|
||||
rowInput.files = dataTransfer.files;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to createEvent Pricedetails need some special handling to fill min amount
|
||||
* @param event
|
||||
*/
|
||||
createPricedetail(event) {
|
||||
//First insert our new element
|
||||
this.createElement(event);
|
||||
|
||||
const extractElementsFromRow = (row) => {
|
||||
const priceRelated = row.querySelector("input[id$='price_related_quantity_value']");
|
||||
const minDiscount = row.querySelector("input[id$='min_discount_quantity_value']");
|
||||
|
||||
return [priceRelated, minDiscount];
|
||||
}
|
||||
|
||||
const targetTable = this.targetTarget;
|
||||
const targetRows = targetTable.tBodies[0].rows;
|
||||
const targetRowsCount = targetRows.length;
|
||||
|
||||
//If we just have one element we dont have to do anything as 1 is already the default
|
||||
if(targetRowsCount <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Our new element is the last child of the table
|
||||
const newlyCreatedRow = targetRows[targetRowsCount - 1];
|
||||
const [newPriceRelated, newMinDiscount] = extractElementsFromRow(newlyCreatedRow);
|
||||
|
||||
const oldRow = targetRows[targetRowsCount - 2];
|
||||
const [oldPriceRelated, oldMinDiscount] = extractElementsFromRow(oldRow);
|
||||
|
||||
//Use the old PriceRelated value to determine the next 10 decade value for the new row
|
||||
const oldMinAmount = parseInt(oldMinDiscount.value)
|
||||
//The next 10 power can be achieved by creating a string beginning with "1" and adding 0 times the length of the old string
|
||||
const oldMinAmountLength = oldMinAmount.toString().length;
|
||||
const newMinAmountStr = '1' + '0'.repeat(oldMinAmountLength);
|
||||
//Parse the sting back to an integer and we have our new min amount
|
||||
const newMinAmount = parseInt(newMinAmountStr);
|
||||
|
||||
|
||||
//Assign it to our new element
|
||||
newMinDiscount.value = newMinAmount;
|
||||
}
|
||||
|
||||
deleteElement(event) {
|
||||
const del = () => {
|
||||
const target = event.target;
|
||||
//Remove the row element from the table
|
||||
const current_row = target.closest("tr");
|
||||
for(let i = this.rowsToDeleteValue; i > 1; i--) {
|
||||
let nextSibling = current_row.nextElementSibling;
|
||||
//Ensure that nextSibling is really a tr
|
||||
if (nextSibling && nextSibling.tagName === "TR") {
|
||||
nextSibling.remove();
|
||||
}
|
||||
}
|
||||
|
||||
//Finally delete the current row
|
||||
current_row.remove();
|
||||
}
|
||||
|
||||
if(this.deleteMessageValue) {
|
||||
bootbox.confirm(this.deleteMessageValue, (result) => {
|
||||
if (result) {
|
||||
del();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
del();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
//Styles
|
||||
import 'datatables.net-bs5/css/dataTables.bootstrap5.css'
|
||||
import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css'
|
||||
import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css'
|
||||
import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css';
|
||||
|
||||
//Use our own styles for the select extension which fit the bootstrap theme better
|
||||
//import 'datatables.net-select-bs5/css/select.bootstrap5.css';
|
||||
import '../../../css/components/datatables_select_bs5.css';
|
||||
|
||||
//JS
|
||||
import 'datatables.net-bs5';
|
||||
import 'datatables.net-buttons-bs5';
|
||||
import 'datatables.net-buttons/js/buttons.colVis.js';
|
||||
import 'datatables.net-fixedheader-bs5';
|
||||
import 'datatables.net-colreorder-bs5';
|
||||
import 'datatables.net-responsive-bs5';
|
||||
import '../../../js/lib/datatables';
|
||||
|
||||
//import 'datatables.net-select-bs5';
|
||||
//Use the local version containing the fix for the select extension
|
||||
import '../../../js/lib/dataTables.select.mjs';
|
||||
|
||||
|
||||
const EVENT_DT_LOADED = 'dt:loaded';
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ['dt'];
|
||||
|
||||
static values = {
|
||||
stateSaveTag: String
|
||||
};
|
||||
|
||||
/** The datatable instance associated with this controller instance */
|
||||
_dt;
|
||||
|
||||
getStateSaveKey() {
|
||||
let key = 'dt_state_'
|
||||
|
||||
if(this.stateSaveTagValue) { //If a tag is provided, use it to store the state
|
||||
key += this.stateSaveTagValue;
|
||||
} else { //Otherwise generate one from the current url
|
||||
key += window.location.pathname;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
stateSaveCallback(settings, data) {
|
||||
localStorage.setItem( this.getStateSaveKey(), JSON.stringify(data) );
|
||||
}
|
||||
|
||||
stateLoadCallback() {
|
||||
const json = localStorage.getItem(this.getStateSaveKey());
|
||||
if(json === null || json === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = JSON.parse(json);
|
||||
|
||||
if (data) {
|
||||
//Do not save the start value (current page), as we want to always start at the first page on a page reload
|
||||
delete data.start;
|
||||
//Reset the data length to the default value by deleting the length property
|
||||
delete data.length;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//$($.fn.DataTable.tables()).DataTable().fixedHeader.disable();
|
||||
//$($.fn.DataTable.tables()).DataTable().destroy();
|
||||
|
||||
const settings = JSON.parse(this.element.dataset.dtSettings);
|
||||
if(!settings) {
|
||||
throw new Error("No settings provided for datatable!");
|
||||
}
|
||||
|
||||
//Add url info, as the one available in the history is not enough, as Turbo may have not changed it yet
|
||||
settings.url = this.element.dataset.dtUrl;
|
||||
|
||||
//Add initial_order info to the settings, so that the order on the initial page load is the one saved in the state
|
||||
const saved_state = this.stateLoadCallback();
|
||||
if (saved_state !== null) {
|
||||
const raw_order = saved_state.order;
|
||||
|
||||
settings.initial_order = raw_order.map((order) => {
|
||||
return {
|
||||
column: order[0],
|
||||
dir: order[1]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let options = {
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
fixedHeader: {
|
||||
header: $(window).width() >= 768, //Only enable fixedHeaders on devices with big screen. Fixes scrolling issues on smartphones.
|
||||
headerOffset: $("#navbar").outerHeight()
|
||||
},
|
||||
buttons: [{
|
||||
"extend": 'colvis',
|
||||
'className': 'mr-2 btn-outline-secondary',
|
||||
'columns': ':not(.no-colvis)',
|
||||
"text": "<i class='fa fa-cog'></i>"
|
||||
}],
|
||||
|
||||
|
||||
rowCallback: this._rowCallback.bind(this),
|
||||
stateSave: true,
|
||||
stateSaveCallback: this.stateSaveCallback.bind(this),
|
||||
stateLoadCallback: this.stateLoadCallback.bind(this),
|
||||
};
|
||||
|
||||
if(this.isSelectable()) {
|
||||
options.select = {
|
||||
style: 'multi+shift',
|
||||
selector: 'td.dt-select',
|
||||
};
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const promise = $(this.dtTarget).initDataTables(settings, options)
|
||||
//Register error handler
|
||||
.catch(err => {
|
||||
console.error("Error initializing datatables: " + err);
|
||||
});
|
||||
|
||||
//Fix height of the length selector
|
||||
promise.then((dt) => {
|
||||
|
||||
//Draw the rows to make sure the correct status text is displayed ("No matching records found" instead of "Loading...")
|
||||
if (dt.data().length === 0) {
|
||||
dt.rows().draw()
|
||||
}
|
||||
|
||||
//Find all length selectors (select with name dt_length), which are inside a label
|
||||
const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]');
|
||||
//And remove the surrounding label, while keeping the select with all event handlers
|
||||
lengthSelectors.forEach((selector) => {
|
||||
selector.parentElement.replaceWith(selector);
|
||||
});
|
||||
|
||||
//Find all column visibility buttons (button with buttons-colvis class) and remove the btn-secondary class
|
||||
const colVisButtons = document.querySelectorAll('button.buttons-colvis');
|
||||
colVisButtons.forEach((button) => {
|
||||
button.classList.remove('btn-secondary');
|
||||
});
|
||||
});
|
||||
|
||||
//Dispatch an event to let others know that the datatables has been loaded
|
||||
promise.then((dt) => {
|
||||
const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true});
|
||||
this.element.dispatchEvent(event);
|
||||
|
||||
this._dt = dt;
|
||||
});
|
||||
|
||||
//Register event handlers
|
||||
promise.then((dt) => {
|
||||
//Deselect all rows before registering the event handler
|
||||
dt.rows().deselect();
|
||||
|
||||
dt.on('select.dt deselect.dt', this._onSelectionChange.bind(this));
|
||||
});
|
||||
|
||||
promise.then((dt) => {
|
||||
//Recalculate the fixed header offset, as the navbar should be rendered now
|
||||
dt.fixedHeader.headerOffset($("#navbar").outerHeight());
|
||||
});
|
||||
|
||||
//Allow to further configure the datatable
|
||||
promise.then(this._afterLoaded.bind(this));
|
||||
|
||||
|
||||
console.debug('Datatables inited.');
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
//Destroy the datatable element
|
||||
this._dt.destroy();
|
||||
console.debug("Datatables destroyed.");
|
||||
}
|
||||
|
||||
_rowCallback(row, data, index) {
|
||||
//Set the row class based on the optional $$rowClass column data, can be used to color the rows
|
||||
|
||||
//Check if we have a level, then change color of this row
|
||||
if (data.$$rowClass) {
|
||||
$(row).addClass(data.$$rowClass);
|
||||
}
|
||||
}
|
||||
|
||||
_onSelectionChange(e, dt, items ) {
|
||||
//Empty by default but can be overridden by child classes
|
||||
}
|
||||
|
||||
_afterLoaded(dt) {
|
||||
//Empty by default but can be overridden by child classes
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this datatable has selection feature enabled
|
||||
*/
|
||||
isSelectable()
|
||||
{
|
||||
return this.element.dataset.select ?? false;
|
||||
}
|
||||
|
||||
invertSelection() {
|
||||
//Do nothing if the datatable is not selectable
|
||||
if(!this.isSelectable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Invert the selected rows on the datatable
|
||||
const selected_rows = this._dt.rows({selected: true});
|
||||
this._dt.rows().select();
|
||||
selected_rows.deselect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import DatatablesController from "./datatables_controller.js";
|
||||
|
||||
/**
|
||||
* This is the datatables controller for log pages, it includes an mechanism to color lines based on their level.
|
||||
*/
|
||||
export default class extends DatatablesController {
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import DatatablesController from "./datatables_controller.js";
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import * as bootbox from "bootbox";
|
||||
|
||||
/**
|
||||
* This is the datatables controller for parts lists
|
||||
*/
|
||||
export default class extends DatatablesController {
|
||||
|
||||
static targets = ['dt', 'selectPanel', 'selectIDs', 'selectCount', 'selectTargetPicker'];
|
||||
|
||||
_confirmed = false;
|
||||
|
||||
isSelectable() {
|
||||
//Parts controller is always selectable
|
||||
return true;
|
||||
}
|
||||
|
||||
_onSelectionChange(e, dt, items) {
|
||||
const selected_elements = dt.rows({selected: true});
|
||||
const count = selected_elements.count();
|
||||
|
||||
const selectPanel = this.selectPanelTarget;
|
||||
|
||||
//Hide/Unhide panel with the selection tools
|
||||
if (count > 0) {
|
||||
selectPanel.classList.remove('d-none');
|
||||
selectPanel.classList.add('sticky-select-bar');
|
||||
} else {
|
||||
selectPanel.classList.add('d-none');
|
||||
selectPanel.classList.remove('sticky-select-bar');
|
||||
}
|
||||
|
||||
//Update selection count text
|
||||
this.selectCountTarget.innerText = count;
|
||||
|
||||
//Fill selection ID input
|
||||
let selected_ids_string = selected_elements.data().map(function(value, index) {
|
||||
return value['id']; }
|
||||
).join(",");
|
||||
|
||||
this.selectIDsTarget.value = selected_ids_string;
|
||||
}
|
||||
|
||||
updateOptions(select_element, json)
|
||||
{
|
||||
//Clear options
|
||||
select_element.innerHTML = null;
|
||||
//$(select_element).selectpicker('destroy');
|
||||
|
||||
//Retrieve the select controller instance
|
||||
const select_controller = this.application.getControllerForElementAndIdentifier(select_element, 'elements--structural-entity-select');
|
||||
/** @var {TomSelect} tom_select */
|
||||
const tom_select = select_controller.getTomSelect();
|
||||
|
||||
tom_select.clear();
|
||||
tom_select.clearOptions();
|
||||
|
||||
tom_select.addOptions(json, false);
|
||||
|
||||
//Select first element if there is one (so category select is not empty)
|
||||
if(json.length > 0) {
|
||||
tom_select.setValue(json[0].value);
|
||||
}
|
||||
|
||||
select_element.nextElementSibling.classList.remove('d-none');
|
||||
|
||||
//$(select_element).selectpicker('show');
|
||||
|
||||
}
|
||||
|
||||
updateTargetPicker(event) {
|
||||
const element = event.target;
|
||||
|
||||
//Extract the url from the selected option
|
||||
const selected_option = element.options[element.options.selectedIndex];
|
||||
const url = selected_option.dataset.url;
|
||||
|
||||
const select_target = this.selectTargetPickerTarget;
|
||||
|
||||
if (url) {
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
response.json().then(json => {
|
||||
this.updateOptions(select_target, json);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
//Hide the select element (the tomselect button is the sibling of the select element)
|
||||
select_target.nextElementSibling.classList.add('d-none');
|
||||
}
|
||||
|
||||
//If the selected option has a data-turbo attribute, set it to the form
|
||||
if (selected_option.dataset.turbo) {
|
||||
this.element.dataset.turbo = selected_option.dataset.turbo;
|
||||
} else {
|
||||
this.element.dataset.turbo = true;
|
||||
}
|
||||
}
|
||||
|
||||
confirmDeletionAtSubmit(event) {
|
||||
//Only show the dialog when selected action is delete
|
||||
if (event.target.elements["action"].value !== "delete") {
|
||||
return;
|
||||
}
|
||||
|
||||
//If a user has not already confirmed the deletion, just let turbo do its work
|
||||
if(this._confirmed) {
|
||||
this._confirmed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//Prevent turbo from doing its work
|
||||
event.preventDefault();
|
||||
|
||||
const message = this.element.dataset.deleteMessage;
|
||||
const title = this.element.dataset.deleteTitle;
|
||||
|
||||
const form = this.element;
|
||||
const that = this;
|
||||
|
||||
//Create a clone of the event with the same submitter, so we can redispatch it if needed
|
||||
//We need to do this that way, as we need the submitter info, just calling form.submit() would not work
|
||||
this._our_event = new SubmitEvent('submit', {
|
||||
submitter: event.submitter,
|
||||
bubbles: true, //This line is important, otherwise Turbo will not receive the event
|
||||
});
|
||||
|
||||
const confirm = bootbox.confirm({
|
||||
message: message, title: title, callback: function (result) {
|
||||
//If the dialog was confirmed, then submit the form.
|
||||
if (result) {
|
||||
that._confirmed = true;
|
||||
form.dispatchEvent(that._our_event);
|
||||
} else {
|
||||
that._confirmed = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import * as bootbox from "bootbox";
|
||||
import "../../css/components/bootbox_extensions.css";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
connect()
|
||||
{
|
||||
this._confirmed = false;
|
||||
}
|
||||
|
||||
submit(event) {
|
||||
//If a user has not already confirmed the deletion, just let turbo do its work
|
||||
if (this._confirmed) {
|
||||
this._confirmed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//Prevent turbo from doing its work
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const message = this.element.dataset.deleteMessage;
|
||||
const title = this.element.dataset.deleteTitle;
|
||||
|
||||
//Use event target, to find the form, where the submit button was clicked
|
||||
const form = event.target;
|
||||
const submitter = event.submitter;
|
||||
const that = this;
|
||||
|
||||
const confirm = bootbox.confirm({
|
||||
message: message, title: title, callback: function (result) {
|
||||
//If the dialog was confirmed, then submit the form.
|
||||
if (result) {
|
||||
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
|
||||
that._confirmed = true;
|
||||
|
||||
//Create a submit button in the form and click it to submit the form
|
||||
//Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
|
||||
const submit_btn = document.createElement('button');
|
||||
submit_btn.type = 'submit';
|
||||
submit_btn.style.display = 'none';
|
||||
|
||||
//If the clicked button has a value, set it on the submit button
|
||||
if (submitter.value) {
|
||||
submit_btn.value = submitter.value;
|
||||
}
|
||||
if (submitter.name) {
|
||||
submit_btn.name = submitter.name;
|
||||
}
|
||||
form.appendChild(submit_btn);
|
||||
submit_btn.click();
|
||||
} else {
|
||||
that._confirmed = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import {Popover} from "bootstrap";
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
const thumbnail_url = this.element.dataset.thumbnail;
|
||||
|
||||
this._popover = Popover.getOrCreateInstance(this.element, {
|
||||
html: true,
|
||||
trigger: 'hover',
|
||||
placement: 'right',
|
||||
container: 'body',
|
||||
'title': this.element.dataset.title ?? '',
|
||||
content: function () {
|
||||
return '<img class="img-fluid" src="' + thumbnail_url + '" />';
|
||||
}
|
||||
});
|
||||
|
||||
this._popover.hide();
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
import "../../css/components/autocomplete_bootstrap_theme.css";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["input"];
|
||||
static values = {
|
||||
partId: Number,
|
||||
partCategoryId: Number,
|
||||
partDescription: String,
|
||||
suggestions: Object,
|
||||
commonSectionHeader: String, // Dynamic header for common Prefixes
|
||||
partIncrementHeader: String, // Dynamic header for new possible part increment
|
||||
suggestUrl: String,
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.configureAutocomplete();
|
||||
this.watchCategoryChanges();
|
||||
this.watchDescriptionChanges();
|
||||
}
|
||||
|
||||
templates = {
|
||||
commonSectionHeader({ title, html }) {
|
||||
return html`
|
||||
<section class="aa-Source">
|
||||
<div class="aa-SourceHeader">
|
||||
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||
<div class="aa-SourceHeaderLine"></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
},
|
||||
partIncrementHeader({ title, html }) {
|
||||
return html`
|
||||
<section class="aa-Source">
|
||||
<div class="aa-SourceHeader">
|
||||
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||
<div class="aa-SourceHeaderLine"></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
},
|
||||
list({ html }) {
|
||||
return html`
|
||||
<ul class="aa-List" role="listbox"></ul>
|
||||
`;
|
||||
},
|
||||
item({ suggestion, description, html }) {
|
||||
return html`
|
||||
<li class="aa-Item" role="option" data-suggestion="${suggestion}" aria-selected="false">
|
||||
<div class="aa-ItemWrapper">
|
||||
<div class="aa-ItemContent">
|
||||
<div class="aa-ItemIcon aa-ItemIcon--noBorder">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="aa-ItemContentBody">
|
||||
<div class="aa-ItemContentTitle">${suggestion}</div>
|
||||
<div class="aa-ItemContentDescription">${description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
configureAutocomplete() {
|
||||
const inputField = this.inputTarget;
|
||||
const commonPrefixes = this.suggestionsValue.commonPrefixes || [];
|
||||
const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || [];
|
||||
const commonHeader = this.commonSectionHeaderValue;
|
||||
const partIncrementHeader = this.partIncrementHeaderValue;
|
||||
|
||||
if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return;
|
||||
|
||||
// Check whether the panel should be created at the update
|
||||
if (this.isPanelInitialized) {
|
||||
const existingPanel = inputField.parentNode.querySelector(".aa-Panel");
|
||||
if (existingPanel) {
|
||||
// Only remove the panel in the update phase
|
||||
|
||||
existingPanel.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Create panel
|
||||
const panel = document.createElement("div");
|
||||
panel.classList.add("aa-Panel");
|
||||
panel.style.display = "none";
|
||||
|
||||
// Create panel layout
|
||||
const panelLayout = document.createElement("div");
|
||||
panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable");
|
||||
|
||||
// Section for prefixes part increment
|
||||
if (prefixesPartIncrement.length) {
|
||||
const partIncrementSection = document.createElement("section");
|
||||
partIncrementSection.classList.add("aa-Source");
|
||||
|
||||
const partIncrementHeaderHtml = this.templates.partIncrementHeader({
|
||||
title: partIncrementHeader,
|
||||
html: String.raw,
|
||||
});
|
||||
partIncrementSection.innerHTML += partIncrementHeaderHtml;
|
||||
|
||||
const partIncrementList = document.createElement("ul");
|
||||
partIncrementList.classList.add("aa-List");
|
||||
partIncrementList.setAttribute("role", "listbox");
|
||||
|
||||
prefixesPartIncrement.forEach((prefix) => {
|
||||
const itemHTML = this.templates.item({
|
||||
suggestion: prefix.title,
|
||||
description: prefix.description,
|
||||
html: String.raw,
|
||||
});
|
||||
partIncrementList.innerHTML += itemHTML;
|
||||
});
|
||||
|
||||
partIncrementSection.appendChild(partIncrementList);
|
||||
panelLayout.appendChild(partIncrementSection);
|
||||
}
|
||||
|
||||
// Section for common prefixes
|
||||
if (commonPrefixes.length) {
|
||||
const commonSection = document.createElement("section");
|
||||
commonSection.classList.add("aa-Source");
|
||||
|
||||
const commonSectionHeader = this.templates.commonSectionHeader({
|
||||
title: commonHeader,
|
||||
html: String.raw,
|
||||
});
|
||||
commonSection.innerHTML += commonSectionHeader;
|
||||
|
||||
const commonList = document.createElement("ul");
|
||||
commonList.classList.add("aa-List");
|
||||
commonList.setAttribute("role", "listbox");
|
||||
|
||||
commonPrefixes.forEach((prefix) => {
|
||||
const itemHTML = this.templates.item({
|
||||
suggestion: prefix.title,
|
||||
description: prefix.description,
|
||||
html: String.raw,
|
||||
});
|
||||
commonList.innerHTML += itemHTML;
|
||||
});
|
||||
|
||||
commonSection.appendChild(commonList);
|
||||
panelLayout.appendChild(commonSection);
|
||||
}
|
||||
|
||||
panel.appendChild(panelLayout);
|
||||
inputField.parentNode.appendChild(panel);
|
||||
|
||||
inputField.addEventListener("focus", () => {
|
||||
panel.style.display = "block";
|
||||
});
|
||||
|
||||
inputField.addEventListener("blur", () => {
|
||||
setTimeout(() => {
|
||||
panel.style.display = "none";
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Selection of an item
|
||||
panelLayout.addEventListener("mousedown", (event) => {
|
||||
const target = event.target.closest("li");
|
||||
|
||||
if (target) {
|
||||
inputField.value = target.dataset.suggestion;
|
||||
panel.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
this.isPanelInitialized = true;
|
||||
};
|
||||
|
||||
watchCategoryChanges() {
|
||||
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||
this.previousCategoryId = Number(this.partCategoryIdValue);
|
||||
|
||||
if (categoryField) {
|
||||
categoryField.addEventListener("change", () => {
|
||||
const categoryId = Number(categoryField.value);
|
||||
const description = String(descriptionField?.value ?? '');
|
||||
|
||||
// Check whether the category has changed compared to the previous ID
|
||||
if (categoryId !== this.previousCategoryId) {
|
||||
this.fetchNewSuggestions(categoryId, description);
|
||||
this.previousCategoryId = categoryId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchDescriptionChanges() {
|
||||
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||
this.previousDescription = String(this.partDescriptionValue);
|
||||
|
||||
if (descriptionField) {
|
||||
descriptionField.addEventListener("input", () => {
|
||||
const categoryId = Number(categoryField.value);
|
||||
const description = String(descriptionField?.value ?? '');
|
||||
|
||||
// Check whether the description has changed compared to the previous one
|
||||
if (description !== this.previousDescription) {
|
||||
this.fetchNewSuggestions(categoryId, description);
|
||||
this.previousDescription = description;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchNewSuggestions(categoryId, description) {
|
||||
const baseUrl = this.suggestUrlValue;
|
||||
const partId = this.partIdValue;
|
||||
const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description;
|
||||
const encodedDescription = this.base64EncodeUtf8(truncatedDescription);
|
||||
const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : '');
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
this.suggestionsValue = data;
|
||||
this.configureAutocomplete();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Errors when loading the new IPN-suggestions:", error);
|
||||
});
|
||||
};
|
||||
|
||||
base64EncodeUtf8(text) {
|
||||
const utf8Bytes = new TextEncoder().encode(text);
|
||||
return btoa(String.fromCharCode(...utf8Bytes));
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import JSONFormatter from 'json-formatter-js';
|
||||
|
||||
/**
|
||||
* This controller implements an element that renders a JSON object as a collapsible tree.
|
||||
* The JSON object is passed as a data attribute.
|
||||
* You have to apply the controller to a div element or similar block element which can contain other elements.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
const depth_to_open = this.element.dataset.depthToOpen ?? 0;
|
||||
const json_string = this.element.dataset.json;
|
||||
const json_object = JSON.parse(json_string);
|
||||
|
||||
const formatter = new JSONFormatter(json_object, depth_to_open);
|
||||
|
||||
this.element.appendChild(formatter.render());
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import * as bootbox from "bootbox";
|
||||
import "../../css/components/bootbox_extensions.css";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
|
||||
static values = {
|
||||
message: String,
|
||||
title: String
|
||||
}
|
||||
|
||||
|
||||
|
||||
connect()
|
||||
{
|
||||
this._confirmed = false;
|
||||
|
||||
this.element.addEventListener('click', this._onClick.bind(this));
|
||||
}
|
||||
|
||||
_onClick(event)
|
||||
{
|
||||
|
||||
//If a user has not already confirmed the deletion, just let turbo do its work
|
||||
if (this._confirmed) {
|
||||
this._confirmed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const that = this;
|
||||
|
||||
bootbox.confirm({
|
||||
title: this.titleValue,
|
||||
message: this.messageValue,
|
||||
callback: (result) => {
|
||||
if (result) {
|
||||
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
|
||||
that._confirmed = true;
|
||||
|
||||
//Click the link
|
||||
that.element.click();
|
||||
} else {
|
||||
that._confirmed = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static values = {
|
||||
id: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.loadState()
|
||||
this.element.addEventListener('change', () => {
|
||||
this.saveState()
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
let value = localStorage.getItem(storageKey);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
this.element.checked = true
|
||||
}
|
||||
if (value === 'false') {
|
||||
this.element.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
|
||||
if (this.element.checked) {
|
||||
localStorage.setItem(storageKey, 'true');
|
||||
} else {
|
||||
localStorage.setItem(storageKey, 'false');
|
||||
}
|
||||
}
|
||||
|
||||
getStorageKey() {
|
||||
if (this.hasIdValue) {
|
||||
return 'persistent_checkbox_' + this.idValue
|
||||
}
|
||||
|
||||
return 'persistent_checkbox_' + this.element.id;
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
import { autocomplete } from '@algolia/autocomplete-js';
|
||||
//import "@algolia/autocomplete-theme-classic/dist/theme.css";
|
||||
import "../../css/components/autocomplete_bootstrap_theme.css";
|
||||
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
|
||||
import {marked} from "marked";
|
||||
|
||||
import {
|
||||
trans,
|
||||
SEARCH_PLACEHOLDER,
|
||||
SEARCH_SUBMIT,
|
||||
STATISTICS_PARTS
|
||||
} from '../../translator';
|
||||
|
||||
|
||||
/**
|
||||
* This controller is responsible for the search fields in the navbar and the homepage.
|
||||
* It uses the Algolia Autocomplete library to provide a fast and responsive search.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ["input"];
|
||||
|
||||
_autocomplete;
|
||||
|
||||
// Highlight the search query in the results
|
||||
_highlight = (text, query) => {
|
||||
if (!text) return text;
|
||||
if (!query) return text;
|
||||
|
||||
const HIGHLIGHT_PRE_TAG = '__aa-highlight__'
|
||||
const HIGHLIGHT_POST_TAG = '__/aa-highlight__'
|
||||
|
||||
const escaped = query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
const regex = new RegExp(escaped, 'gi');
|
||||
|
||||
return text.replace(regex, (match) => `${HIGHLIGHT_PRE_TAG}${match}${HIGHLIGHT_POST_TAG}`);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// The endpoint for searching parts
|
||||
const base_url = this.element.dataset.autocomplete;
|
||||
// The URL template for the part detail pages
|
||||
const part_detail_uri_template = this.element.dataset.detailUrl;
|
||||
|
||||
//The URL of the placeholder picture
|
||||
const placeholder_image = this.element.dataset.placeholderImage;
|
||||
|
||||
//If the element is in navbar mode, or not
|
||||
const navbar_mode = this.element.dataset.navbarMode === "true";
|
||||
|
||||
const that = this;
|
||||
|
||||
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
|
||||
key: 'RECENT_SEARCH',
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
this._autocomplete = autocomplete({
|
||||
container: this.element,
|
||||
//Place the panel in the navbar, if the element is in navbar mode
|
||||
panelContainer: navbar_mode ? document.getElementById("navbar-search-form") : document.body,
|
||||
panelPlacement: this.element.dataset.panelPlacement,
|
||||
plugins: [recentSearchesPlugin],
|
||||
openOnFocus: true,
|
||||
placeholder: trans(SEARCH_PLACEHOLDER),
|
||||
translations: {
|
||||
submitButtonTitle: trans(SEARCH_SUBMIT)
|
||||
},
|
||||
|
||||
// Use a navigator compatible with turbo:
|
||||
navigator: {
|
||||
navigate({ itemUrl }) {
|
||||
window.Turbo.visit(itemUrl, { action: "advance" });
|
||||
},
|
||||
navigateNewTab({ itemUrl }) {
|
||||
const windowReference = window.open(itemUrl, '_blank', 'noopener');
|
||||
|
||||
if (windowReference) {
|
||||
windowReference.focus();
|
||||
}
|
||||
},
|
||||
navigateNewWindow({ itemUrl }) {
|
||||
window.open(itemUrl, '_blank', 'noopener');
|
||||
},
|
||||
},
|
||||
|
||||
// If the form is submitted, forward the term to the form
|
||||
onSubmit({state, event, ...setters}) {
|
||||
//Put the current text into each target input field
|
||||
const input = that.inputTarget;
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Do not submit the form, if the input is empty
|
||||
if (state.query === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = state.query;
|
||||
input.form.requestSubmit();
|
||||
},
|
||||
|
||||
|
||||
getSources({ query }) {
|
||||
return [
|
||||
// The parts source
|
||||
{
|
||||
sourceId: 'parts',
|
||||
getItems() {
|
||||
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
|
||||
|
||||
const data = fetch(url)
|
||||
.then((response) => response.json())
|
||||
;
|
||||
|
||||
//Iterate over all fields besides the id and highlight them
|
||||
const fields = ["name", "description", "category", "footprint"];
|
||||
|
||||
data.then((items) => {
|
||||
items.forEach((item) => {
|
||||
for (const field of fields) {
|
||||
item[field] = that._highlight(item[field], query);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
getItemUrl({ item }) {
|
||||
return part_detail_uri_template.replace('__ID__', item.id);
|
||||
},
|
||||
templates: {
|
||||
header({ html }) {
|
||||
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PARTS)}</span>
|
||||
<div class="aa-SourceHeaderLine" />`;
|
||||
},
|
||||
item({item, components, html}) {
|
||||
const details_url = part_detail_uri_template.replace('__ID__', item.id);
|
||||
|
||||
return html`
|
||||
<a class="aa-ItemLink" href="${details_url}">
|
||||
<div class="aa-ItemContent">
|
||||
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
|
||||
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
|
||||
</div>
|
||||
<div class="aa-ItemContentBody">
|
||||
<div class="aa-ItemContentTitle">
|
||||
<b>
|
||||
${components.Highlight({hit: item, attribute: 'name'})}
|
||||
</b>
|
||||
</div>
|
||||
<div class="aa-ItemContentDescription">
|
||||
${components.Highlight({hit: item, attribute: 'description'})}
|
||||
${item.category ? html`<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span>${components.Highlight({hit: item, attribute: 'category'})}</p>` : ""}
|
||||
${item.footprint ? html`<p class="m-0"><span class="fa-solid fa-microchip fa-fw"></span>${components.Highlight({hit: item, attribute: 'footprint'})}</p>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
//Try to find the input field and register a defocus handler. This is necessarry, as by default the autocomplete
|
||||
//lib has problems when multiple inputs are present on the page. (see https://github.com/algolia/autocomplete/issues/1216)
|
||||
const inputs = this.element.getElementsByClassName('aa-Input');
|
||||
for (const input of inputs) {
|
||||
input.addEventListener('blur', () => {
|
||||
this._autocomplete.setIsOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
import {marked} from "marked";
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
//Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal)
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
plugins: ['dropdown_input'],
|
||||
searchField: ["name", "description", "category", "footprint"],
|
||||
valueField: "id",
|
||||
labelField: "name",
|
||||
dropdownParent: dropdownParent,
|
||||
preload: "focus",
|
||||
render: {
|
||||
item: (data, escape) => {
|
||||
return '<span>' + (data.image ? "<img style='height: 1.5rem; margin-right: 5px;' ' src='" + data.image + "'/>" : "") + escape(data.name) + '</span>';
|
||||
},
|
||||
option: (data, escape) => {
|
||||
if(data.text) {
|
||||
return '<span>' + escape(data.text) + '</span>';
|
||||
}
|
||||
|
||||
let tmp = '<div class="row m-0">' +
|
||||
"<div class='col-2 p-0 d-flex align-items-center' style='max-width: 80px;'>" +
|
||||
(data.image ? "<img class='typeahead-image' src='" + data.image + "'/>" : "") +
|
||||
"</div>" +
|
||||
"<div class='col-10'>" +
|
||||
'<h6 class="m-0">' + escape(data.name) + '</h6>' +
|
||||
(data.description ? '<p class="m-0">' + marked.parseInline(data.description) + '</p>' : "") +
|
||||
(data.category ? '<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span> ' + escape(data.category) : "");
|
||||
|
||||
if (data.footprint) { //If footprint is defined for the part show it next to the category
|
||||
tmp += ' <span class="fa-solid fa-microchip fa-fw"></span> ' + escape(data.footprint);
|
||||
}
|
||||
|
||||
return tmp + '</p>' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (this.element.dataset.autocomplete) {
|
||||
const base_url = this.element.dataset.autocomplete;
|
||||
settings.valueField = "id";
|
||||
settings.load = (query, callback) => {
|
||||
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {callback(json);})
|
||||
.catch(() => {
|
||||
callback()
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
//this._tomSelect.clearOptions();
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
|
||||
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
|
||||
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
|
||||
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
|
||||
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
|
||||
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
|
||||
import {trans, USER_PASSWORD_STRENGTH_VERY_WEAK, USER_PASSWORD_STRENGTH_WEAK, USER_PASSWORD_STRENGTH_MEDIUM,
|
||||
USER_PASSWORD_STRENGTH_STRONG, USER_PASSWORD_STRENGTH_VERY_STRONG} from '../../translator.js';
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
_passwordInput;
|
||||
|
||||
static targets = ["badge", "warning"]
|
||||
|
||||
_getTranslations() {
|
||||
//Get the current locale
|
||||
const locale = document.documentElement.lang;
|
||||
if (locale.includes('de')) {
|
||||
return zxcvbnDePackage.translations;
|
||||
} else if (locale.includes('fr')) {
|
||||
return zxcvbnFrPackage.translations;
|
||||
} else if (locale.includes('ja')) {
|
||||
return zxcvbnJaPackage.translations;
|
||||
}
|
||||
|
||||
//Fallback to english
|
||||
return zxcvbnEnPackage.translations;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//Find the password input field
|
||||
this._passwordInput = this.element.querySelector('input[type="password"]');
|
||||
|
||||
//Configure zxcvbn
|
||||
const options = {
|
||||
graphs: zxcvbnCommonPackage.adjacencyGraphs,
|
||||
dictionary: {
|
||||
...zxcvbnCommonPackage.dictionary,
|
||||
// We could use the english dictionary here too, but it is very big. So we just use the common words
|
||||
//...zxcvbnEnPackage.dictionary,
|
||||
},
|
||||
translations: this._getTranslations(),
|
||||
};
|
||||
zxcvbnOptions.setOptions(options);
|
||||
|
||||
//Add event listener to the password input field
|
||||
this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));
|
||||
}
|
||||
|
||||
_onPasswordInput() {
|
||||
//Retrieve the password
|
||||
const password = this._passwordInput.value;
|
||||
|
||||
//Estimate the password strength
|
||||
const result = zxcvbn(password);
|
||||
|
||||
//Update the badge
|
||||
this.badgeTarget.parentElement.classList.remove("d-none");
|
||||
this._setBadgeToLevel(result.score);
|
||||
|
||||
this.warningTarget.innerHTML = result.feedback.warning;
|
||||
}
|
||||
|
||||
_setBadgeToLevel(level) {
|
||||
let text, classes;
|
||||
|
||||
switch (level) {
|
||||
case 0:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_WEAK);
|
||||
classes = "bg-danger badge-danger";
|
||||
break;
|
||||
case 1:
|
||||
text = trans(USER_PASSWORD_STRENGTH_WEAK);
|
||||
classes = "bg-warning badge-warning";
|
||||
break;
|
||||
case 2:
|
||||
text = trans(USER_PASSWORD_STRENGTH_MEDIUM)
|
||||
classes = "bg-info badge-info";
|
||||
break;
|
||||
case 3:
|
||||
text = trans(USER_PASSWORD_STRENGTH_STRONG);
|
||||
classes = "bg-primary badge-primary";
|
||||
break;
|
||||
case 4:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_STRONG);
|
||||
classes = "bg-success badge-success";
|
||||
break;
|
||||
default:
|
||||
text = "???";
|
||||
classes = "bg-secondary badge-secondary";
|
||||
}
|
||||
|
||||
this.badgeTarget.innerHTML = text;
|
||||
//Remove all classes
|
||||
this.badgeTarget.className = '';
|
||||
//Re-add the classes
|
||||
this.badgeTarget.classList.add("badge");
|
||||
this.badgeTarget.classList.add(...classes.split(" "));
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["input"];
|
||||
|
||||
connect() {
|
||||
if(!this.inputTarget) {
|
||||
throw new Error("Target input not found");
|
||||
}
|
||||
}
|
||||
|
||||
load(event) {
|
||||
//Use the data-value attribute to load the value of our target input
|
||||
this.inputTarget.value = event.target.dataset.value;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
_tomSelect;
|
||||
|
||||
_emptyMessage;
|
||||
|
||||
connect() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
_init() {
|
||||
this._emptyMessage = this.element.getAttribute("data-empty-message") ?? "";
|
||||
if (this._emptyMessage === "" && this.element.hasAttribute('title')) {
|
||||
this._emptyMessage = this.element.getAttribute('title');
|
||||
}
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
plugins: ["clear_button"],
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
render: {
|
||||
item: this.renderItem.bind(this),
|
||||
option: this.renderOption.bind(this),
|
||||
}
|
||||
};
|
||||
|
||||
//Load the drag_drop plugin if the select is ordered
|
||||
if (this.element.dataset.orderedValue) {
|
||||
settings.plugins.push('drag_drop');
|
||||
settings.plugins.push("caret_position");
|
||||
}
|
||||
|
||||
//If multiple items can be selected, enable the remove_button plugin
|
||||
if (this.element.multiple) {
|
||||
settings.plugins.push('remove_button');
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
|
||||
//If the select is ordered, we need to update the value field (with the decoded value from the orderedValue field)
|
||||
if (this.element.dataset.orderedValue) {
|
||||
const data = JSON.parse(this.element.dataset.orderedValue);
|
||||
this._tomSelect.setValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
getTomSelect() {
|
||||
return this._tomSelect;
|
||||
}
|
||||
|
||||
renderItem(data, escape) {
|
||||
//The empty option is rendered muted
|
||||
if (data.value === "") {
|
||||
let text = data.text;
|
||||
//If no text was defined on the option, we use the empty message
|
||||
if (!text) {
|
||||
text = this._emptyMessage;
|
||||
}
|
||||
//And if that is not defined, we use a space to make the option visible
|
||||
if (!text) {
|
||||
text = " ";
|
||||
}
|
||||
return '<div class="text-muted">' + escape(text) + '</div>';
|
||||
|
||||
}
|
||||
|
||||
return '<div>' + escape(data.text) + '</div>';
|
||||
}
|
||||
|
||||
renderOption(data, escape) {
|
||||
//The empty option is rendered muted
|
||||
if (data.value === "" && data.text === "") {
|
||||
return '<div> </div>';
|
||||
}
|
||||
|
||||
return '<div>' + escape(data.text) + '</div>';
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
// TODO: Merge with select_controller.js
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, {
|
||||
maxItems: 1000,
|
||||
allowEmptyOption: true,
|
||||
dropdownParent: dropdownParent,
|
||||
plugins: ['remove_button'],
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import {default as TreeController} from "./tree_controller";
|
||||
|
||||
export default class extends TreeController {
|
||||
static targets = [ "tree", 'sourceText' ];
|
||||
|
||||
_storage_key;
|
||||
|
||||
_lastUpdate;
|
||||
|
||||
connect() {
|
||||
//Check if the tree is already initialized, if so then skip initialization (useful when going back) to in history using Turbo
|
||||
if(this._isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const default_mode = this.element.dataset.defaultMode;
|
||||
|
||||
this._storage_key = 'tree_' + this.element.id;
|
||||
|
||||
//Check if we have a saved mode
|
||||
const stored_mode = localStorage.getItem(this._storage_key);
|
||||
|
||||
//Use stored mode if possible, otherwise use default
|
||||
if(stored_mode) {
|
||||
try {
|
||||
this.setMode(stored_mode);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
//If an error happenes, use the default mode
|
||||
this.setMode(default_mode);
|
||||
}
|
||||
} else {
|
||||
this.setMode(default_mode);
|
||||
}
|
||||
|
||||
//Register an event listener which checks if the tree needs to be updated
|
||||
document.addEventListener('turbo:render', this.doUpdateIfNeeded.bind(this));
|
||||
}
|
||||
|
||||
doUpdateIfNeeded()
|
||||
{
|
||||
const info_element = document.getElementById('sidebar-last-time-updated');
|
||||
const date_str = info_element.dataset.lastUpdate;
|
||||
const server_last_update = new Date(date_str);
|
||||
|
||||
if(this._lastUpdate < server_last_update) {
|
||||
console.log("Sidebar tree is outdated, reloading (last update: " + this._lastUpdate + ", server update: " + server_last_update + ")");
|
||||
this._lastUpdate = new Date();
|
||||
|
||||
|
||||
|
||||
this.reinitTree();
|
||||
}
|
||||
}
|
||||
|
||||
setMode(mode) {
|
||||
//Find the button for this mode
|
||||
const modeButton = this.element.querySelector(`[data-mode="${mode}"]`);
|
||||
if(!modeButton) {
|
||||
throw new Error(`Could not find button for mode ${mode}`);
|
||||
}
|
||||
|
||||
//Get the url and text from the button
|
||||
const url = modeButton.dataset.url;
|
||||
const text = modeButton.dataset.text;
|
||||
|
||||
this.sourceTextTarget.innerText = text;
|
||||
|
||||
this.setURL(url);
|
||||
|
||||
//Update the last update time
|
||||
this._lastUpdate = new Date();
|
||||
}
|
||||
|
||||
changeDataSource(event)
|
||||
{
|
||||
const mode = event.params.mode ?? event.target.dataset.mode;
|
||||
const url = event.params.url ?? event.target.dataset.url;
|
||||
const text = event.params.text ?? event.target.dataset.text;
|
||||
|
||||
this.sourceTextTarget.innerText = text;
|
||||
|
||||
this.setURL(url);
|
||||
|
||||
//Save the mode in local storage
|
||||
localStorage.setItem(this._storage_key, mode);
|
||||
|
||||
this._lastUpdate = new Date();
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
/**
|
||||
* This is the frontend controller for StaticFileAutocompleteType form element.
|
||||
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
|
||||
* The file is just a list of strings, one per line, which will be used as the autocomplete options.
|
||||
* Lines starting with # will be ignored.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
maxItems: 1,
|
||||
maxOptions: 100,
|
||||
createOnBlur: true,
|
||||
selectOnTab: true,
|
||||
valueField: 'text',
|
||||
searchField: 'text',
|
||||
orderField: 'text',
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
'restore_on_backspace': {}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.element.dataset.url) {
|
||||
const url = this.element.dataset.url;
|
||||
settings.load = (query, callback) => {
|
||||
const self = this;
|
||||
if (self.loading > 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
// Convert the text file to array
|
||||
let lines = text.split("\n");
|
||||
//Remove all lines beginning with #
|
||||
lines = lines.filter(x => !x.startsWith("#"));
|
||||
|
||||
//Convert the array to an object, where each line is in the text field
|
||||
lines = lines.map(x => {
|
||||
return {text: x};
|
||||
});
|
||||
|
||||
|
||||
//Unset the load function to prevent endless recursion
|
||||
self._tomSelect.settings.load = null;
|
||||
|
||||
callback(lines);
|
||||
}).catch(() => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
|
||||
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
_emptyMessage;
|
||||
|
||||
connect() {
|
||||
|
||||
//Extract empty message from data attribute
|
||||
this._emptyMessage = this.element.getAttribute("data-empty-message") ?? "";
|
||||
|
||||
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
|
||||
const addHint = this.element.getAttribute("data-add-hint") ?? "";
|
||||
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
create: allowAdd ? this.createItem.bind(this) : false,
|
||||
createFilter: this.createFilter.bind(this),
|
||||
|
||||
// This three options allow us to paste element names with commas: (see issue #538)
|
||||
maxItems: 1,
|
||||
delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$",
|
||||
splitOn: null,
|
||||
dropdownParent: dropdownParent,
|
||||
|
||||
searchField: [
|
||||
{field: "text", weight : 2},
|
||||
{field: "parent", weight : 0.5},
|
||||
{field: "path", weight : 1.0},
|
||||
],
|
||||
|
||||
render: {
|
||||
item: this.renderItem.bind(this),
|
||||
option: this.renderOption.bind(this),
|
||||
option_create: (data, escape) => {
|
||||
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
|
||||
//This here handles the display part, while the createItem function handles the actual creation
|
||||
if (data.input.startsWith("->")) {
|
||||
//Get current selected value
|
||||
const current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
|
||||
//Prepend it to the input
|
||||
if (current) {
|
||||
data.input = current + " " + data.input;
|
||||
} else {
|
||||
//If there is no current value, we remove the "->"
|
||||
data.input = data.input.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
return '<div class="create"><i class="fa-solid fa-plus fa-fw"></i> <strong>' + escape(data.input) + '</strong>… ' +
|
||||
'<small class="text-muted float-end">(' + addHint +')</small>' +
|
||||
'</div>';
|
||||
},
|
||||
},
|
||||
|
||||
//Add callbacks to update validity
|
||||
onInitialize: this.updateValidity.bind(this),
|
||||
onChange: this.updateValidity.bind(this),
|
||||
|
||||
plugins: {
|
||||
"autoselect_typed": {},
|
||||
}
|
||||
};
|
||||
|
||||
//Add clear button plugin, if an empty option is present
|
||||
if (this.element.querySelector("option[value='']") !== null) {
|
||||
settings.plugins["clear_button"] = {};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
//Do not do a sync here as this breaks the initial rendering of the empty option
|
||||
//this._tomSelect.sync();
|
||||
}
|
||||
|
||||
createItem(input, callback) {
|
||||
|
||||
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
|
||||
if (input.startsWith("->")) {
|
||||
//Get current selected value
|
||||
let current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
|
||||
//Replace no break spaces with normal spaces
|
||||
current = current.replaceAll("\u00A0", " ");
|
||||
//Prepend it to the input
|
||||
if (current) {
|
||||
input = current + " " + input;
|
||||
} else {
|
||||
//If there is no current value, we remove the "->"
|
||||
input = input.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
callback({
|
||||
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
|
||||
value: '$%$' + input,
|
||||
text: input,
|
||||
not_in_db_yet: true,
|
||||
});
|
||||
}
|
||||
|
||||
createFilter(input) {
|
||||
|
||||
//Normalize the input (replace spacing around arrows)
|
||||
if (input.includes("->")) {
|
||||
const inputs = input.split("->");
|
||||
inputs.forEach((value, index) => {
|
||||
inputs[index] = value.trim();
|
||||
});
|
||||
input = inputs.join("->");
|
||||
} else {
|
||||
input = input.trim();
|
||||
}
|
||||
|
||||
const options = this._tomSelect.options;
|
||||
//Iterate over all options and check if the input is already present
|
||||
for (let index in options) {
|
||||
const option = options[index];
|
||||
if (option.path === input) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
updateValidity() {
|
||||
//Mark this input as invalid, if the selected option is disabled
|
||||
|
||||
const input = this.element;
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
|
||||
if (selectedOption && selectedOption.disabled) {
|
||||
input.setCustomValidity("This option was disabled. Please select another option.");
|
||||
} else {
|
||||
input.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
|
||||
getTomSelect() {
|
||||
return this._tomSelect;
|
||||
}
|
||||
|
||||
renderItem(data, escape) {
|
||||
//Render empty option as full row
|
||||
if (data.value === "") {
|
||||
if (this._emptyMessage) {
|
||||
return '<div class="tom-select-empty-option"><span class="text-muted"><b>' + escape(this._emptyMessage) + '</b></span></div>';
|
||||
} else {
|
||||
return '<div> </div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (data.short) {
|
||||
let short = escape(data.short)
|
||||
|
||||
//Make text italic, if the item is not yet in the DB
|
||||
if (data.not_in_db_yet) {
|
||||
short = '<i>' + short + '</i>';
|
||||
}
|
||||
|
||||
return '<div><b>' + short + '</b></div>';
|
||||
}
|
||||
|
||||
let name = "";
|
||||
if (data.parent) {
|
||||
name += escape(data.parent) + " → ";
|
||||
}
|
||||
|
||||
if (data.not_in_db_yet) {
|
||||
//Not yet added items are shown italic and with a badge
|
||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
|
||||
} else {
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
}
|
||||
|
||||
return '<div>' + (data.image ? "<img class='structural-entity-select-image' style='margin-right: 5px;' ' src='" + data.image + "'/>" : "") + name + '</div>';
|
||||
}
|
||||
|
||||
renderOption(data, escape) {
|
||||
//Render empty option as full row
|
||||
if (data.value === "") {
|
||||
if (this._emptyMessage) {
|
||||
return '<div class="tom-select-empty-option"><span class="text-muted">' + escape(this._emptyMessage) + '</span></div>';
|
||||
} else {
|
||||
return '<div> </div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Indent the option according to the level
|
||||
let level_html = ' '.repeat(data.level);
|
||||
|
||||
let filter_badge = "";
|
||||
if (data.filetype_filter) {
|
||||
filter_badge = '<span class="badge bg-warning float-end"><i class="fa-solid fa-file-circle-exclamation"></i> ' + escape(data.filetype_filter) + '</span>';
|
||||
}
|
||||
|
||||
let symbol_badge = "";
|
||||
if (data.symbol) {
|
||||
symbol_badge = '<span class="badge bg-primary ms-2">' + escape(data.symbol) + '</span>';
|
||||
}
|
||||
|
||||
let parent_badge = "";
|
||||
if (data.parent) {
|
||||
parent_badge = '<span class="ms-3 badge rounded-pill bg-secondary float-end picker-us"><i class="fa-solid fa-folder-tree"></i> ' + escape(data.parent) + '</span>';
|
||||
}
|
||||
|
||||
let image = "";
|
||||
if (data.image) {
|
||||
image = '<img class="structural-entity-select-image" style="margin-left: 5px;" src="' + data.image + '"/>';
|
||||
}
|
||||
|
||||
return '<div>' + level_html + escape(data.text) + image + symbol_badge + parent_badge + filter_badge + '</div>';
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let dropdownParent = "body";
|
||||
if (this.element.closest('.modal')) {
|
||||
dropdownParent = null
|
||||
}
|
||||
|
||||
let settings = {
|
||||
plugins: {
|
||||
remove_button:{},
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
},
|
||||
persistent: false,
|
||||
selectOnTab: true,
|
||||
createOnBlur: true,
|
||||
create: true,
|
||||
dropdownParent: dropdownParent,
|
||||
};
|
||||
|
||||
if(this.element.dataset.autocomplete) {
|
||||
const base_url = this.element.dataset.autocomplete;
|
||||
settings.load = (query, callback) => {
|
||||
if(query.length < 2){
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
const data = json.map(x => {return {"value": x, "text": x}});
|
||||
callback(data);
|
||||
}).catch(()=>{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import {BSTreeView, BSTreeViewNode, BS5Theme, FAIconTheme, EVENT_INITIALIZED} from "@jbtronics/bs-treeview";
|
||||
import "@jbtronics/bs-treeview/styles/bs-treeview.css";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "tree" ];
|
||||
|
||||
/** @type {string} */
|
||||
_url = null;
|
||||
/** @type {BSTreeViewNode[]} */
|
||||
_data = null;
|
||||
|
||||
/** @type {boolean} */
|
||||
_showTags = false;
|
||||
|
||||
/**
|
||||
* @type {BSTreeView}
|
||||
* @private
|
||||
*/
|
||||
_tree = null;
|
||||
|
||||
connect() {
|
||||
const treeElement = this.treeTarget;
|
||||
if (!treeElement) {
|
||||
console.error("You need to define a tree target for the controller!");
|
||||
return;
|
||||
}
|
||||
|
||||
this._url = this.element.dataset.treeUrl;
|
||||
this._data = this.element.dataset.treeData;
|
||||
|
||||
if(this.element.dataset.treeShowTags === "true") {
|
||||
this._showTags = true;
|
||||
}
|
||||
|
||||
this.reinitTree();
|
||||
}
|
||||
|
||||
reinitTree()
|
||||
{
|
||||
//Fetch data and initialize tree
|
||||
this._getData()
|
||||
.then(this._fillTree.bind(this))
|
||||
.catch((err) => {
|
||||
console.error("Could not load the tree data: " + err);
|
||||
});
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this._data = data;
|
||||
this.reinitTree();
|
||||
}
|
||||
|
||||
setURL(url) {
|
||||
this._url = url;
|
||||
this.reinitTree();
|
||||
}
|
||||
|
||||
_fillTree(data) {
|
||||
if(this._tree) {
|
||||
this._tree.remove();
|
||||
}
|
||||
|
||||
const BS53Theme = {
|
||||
getOptions() {
|
||||
return {
|
||||
onhoverColor: 'var(--bs-secondary-bg)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this._tree = new BSTreeView(this.treeTarget, {
|
||||
levels: 1,
|
||||
showTags: this._showTags,
|
||||
data: data,
|
||||
showIcon: true,
|
||||
preventUnselect: true,
|
||||
allowReselect: true,
|
||||
onNodeSelected: (event) => {
|
||||
const node = event.detail.node;
|
||||
if (node.href) {
|
||||
window.Turbo.visit(node.href, {action: "advance"});
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
},
|
||||
}, [BS5Theme, BS53Theme, FAIconTheme]);
|
||||
|
||||
this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => {
|
||||
/** @type {BSTreeView} */
|
||||
const treeView = event.detail.treeView;
|
||||
treeView.revealNode(treeView.getSelected());
|
||||
|
||||
//Add the url watcher to all selected nodes
|
||||
for (const node of treeView.getSelected()) {
|
||||
this._registerURLWatcher(node);
|
||||
}
|
||||
|
||||
//Add contextmenu event listener to the tree, which allows us to open the links in a new tab with a right click
|
||||
treeView.getTreeElement().addEventListener("contextmenu", this._onContextMenu.bind(this));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_registerURLWatcher(node)
|
||||
{
|
||||
//Register a watcher for a location change, which will unselect the node, if the location changes
|
||||
const desired_url = node.href;
|
||||
|
||||
//Ensure that the node is unselected, if the location changes
|
||||
const unselectNode = () => {
|
||||
//Parse url so we can properly compare them
|
||||
const desired = new URL(node.href, window.location.origin);
|
||||
|
||||
//We only compare the pathname, because the hash and parameters should not matter
|
||||
if(window.location.pathname !== desired.pathname) {
|
||||
//The ignore parameter is important here, otherwise the node will not be unselected
|
||||
node.setSelected(false, {silent: true, ignorePreventUnselect: true});
|
||||
|
||||
//Unregister the watcher
|
||||
document.removeEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
};
|
||||
|
||||
//Register the watcher via hotwire turbo
|
||||
//We must just load to have the new url in window.location
|
||||
document.addEventListener('turbo:load', unselectNode);
|
||||
}
|
||||
|
||||
_onContextMenu(event)
|
||||
{
|
||||
//Find the node that was clicked and open link in new tab
|
||||
const node = this._tree._domToNode(event.target);
|
||||
if(node && node.href) {
|
||||
event.preventDefault();
|
||||
window.open(node.href, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
collapseAll() {
|
||||
this._tree.collapseAll({silent: true});
|
||||
}
|
||||
|
||||
expandAll() {
|
||||
this._tree.expandAll({silent: true});
|
||||
}
|
||||
|
||||
searchInput(event) {
|
||||
const data = event.target.value;
|
||||
//Do nothing if no data was passed
|
||||
|
||||
const tree = this.treeTarget;
|
||||
this._tree.collapseAll({silent: true});
|
||||
this._tree.search(data);
|
||||
|
||||
//Rereveal the selected node again
|
||||
this._tree.revealNode(this._tree.getSelected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the tree is already initialized (meaning bootstrap treeview was called on the object)
|
||||
* @private
|
||||
*/
|
||||
_isInitialized() {
|
||||
return this._tree !== null;
|
||||
}
|
||||
|
||||
_getData() {
|
||||
//Use lambda function to preserve this context
|
||||
return new Promise((myResolve, myReject) => {
|
||||
//If a url is defined, fetch the data from the url
|
||||
if (this._url) {
|
||||
return fetch(this._url)
|
||||
.then((response) => myResolve(response.json()))
|
||||
.catch((err) => myReject(err));
|
||||
}
|
||||
|
||||
//Otherwise load the data provided via the data attribute
|
||||
return myResolve(this._data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["tbody", "addButton", "submitButton"]
|
||||
static values = {
|
||||
mappingIndex: Number,
|
||||
maxMappings: Number,
|
||||
prototype: String,
|
||||
maxMappingsReachedMessage: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.updateAddButtonState()
|
||||
this.updateFieldOptions()
|
||||
this.attachEventListeners()
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
// Add event listeners to existing field selects
|
||||
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
|
||||
fieldSelects.forEach(select => {
|
||||
select.addEventListener('change', this.updateFieldOptions.bind(this))
|
||||
})
|
||||
|
||||
// Note: Add button click is handled by Stimulus action in template (data-action="click->field-mapping#addMapping")
|
||||
// No manual event listener needed
|
||||
|
||||
// Form submit handler
|
||||
const form = this.element.querySelector('form')
|
||||
if (form && this.hasSubmitButtonTarget) {
|
||||
form.addEventListener('submit', this.handleFormSubmit.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
addMapping() {
|
||||
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
|
||||
|
||||
if (currentMappings >= this.maxMappingsValue) {
|
||||
alert(this.maxMappingsReachedMessageValue)
|
||||
return
|
||||
}
|
||||
|
||||
const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue)
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.innerHTML = newRowHtml
|
||||
|
||||
const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0]
|
||||
const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1]
|
||||
const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2]
|
||||
|
||||
const newRow = document.createElement('tr')
|
||||
newRow.className = 'mapping-row'
|
||||
newRow.innerHTML = `
|
||||
<td>${fieldWidget ? fieldWidget.outerHTML : ''}</td>
|
||||
<td>${providerWidget ? providerWidget.outerHTML : ''}</td>
|
||||
<td>${priorityWidget ? priorityWidget.outerHTML : ''}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-action="click->field-mapping#removeMapping">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
`
|
||||
|
||||
this.tbodyTarget.appendChild(newRow)
|
||||
this.mappingIndexValue++
|
||||
|
||||
const newFieldSelect = newRow.querySelector('select[name*="[field]"]')
|
||||
if (newFieldSelect) {
|
||||
newFieldSelect.value = ''
|
||||
newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this))
|
||||
}
|
||||
|
||||
this.updateFieldOptions()
|
||||
this.updateAddButtonState()
|
||||
}
|
||||
|
||||
removeMapping(event) {
|
||||
const row = event.target.closest('tr')
|
||||
row.remove()
|
||||
this.updateFieldOptions()
|
||||
this.updateAddButtonState()
|
||||
}
|
||||
|
||||
updateFieldOptions() {
|
||||
const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]')
|
||||
|
||||
const selectedFields = Array.from(fieldSelects)
|
||||
.map(select => select.value)
|
||||
.filter(value => value && value !== '')
|
||||
|
||||
fieldSelects.forEach(select => {
|
||||
Array.from(select.options).forEach(option => {
|
||||
const isCurrentValue = option.value === select.value
|
||||
const isEmptyOption = !option.value || option.value === ''
|
||||
const isAlreadySelected = selectedFields.includes(option.value)
|
||||
|
||||
if (!isEmptyOption && isAlreadySelected && !isCurrentValue) {
|
||||
option.disabled = true
|
||||
option.style.display = 'none'
|
||||
} else {
|
||||
option.disabled = false
|
||||
option.style.display = ''
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateAddButtonState() {
|
||||
const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length
|
||||
|
||||
if (this.hasAddButtonTarget) {
|
||||
if (currentMappings >= this.maxMappingsValue) {
|
||||
this.addButtonTarget.disabled = true
|
||||
this.addButtonTarget.title = this.maxMappingsReachedMessageValue
|
||||
} else {
|
||||
this.addButtonTarget.disabled = false
|
||||
this.addButtonTarget.title = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleFormSubmit(event) {
|
||||
if (this.hasSubmitButtonTarget) {
|
||||
this.submitButtonTarget.disabled = true
|
||||
|
||||
// Disable the entire form to prevent changes during processing
|
||||
const form = event.target
|
||||
const formElements = form.querySelectorAll('input, select, textarea, button')
|
||||
formElements.forEach(element => {
|
||||
if (element !== this.submitButtonTarget) {
|
||||
element.disabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ["operator", "thingsToHide"];
|
||||
|
||||
connect() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility state of the value2 input, based on the operator selection.
|
||||
*/
|
||||
update()
|
||||
{
|
||||
const two_element_values = [
|
||||
"BETWEEN",
|
||||
'RANGE_IN_RANGE',
|
||||
'RANGE_INTERSECT_RANGE'
|
||||
];
|
||||
|
||||
for (const thingToHide of this.thingsToHideTargets) {
|
||||
thingToHide.classList.toggle("d-none", !two_element_values.includes(this.operatorTarget.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
assets/controllers/hello_controller.js
Normal file
16
assets/controllers/hello_controller.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Controller } from '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';
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
/**
|
||||
* Purpose of this controller is to clean up the form before it is finally submitted. This means empty fields get disabled, so they are not submitted.
|
||||
* This is especially useful for GET forms, to prevent very long URLs
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
/**
|
||||
* Call during the submit event of the form. This will disable all empty fields, so they are not submitted.
|
||||
* @param event
|
||||
*/
|
||||
submit(event) {
|
||||
/** Find the form this event belongs to */
|
||||
/** @type {HTMLFormElement} */
|
||||
const form = event.target.closest('form');
|
||||
|
||||
for(const element of form.elements) {
|
||||
if(! element.value) {
|
||||
element.disabled = true;
|
||||
}
|
||||
|
||||
//Workaround for tristate checkboxes which use a hidden field to store the value
|
||||
if ((element.type === 'hidden' || element.type === 'checkbox') && element.value === 'indeterminate') {
|
||||
element.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the form with all form elements disabled, so they are not submitted. This is useful for GET forms, to reset the form to not filled state.
|
||||
* @param event
|
||||
*/
|
||||
clearAll(event)
|
||||
{
|
||||
const form = event.target.closest('form');
|
||||
for(const element of form.elements) {
|
||||
// Do not clear elements with data-no-clear attribute
|
||||
if(element.dataset.noClear) {
|
||||
continue;
|
||||
}
|
||||
|
||||
element.disabled = true;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ "display", "select" ]
|
||||
|
||||
connect()
|
||||
{
|
||||
this.update();
|
||||
this.selectTarget.addEventListener('change', this.update.bind(this));
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
//If the select value is 0, then we show the input field
|
||||
if( this.selectTarget.value === '0')
|
||||
{
|
||||
this.displayTarget.classList.remove('d-none');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.displayTarget.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
//import * as ZXing from "@zxing/library";
|
||||
|
||||
import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode";
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
//codeReader = null;
|
||||
|
||||
_scanner = null;
|
||||
|
||||
|
||||
connect() {
|
||||
console.log('Init Scanner');
|
||||
|
||||
//This function ensures, that the qrbox is 70% of the total viewport
|
||||
let qrboxFunction = function(viewfinderWidth, viewfinderHeight) {
|
||||
let minEdgePercentage = 0.7; // 70%
|
||||
let minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
|
||||
let qrboxSize = Math.floor(minEdgeSize * minEdgePercentage);
|
||||
return {
|
||||
width: qrboxSize,
|
||||
height: qrboxSize
|
||||
};
|
||||
}
|
||||
|
||||
//Try to get the number of cameras. If the number is 0, then the promise will fail, and we show the warning dialog
|
||||
Html5Qrcode.getCameras().catch((devices) => {
|
||||
document.getElementById('scanner-warning').classList.remove('d-none');
|
||||
});
|
||||
|
||||
this._scanner = new Html5QrcodeScanner(this.element.id, {
|
||||
fps: 10,
|
||||
qrbox: qrboxFunction,
|
||||
experimentalFeatures: {
|
||||
//This option improves reading quality on android chrome
|
||||
useBarCodeDetectorIfSupported: true
|
||||
}
|
||||
}, false);
|
||||
|
||||
this._scanner.render(this.onScanSuccess.bind(this));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._scanner.pause();
|
||||
this._scanner.clear();
|
||||
}
|
||||
|
||||
onScanSuccess(decodedText, decodedResult) {
|
||||
//Put our decoded Text into the input box
|
||||
document.getElementById('scan_dialog_input').value = decodedText;
|
||||
//Submit form
|
||||
document.getElementById('scan_dialog_form').submit();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
/*
|
||||
* Define this controller on a checkbox, which should be used as a master to select/deselect all other checkboxes
|
||||
* with the same data-multicheck-name attribute.
|
||||
*/
|
||||
export default class extends Controller
|
||||
{
|
||||
connect() {
|
||||
this.element.addEventListener("change", this.toggleAll.bind(this));
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
//Retrieve all checkboxes, which have the same data-multicheck-name attribute as the current checkbox
|
||||
const checkboxes = document.querySelectorAll(`input[type="checkbox"][data-multicheck-name="${this.element.dataset.multicheckName}"]`);
|
||||
for (let checkbox of checkboxes) {
|
||||
checkbox.checked = this.element.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
/**
|
||||
* This controller is used on a checkbox, which toggles the max value of all number input fields
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
_checkbox;
|
||||
|
||||
getCheckbox() {
|
||||
if (this._checkbox) {
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
//Find the checkbox inside the controller element
|
||||
this._checkbox = this.element.querySelector('input[type="checkbox"]');
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//Add event listener to the checkbox
|
||||
this.getCheckbox().addEventListener('change', this.toggleInputLimits.bind(this));
|
||||
}
|
||||
|
||||
toggleInputLimits() {
|
||||
//Find all input fields with the data-toggle-input-limits-target="max"
|
||||
const inputFields = document.querySelectorAll("input[type='number']");
|
||||
|
||||
inputFields.forEach((inputField) => {
|
||||
//Ensure that the input field has either a max or a data-max attribute
|
||||
if (!inputField.hasAttribute('max') && !inputField.hasAttribute('data-max')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//If the checkbox is checked, rename the max attribute to data-max
|
||||
if (this.getCheckbox().checked) {
|
||||
inputField.setAttribute('data-max', inputField.getAttribute('max'));
|
||||
inputField.removeAttribute('max');
|
||||
} else {
|
||||
//If the checkbox is not checked, rename the data-max attribute back to max
|
||||
inputField.setAttribute('max', inputField.getAttribute('data-max'));
|
||||
inputField.removeAttribute('data-max');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
download(event) {
|
||||
this.element.href = document.getElementById('pdf_preview').data
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import katex from "katex";
|
||||
import "katex/dist/katex.css";
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "preview"];
|
||||
|
||||
static values = {
|
||||
unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units
|
||||
}
|
||||
|
||||
updatePreview()
|
||||
{
|
||||
let value = "";
|
||||
if (this.unitValue) {
|
||||
//Escape percentage signs
|
||||
value = this.inputTarget.value.replace(/%/g, '\\%');
|
||||
|
||||
value = "\\mathrm{" + value + "}";
|
||||
} else {
|
||||
value = this.inputTarget.value;
|
||||
}
|
||||
|
||||
katex.render(value, this.previewTarget, {
|
||||
throwOnError: false,
|
||||
});
|
||||
}
|
||||
|
||||
connect()
|
||||
{
|
||||
this.updatePreview();
|
||||
this.inputTarget.addEventListener('input', this.updatePreview.bind(this));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user