Compare commits

..

23 Commits

Author SHA1 Message Date
caribaud 426c18bfef Merge pull request 'Added pipenv files to offer a consistent python environment, including quality.' (#20) from issues/14 into release/1.0.0
Reviewed-on: #20
Reviewed-by: caribaud <christian@aribaud.net>
2023-06-25 11:32:29 +02:00
Kronk 757ff3a7c7 Fixed vscode settings markdown. 2023-06-25 11:24:14 +02:00
Kronk 25cb269334 Added VSCode settings to README.
Added .vscode to .gitignore.
2023-06-25 11:13:44 +02:00
Christian Aribaud ce00b327bb fixing prospector findings in zoom generator 2023-06-25 10:37:59 +02:00
Christian Aribaud 7e52c5a532 Running black on python files 2023-06-25 09:20:38 +02:00
Christian Aribaud 3d7428e76f Adding info regarding python quality checking 2023-06-25 09:20:06 +02:00
Kronk cb3c17c5db Added pipenv files to offer a consistent python environment.
Includes Python version, with black and prospector in the dev packages.
2023-06-24 23:35:42 +02:00
caribaud 21e17a294e more corrections 2023-06-18 17:50:09 +02:00
Kronk b3fddef4e0 Added script to update version in headers and variables. 2023-06-18 17:05:36 +02:00
caribaud b12cf2c4f1 new eslint fixes run 2023-06-05 00:40:25 +02:00
caribaud 5a1987f77b single quote -> double quote 2023-06-05 00:20:32 +02:00
caribaud f1cfb326d5 Fix declaration order 2023-06-05 00:19:28 +02:00
caribaud 7299157521 new round of fixing eslint findings 2023-06-05 00:16:02 +02:00
caribaud c6f88e8d85 fix trailling whitespace 2023-06-04 21:38:56 +02:00
caribaud f87f6c75f5 Fixing unused functions and params 2023-06-04 21:37:56 +02:00
caribaud ca0f9f4e59 Fixing Dockerfile to match with Readme.md 2023-06-04 20:57:51 +02:00
Christian Aribaud c8451c07aa Adding info about ESLint in README.md 2023-06-04 20:46:43 +02:00
caribaud 5f3060995b Rework melpomene to respect newly added ESLINT
This includes:

+ Removing trailling whitespaces
+ Adding missing const keywords
+ Fixing equality checks
+ Removing unused variables, functions, etc...
+ Adding missing let & const statements
+ Use strict
+ Correcting all names to use camelCase
+ Using semicolons everywhere
+ Use Allman style everywhere
+ Make comments headers uniform

This also includes:

+ Fixing displayed version number
2023-06-04 20:16:56 +02:00
caribaud 230abf08e8 Adding eslint 2023-06-04 20:16:55 +02:00
caribaud 5f8d8873b3 Warn about floating height/width and round them
Closes #8
2023-05-27 16:16:49 +02:00
caribaud 81be7d477b Prevent external CSS img interferences
Closes #10
2023-05-27 16:02:06 +02:00
caribaud 350dd4bb0e Fix width / height computation
Closes #9
2023-05-27 15:56:31 +02:00
caribaud c1df87582d Change scrolling behaviour 2023-05-26 23:32:11 +02:00
10 changed files with 1338 additions and 464 deletions

7
.gitignore vendored
View File

@ -1,2 +1,9 @@
# ESLINT report
eslint_report.html
# Demo sources
demos/src_highres
demos/src_lowres
# Local dev environment settings
.vscode

13
Pipfile Normal file
View File

@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
black = "*"
prospector = {extras = ["with_mypy"], version = "*"}
[requires]
python_version = "3.11"

509
Pipfile.lock generated Normal file
View File

@ -0,0 +1,509 @@
{
"_meta": {
"hash": {
"sha256": "6bf84a05ccc0682985bf11d77db01419dde589c614d97f59ca2bac4c4b43c80f"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {
"astroid": {
"hashes": [
"sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324",
"sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.15.5"
},
"black": {
"hashes": [
"sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
"sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
"sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
"sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
"sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
"sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
"sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
"sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
"sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
"sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
"sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
"sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
"sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
"sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
"sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
"sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
"sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
"sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
"sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
"sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
"sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
"sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
"sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
"sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
"sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
],
"index": "pypi",
"version": "==23.3.0"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.6"
},
"dill": {
"hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
],
"markers": "python_version >= '3.11'",
"version": "==0.3.6"
},
"dodgy": {
"hashes": [
"sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
"sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"
],
"version": "==0.2.1"
},
"flake8": {
"hashes": [
"sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
"sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==5.0.4"
},
"flake8-polyfill": {
"hashes": [
"sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9",
"sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"
],
"version": "==1.0.2"
},
"gitdb": {
"hashes": [
"sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a",
"sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"
],
"markers": "python_version >= '3.7'",
"version": "==4.0.10"
},
"gitpython": {
"hashes": [
"sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573",
"sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.31"
},
"isort": {
"hashes": [
"sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
"sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==5.12.0"
},
"lazy-object-proxy": {
"hashes": [
"sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382",
"sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82",
"sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9",
"sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494",
"sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46",
"sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30",
"sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63",
"sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4",
"sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae",
"sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be",
"sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701",
"sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd",
"sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006",
"sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a",
"sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586",
"sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8",
"sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821",
"sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07",
"sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b",
"sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171",
"sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b",
"sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2",
"sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7",
"sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4",
"sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8",
"sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e",
"sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f",
"sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda",
"sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4",
"sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e",
"sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671",
"sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11",
"sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455",
"sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734",
"sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb",
"sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"
],
"markers": "python_version >= '3.7'",
"version": "==1.9.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"mypy": {
"hashes": [
"sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd",
"sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d",
"sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2",
"sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965",
"sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e",
"sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758",
"sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981",
"sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135",
"sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42",
"sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5",
"sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd",
"sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff",
"sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9",
"sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352",
"sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8",
"sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d",
"sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf",
"sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f",
"sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05",
"sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840",
"sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de",
"sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b",
"sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042",
"sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d",
"sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af",
"sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"
],
"version": "==1.4.0"
},
"mypy-extensions": {
"hashes": [
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"packaging": {
"hashes": [
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.1"
},
"pathspec": {
"hashes": [
"sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
"sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.1"
},
"pep8-naming": {
"hashes": [
"sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164",
"sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"
],
"version": "==0.10.0"
},
"platformdirs": {
"hashes": [
"sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc",
"sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
},
"prospector": {
"extras": [
"with_mypy"
],
"hashes": [
"sha256:3bfe103c28bb821cca84926ca31357fbfd32405e4bf8c34ca2e55885684557e4",
"sha256:cc8f09e79bdd32247edddf05b666940e88ad96338a84f5717b1e8c0678337821"
],
"index": "pypi",
"version": "==1.10.2"
},
"pycodestyle": {
"hashes": [
"sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785",
"sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"
],
"markers": "python_version >= '3.6'",
"version": "==2.9.1"
},
"pydocstyle": {
"hashes": [
"sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019",
"sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"
],
"markers": "python_version >= '3.6'",
"version": "==6.3.0"
},
"pyflakes": {
"hashes": [
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
],
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
"pylint": {
"hashes": [
"sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1",
"sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.17.4"
},
"pylint-celery": {
"hashes": [
"sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"
],
"version": "==0.3"
},
"pylint-django": {
"hashes": [
"sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591",
"sha256:56b12b6adf56d548412445bd35483034394a1a94901c3f8571980a13882299d5"
],
"version": "==2.5.3"
},
"pylint-flask": {
"hashes": [
"sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"
],
"version": "==0.6"
},
"pylint-plugin-utils": {
"hashes": [
"sha256:b3d43e85ab74c4f48bb46ae4ce771e39c3a20f8b3d56982ab17aa73b4f98d535",
"sha256:ce48bc0516ae9415dd5c752c940dfe601b18fe0f48aa249f2386adfa95a004dd"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==0.7"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"markers": "python_version >= '3.6'",
"version": "==6.0"
},
"requirements-detector": {
"hashes": [
"sha256:3642cd7a5b261d79536c36bb7ecacf2adabd902d2e0e42bfb2ba82515da10501",
"sha256:d7c60493bf166da3dd59de0e6cb25765e0e32a1931aeae92614034e5786d0bd0"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==1.2.2"
},
"semver": {
"hashes": [
"sha256:2a23844ba1647362c7490fe3995a86e097bb590d16f0f32dfc383008f19e4cdf",
"sha256:9ec78c5447883c67b97f98c3b6212796708191d22e4ad30f4570f840171cbce1"
],
"markers": "python_version >= '3.7'",
"version": "==3.0.1"
},
"setoptconf-tmp": {
"hashes": [
"sha256:76035d5cd1593d38b9056ae12d460eca3aaa34ad05c315b69145e138ba80a745",
"sha256:e0480addd11347ba52f762f3c4d8afa3e10ad0affbc53e3ffddc0ca5f27d5778"
],
"version": "==0.3.1"
},
"smmap": {
"hashes": [
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.0"
},
"snowballstemmer": {
"hashes": [
"sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
"sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
],
"version": "==2.2.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"tomlkit": {
"hashes": [
"sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171",
"sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.8"
},
"typing-extensions": {
"hashes": [
"sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26",
"sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"
],
"markers": "python_version >= '3.7'",
"version": "==4.6.3"
},
"wrapt": {
"hashes": [
"sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
"sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
"sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
"sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
"sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
"sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
"sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
"sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
"sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
"sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
"sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
"sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
"sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
"sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
"sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
"sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
"sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
"sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
"sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
"sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
"sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
"sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
"sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
"sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
"sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
"sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
"sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
"sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
"sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
"sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
"sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
"sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
"sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
"sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
"sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
"sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
"sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
"sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
"sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
"sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
"sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
"sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
"sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
"sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
"sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
"sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
"sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
"sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
"sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
"sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
"sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
"sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
"sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
"sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
"sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
"sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
"sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
"sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
"sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
"sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
"sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
"sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
"sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
"sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
"sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
"sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
"sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
"sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
"sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
"sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
"sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
"sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
"sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
"sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
"sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
],
"markers": "python_version >= '3.11'",
"version": "==1.15.0"
}
}
}

View File

@ -25,16 +25,19 @@ flowchart TB
```
Melpomene is mainly one JS file and one CSS file. You only need to include them in you web page. They are :
+ `melpomene.js`
+ `melpomene.css`
+ `melpomene.js`
+ `melpomene.css`
The JS files expect you to write some very specific HTML tags to be able to work.
To simplify things, you only need to copy-paste the content of `melpomene.html` into your own page and change a few things :
+ You must duplicate the `img` tag for each of you comic page and :
+ set `url` to the actual URL of your page
+ set `height` and `width` to the actual image sizes
+ set `data-zooms` with the zooms information, like so : `<zoom 1 width>, <zoom 1 height>, <zoom 1 x offset>, <zoom 1 y offset>; <zoom 2 width> ...`
+ example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>`
+ example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.4470    2,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>`
Because doing this by hand would be very tedious, Melpomene comes with an helper script that allows generating that HTML for you from SVGs.
@ -45,7 +48,7 @@ The following limitations are known and will be improved upon :
+ Mobile support is currently limited
+ There are some performences issues
# How to setup Melpomene ?
# How to use Melpomene ?
## Defining the zooms
@ -61,12 +64,12 @@ To create the zooms for a comic page, what you need to do is :
* If you want to change the zoom order, you can change their order in the layer view of your SVG tool if they support it
3. Once you are done, save the SGV
## Generating the HTML files
Once the SVG for your pages are done, put them in one folder. Then, you can run the zoom generator.
You need to open a terminal and then run :
+ `python zooms_generator.py <path to the SVG folder> html -p <prefix of the img's url> -e <extension of the img's url>`
+ For example, if your comic pages are hosted at `static/comic/page_x.jpg`, the svg must be named `page_x.svg`
and you must run `python zooms_generator.py <path to the SVG folder> html -p /static/comic/ -e jpg`
@ -89,6 +92,74 @@ If you need to do some global scaling / offset of all zooms in HTML (if for exam
+ If they become greater than the page size, they get clamped to the page size and width / height get reduced to compensate
+ `data-global-zoom-scale="<float value>"` : scale all positions / sizes by this factor
# Developpement
## Setting up quality checking for JS
Regarding JS, quality checking is done using [eslint](https://eslint.org/).
The configuration file is `eslint/eslintrc.json`.
To setup eslint, you can either install it on your system reading it's documentation, or use the provided Dockerfile to run it. This requires [docker](https://www.docker.com/).
To do so, assuming you are using linux, after installing docker, you can run from this repository root:
+ `docker build -t melpomene-eslint eslint/`, once
+ `docker run -v .:/melpomene:rw -w /melpomene --user $(id -u):$(id -g) melpomene-eslint`, every time you want to run the analysis
You can now open `eslint_report.html` to see the result.
## Setting up quality checking for Python
Regarding Python, quality checking is done using [prospector](https://prospector.landscape.io/en/master/) using [mypy](https://mypy.readthedocs.io/en/stable/) as an additional checker.
Auto-formating is done using [Black](https://pypi.org/project/black/)
Dependencies are managed not using `pip` but [`pipenv`](https://pipenv.pypa.io/en/latest/).
To setup prospector, you need to run:
+ Only once, in melpomene's root folder:
+ Install pipenv: `pip install pipenv`
+ Install melpomene's root folder: `pipenv install --dev`
+ Every time to run the quality checking:
+ `pipenv shell`
+ `prospector -s veryhigh -w mypy --max-line-length 88 .`
+ `black .`
For VSCode users, you can run those automatically in your IDE. Here is what your .vscode/settings.json file should include :
```json
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.pyc": true,
"**/__pycache__": true
},
// Replace by your own venv python path
"python.defaultInterpreterPath": "~\\.virtualenvs\\melpomene-ajDJjHHp\\Scripts\\python.exe",
"python.formatting.provider": "black",
"[python]": {
"editor.formatOnType": true,
},
"python.linting.enabled": true,
"python.linting.prospectorEnabled": true,
"python.linting.prospectorArgs": [
"-s",
"veryhigh",
"-w",
"mypy",
"--max-line-length",
"88"
],
"editor.rulers": [
88
],
```
# Credits
Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html).
Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html). 

11
eslint/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM debian:latest
RUN apt-get update && apt-get install -y --no-install-recommends npm
RUN npm install eslint --global
COPY eslintrc.json /eslintrc.json
WORKDIR melpomene
CMD ["eslint", "--no-eslintrc", "-c", "eslint/eslintrc.json", "-f", "html", "-o", "eslint_report.html", "melpomene.js"]

40
eslint/eslintrc.json Normal file
View File

@ -0,0 +1,40 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:all"
],
"rules": {
"semi": "warn",
"spaced-comment": "warn",
"padded-blocks": ["warn", "never"],
"camelcase": "warn",
"multiline-comment-style": "off",
"max-len": "warn",
"prefer-template": "warn",
"object-curly-spacing" : ["warn", "always"],
"func-style": ["warn", "declaration"],
"key-spacing": "warn",
"one-var": ["warn", "never"],
"quotes": "warn",
"space-before-function-paren": ["warn", "never"],
"no-undefined": "off",
"strict": ["error", "global"],
"keyword-spacing": "warn",
"function-call-argument-newline": "warn",
"operator-assignment": "off",
"space-before-blocks": "warn",
"brace-style": ["warn", "allman", {"allowSingleLine": true}],
"function-call-argument-newline" : ["warn", "consistent"],
"function-paren-newline": ["warn", "consistent"],
"no-magic-numbers": ["error", {"ignore": [0,1,2,100]}],
"quote-props": ["warn", "as-needed"],
"no-unused-vars": ["error", {"argsIgnorePattern": "^_"}],
"max-len" : ["warn", {"code": 88}],
"capitalized-comments": "off",
"operator-linebreak": ["warn", "before"],
"max-lines": "off"
}
}

45
header_version_updater.py Normal file
View File

@ -0,0 +1,45 @@
import re
from pathlib import Path
version = input("New version = ").strip()
js_file = Path("melpomene.js")
css_file = Path("melpomene.css")
js_css_match = r"(?<=/\* Version ).*(?= \*/)"
js_variable_match = r"(?<=const MELPOMENE_VERSION = \").*(?=\";)"
html_file = Path("melpomene.html")
demo_high_res_file = Path("demos/pepper_and_carrot_e35_highres.html")
demo_low_res_file = Path("demos/pepper_and_carrot_e35_lowres.html")
html_match = r"(?<=<!-- Version ).*(?= -->)"
zoom_generator_file = Path("zooms_generator.py")
zoom_generator_match = r"(?<=# Version ).*(?=$)"
def replace_version(version, file_path, matcher):
with open(file=file_path, mode="r", encoding="UTF8") as file:
file_data = file.read()
file_data = re.sub(matcher, version, file_data)
with open(file=file_path, mode="w", encoding="UTF8") as file:
file.write(file_data)
if re.match(r"[.a-zA-Z\ \-0-9]{1,30}", version):
replace_version(version, js_file, js_css_match)
replace_version(version, js_file, js_variable_match)
replace_version(version, css_file, js_css_match)
replace_version(version, html_file, html_match)
replace_version(version, demo_high_res_file, html_match)
replace_version(version, demo_low_res_file, html_match)
replace_version(version, zoom_generator_file, zoom_generator_match)
print("Done")
else:
print("Input is not valid : only use letters, numbers, spaces, dots and dashes")

View File

@ -31,6 +31,11 @@
background-color: black;
}
/* Reset images style to avoid external CSS interfering */
#melpomene img {
all: initial;
}
#melpomene-content-frame {
position: relative;
flex: 1;

View File

@ -2,422 +2,504 @@
/* Version 1.0.0_RC1 */
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
//============
"use strict";
// ============
// CONTROLS
//============
// ============
MOVE_NEXT = "ArrowRight"
MOVE_BACK = "ArrowLeft"
TOGGLE_FULLSCREEN = "F"
TOGGLE_PROGRESSBAR = "P"
TOGGLE_VIEW_MODE = "V"
const MOVE_NEXT = "ArrowRight";
const MOVE_BACK = "ArrowLeft";
const TOGGLE_FULLSCREEN = "F";
const TOGGLE_PROGRESSBAR = "P";
const TOGGLE_VIEW_MODE = "V";
//========================
// ========================
// NAVIGATION CONSTANTS
//========================
// ========================
PAGE_TRANSITION_SPEED = "1.5s"
MOUSEWHELL_MIN_DELAY = 50
DELAY_BEFORE_HIDDING_CONTROLS = 4000;
const MOUSEWHELL_MIN_DELAY = 50;
const DELAY_BEFORE_HIDDING_CONTROLS = 4000;
//====================
// ====================
// STATES CONSTANTS
//====================
// ====================
MELPOMENE_VERSION = "1.0.0_RC1"
const MELPOMENE_VERSION = "1.0.0_UNSTABLE";
READER_FRAME = document.getElementById("melpomene")
READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame")
READER_PAGES = document.getElementById("melpomene-pages")
FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus")
FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col")
HELP_CONTROLS = document.getElementById("melpomene-help-menu")
PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container")
PROGRESS_BAR = document.getElementById("melpomene-progress-bar")
PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections")
VERSION_DISPLAY = document.getElementById("melpomene-version")
const READER_FRAME = document.getElementById("melpomene");
const READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame");
const READER_PAGES = document.getElementById("melpomene-pages");
const FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus");
const FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col");
const HELP_CONTROLS = document.getElementById("melpomene-help-menu");
const PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container");
const PROGRESS_BAR = document.getElementById("melpomene-progress-bar");
const PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections");
const VERSION_DISPLAY = document.getElementById("melpomene-version");
//===========================
// ====================
// INDEX CONSTANTS
// ====================
const ZOOM_PAGE_INDEX = 0;
const ZOOM_WIDTH_INDEX = 1;
const ZOOM_HEIGHT_INDEX = 2;
const ZOOM_X_INDEX = 3;
const ZOOM_Y_INDEX = 4;
// ===========================
// STATES GLOBAL VARIABLES
//===========================
// ===========================
let PAGES_ZOOMS;
// The variable ZOOMS can either be defined by another JS file or contructed at init
if (typeof PAGES_ZOOMS == 'undefined') {
PAGES_ZOOMS = null
if (typeof PAGES_ZOOMS === "undefined")
{
PAGES_ZOOMS = null;
}
CURRENT_ZOOM = 0
CURRENT_PAGE = 1
CURRENT_WIDTH = 0
CURRENT_HEIGHT = 0
CURRENT_X = 0
CURRENT_Y = 0
let CURRENT_ZOOM = 0;
let CURRENT_PAGE = 1;
let CURRENT_WIDTH = 0;
let CURRENT_HEIGHT = 0;
let CURRENT_X = 0;
let CURRENT_Y = 0;
IS_PAGE_MODE = false
MOUSEWHELL_WAIT = false
let IS_PAGE_MODE = false;
let MOUSEWHELL_WAIT = false;
// =============
// UTILITIES
// =============
// Zooms utilites
// --------------
function globalZoomScale(){
if (READER_PAGES.dataset.globalZoomScale != undefined){
return parseFloat(READER_PAGES.dataset.globalZoomScale)
}
return 1.0
}
function globalZoomOffsetX(){
if (READER_PAGES.dataset.globalZoomOffset != undefined){
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[0])
}
return 0.0
}
function globalZoomOffsetY(){
if (READER_PAGES.dataset.globalZoomOffset != undefined){
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[1])
}
return 0.0
}
function loadZoomsFromImgTagsIfRequired(){
// Zooms may be defined by another JS file
if (PAGES_ZOOMS == null){
PAGES_ZOOMS = []
// parse the data-zooms of each img and and the page number info
for (var i = 0; i < READER_PAGES.children.length; i++) {
zooms_raw_data = READER_PAGES.children[i].dataset.zooms
// ';' separates zooms data, ',' separates values
// We add the page number (adding 1 because of indexing)
zooms = zooms_raw_data.split(";").map(
zoom => [i + 1].concat(
zoom.split(',').map(
value => parseFloat(value)
)
)
)
PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms)
}
}
}
function getFirstZoomOfPage(pageNumber){
for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
return zoom_idx
}
}
}
function getLastZoomOfPage(pageNumber){
let res = null
for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
res = zoom_idx
}
if (res != null && PAGES_ZOOMS[zoom_idx][0] != pageNumber) {
break
}
}
return res
}
function getZoomCountForPage(pageNumber) {
return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
}
function getCurrentZoomIndexForPage() {
previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length
return CURRENT_ZOOM - previousZoomsCount + 1
}
function getReadingProgressPercent() {
progressPerPage = 1 / getPagesCount()
if (IS_PAGE_MODE){
return 100 * progressPerPage * CURRENT_PAGE
}
progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE)
readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom
return 100 * readingProgress
}
function updateProgressBar(){
PROGRESS_BAR.style.width = getReadingProgressPercent() + "%"
}
// Dimensions utilites
// -------------------
function getPagesCount() {
return READER_PAGES.childElementCount
function getPagesCount()
{
return READER_PAGES.childElementCount;
}
function pageOriginalHeight(pageNumber) {
return READER_PAGES.children[pageNumber - 1].height
function pageOriginalHeight(pageNumber)
{
return READER_PAGES.children[pageNumber - 1].naturalHeight;
}
function pageOriginalWidth(pageNumber) {
return READER_PAGES.children[pageNumber - 1].width
function pageOriginalWidth(pageNumber)
{
return READER_PAGES.children[pageNumber - 1].naturalWidth;
}
function readerFrameRatio() {
return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight
function readerFrameRatio()
{
return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight;
}
function pageRatio(pageNumber) {
return READER_PAGES.children[pageNumber - 1].width / READER_PAGES.children[pageNumber - 1].height
}
function pageMaxHeight()
{
let maxHeight = 0;
function pageMaxHeight(){
let max_height = 0
for (var i = 0; i < READER_PAGES.children.length; i++) {
if(READER_PAGES.children[i].height > max_height){
max_height = READER_PAGES.children[i].height
for (let pageIdx = 0; pageIdx < READER_PAGES.children.length; pageIdx += 1)
{
if (READER_PAGES.children[pageIdx].naturalHeight > maxHeight)
{
maxHeight = READER_PAGES.children[pageIdx].naturalHeight;
}
}
return max_height
return maxHeight;
}
function pageVerticalOffset(pageNumber) {
return ( pageMaxHeight() - pageOriginalHeight(pageNumber) ) / 2
function pageVerticalOffset(pageNumber)
{
return (pageMaxHeight() - pageOriginalHeight(pageNumber)) / 2;
}
function previousPagesWidth(pageNumber) {
function previousPagesWidth(pageNumber)
{
// The width of all previous pages relative to the provided index
let totalWidth = 0
let totalWidth = 0;
for (let idx = 0; idx < pageNumber - 1; idx++){
totalWidth = totalWidth + READER_PAGES.children[idx].width
for (let idx = 0; idx < pageNumber - 1; idx += 1)
{
totalWidth += READER_PAGES.children[idx].naturalWidth;
}
return totalWidth
return totalWidth;
}
// Zooms utilites
// --------------
function globalZoomScale()
{
if (READER_PAGES.dataset.globalZoomScale !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomScale);
}
return 1.0;
}
function globalZoomOffsetX()
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[0]);
}
return 0.0;
}
function globalZoomOffsetY()
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[1]);
}
return 0.0;
}
function loadZoomsFromImgTagsIfRequired()
{
// Zooms may be defined by another JS file
if (PAGES_ZOOMS === null)
{
PAGES_ZOOMS = [];
// parse the data-zooms of each img and and the page number info
for (let idx = 0; idx < READER_PAGES.children.length; idx += 1)
{
const zoomsRawData = READER_PAGES.children[idx].dataset.zooms;
// ";" separates zooms data, "," separates values
// We add the page number (adding 1 because of indexing)
const zooms = zoomsRawData.split(";").map(
(zoom) => [idx + 1].concat(
zoom.split(",").map(
(value) => parseFloat(value)
)
)
);
PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms);
}
}
}
function getFirstZoomOfPage(pageNumber)
{
for (let zoomIdx = 0; zoomIdx < PAGES_ZOOMS.length; zoomIdx += 1)
{
if (PAGES_ZOOMS[zoomIdx][0] === pageNumber)
{
return zoomIdx;
}
}
return undefined;
}
function getZoomCountForPage(pageNumber)
{
return PAGES_ZOOMS.filter((zoom) => zoom[0] === pageNumber).length;
}
function getCurrentZoomIndexForPage()
{
const previousZoomsCount = PAGES_ZOOMS.filter((zoom) => zoom[0] < CURRENT_PAGE).length;
return CURRENT_ZOOM - previousZoomsCount + 1;
}
function getReadingProgressPercent()
{
const progressPerPage = 1 / getPagesCount();
if (IS_PAGE_MODE)
{
return 100 * progressPerPage * CURRENT_PAGE;
}
const progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE);
let readingProgress = (CURRENT_PAGE - 1) * progressPerPage;
readingProgress += getCurrentZoomIndexForPage() * progressPerZoom;
return 100 * readingProgress;
}
function updateProgressBar()
{
PROGRESS_BAR.style.width = `${getReadingProgressPercent()}%`;
}
// =========
// ACTIONS
// =========
function initReader(){
loadZoomsFromImgTagsIfRequired()
moveReaderDisplayToZoom(0)
// Smoothly show pictures when they intersect with the viewport
let visibilityObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.style.opacity = 1
entry.target.style.visibility = "visible"
} else {
entry.target.style.opacity = 0
entry.target.style.visibility = "hidden"
}
});
}, { root: READER_CONTENT_FRAME, rootMargin: "-10px" });
for (var i = 0; i < READER_PAGES.children.length; i++) {
let img = READER_PAGES.children[i];
visibilityObserver.observe(img)
PROGRESS_BAR_PAGES.appendChild(document.createElement("div"))
}
READER_PAGES.style.display = "flex"
setTimeout(() => {
READER_PAGES.hidden = false
}, "300")
setTimeout(() => {
HELP_CONTROLS.style.opacity = null;
HELP_CONTROLS.style.transform = null;
}, DELAY_BEFORE_HIDDING_CONTROLS)
function updateFocusByWidth(width)
{
FOCUS_OVERLAY_WIDTH.style.width = `${width / READER_CONTENT_FRAME.clientWidth * 100}%`;
FOCUS_OVERLAY_HEIGHT.style.height = "100%";
}
function updateFocusByHeight(height)
{
FOCUS_OVERLAY_WIDTH.style.width = "100%";
FOCUS_OVERLAY_HEIGHT.style.height = `${height / READER_CONTENT_FRAME.clientHeight * 100}%`;
}
function moveReaderDisplayToArea(pageNumber, width, height, posx, posy){
function moveReaderDisplayToArea(pageNumber, oWidth, oHeight, oPosx, oPosy)
{
// Keep original values for registering
o_width = width
o_height = height
o_posx = posx
o_posy = posy
let width = oWidth;
let height = oHeight;
let posx = oPosx;
let posy = oPosy;
// Apply global offsets before scales if we are displaying a zoom
// Pages display uses width & height = 0
if (width != 0 || height != 0){
width = width * globalZoomScale()
height = height * globalZoomScale()
posx = (posx + globalZoomOffsetX()) * globalZoomScale()
posy = (posy + globalZoomOffsetY()) * globalZoomScale()
if (width !== 0 || height !== 0)
{
width = width * globalZoomScale();
height = height * globalZoomScale();
posx = (posx + globalZoomOffsetX()) * globalZoomScale();
posy = (posy + globalZoomOffsetY()) * globalZoomScale();
}
// reduce width if offset sent us outside of page
if (posx < 0) {
width = width + posx
posx = 0
if (posx < 0)
{
width = width + posx;
posx = 0;
}
if ((posx + width) > pageOriginalWidth(pageNumber)) {
width = pageOriginalWidth(pageNumber) - posx
if (posx + width > pageOriginalWidth(pageNumber))
{
width = pageOriginalWidth(pageNumber) - posx;
}
// reduce height if offset sent us outside of page
if (posy < 0) {
height = height + posy
posy = 0
if (posy < 0)
{
height = height + posy;
posy = 0;
}
if ((posy + height) > pageOriginalHeight(pageNumber)) {
height = pageOriginalHeight(pageNumber) - posy
if (posy + height > pageOriginalHeight(pageNumber))
{
height = pageOriginalHeight(pageNumber) - posy;
}
// Align the top-left corner of the frame with the page
READER_PAGES.style.transform = "translate(-" + previousPagesWidth(pageNumber) + "px, -" + pageVerticalOffset(pageNumber) + "px )"
READER_PAGES.style.transform = `translate(-${previousPagesWidth(pageNumber)}px, -${pageVerticalOffset(pageNumber)}px)`;
// Then move so the top-left point of the zoom match the frame top-left
READER_PAGES.style.transform = "translate(" + (- posx) + "px, " + (-posy) + "px )" + READER_PAGES.style.transform
READER_PAGES.style.transform = `translate(${-posx}px, ${-posy}px) ${READER_PAGES.style.transform}`;
// Then, scale so the zoom would fit the frame, and center the zoom
if (width == 0){
width = pageOriginalWidth(pageNumber)
if (width === 0)
{
width = pageOriginalWidth(pageNumber);
}
if (height == 0){
height = pageOriginalHeight(pageNumber)
if (height === 0)
{
height = pageOriginalHeight(pageNumber);
}
zoomRatio = width / height
const zoomRatio = width / height;
if (readerFrameRatio() > zoomRatio) {
if (readerFrameRatio() > zoomRatio)
{
// Frame wider than zoom => scale so heights are the same, offset on x
var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height;
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
var scaledWidth = width * zoomToFrameScaleFactor
var offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2
const scaledWidth = width * zoomToFrameScaleFactor;
const offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2;
READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform
READER_PAGES.style.transform = `translateX(${offset}px) ${READER_PAGES.style.transform}`;
updateFocusByWidth(scaledWidth)
} else {
updateFocusByWidth(scaledWidth);
}
else
{
// Frame narower than zoom => scale so left/right match, offset on y
var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width;
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
var scaledHeight = height * zoomToFrameScaleFactor
var offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2
const scaledHeight = height * zoomToFrameScaleFactor;
const offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2;
READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform
READER_PAGES.style.transform = `translateY(${offset}px) ${READER_PAGES.style.transform}"`;
updateFocusByHeight(scaledHeight)
updateFocusByHeight(scaledHeight);
}
// Use values before global offset / scale
CURRENT_PAGE = pageNumber
CURRENT_WIDTH = o_width
CURRENT_HEIGHT = o_height
CURRENT_X = o_posx
CURRENT_Y = o_posy
CURRENT_PAGE = pageNumber;
CURRENT_WIDTH = oWidth;
CURRENT_HEIGHT = oHeight;
CURRENT_X = oPosx;
CURRENT_Y = oPosy;
}
function refreshReaderDisplay() {
moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y)
function refreshReaderDisplay()
{
moveReaderDisplayToArea(
CURRENT_PAGE,
CURRENT_WIDTH,
CURRENT_HEIGHT,
CURRENT_X,
CURRENT_Y
);
}
function moveReaderDisplayToPage(pageNumber) {
moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0)
function moveReaderDisplayToPage(pageNumber)
{
moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0);
}
function moveReaderDisplayToZoom(index) {
function moveReaderDisplayToZoom(zoomIdx)
{
moveReaderDisplayToArea(
PAGES_ZOOMS[zoomIdx][ZOOM_PAGE_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_WIDTH_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_HEIGHT_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_X_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_Y_INDEX]
);
moveReaderDisplayToArea(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_ZOOMS[index][4])
CURRENT_ZOOM = index
CURRENT_ZOOM = zoomIdx;
}
function updateFocusByWidth(width){
FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%"
FOCUS_OVERLAY_HEIGHT.style.height = "100%"
function toggleViewMode()
{
if (IS_PAGE_MODE)
{
if (CURRENT_ZOOM === null)
{
moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE));
}
else
{
moveReaderDisplayToZoom(CURRENT_ZOOM);
}
IS_PAGE_MODE = false;
}
else
{
moveReaderDisplayToPage(CURRENT_PAGE);
IS_PAGE_MODE = true;
}
updateProgressBar();
}
function updateFocusByHeight(height){
FOCUS_OVERLAY_WIDTH.style.width = "100%"
FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%"
function moveReader(toNext)
{
if (IS_PAGE_MODE)
{
if (toNext && CURRENT_PAGE < getPagesCount())
{
moveReaderDisplayToPage(CURRENT_PAGE + 1);
CURRENT_ZOOM = null;
}
else if (!toNext && CURRENT_PAGE > 1)
{
moveReaderDisplayToPage(CURRENT_PAGE - 1);
CURRENT_ZOOM = null;
}
updateProgressBar();
}
// Zoom mode
else
{
if (toNext && CURRENT_ZOOM < PAGES_ZOOMS.length - 1)
{
moveReaderDisplayToZoom(CURRENT_ZOOM + 1);
}
else if (!toNext && CURRENT_ZOOM > 0)
{
moveReaderDisplayToZoom(CURRENT_ZOOM - 1);
}
updateProgressBar();
}
}
function toggleViewMode() {
if (IS_PAGE_MODE){
if (CURRENT_ZOOM != null){
moveReaderDisplayToZoom(CURRENT_ZOOM)
} else {
moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE))
}
IS_PAGE_MODE = false
} else {
moveReaderDisplayToPage(CURRENT_PAGE)
IS_PAGE_MODE = true
function initReader()
{
VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION);
loadZoomsFromImgTagsIfRequired();
moveReaderDisplayToZoom(0);
// Smoothly show pictures when they intersect with the viewport
const visibilityObserver = new IntersectionObserver(
(entries, _observer) =>
{
entries.forEach((entry) =>
{
if (entry.isIntersecting)
{
entry.target.style.opacity = 1;
entry.target.style.visibility = "visible";
}
updateProgressBar()
}
else
{
entry.target.style.opacity = 0;
entry.target.style.visibility = "hidden";
}
});
},
{
root: READER_CONTENT_FRAME,
rootMargin: "-10px"
}
);
function moveReader(to_next) {
for (let pageIndex = 0; pageIndex < READER_PAGES.children.length; pageIndex += 1)
{
const img = READER_PAGES.children[pageIndex];
visibilityObserver.observe(img);
if (IS_PAGE_MODE){
if (to_next && CURRENT_PAGE < getPagesCount()) {
moveReaderDisplayToPage(CURRENT_PAGE + 1)
CURRENT_ZOOM = null
PROGRESS_BAR_PAGES.appendChild(document.createElement("div"));
}
else if (!to_next && CURRENT_PAGE > 1) {
moveReaderDisplayToPage(CURRENT_PAGE - 1)
CURRENT_ZOOM = null
}
READER_PAGES.style.display = "flex";
} else {
if (to_next && CURRENT_ZOOM < PAGES_ZOOMS.length - 1) {
moveReaderDisplayToZoom(CURRENT_ZOOM + 1)
}
else if (!to_next && CURRENT_ZOOM > 0) {
moveReaderDisplayToZoom(CURRENT_ZOOM - 1)
}
}
updateProgressBar()
setTimeout(
() => { READER_PAGES.hidden = false; },
"300"
);
setTimeout(
() =>
{
HELP_CONTROLS.style.opacity = null;
HELP_CONTROLS.style.transform = null;
},
DELAY_BEFORE_HIDDING_CONTROLS
);
}
@ -425,57 +507,86 @@ function moveReader(to_next) {
// CALLBACKS
// =============
function handleKeyPress(key){
if (key == MOVE_NEXT) {
moveReader(true)
function handleKeyPress(key)
{
if (key === MOVE_NEXT)
{
moveReader(true);
}
else if (key == MOVE_BACK) {
moveReader(false)
else if (key === MOVE_BACK)
{
moveReader(false);
}
else if (key.toUpperCase() == TOGGLE_FULLSCREEN){
if (document.fullscreenElement == null){
else if (key.toUpperCase() === TOGGLE_FULLSCREEN)
{
if (document.fullscreenElement === null)
{
READER_FRAME.requestFullscreen();
} else {
}
else
{
document.exitFullscreen();
}
}
else if (key.toUpperCase() == TOGGLE_PROGRESSBAR){
if (PROGRESS_BAR_CONTAINER.hidden == true) {
PROGRESS_BAR_CONTAINER.hidden = false
} else {
PROGRESS_BAR_CONTAINER.hidden = true
else if (key.toUpperCase() === TOGGLE_PROGRESSBAR)
{
if (PROGRESS_BAR_CONTAINER.hidden === true)
{
PROGRESS_BAR_CONTAINER.hidden = false;
}
else
{
PROGRESS_BAR_CONTAINER.hidden = true;
}
refreshReaderDisplay();
}
else if (key.toUpperCase() == TOGGLE_VIEW_MODE) {
toggleViewMode()
else if (key.toUpperCase() === TOGGLE_VIEW_MODE)
{
toggleViewMode();
}
}
function handleMouseWhell(deltaY){
function handleMouseWhell(event)
{
// Only handle scroll event if the target is the nav controls
// to avoid preventing page scrolling.
if (MOUSEWHELL_WAIT){
return
} else {
MOUSEWHELL_WAIT = true
setTimeout(() => {
MOUSEWHELL_WAIT = false
}, MOUSEWHELL_MIN_DELAY)
// Do disable page scrolling when we do prev/next, though
if (!READER_FRAME.contains(event.target))
{
return;
}
if (deltaY > 0) {
moveReader(true, false)
event.preventDefault();
event.stopPropagation();
if (MOUSEWHELL_WAIT)
{
return;
}
else {
moveReader(false, false)
MOUSEWHELL_WAIT = true;
setTimeout(
() => { MOUSEWHELL_WAIT = false; },
MOUSEWHELL_MIN_DELAY
);
if (event.deltaY > 0)
{
moveReader(true, false);
}
else
{
moveReader(false, false);
}
}
@ -483,19 +594,26 @@ function handleMouseWhell(deltaY){
// INIT
// ======
window.addEventListener("load", (event) => {
VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION)
initReader()
});
window.addEventListener(
"load",
(_event) => { initReader(); }
);
addEventListener("resize", (event) => {
refreshReaderDisplay();
});
addEventListener(
"resize",
(_event) => { refreshReaderDisplay(); }
);
addEventListener("keydown", (event) => {
handleKeyPress(event.key, event.shiftKey)
});
addEventListener(
"keydown",
(event) =>
{
handleKeyPress(event.key, event.shiftKey);
}
);
addEventListener("wheel", (event) => {
handleMouseWhell(event.deltaY)
});
addEventListener(
"wheel",
(event) => { handleMouseWhell(event); },
{ passive: false }
);

View File

@ -3,10 +3,10 @@
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
import sys
import re
import xml.etree.ElementTree as ET
import argparse
from argparse import ArgumentParser
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
from typing import Any
from pathlib import Path
@ -14,59 +14,84 @@ HTML_TEMPLATE = Path(__file__).parent / "melpomene.html"
HTML_TO_REPLACE = "<!-- your img tags here, see documentation -->"
def extract_zooms(src_folder):
def get_val_has_str(elem: Element, attrib: str, filepath: str | Path) -> str:
value = elem.get(attrib)
if value is None:
sys.exit(f"Attribute '{attrib}' is not valid in file {filepath}")
return str(value)
def extract_zooms(src_folder) -> dict[int, Any]:
folder = Path(src_folder)
zooms = {}
max_width = 0
max_height = 0
pages_zooms: dict[int, Any] = {}
idx = 0
for svg_path in folder.glob("*.svg"):
idx += 1
print(f"page {idx} : {svg_path.name}")
zooms[idx] = {
# Setting up default values
pages_zooms[idx] = {
"name": svg_path.stem,
"width": 0,
"height": 0,
"zooms": [],
}
tree = ET.parse(svg_path)
tree = ElementTree.parse(svg_path)
root = tree.getroot()
zooms[idx]["width"] = int(root.get("width"))
zooms[idx]["height"] = int(root.get("height"))
width = get_val_has_str(root, "width", svg_path)
height = get_val_has_str(root, "height", svg_path)
for area in root.findall('.//{*}rect'):
zooms[idx]["zooms"].append([
float(area.get("width")),
float(area.get("height")),
float(area.get("x")),
float(area.get("y")),
])
if "." in width:
print(
f"WARNING: file {svg_path} has a floating width, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["width"] = round(float(width))
return zooms, max_width, max_height
if "." in height:
print(
f"WARNING: file {svg_path} has a floating height, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["height"] = round(float(height))
zooms = []
for area in root.findall(".//{*}rect"):
zooms.append(
[
float(get_val_has_str(area, "width", svg_path)),
float(get_val_has_str(area, "height", svg_path)),
float(get_val_has_str(area, "x", svg_path)),
float(get_val_has_str(area, "y", svg_path)),
]
)
pages_zooms[idx]["zooms"] = zooms
return pages_zooms
def write_json_or_js(zooms, dest_file, is_js):
with open(dest_file, "w") as data_file:
def write_json_or_js(zooms, dest_file, is_js) -> None:
with open(dest_file, "w", encoding="UTF-8") as data_file:
if is_js:
data_file.write("PAGES_ZOOMS = ")
data_file.write("[\n")
first_coma_skiped = False
for page_idx in sorted(zooms.keys()):
for zoom in zooms[page_idx]["zooms"]:
if zoom[2] < 0 or zoom[3] < 0 :
print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)")
if zoom[2] < 0 or zoom[3] < 0:
print(
f"WARNING: negative pos x / pos y in page {page_idx} for "
f"zoom {zoom} (is the rectangle flipped?)"
)
if first_coma_skiped:
data_file.write(",\n")
@ -76,45 +101,71 @@ def write_json_or_js(zooms, dest_file, is_js):
data_file.write("\n]\n")
def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention):
def write_html(zooms, dest_file, prefix, extention) -> None:
img_tags = ""
for page_idx in sorted(zooms.keys()):
img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
zoom_html_data = [','.join([str(zoom) for zoom in page_zooms]) for page_zooms in zooms[page_idx]["zooms"]]
zoom_html_str = ';'.join(zoom_html_data)
img_tags = img_tags + f' <img loading="lazy" height="{zooms[page_idx]["height"]}" width="{zooms[page_idx]["width"]}" src="{img_url}" data-zooms="{zoom_html_str}"/>\n'
zoom_html_data = [
",".join([str(zoom) for zoom in page_zooms])
for page_zooms in zooms[page_idx]["zooms"]
]
zoom_html_str = ";".join(zoom_html_data)
img_tags = (
img_tags
+ " "
+ f'<img loading="lazy" height="{zooms[page_idx]["height"]}" '
+ f'width="{zooms[page_idx]["width"]}" src="{img_url}" '
+ f'data-zooms="{zoom_html_str}"/>\n'
)
img_tags = img_tags.strip()
with open(HTML_TEMPLATE) as template_file, open(dest_file, "w") as data_file:
with open(HTML_TEMPLATE, "r", encoding="UTF-8") as template_file, open(
dest_file, "w", encoding="UTF-8"
) as data_file:
data = template_file.read().replace(HTML_TO_REPLACE, img_tags)
data_file.write(data)
def generate_argparse():
""" Generate Melpomene's generator input parser"""
def generate_argparse() -> ArgumentParser:
"""Generate Melpomene's generator input parser"""
parser = argparse.ArgumentParser(
description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader"
parser = ArgumentParser(
description=(
"Helper that can generate JSON / JS / "
"HTML files for Melpomene webcomic reader"
)
)
parser.add_argument("output_format", choices=["html", "json", "js"], help="The type of output to generate")
parser.add_argument(
"output_format",
choices=["html", "json", "js"],
help="The type of output to generate",
)
parser.add_argument("svg_folders", help="Path of the folder containing the SVGs")
parser.add_argument("-o", metavar="dest_file", help="Where to write the generator output to")
parser.add_argument("-p", default="", metavar="img_url_prefix", help="What to prefix the URL of the images when using HTML format.")
parser.add_argument("-e", default="png", metavar="img_ext", help="What extention to use in the URL of the images when using HTML format.")
parser.add_argument(
"-o", metavar="dest_file", help="Where to write the generator output to"
)
parser.add_argument(
"-p",
default="",
metavar="img_url_prefix",
help="What to prefix the URL of the images when using HTML format.",
)
parser.add_argument(
"-e",
default="png",
metavar="img_ext",
help="What extention to use in the URL of the images when using HTML format.",
)
return parser
if __name__ == "__main__":
def run():
args = generate_argparse().parse_args()
# Get the final outout name
output = None
@ -132,13 +183,17 @@ if __name__ == "__main__":
elif args.output_format == "js" and not output.endswith(".js"):
output += ".js"
zooms, max_width, max_height = extract_zooms(args.svg_folders)
zooms = extract_zooms(args.svg_folders)
if args.output_format == "html":
write_html(zooms, output, max_width, max_height, args.p, args.e)
write_html(zooms, output, args.p, args.e)
elif args.output_format == "json":
write_json_or_js(zooms, output, False)
elif args.output_format == "js":
write_json_or_js(zooms, output, True)
if __name__ == "__main__":
run()