diff --git a/.gitignore b/.gitignore index d50a09fc9169b4517adb37c586c510db9cd4cf8b..f44dd86dd947e16ac740ded342627096007ed8c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env __pycache__/ +server/templates/* diff --git a/Makefile b/Makefile index 333c386c6c865863498542d6e391c789417fb353..f1f4b3cb66bc64e93c66637c2507496d3b6aa92b 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ venv: install: venv cd server && \ ${VENV}/bin/pip install -r requirements/base.txt - + cd frontend && \ npm install diff --git a/VERSION b/VERSION index 227cea215648b1af34a87c9acf5b707fe02d2072..7ec1d6db40877765247db18e7f9a4e36a0def4ad 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0 +2.1.0 diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json index 66e23359c3dabfe3929b4e2fa049c41037afb15f..ecdf3e07a7b3261f09531736cffbd41ccbb28795 100644 --- a/frontend/.prettierrc.json +++ b/frontend/.prettierrc.json @@ -5,4 +5,4 @@ "singleQuote": true, "printWidth": 100, "trailingComma": "none" -} \ No newline at end of file +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c7e522ee541c30748fba4754181a6a3525946c4..8e9304506eb20849ca54ebcfacba05258540c949 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,6 +29,15 @@ "vite": "^4.3.4" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -55,47 +64,47 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", - "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", - "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-compilation-targets": "^7.21.5", - "@babel/helper-module-transforms": "^7.21.5", - "@babel/helpers": "^7.21.5", - "@babel/parser": "^7.21.8", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.2" }, "engines": { "node": ">=6.9.0" @@ -106,12 +115,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -121,28 +130,28 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", - "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.21.5", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "@babel/compat-data": "^7.22.6", + "@babel/helper-validator-option": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" }, "engines": { "node": ">=6.9.0" @@ -152,20 +161,20 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz", - "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz", + "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.5", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.21.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6", - "semver": "^6.3.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@nicolo-ribaudo/semver-v6": "^6.3.3" }, "engines": { "node": ">=6.9.0" @@ -175,89 +184,89 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz", - "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", - "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-module-imports": "^7.21.4", - "@babel/helper-simple-access": "^7.21.5", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -273,106 +282,106 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz", - "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-member-expression-to-functions": "^7.21.5", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", - "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dev": true, "dependencies": { - "@babel/types": "^7.20.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", - "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -381,9 +390,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz", + "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -440,33 +449,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz", + "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -475,13 +484,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1031,9 +1040,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "optional": true, "dependencies": { "lru-cache": "^6.0.0" @@ -1051,6 +1060,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "optional": true }, + "node_modules/@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1533,9 +1551,9 @@ "optional": true }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "funding": [ { @@ -1545,13 +1563,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -1591,9 +1613,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001486", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", - "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", + "version": "1.0.30001512", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", + "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", "dev": true, "funding": [ { @@ -1934,9 +1956,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.388", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.388.tgz", - "integrity": "sha512-xZ0y4zjWZgp65okzwwt00f2rYibkFPHUv9qBz+Vzn8cB9UXIo9Zc6Dw81LJYhhNt0G/vR1OJEfStZ49NKl0YxQ==", + "version": "1.4.449", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.449.tgz", + "integrity": "sha512-TxLRpRUj/107ATefeP8VIUWNOv90xJxZZbCW/eIbSZQiuiFANCx2b7u+GbVc9X4gU+xnbvypNMYVM/WArE1DNQ==", "dev": true }, "node_modules/emoji-regex": { @@ -2197,9 +2219,9 @@ } }, "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3348,9 +3370,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", "dev": true }, "node_modules/nopt": { @@ -3444,17 +3466,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -3956,7 +3978,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, + "optional": true, "bin": { "semver": "bin/semver.js" } @@ -4499,9 +4521,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4643,7 +4665,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "devOptional": true, + "optional": true, "engines": { "node": ">=0.10.0" } diff --git a/frontend/src/assets/news_sources/rozhlas.png b/frontend/src/assets/news_sources/rozhlas.png new file mode 100644 index 0000000000000000000000000000000000000000..7edb2d77f158b1a1f411fe385fb0f6a9b2918c42 Binary files /dev/null and b/frontend/src/assets/news_sources/rozhlas.png differ diff --git a/frontend/src/components/canvas/Canvas.vue b/frontend/src/components/canvas/Canvas.vue index 729e5a7e3b0a660d55537227cb4b986b57740462..6b535cdeb6c0fbe85a613e28caae4a272b4d313c 100644 --- a/frontend/src/components/canvas/Canvas.vue +++ b/frontend/src/components/canvas/Canvas.vue @@ -44,7 +44,7 @@ export default { this.canvas.discardActiveObject().renderAll() let link = document.createElement('a') - + link.download = "Vyhrajem.png" link.href = this.$refs.canvas.toDataURL() @@ -78,7 +78,8 @@ export default { <div class="object-contain"> <canvas ref="canvas" - class="w-full border border-gray-300 drop-shadow-md" + class="w-full border border-gray-300 drop-shadow-md duration-150" + :class="{'blur': redrawing}" :width="width" :height="height" ></canvas> diff --git a/frontend/src/components/canvas/textbox.js b/frontend/src/components/canvas/textbox.js index 0c040b377f353a73b0e9f15cb0f7893cce289ee4..5954a3b33d9e53e27ab2b622410fac8874c7706c 100644 --- a/frontend/src/components/canvas/textbox.js +++ b/frontend/src/components/canvas/textbox.js @@ -56,7 +56,7 @@ class PaddedHighlightingTextbox extends fabric.Textbox { if (this.direction === 'rtl') { drawStart = this.width - drawStart - boxWidth; } - + ctx.fillStyle = lastColor; lastColor && ctx.fillRect( drawStart, diff --git a/frontend/src/components/canvas/utils.js b/frontend/src/components/canvas/utils.js index 8d42301cc16d519ca49cf97c70a8607da567f719..073b008e5043220fa6c0193436913a8c0131796a 100644 --- a/frontend/src/components/canvas/utils.js +++ b/frontend/src/components/canvas/utils.js @@ -16,7 +16,7 @@ const clearObjects = (clearableItems, canvas) => { canvas.remove(clearableItem); } } -} +} const sortObjects = (canvas) => { canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1) diff --git a/frontend/src/components/inputs/text/ShortTextInput.vue b/frontend/src/components/inputs/text/ShortTextInput.vue index 0d95fb60a8789fad1cf86f1706986410fd4e8349..6a35e7da14ef9e4561a128e1774e62f40f8bf662 100644 --- a/frontend/src/components/inputs/text/ShortTextInput.vue +++ b/frontend/src/components/inputs/text/ShortTextInput.vue @@ -8,9 +8,6 @@ export default { components: { InputHeading }, props: ['name', 'important', 'zIndex', 'relatedModel', 'predefinedValues', 'modelValue'], emits: ['update:modelValue', 'update:relatedModel'], - mounted () { - console.log(this.predefinedValues) - }, methods: { emitChanges (event) { this.$emit('update:modelValue', sanitizeValue(event.currentTarget.value)) diff --git a/frontend/src/components/inputs/text/utils.js b/frontend/src/components/inputs/text/utils.js index f9869c30420168e8b9f023e6cc809f2c0ab67df7..2fc05e2544dd186e1d96061cdefc977cc1cc1886 100644 --- a/frontend/src/components/inputs/text/utils.js +++ b/frontend/src/components/inputs/text/utils.js @@ -4,6 +4,6 @@ const sanitizeValue = (value) => { } return value -} +} export { sanitizeValue } diff --git a/frontend/src/components/reload/AutoReloadCheckbox.vue b/frontend/src/components/reload/AutoReloadCheckbox.vue new file mode 100644 index 0000000000000000000000000000000000000000..679a8082b4e05aa63dba8e92e7bfe263bf736f04 --- /dev/null +++ b/frontend/src/components/reload/AutoReloadCheckbox.vue @@ -0,0 +1,20 @@ +<script> + export default { + props: ['modelValue'], + emits: ['update:modelValue'] + } +</script> + +<template> + <div class="flex gap-3 justify-end mb-3"> + <input + id="auto-reload-checkbox" + name="auto-reload-checkbox" + type="checkbox" + @input="$emit('update:modelValue', $event.target.value)" + > + <label + for="auto-reload-checkbox" + >Obnovovat automaticky (může zatížit prohlížeč)</label> + </div> +</template> diff --git a/frontend/src/components/reload/ReloadButton.vue b/frontend/src/components/reload/ReloadButton.vue new file mode 100644 index 0000000000000000000000000000000000000000..49b7d30d44c78996be998ae6b6b9757772d264fa --- /dev/null +++ b/frontend/src/components/reload/ReloadButton.vue @@ -0,0 +1,19 @@ +<script> + export default { + props: ['parentRefs'] + } +</script> + +<template> + <button + class="btn btn--icon max-w-[unset]" + :disabled="$props.parentRefs.canvas ? $props.parentRefs.canvas.redrawing : true" + > + <div class="btn__body-wrap"> + <div class="btn__body w-full">Obnovit šablonu</div> + <div class="btn__icon"> + <i class="ico--refresh"></i> + </div> + </div> + </button> +</template> diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 0ef47aac5fe2c88ae7c6bc7af852d59c7fc5c76f..9f00d80b65bd6f0a7676f3afb8f00d3d5547e602 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,6 +1,6 @@ -const loadFonts = (fonts) => { +const loadFonts = async (fonts) => { for (const font of fonts) { - document.fonts.load( + await document.fonts.load( `${font}`, 'Příliš žluťoučký kůň úpěl ďábelské ó.' ) diff --git a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue index 148b4e135819fc8c7f884f79dc7db0566b1b871f..6fd923c177830daa79ac02191c787f68409db5fe 100644 --- a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue +++ b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue @@ -18,6 +18,8 @@ import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue' import RangeInput from '../../components/inputs/RangeInput.vue' import InputSeparator from '../../components/inputs/InputSeparator.vue' import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -69,7 +71,23 @@ export default { }, predefinedColors: predefinedColors, colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight') + predefinedLogoImages: generateDefaultLogos('defaultLight'), + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + logoImage: this.logoImage, + gradientHeightMultiplier: this.gradientHeightMultiplier, + colors: this.colors + } + ) } }, mounted () { @@ -84,17 +102,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - mainImage: this.mainImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - logoImage: this.logoImage, - gradientHeightMultiplier: this.gradientHeightMultiplier, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -123,6 +133,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <ImageInput name="Obrázek" v-model="mainImage" @@ -144,7 +161,7 @@ export default { :important="true" zIndex="8" /> - <ShortTextInput + <LongTextInput ref="refPersonPosition" name="Pozice osoby" v-model="personPosition" diff --git a/frontend/src/views/basic_photo_banner/canvas.js b/frontend/src/views/basic_photo_banner/canvas.js index c02aeeba09460192deed20fdd4797d0bde3a393d..8378144352c178e02e1700af96c399f9d448f94a 100644 --- a/frontend/src/views/basic_photo_banner/canvas.js +++ b/frontend/src/views/basic_photo_banner/canvas.js @@ -5,7 +5,9 @@ import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' let mainTextBox = null let mainTextBoxBackground = null -let personInfoText = null +let personNameText = null +let personInfoSeparator = null +let personPositionText = null let mainImage = null let logoImage = null @@ -17,7 +19,9 @@ const redraw = async (canvas, options) => { [ mainTextBox, mainTextBoxBackground, - personInfoText, + personNameText, + personInfoSeparator, + personPositionText, arrow ], canvas @@ -34,9 +38,12 @@ const redraw = async (canvas, options) => { const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3) const mainTextLineHeight = 1 - const nameTextSize = Math.ceil(canvas.height * 0.03) + const bottomTextSize = Math.ceil(canvas.height * 0.03) const nameTextMarginTop = Math.ceil(canvas.height * 0.015) const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06) + const positionTextSideGap = Math.ceil(canvas.width * 0.01) + const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035) + const positionTextMaxWidth = Math.ceil(canvas.width * 0.4) const arrowWidth = Math.ceil(canvas.width * 0.047) const arrowHeight = Math.ceil(canvas.width * 0.055) @@ -57,7 +64,7 @@ const redraw = async (canvas, options) => { let styles = { 0: {} } - + for (let position = 0; position < options.personName.length; position++) { styles[0][position] = { @@ -65,18 +72,8 @@ const redraw = async (canvas, options) => { } } - let nameText = options.personName; - - if (options.personPosition) { - nameText += `|${options.personPosition}` - - styles[0][options.personName.length] = { - fontWeight: 'bold' - } - } - - personInfoText = new fabric.Text( - nameText, + personNameText = new fabric.Text( + options.personName, { left: textMarginLeft, top: ( @@ -85,7 +82,7 @@ const redraw = async (canvas, options) => { + nameTextMarginTop ), fontFamily: 'Roboto Condensed', - fontSize: nameTextSize, + fontSize: bottomTextSize, styles: styles, fill: options.colors.baseText.value, selectable: false, @@ -93,7 +90,47 @@ const redraw = async (canvas, options) => { } ) - canvas.add(personInfoText) + if (options.personPosition !== null) { + personInfoSeparator = new fabric.Rect({ + left: personNameText.left + personNameText.width + positionTextSideGap, + width: positionTextSeparatorWidth, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10 + }) + + personPositionText = new fabric.Textbox( + options.personPosition, + { + left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap, + top: personNameText.top, + width: positionTextMaxWidth, + fontFamily: 'Roboto Condensed', + fontSize: bottomTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10 + } + ) + + checkTextBoxHeight(personPositionText, 2) + + if (personPositionText._textLines.length === 2) { + mainTextMarginBottom += nameTextExtraBottomMargin + personNameText.set({top: personNameText.top - nameTextExtraBottomMargin}) + personPositionText.set({top: personPositionText.top - nameTextExtraBottomMargin}) + } + + canvas.add(personPositionText) + + personInfoSeparator.set({top: personPositionText.top}) + personInfoSeparator.set({height: personPositionText.height}) + canvas.add(personInfoSeparator) + + canvas.renderAll() + } + + canvas.add(personNameText) } /* END Name text render */ @@ -204,9 +241,9 @@ const redraw = async (canvas, options) => { mainTextBoxBackground = new fabric.Rect( { - width: canvas.width, + width: canvas.width + 30, // FIXME: Whhhhyyyyyy???? height: backgroundHeight * options.gradientHeightMultiplier, - left: 0, + left: -20, top: ( mainTextBoxTop - mainTextBackgroundMarginTop @@ -302,6 +339,11 @@ const redraw = async (canvas, options) => { } ) + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }) + } + if (mainImage.width >= mainImage.height) { mainImage.scaleToHeight(canvas.height) } else { diff --git a/frontend/src/views/facebook_survey/FacebookSurvey.vue b/frontend/src/views/facebook_survey/FacebookSurvey.vue index 9d8a9c76d903bd9ac066c836489c9569752089a6..4bcab6024be31c161f7cfec39e198e249a407b1c 100644 --- a/frontend/src/views/facebook_survey/FacebookSurvey.vue +++ b/frontend/src/views/facebook_survey/FacebookSurvey.vue @@ -18,6 +18,8 @@ import EmojiInput from '../../components/inputs/EmojiInput.vue' import RangeInput from '../../components/inputs/RangeInput.vue' import InputSeparator from '../../components/inputs/InputSeparator.vue' import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -69,7 +71,25 @@ export default { gradientHeightMultiplier: 1, predefinedColors: predefinedColors, colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultDark') + predefinedLogoImages: generateDefaultLogos('defaultDark'), + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + mainImage: this.mainImage, + mainText: this.mainText, + logoImage: this.logoImage, + colors: this.colors, + firstEmojiImage: this.firstEmojiImage, + secondEmojiImage: this.secondEmojiImage, + firstEmojiText: this.firstEmojiText, + secondEmojiText: this.secondEmojiText, + gradientHeightMultiplier: this.gradientHeightMultiplier + } + ) } }, mounted () { @@ -80,25 +100,15 @@ export default { vm.logoImage, vm.colors, vm.firstEmojiImage, - vm.firstEmojiText, vm.secondEmojiImage, + vm.firstEmojiText, vm.secondEmojiText, - vm.gradientHeightMultiplier, + vm.gradientHeightMultiplier ], async (value) => { - await this.$refs.canvas.redraw( - { - mainImage: this.mainImage, - mainText: this.mainText, - logoImage: this.logoImage, - colors: this.colors, - firstEmojiImage: this.firstEmojiImage, - secondEmojiImage: this.secondEmojiImage, - firstEmojiText: this.firstEmojiText, - secondEmojiText: this.secondEmojiText, - gradientHeightMultiplier: this.gradientHeightMultiplier - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -112,7 +122,7 @@ export default { <template> <header> <Navbar - :defaultTemplate="TEMPLATES.basic_photo_banner" + :defaultTemplate="TEMPLATES.facebook_survey" ></Navbar> </header> <main> @@ -127,6 +137,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <ImageInput name="Obrázek" v-model="mainImage" diff --git a/frontend/src/views/facebook_survey/canvas.js b/frontend/src/views/facebook_survey/canvas.js index 8ce15a81e107b79030fe79cfd70608bbd605ddfb..73f7d136610c4f7569c53ac6069fec6f8a06bfc1 100644 --- a/frontend/src/views/facebook_survey/canvas.js +++ b/frontend/src/views/facebook_survey/canvas.js @@ -1,3 +1,6 @@ +import alertifyjs from "alertifyjs" +import "alertifyjs/build/css/alertify.css" + import { fabric } from 'fabric' import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' @@ -29,8 +32,7 @@ const redraw = async (canvas, options) => { canvas.preserveObjectStacking = true - const textMarginLeft = Math.ceil(canvas.width * 0.14) - const textMarginRight = Math.ceil(canvas.width * 0.14) + const textMarginSides = Math.ceil(canvas.width * 0.14) let mainTextMarginBottom = Math.ceil(canvas.height * 0.06) const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14) @@ -50,8 +52,8 @@ const redraw = async (canvas, options) => { if (options.mainText !== null) { /* BEGIN Main text render */ - const mainTextWidth = (canvas.width - textMarginLeft - textMarginRight) - + const mainTextWidth = (canvas.width - textMarginSides * 2) + const highlightedData = transformHighlightedText( options.mainText, mainTextSize, @@ -65,7 +67,7 @@ const redraw = async (canvas, options) => { highlightedData.text, { width: canvas.width, - left: textMarginLeft, + left: 0, textAlign: 'center', fontFamily: 'Bebas Neue', fontSize: mainTextSize, @@ -114,10 +116,11 @@ const redraw = async (canvas, options) => { } if (options.firstEmojiText !== null && options.firstEmojiImage !== null) { - firstEmojiText = new fabric.Text( + firstEmojiText = new fabric.Textbox( options.firstEmojiText, { left: firstEmojiImage.left + 250, + width: 500, top: ( mainTextBox.top + mainTextBox.height @@ -133,6 +136,37 @@ const redraw = async (canvas, options) => { ) canvas.add(firstEmojiText) + + if (firstEmojiText._textLines.length > 1) { + firstEmojiText.set({ + fontSize: firstEmojiText.fontSize - (mainTextSize / 2), + lineHeight: 0.9 + }) + canvas.renderAll() + } else if (firstEmojiText.width > 500) { + firstEmojiText.set({ + fontSize: firstEmojiText.fontSize - (mainTextSize / 2), + lineHeight: 0.9 + }) + firstEmojiText.top += firstEmojiText.fontSize / 2.5 + canvas.renderAll() + } + + if (firstEmojiText._textLines.length > 2) { + canvas.remove(firstEmojiText) + + if (!window.showingMaxLinesWarning) { + window.showingMaxLinesWarning = true + + const errorMessage = alertifyjs.error( + "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho." + ) + + errorMessage.callback = () => { + window.showingMaxLinesWarning = false + } + } + } } /* END First emoji render */ @@ -158,10 +192,11 @@ const redraw = async (canvas, options) => { } if (options.secondEmojiText !== null && options.secondEmojiImage !== null) { - secondEmojiText = new fabric.Text( + secondEmojiText = new fabric.Textbox( options.secondEmojiText, { left: secondEmojiImage.left + 250, + width: 300, top: ( mainTextBox.top + mainTextBox.height @@ -177,6 +212,37 @@ const redraw = async (canvas, options) => { ) canvas.add(secondEmojiText) + + if (secondEmojiText._textLines.length > 1) { + secondEmojiText.set({ + fontSize: secondEmojiText.fontSize - (mainTextSize / 2), + lineHeight: 0.9 + }) + canvas.renderAll() + } else if (secondEmojiText.width > 500) { + secondEmojiText.set({ + fontSize: secondEmojiText.fontSize - (mainTextSize / 2), + lineHeight: 0.9 + }) + secondEmojiText.top += secondEmojiText.fontSize / 2.5 + canvas.renderAll() + } + + if (secondEmojiText._textLines.length > 2) { + canvas.remove(secondEmojiText) + + if (!window.showingMaxLinesWarning) { + window.showingMaxLinesWarning = true + + const errorMessage = alertifyjs.error( + "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho." + ) + + errorMessage.callback = () => { + window.showingMaxLinesWarning = false + } + } + } } /* END Second Emoji render */ @@ -192,9 +258,9 @@ const redraw = async (canvas, options) => { mainTextBoxBackground = new fabric.Rect( { - width: canvas.width, + width: canvas.width + 40, // FIXME: My god! height: backgroundHeight * options.gradientHeightMultiplier, - left: 0, + left: -20, top: ( mainTextBox.top - mainTextBackgroundMarginTop @@ -290,6 +356,11 @@ const redraw = async (canvas, options) => { } ) + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }) + } + if (mainImage.width >= mainImage.height) { mainImage.scaleToHeight(canvas.height) } else { diff --git a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue index aa2ba812d440d9018f4a1cf9c0139335f1678498..3fe3be7a22d0db062448e322980e4107086a6d2e 100644 --- a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue +++ b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue @@ -18,6 +18,8 @@ import LongTextInput from '../../components/inputs/text/LongTextInput.vue' import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue'; import InputSeparator from '../../components/inputs/InputSeparator.vue' import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -79,7 +81,23 @@ export default { predefinedColors: predefinedColors, colors: predefinedColors.base.colors, predefinedLogoImages: generateDefaultLogos('defaultLight'), - predefinedSourceImages: SOURCE_IMAGES + predefinedSourceImages: SOURCE_IMAGES, + autoRedraw: false + } + }, + methods: { + async reloadCanvasProperties () { + await this.$refs.canvas.redraw( + { + mainImage: this.mainImage, + sourceImage: this.sourceImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + logoImage: this.logoImage, + colors: this.colors + } + ) } }, mounted () { @@ -94,17 +112,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - mainImage: this.mainImage, - sourceImage: this.sourceImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - logoImage: this.logoImage, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -133,6 +143,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <ImageInput name="Obrázek" v-model="mainImage" diff --git a/frontend/src/views/newspaper_quote_bottom/canvas.js b/frontend/src/views/newspaper_quote_bottom/canvas.js index ca8492f2d93c71e795867cfefbb74ee6d41b7c5a..66cda5c510cd9e796b6ffb8b6982f4a1a7ea0713 100644 --- a/frontend/src/views/newspaper_quote_bottom/canvas.js +++ b/frontend/src/views/newspaper_quote_bottom/canvas.js @@ -51,7 +51,7 @@ const redraw = async (canvas, options) => { const nameTextMarginTop = Math.ceil(canvas.height * 0.025) const positionTextSideGap = Math.ceil(canvas.width * 0.01) const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035) - const positionTextMaxWidth = Math.ceil(canvas.width * 0.4) + const personInfoTextMaxWidth = Math.ceil(canvas.width * 0.5) const bottomTextSize = Math.ceil(canvas.height * 0.03) const additionalContentExtraBottomMargin = Math.ceil(canvas.height * 0.1) @@ -60,7 +60,7 @@ const redraw = async (canvas, options) => { const logoSideMargin = Math.ceil(canvas.width * 0.07) const sourceImageHeight = Math.ceil(canvas.height * 0.07) - const sourceImageMarginBottom = Math.ceil(canvas.width * 0.07) + const sourceImageMarginBottom = Math.ceil(canvas.width * 0.075) const sourceImageMarginSide = Math.ceil(canvas.width * 0.07) const sourceTextMarginSide = Math.ceil(canvas.width * 0.005) @@ -112,7 +112,7 @@ const redraw = async (canvas, options) => { selectable: false, zIndex: 10 }) - + canvas.add(personInfoSeparator) personPositionText = new fabric.Textbox( @@ -120,7 +120,7 @@ const redraw = async (canvas, options) => { { left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap, top: personNameText.top, - width: positionTextMaxWidth, + width: personInfoTextMaxWidth - personNameText.width, fontFamily: 'Roboto Condensed', fontSize: bottomTextSize, fill: options.colors.baseText.value, @@ -174,6 +174,7 @@ const redraw = async (canvas, options) => { - sourceImageHeight - sourceImageMarginBottom ), + selectable: false, zIndex: 10, }) @@ -186,6 +187,7 @@ const redraw = async (canvas, options) => { fontFamily: 'Roboto Condensed', left: sourceImage.left - sourceTextMarginSide, top: sourceImage.top, + selectable: false, zIndex: 10 }) @@ -444,6 +446,11 @@ const redraw = async (canvas, options) => { } ) + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }) + } + if (mainImage.width >= mainImage.height) { mainImage.scaleToHeight(canvas.height) } else { diff --git a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue index 356dc98c127b0a67d36eddccd025d6daa328de3c..7f000233ce43e0b22ac2e1ace8f05ccd1e3364bf 100644 --- a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue +++ b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue @@ -18,6 +18,8 @@ import LongTextInput from '../../components/inputs/text/LongTextInput.vue' import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue' import InputSeparator from '../../components/inputs/InputSeparator.vue' import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -84,7 +86,22 @@ export default { predefinedColors: predefinedColors, colors: predefinedColors.base.colors, predefinedLogoImages: generateDefaultLogos('defaultDark'), - predefinedSourceImages: SOURCE_IMAGES + predefinedSourceImages: SOURCE_IMAGES, + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + sourceImage: this.sourceImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + logoImage: this.logoImage, + colors: this.colors + } + ) } }, mounted () { @@ -98,16 +115,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - sourceImage: this.sourceImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - logoImage: this.logoImage, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -136,6 +146,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <LongTextInput name="Hlavní text" v-model="mainText" diff --git a/frontend/src/views/newspaper_quote_middle/canvas.js b/frontend/src/views/newspaper_quote_middle/canvas.js index d8e0be2f26989952518cae72bedd10b0d2579fca..63cdaacfcd50b8f690bc89f5efc8861af9ad0ab4 100644 --- a/frontend/src/views/newspaper_quote_middle/canvas.js +++ b/frontend/src/views/newspaper_quote_middle/canvas.js @@ -90,10 +90,10 @@ const redraw = async (canvas, options) => { /* BEGIN Background render */ background = new fabric.Rect({ - left: 0, - top: 0, - width: canvas.width, - height: canvas.height, + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, fill: options.colors.background.value, selectable: false, zIndex: 0 @@ -151,7 +151,7 @@ const redraw = async (canvas, options) => { selectable: false, zIndex: 10 }) - + canvas.add(personInfoSeparator) personPositionText = new fabric.Text( @@ -454,7 +454,7 @@ const redraw = async (canvas, options) => { // Hacky seam fix {x: canvas.width, y: mainTextBoxForegroundBottomY}, {x: canvas.width, y: bottomTearBottom - (canvas.height * 0.063)}, - {x: canvas.width * 0.95, y: bottomTearBottom - (canvas.height * 0.055)}, + {x: canvas.width * 0.95, y: bottomTearBottom - (canvas.height * 0.06)}, {x: canvas.width * 0.92, y: bottomTearBottom - (canvas.height * 0.045)}, {x: canvas.width * 0.85, y: bottomTearBottom - (canvas.height * 0.07)}, {x: canvas.width * 0.77, y: bottomTearBottom - (canvas.height * 0.06)}, @@ -543,6 +543,7 @@ const redraw = async (canvas, options) => { sourceImage.set({ left: sourceImageMarginSide, top: sourceImageMarginTop, + selectable: false, zIndex: 10, }) @@ -554,7 +555,8 @@ const redraw = async (canvas, options) => { fontFamily: 'Roboto Condensed', left: sourceImage.left - sourceTextMarginSide, top: sourceImage.top, - zIndex: 10 + selectable: false, + zIndex: 10, }) sourceText.set({ diff --git a/frontend/src/views/text_banner/TextBanner.vue b/frontend/src/views/text_banner/TextBanner.vue index 7f3e1aae8427e8bd63c03bbd4c1900f58513bb5d..8db304a740495e2054e30a14f8e3cdd04d864c5e 100644 --- a/frontend/src/views/text_banner/TextBanner.vue +++ b/frontend/src/views/text_banner/TextBanner.vue @@ -14,6 +14,8 @@ import MainContainer from '../../components/MainContainer.vue'; import ImageInput from '../../components/inputs/ImageInput.vue'; import LongTextInput from '../../components/inputs/text/LongTextInput.vue'; import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -54,7 +56,19 @@ export default { }, predefinedColors: predefinedColors, colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight') + predefinedLogoImages: generateDefaultLogos('defaultLight'), + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + text: this.text, + logoImage: this.logoImage, + colors: this.colors + } + ) } }, mounted () { @@ -65,13 +79,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - text: this.text, - logoImage: this.logoImage, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -100,6 +110,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <LongTextInput name="Text" v-model="text" diff --git a/frontend/src/views/text_banner/canvas.js b/frontend/src/views/text_banner/canvas.js index 8b7da06ba598f3a588067c1dfcfae0a39bed1150..3c614fd3ad2af5b5650e3456037be18af1f7b512 100644 --- a/frontend/src/views/text_banner/canvas.js +++ b/frontend/src/views/text_banner/canvas.js @@ -42,10 +42,10 @@ const redraw = async (canvas, options) => { /* BEGIN Background render */ backgroundRect = new fabric.Rect({ - width: canvas.width, - height: canvas.height, - top: 0, - left: 0, + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, fill: options.colors.background.value, selectable: false, zIndex: 0 diff --git a/frontend/src/views/twitter_banner/PersonInput.vue b/frontend/src/views/twitter_banner/PersonInput.vue index 99d784867f4640c059385e4cd63377624b00b31e..4255c9e8f492c664947df8e3049dc8520333fe2b 100644 --- a/frontend/src/views/twitter_banner/PersonInput.vue +++ b/frontend/src/views/twitter_banner/PersonInput.vue @@ -25,7 +25,6 @@ export default { watch: { selectedOption: { async handler (value) { - console.log(value) const mainImage = new Image() await new Promise( diff --git a/frontend/src/views/twitter_banner/TwitterBanner.vue b/frontend/src/views/twitter_banner/TwitterBanner.vue index cf6ff49d166a747f5bbc379f1ebbafdd9cf7e025..c9a4f82dd030ccc6af2389c8c7ee51fec843a882 100644 --- a/frontend/src/views/twitter_banner/TwitterBanner.vue +++ b/frontend/src/views/twitter_banner/TwitterBanner.vue @@ -15,6 +15,8 @@ import Navbar from '../../components/Navbar.vue' import MainContainer from '../../components/MainContainer.vue' import LongTextInput from '../../components/inputs/text/LongTextInput.vue' import PersonInput from './PersonInput.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' import klaraImage from '../../assets/template/twitter_banner/klara.png' import ivanImage from '../../assets/template/twitter_banner/ivan.png' @@ -83,33 +85,43 @@ export default { text: COLORS.black, highlight: COLORS.yellow1, highlightedText: COLORS.black - } + }, + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + colors: this.colors, + logoImageSource: this.logoImageSource, + twitterLogoImageSource: this.twitterLogoImageSource, + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personTwitter: this.personTwitter + } + ) } }, mounted () { this.$watch( vm => [ + vm.colors, + vm.logoImageSource, + vm.twitterLogoImageSource, vm.mainImage, vm.mainText, vm.personName, vm.personTwitter ], async (value) => { - await this.$refs.canvas.redraw( - { - colors: this.colors, - logoImageSource: this.logoImageSource, - twitterLogoImageSource: this.twitterLogoImageSource, - mainImage: this.mainImage, - mainText: this.mainText, - personName: this.personName, - personTwitter: this.personTwitter - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { - // Let the person chooser element run the first redraw - // immediate: true, + immediate: true, deep: true } ) @@ -135,6 +147,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <PersonInput name="Člověk" v-model:mainImage="mainImage" diff --git a/frontend/src/views/twitter_banner/canvas.js b/frontend/src/views/twitter_banner/canvas.js index cdc7b320f0c523ad120e2c400d889af15e380e6d..2156f877b38f1878abfd01ad2365058b482dfd5b 100644 --- a/frontend/src/views/twitter_banner/canvas.js +++ b/frontend/src/views/twitter_banner/canvas.js @@ -53,11 +53,11 @@ const redraw = async (canvas, options) => { if (background === null) { background = new fabric.Rect({ - left: 0, - top: 0, - fill: options.colors.background.value, - width: canvas.width, - height: canvas.height + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value }) canvas.add(background) diff --git a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue index 847af4f8e3d92d11234328eb8d715c407af52c5f..b33702e87a9733039a6dcb98149a5ed6b0bf1db0 100644 --- a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue +++ b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue @@ -18,6 +18,8 @@ import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue' import RangeInput from '../../components/inputs/RangeInput.vue' import InputSeparator from '../../components/inputs/InputSeparator.vue' import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -69,7 +71,23 @@ export default { }, predefinedColors: predefinedColors, colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight') + predefinedLogoImages: generateDefaultLogos('defaultLight'), + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + logoImage: this.logoImage, + gradientHeightMultiplier: this.gradientHeightMultiplier, + colors: this.colors + } + ) } }, mounted () { @@ -84,17 +102,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - mainImage: this.mainImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - logoImage: this.logoImage, - gradientHeightMultiplier: this.gradientHeightMultiplier, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -123,6 +133,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <ImageInput name="Obrázek" v-model="mainImage" diff --git a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue index 5eb79475c964c7e0ff6a141fca65cf575b3fa6cb..0d9f15e45471738e11267495cdbe5068618d9312 100644 --- a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue +++ b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue @@ -14,6 +14,8 @@ import MainContainer from '../../components/MainContainer.vue'; import ImageInput from '../../components/inputs/ImageInput.vue'; import LongTextInput from '../../components/inputs/text/LongTextInput.vue'; import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' </script> <script> @@ -60,7 +62,19 @@ export default { src: LOGOS.defaultDark.src, defaultSelected: true, }, - ] + ], + autoRedraw: false + } + }, + methods: { + reloadCanvasProperties () { + this.$refs.canvas.redraw( + { + text: this.text, + logoImage: this.logoImage, + colors: this.colors + } + ) } }, mounted () { @@ -71,13 +85,9 @@ export default { vm.colors ], async (value) => { - await this.$refs.canvas.redraw( - { - text: this.text, - logoImage: this.logoImage, - colors: this.colors - } - ); + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } }, { immediate: true, @@ -106,6 +116,13 @@ export default { </template> <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> <LongTextInput name="Text" v-model="text" diff --git a/frontend/src/views/urgent_text_banner/canvas.js b/frontend/src/views/urgent_text_banner/canvas.js index 80687235a18bebe6a062272b17ead657e307227e..9e7a9adc880ac3cad8731c8e55553caa644ddccc 100644 --- a/frontend/src/views/urgent_text_banner/canvas.js +++ b/frontend/src/views/urgent_text_banner/canvas.js @@ -39,10 +39,10 @@ const redraw = async (canvas, options) => { backgroundImage = new fabric.Image( backgroundImage, { - left: 0, - top: 0, - width: canvas.width, - height: canvas.height, + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, zIndex: 0, selectable: false } diff --git a/frontend/src/views/utils/newspaper_quotes.js b/frontend/src/views/utils/newspaper_quotes.js index ac04b8f6450efffd6620e0aa2b5de0672ed84473..04ea6f810b96ca2554d4e2b63a4fa41f0a6802f7 100644 --- a/frontend/src/views/utils/newspaper_quotes.js +++ b/frontend/src/views/utils/newspaper_quotes.js @@ -2,6 +2,7 @@ import sourceImageCT from '../../assets/news_sources/ct.png' import sourceImageDenikN from '../../assets/news_sources/denik_n.png' import sourceImageSeznam from '../../assets/news_sources/seznam_zpravy.png' import sourceImageCNN from '../../assets/news_sources/cnn.png' +import sourceImageCzechRadio from '../../assets/news_sources/rozhlas.png' const SOURCE_IMAGES = [ { @@ -23,7 +24,12 @@ const SOURCE_IMAGES = [ name: 'CNN', src: sourceImageCNN, defaultSelected: false + }, + { + name: 'Český Rozhlas', + src: sourceImageCzechRadio, + defaultSelected: false } -] +] export { SOURCE_IMAGES } diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 4f73cadebd562a73a6c4d2761543e93bf493777b..76edd3b45c9f66fef1df1ef708786cf0f761507b 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -9,4 +9,3 @@ module.exports = { }, plugins: [], } - diff --git a/server/server/__init__.py b/server/server/__init__.py index d07d396624e60960031ee6e0cfe533197fac7106..1eca9d8e276b677738d490e46b1373150cc144c4 100644 --- a/server/server/__init__.py +++ b/server/server/__init__.py @@ -21,9 +21,7 @@ def create_app() -> flask.Flask: "OIDC_RP_REALM_URL", "OIDC_RP_CLIENT_ID", "OIDC_RP_CLIENT_SECRET", - "SITE_URL", - "SECRET_KEY", "JWT_SECRET_KEY", ): @@ -32,14 +30,8 @@ def create_app() -> flask.Flask: app.config["JWT_EXPIRES_AFTER"] = int(os.getenv("JWT_EXPIRES_AFTER")) app.config["ALLOWED_OIDC_GROUPS"] = os.getenv("ALLOWED_OIDC_GROUPS").split(",") - for blueprint in ( - views.frontend_blueprint, - views.oidc_blueprint - ): - app.logger.debug( - "Registering blueprint: %s", - blueprint - ) + for blueprint in (views.frontend_blueprint, views.oidc_blueprint): + app.logger.debug("Registering blueprint: %s", blueprint) app.register_blueprint(blueprint) diff --git a/server/server/authentication/__init__.py b/server/server/authentication/__init__.py index 443bf8566bcc932ef680c3dfb59acaecaa60285f..80804f8de7e6295f791f92b681544e1a55aea28e 100644 --- a/server/server/authentication/__init__.py +++ b/server/server/authentication/__init__.py @@ -6,20 +6,16 @@ import flask import werkzeug -def authentication_required( - func: typing.Callable -) -> typing.Callable: +def authentication_required(func: typing.Callable) -> typing.Callable: @functools.wraps(func) def decorator(*args, **kwargs) -> typing.Any: if "token" not in flask.request.cookies: - return flask.redirect( - flask.url_for("oidc.login") - ) + return flask.redirect(flask.url_for("oidc.login")) try: claims = authlib.jose.jwt.decode( flask.request.cookies["token"], - flask.current_app.config["JWT_SECRET_KEY"] + flask.current_app.config["JWT_SECRET_KEY"], ) except authlib.jose.JoseError as exception: raise werkzeug.exceptions.BadRequest from exception @@ -28,14 +24,11 @@ def authentication_required( claims.validate() except authlib.jose.JoseError as exception: flask.current_app.logger.info( - "Claim validation failed for user %s: %s", - claims["sub"], - exception + "Claim validation failed for user %s: %s", claims["sub"], exception ) - return flask.redirect( - flask.url_for("oidc.login") - ) + return flask.redirect(flask.url_for("oidc.login")) return func(*args, **kwargs) + return decorator diff --git a/server/server/templates/index.html b/server/server/templates/index.html deleted file mode 100644 index 3d4b487726882c45175985519984ce3b65b965e6..0000000000000000000000000000000000000000 --- a/server/server/templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <link rel="icon" href="/static/favicon.ico"> - <link rel="stylesheet" href="https://styleguide.pirati.cz/2.12.x/css/styles.css"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Generátor grafiky</title> - <script type="module" crossorigin src="/static/index-a12fbb13.js"></script> - <link rel="stylesheet" href="/static/index-ea030523.css"> - </head> - <body> - <div id="app"></div> - - </body> -</html> diff --git a/server/server/views/frontend.py b/server/server/views/frontend.py index a30ce77b57293f54bbdfa31f0e291ed5e1673763..adbef73d70cc5c1a6a78da6d4659ad20f747ed9d 100644 --- a/server/server/views/frontend.py +++ b/server/server/views/frontend.py @@ -2,11 +2,7 @@ import flask from server.authentication import authentication_required - -frontend_blueprint = flask.Blueprint( - "frontend", - __name__ -) +frontend_blueprint = flask.Blueprint("frontend", __name__) @frontend_blueprint.route("/", defaults={"path": ""}) diff --git a/server/server/views/oidc.py b/server/server/views/oidc.py index cc03d6bd8059563cdd21145c76f03a95683cb7a4..bfae17d86092dec85370456a11ab729b218f313f 100644 --- a/server/server/views/oidc.py +++ b/server/server/views/oidc.py @@ -8,12 +8,7 @@ import flask import requests import werkzeug.exceptions - -oidc_blueprint = flask.Blueprint( - "oidc", - __name__, - url_prefix="/oidc" -) +oidc_blueprint = flask.Blueprint("oidc", __name__, url_prefix="/oidc") def generate_session() -> authlib.integrations.requests_client.OAuth2Session: @@ -26,7 +21,7 @@ def generate_session() -> authlib.integrations.requests_client.OAuth2Session: token_endpoint=f"{realm_url}protocol/openid-connect/token", userinfo_endpoint=f"{realm_url}protocol/openid-connect/userinfo", jwks_uri=f"{realm_url}protocol/openid-connect/certs", - scope="openid profile groups" + scope="openid profile groups", ) @@ -37,9 +32,9 @@ def generate_redirect_uri() -> str: _scheme=( flask.request.headers.get( "X-Forwarded-Proto", - "http" if flask.request.url.startswith("http://") else "https" + "http" if flask.request.url.startswith("http://") else "https", ) - ) + ), ) @@ -48,7 +43,7 @@ def login() -> flask.Response: with generate_session() as session: url, state = session.create_authorization_url( session.metadata["authorization_endpoint"], - redirect_uri=generate_redirect_uri() + redirect_uri=generate_redirect_uri(), ) return flask.redirect(url) @@ -68,15 +63,14 @@ def authenticate() -> flask.Response: session.metadata["token_endpoint"], code=code, state=state, - redirect_uri=generate_redirect_uri() + redirect_uri=generate_redirect_uri(), ) except ( authlib.integrations.requests_client.OAuthError, - requests.HTTPError + requests.HTTPError, ) as exception: flask.current_app.logger.debug( - "Error during OIDC authentication: %s", - exception + "Error during OIDC authentication: %s", exception ) raise werkzeug.exceptions.Unauthorized from exception @@ -84,9 +78,7 @@ def authenticate() -> flask.Response: jwks = session.get(session.metadata["jwks_uri"]).json() claims = authlib.jose.jwt.decode( - token["id_token"], - jwks, - claims_cls=authlib.oidc.core.CodeIDToken + token["id_token"], jwks, claims_cls=authlib.oidc.core.CodeIDToken ) claims.validate() @@ -94,46 +86,31 @@ def authenticate() -> flask.Response: current_timestamp = round(datetime.datetime.now().timestamp()) - #found_allowed_group = False - - #for group in claims["groups"]: - #if group in flask.current_app.config["ALLOWED_OIDC_GROUPS"]: - #found_allowed_group = True - #break - - #if not found_allowed_group: - #raise werkzeug.exceptions.Unauthorized( - #"Nemáš dostatečné skupiny k přístupu do Generátoru." - #) - - jwt = ( - authlib.jose.jwt.encode( - { - "alg": "HS256" - }, - { - "iss": "graphics-generator", - "iat": current_timestamp, - "exp": ( - current_timestamp - + flask.current_app.config["JWT_EXPIRES_AFTER"] - ), - "sub": userinfo["sub"] # Currently unused, maybe of use later - }, - flask.current_app.config["JWT_SECRET_KEY"] - ). - decode("utf-8") - ) + # found_allowed_group = False - response = flask.make_response( - flask.redirect( - flask.url_for("frontend.index") - ) - ) + # for group in claims["groups"]: + # if group in flask.current_app.config["ALLOWED_OIDC_GROUPS"]: + # found_allowed_group = True + # break - response.set_cookie( - "token", - jwt - ) + # if not found_allowed_group: + # raise werkzeug.exceptions.Unauthorized( + # "Nemáš dostatečné skupiny k přístupu do Generátoru." + # ) + + jwt = authlib.jose.jwt.encode( + {"alg": "HS256"}, + { + "iss": "graphics-generator", + "iat": current_timestamp, + "exp": (current_timestamp + flask.current_app.config["JWT_EXPIRES_AFTER"]), + "sub": userinfo["sub"], # Currently unused, maybe of use later + }, + flask.current_app.config["JWT_SECRET_KEY"], + ).decode("utf-8") + + response = flask.make_response(flask.redirect(flask.url_for("frontend.index"))) + + response.set_cookie("token", jwt) - return response + return response