commit 1cc49fce209fc963ac967f72288a56b8e41fa63e Author: Fabio Iotti Date: Sat Dec 7 13:20:03 2024 +0100 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c55f796 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +res/manual/* filter=lfs diff=lfs merge=lfs -text diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..62614e9 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +Tunic Manual Translation +======================== + +This is a tool I developed to help me translate the game manual for the +[Tunic](https://en.wikipedia.org/wiki/Tunic_(video_game)) video game, which +is written in a mix of English and a constructed language. + +> [!CAUTION] +> If you plan to play Tunic in the future please stop reading immediately, +> this manual contains major spoilers about pretty much the whole game. + + +## Usage + +1. Install [Node.js](https://nodejs.org/); +2. Run `npm start`; +3. Visit http://127.0.0.1:8080/ with a web browser. + + +## License + +Copyright (c) 2024 Fabio Iotti + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along +with this program. If not, see https://www.gnu.org/licenses/. + +Some files may be licensed under different terms, refer to the header at the +beginning of each individual file for file-specific licenses. + +Screenshots of the manual, taken from the Tunic video game, do not fall under +this license, all rights belong to the respective owners. diff --git a/index.html b/index.html new file mode 100644 index 0000000..4a5f7df --- /dev/null +++ b/index.html @@ -0,0 +1,79 @@ + + + + + + Tunic Manual Translation + + + +
+ No page loaded +
+ Hello + +
+
+ +
+ +
+ + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..aef2a44 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "scripts": { + "start": "npx http-server -c-1" + } +} diff --git a/res/data.json b/res/data.json new file mode 100644 index 0000000..1877463 --- /dev/null +++ b/res/data.json @@ -0,0 +1 @@ +{"pages":{"pg03":{"links":{},"words":{"32332262-7342-4bf6-85b4-922eda4570a3":{"x":163,"y":232,"w":25,"h":39},"63b8d0d7-0f9c-4d7a-957c-09665fd34896":{"x":194,"y":232,"w":54,"h":39},"c2545fa5-beaf-477b-9d01-bd57de827487":{"x":254,"y":234,"w":25,"h":36},"858b4d5b-a8a7-4b9c-b597-7a380cb7ae57":{"x":490,"y":234,"w":22,"h":37},"445a5ed9-cfe3-4d0b-809d-5b2c4fc294d6":{"x":521,"y":231,"w":54,"h":40},"a7953b19-fa0c-4bf5-8ab8-27dc4c46e932":{"x":582,"y":231,"w":39,"h":41},"b3facf26-d7ae-495d-8c05-f61f25166ef7":{"x":164,"y":274,"w":23,"h":33},"619c6ead-1cd9-4205-89d5-cb88d201515a":{"x":194,"y":274,"w":54,"h":33},"ab27b3f8-742e-49bc-aef4-8a080317f413":{"x":255,"y":272,"w":23,"h":35},"118c100f-f13f-4593-a2e3-b96f88018ad1":{"x":283,"y":272,"w":39,"h":37},"164c8c73-374a-4ed3-96f4-4baa4f151b08":{"x":343,"y":274,"w":35,"h":34},"532b1a02-8acf-48fe-9cb2-0a0cb538f018":{"x":390,"y":274,"w":50,"h":33},"e662d755-ad4e-46ab-8f1c-0d03ca1097ea":{"x":450,"y":275,"w":36,"h":31},"b86e13dd-e8d9-4cd9-a88c-aa32044d41c0":{"x":497,"y":275,"w":35,"h":32},"d4418b1c-cd73-4c33-af42-9026bb8b99db":{"x":543,"y":275,"w":18,"h":31},"89700574-2fdd-4838-aab0-cef2507f1968":{"x":571,"y":275,"w":52,"h":31}},"symbols":{"d5f71942-9585-401e-84f6-3b30e6a15de8":{"x":164,"y":234,"w":23,"h":36},"6768f488-424f-4676-86a6-a0c498a25f37":{"x":196,"y":234,"w":15,"h":36},"64f318cd-3cef-4aa3-a0b5-2d978cdea80d":{"x":213,"y":234,"w":16,"h":35},"3664d26e-8c02-47e9-a80c-173dd3fb33fd":{"x":232,"y":234,"w":14,"h":35},"0a4bc228-ebb6-47de-8584-3169cecec930":{"x":258,"y":235,"w":18,"h":34},"a6d158a4-a626-4450-8c96-7d145cb1f50a":{"x":492,"y":236,"w":17,"h":33},"6732f249-4c81-472a-b7bb-3942e15dc387":{"x":523,"y":233,"w":15,"h":35},"72cb4e55-30aa-46bb-af83-43f1485b7274":{"x":540,"y":235,"w":16,"h":33},"24b6fde5-2606-4c56-bd90-e08a7c2f01a8":{"x":558,"y":234,"w":15,"h":35},"932b0b7e-412c-48ef-a9d5-80a7ee62adbe":{"x":584,"y":232,"w":14,"h":38},"3d071808-93bc-4f4d-ab29-341b67dca9d6":{"x":600,"y":233,"w":19,"h":37},"6d7d249d-9b78-4b6b-be6f-5096052a77a7":{"x":168,"y":275,"w":17,"h":30},"bfa4de7f-770b-4803-abc6-2661955c6872":{"x":195,"y":275,"w":17,"h":32},"06f3ec2f-7c5a-44d6-b754-9d73856c1c00":{"x":214,"y":274,"w":15,"h":31},"057758e8-e54f-4790-b17d-ef24a258cdbe":{"x":231,"y":276,"w":16,"h":29},"ac3b7561-a251-4523-b145-14f223b2468b":{"x":257,"y":273,"w":20,"h":33},"db9085e4-05cf-4e0a-9adf-1f8892cbf5b3":{"x":287,"y":273,"w":14,"h":34},"2873c3d7-1be7-4a34-bded-6134cfa377fe":{"x":303,"y":274,"w":18,"h":33},"921e2cca-7dd5-4cc2-b829-565cb95ca8ae":{"x":344,"y":273,"w":16,"h":36},"136f0b34-e413-4b96-9914-0dce8308a3eb":{"x":363,"y":274,"w":14,"h":35},"bfe0f2aa-86ab-4e59-a146-a5c29d8d0a1a":{"x":390,"y":272,"w":15,"h":36},"01aded61-660f-474a-84e0-6900ebd2c1c9":{"x":408,"y":273,"w":13,"h":34},"0d0c425d-b5e5-47ee-bcab-161e0e1fc149":{"x":423,"y":272,"w":17,"h":35},"cc63a633-ab15-40b7-a9ec-71f6a5d109bc":{"x":452,"y":273,"w":14,"h":36},"2ac522f2-e6af-4792-b261-6bc8923ec746":{"x":469,"y":273,"w":17,"h":34},"e49a695b-3c15-40d1-8ae9-992d858f9d23":{"x":498,"y":274,"w":15,"h":34},"7f3bf381-891b-48fa-88a4-e2c3cabbb8f3":{"x":515,"y":274,"w":16,"h":33},"47dc35d0-fc6b-4559-ba77-1fd3dfa27ca2":{"x":544,"y":274,"w":15,"h":33},"0d05f104-a6c9-4bd0-801c-09fcfaf90a85":{"x":572,"y":274,"w":15,"h":34},"15387642-c69a-4acd-a673-e39264319e2b":{"x":590,"y":275,"w":12,"h":32},"cb5224f1-2395-4ac8-89d4-75e107cbace2":{"x":605,"y":277,"w":17,"h":28}}},"pg05":{"words":{}},"pg02":{"links":{"0e3b6df9-08c3-4306-b27e-5c1fd09ccc3b":{"x":322,"y":329,"w":331,"h":25},"bb84ce08-f348-4d63-8417-9473eec552e7":{"x":323,"y":359,"w":326,"h":23},"b10ccedb-40b5-4f27-b441-a55c72a17259":{"x":323,"y":385,"w":327,"h":20},"5e7e23b8-86ea-474e-93f0-466cf298ee90":{"x":325,"y":410,"w":325,"h":24},"9acb2d0d-74b7-4725-bd73-a212b6009e7c":{"x":325,"y":437,"w":324,"h":23},"d544113f-b6d7-4487-bd22-fdb6e9336541":{"x":328,"y":462,"w":318,"h":25},"1505bc81-9ec7-40c1-9e9b-7400cb4b0664":{"x":325,"y":490,"w":323,"h":21},"392c787e-1bbc-4e86-b4cf-656c019dd119":{"x":324,"y":515,"w":326,"h":23},"210a42fa-2dfa-49ec-91c9-0b7760b689dc":{"x":325,"y":542,"w":328,"h":22},"cfc6a963-bfeb-4fba-b1dd-309e2da29181":{"x":327,"y":568,"w":327,"h":24},"a28d717c-115e-4181-9d6e-51376622068a":{"x":325,"y":596,"w":330,"h":21},"2a37fac9-c55b-4a92-942c-93d3aa336318":{"x":325,"y":620,"w":326,"h":24},"3dabe8cb-c41b-4911-b91a-95fdce865474":{"x":324,"y":650,"w":324,"h":21},"d2d04f51-6bd1-40a0-a135-2a6db6c7897b":{"x":734,"y":386,"w":227,"h":23},"cf7af980-3edd-4574-bee9-24253c267536":{"x":734,"y":414,"w":227,"h":19},"fc7733c7-acc6-4708-8ebd-517e02c53ee0":{"x":733,"y":439,"w":230,"h":22},"21b4574f-183f-435e-bfc8-15785b352e9a":{"x":735,"y":466,"w":226,"h":20},"077e5199-4729-4491-9656-82ad678e3e58":{"x":734,"y":492,"w":227,"h":22},"b61fcecd-d75c-42e1-87b0-1bf947189922":{"x":735,"y":519,"w":227,"h":19},"ea6ef2fa-fc1a-40b1-82cb-2222ce193ed9":{"x":735,"y":543,"w":227,"h":24},"6487c007-6a36-4357-affc-69b5600643bb":{"x":736,"y":572,"w":226,"h":20},"0cb39363-c19a-4c98-9c9b-60a500230203":{"x":735,"y":598,"w":227,"h":21},"00bb9ed6-7d67-4170-bcbd-50f2afa7abdc":{"x":736,"y":624,"w":226,"h":20},"d4706815-c7bd-43ca-873e-718ba6154753":{"x":713,"y":673,"w":237,"h":24},"bf737810-3704-4998-b291-35c1e4329cc7":{"x":714,"y":701,"w":236,"h":25}},"words":{"84cb3094-309e-4dec-b238-e546f85d238c":{"x":326,"y":567,"w":34,"h":27},"79876321-5cd3-4fb9-acce-08c6ea26b699":{"x":325,"y":620,"w":34,"h":28},"d2400030-ace3-4206-b4b6-d4859036ac38":{"x":736,"y":622,"w":17,"h":25},"77df6f11-a70a-499e-892f-c7f5a63cf08f":{"x":756,"y":623,"w":16,"h":24},"b51e88d8-b12d-401e-9368-87fe5b238661":{"x":776,"y":623,"w":15,"h":24}},"symbols":{"d5d28b6e-cb8a-4a50-9043-ce90ab50a48d":{"x":738,"y":624,"w":14,"h":23},"c3ec64ae-35d3-4c27-8ab4-45c647d68a9b":{"x":757,"y":624,"w":14,"h":22},"d9545189-ecc0-4cd1-a06f-ee15390c0b01":{"x":777,"y":624,"w":13,"h":23},"8d6e3c8c-ed18-4d3e-a371-6e504b155187":{"x":329,"y":570,"w":7,"h":21},"46bef57b-fd07-4a57-b6e6-fc4461ea3ab3":{"x":339,"y":571,"w":9,"h":19},"23831bd6-6776-4986-b231-e9a351a5f71c":{"x":350,"y":570,"w":8,"h":20},"94b5d04a-5408-40a9-b6c7-f44174463db0":{"x":327,"y":621,"w":9,"h":23},"cc52c340-1aa0-4c4c-a32e-d301d3111af6":{"x":338,"y":621,"w":9,"h":24},"163d33cc-d2b0-4f28-acea-1fa139c65683":{"x":349,"y":622,"w":8,"h":24}}},"pg11":{"symbols":{"cf4ceba5-38db-48ca-8232-e54b3ddbe0a5":{"x":1416,"y":373,"w":19,"h":37},"58148148-c26e-4e57-8a43-02d9de646933":{"x":1438,"y":373,"w":15,"h":37},"728261fb-5b5b-4fce-9bd5-f1ba9176e57d":{"x":1456,"y":373,"w":18,"h":38},"ca32be08-9dec-4483-9573-801be4b72fa9":{"x":1335,"y":465,"w":11,"h":24},"d7ec65d2-d110-478f-9ee7-d099811b8cad":{"x":1349,"y":465,"w":11,"h":26},"f754bfc2-c201-4e0a-87fa-b7d5879072dc":{"x":1363,"y":464,"w":12,"h":26},"5f652c51-2958-46c9-8a24-1cf5d385333f":{"x":1383,"y":464,"w":13,"h":29},"fb74f5dc-272a-4324-b231-0d71d8ee4ab3":{"x":1404,"y":463,"w":13,"h":28},"5b1ff7f1-8878-42b4-92a4-abfae89b4774":{"x":1419,"y":464,"w":12,"h":27},"be71106c-7f02-45b4-8590-e60865c44ecc":{"x":1434,"y":465,"w":12,"h":26},"d7bcbd09-1a72-431e-86bc-5bfb3d5865b0":{"x":1447,"y":464,"w":11,"h":27},"19134db6-581e-4c86-bb16-8751d9840173":{"x":1305,"y":559,"w":18,"h":38},"5889ad08-1e15-46b3-9e15-d111f8d543a6":{"x":1326,"y":557,"w":17,"h":38},"ebcb09c6-685a-4000-9528-62c7903c42a0":{"x":1345,"y":556,"w":17,"h":41},"b5c2f230-766e-43c0-8ec3-89e13685ca43":{"x":1364,"y":557,"w":19,"h":35},"3ef5afa1-4069-4c3c-afa2-04a32cbc6fbb":{"x":1385,"y":558,"w":17,"h":36},"fabe16fa-6c45-4a49-81d2-2b7102f511d1":{"x":1439,"y":558,"w":21,"h":35},"06f18ef5-6cb8-42b7-a5c2-afbed224b7b6":{"x":1439,"y":652,"w":18,"h":40},"d940aff4-fdf5-4974-bd7c-c8796646b455":{"x":1459,"y":651,"w":18,"h":40},"37fb65cd-3829-4d87-89d2-9570a560fb3b":{"x":2217,"y":375,"w":20,"h":37},"fe8e942f-8546-47f4-9b0a-a85e2e26ecf8":{"x":2239,"y":375,"w":15,"h":36},"ddaa058d-be5a-43c3-859f-028140b38d9b":{"x":2256,"y":376,"w":20,"h":35},"abbe110f-3666-4fa3-bc97-6d66d59e73a4":{"x":2211,"y":468,"w":19,"h":36},"fe88dc89-fdcc-4263-88e9-c4cd094d8b5b":{"x":2232,"y":467,"w":18,"h":37},"1f2087ed-3cd6-4bf3-a895-70606c76cf5c":{"x":2252,"y":468,"w":20,"h":37},"ab71b0e9-e6c2-41ed-9430-cbf0fdf860da":{"x":2204,"y":556,"w":17,"h":37},"a084612c-2d43-4177-835f-4589b110abf4":{"x":2223,"y":557,"w":18,"h":36},"14679913-ec7b-49f8-9a89-3b6a43673d6f":{"x":2254,"y":556,"w":19,"h":43},"12cccc41-a449-4bed-beac-4e9f6a825348":{"x":2275,"y":557,"w":19,"h":42},"0a3b2fc7-7ec2-4e1e-97e7-b54858d9d65e":{"x":2104,"y":650,"w":19,"h":38},"2c63228d-6f78-4506-87f2-b9ac1d3cb851":{"x":2125,"y":650,"w":19,"h":38},"820bec13-146d-456e-a7b4-588629d319d8":{"x":2167,"y":650,"w":18,"h":37},"e0c131a2-da68-4135-acea-914b3e67a333":{"x":2187,"y":651,"w":19,"h":37},"6e997649-320b-45e3-8853-00423a2f6cd2":{"x":2230,"y":649,"w":22,"h":41},"768c15eb-1c3e-4149-80a2-f11acbde58cc":{"x":2254,"y":650,"w":16,"h":37},"5c63887b-8960-49a8-8408-65463dc6b718":{"x":199,"y":293,"w":12,"h":26},"87772284-ff55-4e9c-b8d4-6914d28a7e1c":{"x":213,"y":293,"w":14,"h":29},"3c165e5d-626a-4b19-a2b3-1bbff551c74d":{"x":236,"y":290,"w":12,"h":29},"5d3ad0c5-8888-4e91-86a5-b42f0f8e9f14":{"x":251,"y":290,"w":12,"h":28},"b2ed71b2-672e-471f-aeac-faf7c6fde806":{"x":279,"y":291,"w":17,"h":28},"2ea61a8f-f9d6-4aff-a42f-3fe262594da7":{"x":194,"y":370,"w":11,"h":23},"2c9bf060-66a6-4fab-9cb8-4ec33654c71e":{"x":207,"y":370,"w":9,"h":24},"5de83abd-b20b-410c-9510-236250b2d73a":{"x":218,"y":370,"w":9,"h":23},"8553e109-e405-489b-964d-accf09c4aaef":{"x":229,"y":370,"w":9,"h":23},"db86e494-a84f-460d-9f2d-72ef1514b103":{"x":241,"y":370,"w":10,"h":22},"7cbed08e-6fd6-432c-a0f6-8b6962988584":{"x":257,"y":371,"w":12,"h":22},"efcbd22d-2807-42d8-a9c9-d3b737603edf":{"x":271,"y":370,"w":11,"h":22},"9b3c98bc-a4a8-4b8c-a174-651693d3e3e2":{"x":201,"y":396,"w":12,"h":23},"091a7209-311f-466e-be76-d8aa8b854c5f":{"x":220,"y":397,"w":12,"h":22},"ee429d41-bd94-4f25-bb15-2a9e11d35d8c":{"x":238,"y":396,"w":14,"h":23},"7a3a51f2-4301-427b-b32a-47754c9343ca":{"x":257,"y":397,"w":14,"h":22},"dd26de17-4a7d-4c98-8b54-90d22fdb6d7a":{"x":995,"y":713,"w":13,"h":29},"a4a1d743-46d7-4e48-a1d7-9d726a1aa0a9":{"x":1011,"y":715,"w":12,"h":25},"39daf6a3-ce02-458e-9bf4-26d0ea5438e6":{"x":1032,"y":713,"w":15,"h":28},"67b09272-cb4d-49e0-bb11-c31518919cfc":{"x":1057,"y":714,"w":15,"h":27},"558aa49e-49fe-4e85-a6a7-a7d34501195b":{"x":1080,"y":713,"w":16,"h":31},"be703117-71f9-449a-8e7c-7eb3dff373db":{"x":1104,"y":713,"w":16,"h":28},"a728a345-3bf4-4998-b848-bf46533caadf":{"x":993,"y":779,"w":13,"h":26},"e8601833-807e-42ad-b724-9c94feedef1f":{"x":1010,"y":779,"w":13,"h":25},"e5a63151-32a6-4a27-ada2-97295d4d7c68":{"x":1032,"y":778,"w":16,"h":26},"c3657496-bdc6-4c40-b50d-20e9dc119d45":{"x":1056,"y":777,"w":15,"h":27},"1ab05bff-8ef6-4514-9d08-bf5aa09549d6":{"x":1074,"y":777,"w":12,"h":29},"b2cdf138-3b7a-482d-8463-a31be0a30225":{"x":994,"y":808,"w":14,"h":29},"040880e0-c380-4715-885a-4b16ed9a71ef":{"x":1017,"y":811,"w":16,"h":27},"b9922ded-f149-415d-874f-fdc8558fb34c":{"x":1041,"y":811,"w":14,"h":29},"63cf2fb5-d647-4387-90b8-89229d22cce7":{"x":1065,"y":811,"w":45,"h":28},"631f64ac-4a7f-43b7-8b8b-0d922ba2b7cb":{"x":1722,"y":164,"w":39,"h":76},"40adb411-2bd7-40b6-a891-a71256a3b02f":{"x":1685,"y":164,"w":37,"h":75},"9c0abc3b-20dd-4039-9a21-66d82f3cc55c":{"x":1758,"y":162,"w":35,"h":78},"852c7eba-e56e-4656-bea5-15a0e5b4d7f8":{"x":1796,"y":164,"w":34,"h":76},"7bafc24f-8e2d-41d3-a1d5-9e33b17c92ee":{"x":1832,"y":165,"w":31,"h":73},"498f5689-a0af-4cad-8028-bd00360eae71":{"x":1866,"y":165,"w":39,"h":74}},"words":{"b2e19f07-17d3-4b6c-a15a-56de7c3ae305":{"x":1414,"y":372,"w":61,"h":41},"bc4b0ae9-5024-4baa-88f6-c20d8bf98820":{"x":1334,"y":463,"w":41,"h":29},"5255bfba-6c2e-4533-9823-a05beb7d8d20":{"x":1382,"y":462,"w":15,"h":31},"9ed124e5-1743-4f71-b70a-cffef325bfc2":{"x":1404,"y":462,"w":55,"h":30},"7d5455fc-6f6a-4a95-903e-f58c74b813a3":{"x":1303,"y":553,"w":101,"h":46},"44748214-e26b-488c-b45c-710e7630f261":{"x":1436,"y":554,"w":24,"h":43},"f90fed05-01bd-4eab-8a54-6181dac8ab23":{"x":1437,"y":651,"w":43,"h":42},"faee9f05-a172-4c2e-a39e-8c75ca30db83":{"x":2214,"y":372,"w":66,"h":43},"1b43ca9b-b0a0-4f68-8163-3e88e1a8d5fb":{"x":2210,"y":464,"w":65,"h":45},"e256125b-2bde-4850-b64d-44b6c30b4be5":{"x":2200,"y":552,"w":44,"h":46},"fdc60882-5d1c-4bf6-803d-3237e2e021d7":{"x":2251,"y":555,"w":46,"h":45},"2c5612fe-2d68-4a66-aafb-6d98481de130":{"x":2103,"y":646,"w":42,"h":43},"860388f1-dfc8-47f9-ab17-286615f24ad0":{"x":2166,"y":647,"w":41,"h":42},"c85c6d09-2c3e-4ecf-9932-1469a47aa087":{"x":2228,"y":645,"w":43,"h":45},"da79348c-b0df-4887-aca8-0ccd39e3959d":{"x":195,"y":289,"w":33,"h":32},"d2b58747-c2d1-432f-870e-6877fbcbfb83":{"x":234,"y":286,"w":32,"h":36},"97ae1982-b02c-4fdb-8a9e-bb09fa1a247f":{"x":277,"y":288,"w":22,"h":32},"e07d0ba7-0209-4a77-82f5-28f49aa58132":{"x":193,"y":368,"w":58,"h":25},"f2d5d972-4053-4df8-a4f6-d672782f4fad":{"x":256,"y":369,"w":28,"h":23},"74329f93-02ab-4575-a222-cf5d535e5b41":{"x":200,"y":396,"w":14,"h":23},"ef6b68ff-232e-46fe-9861-8ad53ee736bc":{"x":219,"y":396,"w":13,"h":23},"8ca8a053-67fc-4171-b13a-423cda61ba13":{"x":237,"y":395,"w":16,"h":24},"0fa30333-db20-490c-adec-c0ef98cc65d2":{"x":256,"y":397,"w":16,"h":22},"49b7c6d8-4cc4-46b4-a0dd-6bdab7d0ffd3":{"x":993,"y":712,"w":31,"h":30},"93e89459-e60b-4df4-a632-0469580b8eb8":{"x":1031,"y":710,"w":18,"h":32},"5afce579-e974-4962-b5c8-45c5aa5b727f":{"x":1055,"y":713,"w":19,"h":29},"97f289df-7b8e-46b8-8212-fdc3b398ff4b":{"x":1080,"y":712,"w":17,"h":31},"a461b769-1d2a-4231-ac53-547a646aca4d":{"x":1104,"y":711,"w":16,"h":30},"9fa2156d-2ff3-4309-8c15-0583bd220c68":{"x":991,"y":777,"w":33,"h":28},"3ce6d23f-d404-4cce-a624-d8758126fa15":{"x":1031,"y":777,"w":19,"h":28},"7eeb9321-a6f1-48d4-83bd-fd297fa79274":{"x":1055,"y":776,"w":32,"h":30},"b72e3a2b-c4a2-4ab5-b0d7-522fd881b784":{"x":993,"y":809,"w":16,"h":29},"9dc131a0-98c4-4166-9500-6c576dc515cb":{"x":1016,"y":810,"w":18,"h":28},"5a00508f-0a5f-4684-bd67-a6c7666f8b35":{"x":1040,"y":811,"w":17,"h":28},"bb6458dd-8310-49c2-bb1c-082f4f6656ac":{"x":1064,"y":810,"w":47,"h":32},"9cb7ba50-6f08-4d43-b2e1-62be5b060700":{"x":1680,"y":161,"w":231,"h":83}},"links":{"832f3bae-4d39-4e2d-86d2-fce569457f04":{"x":1486,"y":451,"w":70,"h":48},"c11f6f38-3f55-45d7-8009-9a7a1b387f11":{"x":2287,"y":460,"w":71,"h":49},"39de3c1c-bdb6-4598-b222-2bee1456f033":{"x":2283,"y":645,"w":78,"h":47},"5ec4c0e5-f62b-449d-a858-53bf25a22122":{"x":303,"y":384,"w":61,"h":41},"284f87ce-58b8-4db1-9518-be05442ac673":{"x":1142,"y":279,"w":74,"h":50},"042ef1f6-4f7e-40c3-bdcb-3b61bff10097":{"x":1142,"y":405,"w":75,"h":52},"d93c0f2f-132d-44e8-9261-9fcb70d66500":{"x":1142,"y":533,"w":76,"h":53},"0eebd0b6-1077-4e1b-aa6a-556d8c7d5d1f":{"x":1143,"y":813,"w":70,"h":43}}},"pg13":{"links":{"1b943de8-4cbd-477a-8ab3-e892bf985ce6":{"x":320,"y":716,"w":88,"h":53},"2927367c-4b79-46fe-ab02-61b19e8ca723":{"x":1076,"y":472,"w":86,"h":50}},"symbols":{"bbdbf0d3-1093-4c03-9dbe-22e89cf6a899":{"x":114,"y":709,"w":29,"h":59},"df0a1477-0675-40fd-a429-88929a597d2e":{"x":147,"y":710,"w":29,"h":58},"cd705c22-b685-4deb-a4a6-e82610f0b256":{"x":578,"y":729,"w":12,"h":29},"c59d16d3-e3e4-450e-ae1d-3ad82d7f457f":{"x":593,"y":728,"w":12,"h":29},"2c605f6b-f907-4b84-936e-fadfb46edfeb":{"x":607,"y":727,"w":12,"h":31},"45b245bf-92f9-4880-9374-0779ea92996b":{"x":585,"y":485,"w":11,"h":27},"8fc506ec-50dd-47f4-aa66-8dd8407b0b63":{"x":598,"y":485,"w":13,"h":28},"e4c1a630-2283-4f7a-9c32-7663a68816b9":{"x":614,"y":485,"w":12,"h":26},"fb36e954-e689-408c-9db5-28c351059b3a":{"x":361,"y":162,"w":33,"h":60},"d20b410d-58ac-4a08-8fad-2bd6de5a9ed1":{"x":396,"y":165,"w":27,"h":58},"7c15845f-05d0-47a0-ab4a-7a8f364a6127":{"x":426,"y":165,"w":29,"h":60},"0ad6de83-867b-48b6-b98a-4616b1d4dfce":{"x":2063,"y":161,"w":30,"h":62},"edd21308-b3d0-4d6f-87f4-95d2666ebd64":{"x":2097,"y":161,"w":24,"h":65},"16b176c1-2af5-45f1-b561-72f96306cf98":{"x":2126,"y":163,"w":31,"h":65}},"words":{"a1ea9393-4000-46c2-bbf8-d12938742c2f":{"x":111,"y":703,"w":69,"h":69},"3b46c019-8067-4276-89dc-e7971af221f8":{"x":575,"y":724,"w":47,"h":37},"646e4b1e-457f-4340-9fa7-0c334e4bb12a":{"x":583,"y":479,"w":45,"h":37},"5afc8096-c70d-4df1-9416-85a77c810032":{"x":358,"y":159,"w":102,"h":72},"3937708f-f541-4ea7-abc7-b3c73a659071":{"x":2060,"y":154,"w":102,"h":80}}}}} \ No newline at end of file diff --git a/res/manual/page02.png b/res/manual/page02.png new file mode 100644 index 0000000..9b3b715 --- /dev/null +++ b/res/manual/page02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9836a20c72520214fcd4b6c1c170ee48455b52659dedf97b0d3fe72643f7be6 +size 1486875 diff --git a/res/manual/page03-04.png b/res/manual/page03-04.png new file mode 100644 index 0000000..1ece767 --- /dev/null +++ b/res/manual/page03-04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1e3903cc96fd7b27276810222ea1d552882835b744a632f4d7f4bd48b06f253 +size 3845323 diff --git a/res/manual/page05-06.png b/res/manual/page05-06.png new file mode 100644 index 0000000..e327417 --- /dev/null +++ b/res/manual/page05-06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63e37a364e7eec94a73ad727e437a635fe2edde9b2428416a09f16fbaf78d30f +size 3775565 diff --git a/res/manual/page07-08.png b/res/manual/page07-08.png new file mode 100644 index 0000000..00989e1 --- /dev/null +++ b/res/manual/page07-08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d9894a331d90d9bb5cf9891c35807df20c0eed2d4de885f54481bd875cab83d +size 3293917 diff --git a/res/manual/page09-10.png b/res/manual/page09-10.png new file mode 100644 index 0000000..401cff8 --- /dev/null +++ b/res/manual/page09-10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f67a1c0ceb2a9810d840a35b0eb65264cf3fa1d9c187bd8940dea0f26fccc7eb +size 3817776 diff --git a/res/manual/page11-12.png b/res/manual/page11-12.png new file mode 100644 index 0000000..003815c --- /dev/null +++ b/res/manual/page11-12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:643b89f5f9d892b4dd56af80ccac6a416cdd457ecfa1bffe071fb7fa267007b9 +size 3546788 diff --git a/res/manual/page13-14.png b/res/manual/page13-14.png new file mode 100644 index 0000000..08ecffd --- /dev/null +++ b/res/manual/page13-14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7658246706283bef525a716c44c5c503cd87c31982fc21166902a4bfff8fa5b6 +size 2994514 diff --git a/res/manual/page15-16.png b/res/manual/page15-16.png new file mode 100644 index 0000000..27292b0 --- /dev/null +++ b/res/manual/page15-16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7e1eb90c93c93b952b3421ed789ad5a15166e4eaebf4049ab1f35e6776e416c +size 2964544 diff --git a/res/manual/page17-18.png b/res/manual/page17-18.png new file mode 100644 index 0000000..0d6c890 --- /dev/null +++ b/res/manual/page17-18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9750292dfaca9042a90f910b91b4c38aad504b9e4cb3d6e70905e2c9e970416e +size 3617057 diff --git a/res/manual/page19-20.png b/res/manual/page19-20.png new file mode 100644 index 0000000..9de9cca --- /dev/null +++ b/res/manual/page19-20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65fbe180ab5fedef482356b5e7c2875878399284ba7d3d6a5d13db792b6b975e +size 3791858 diff --git a/res/manual/page21-22.png b/res/manual/page21-22.png new file mode 100644 index 0000000..9cf5095 --- /dev/null +++ b/res/manual/page21-22.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b8600d8ca0db55cc73cf972f9c485f2c3a3b93d2e30de3464ecb9a5817f711d +size 2635527 diff --git a/res/manual/page23-24.png b/res/manual/page23-24.png new file mode 100644 index 0000000..1fb914f --- /dev/null +++ b/res/manual/page23-24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee5f30ab755f21689a49d6aa03fc2778cf6caada1d3df9875c6d56fdc00d2a20 +size 3818536 diff --git a/res/manual/page25-26.png b/res/manual/page25-26.png new file mode 100644 index 0000000..3cbc28b --- /dev/null +++ b/res/manual/page25-26.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b00e8fe6bc9d4414a10225027c640d16f53b37049138d2bbc7aec537d8f4ae8f +size 3490252 diff --git a/res/manual/page27-28.png b/res/manual/page27-28.png new file mode 100644 index 0000000..9a85d56 --- /dev/null +++ b/res/manual/page27-28.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc2c3eaa5b89b6a3ed1f73617d531a0d3ee1143113e8a5a416837ae289c14226 +size 4105650 diff --git a/res/manual/page29-30.png b/res/manual/page29-30.png new file mode 100644 index 0000000..10f305a --- /dev/null +++ b/res/manual/page29-30.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1b6bbe8944014fc5cb899f5a723fb7bb0c2163ce8835e676f7ed12e5a8adbdc +size 4092493 diff --git a/res/manual/page31-32.png b/res/manual/page31-32.png new file mode 100644 index 0000000..9594bdb --- /dev/null +++ b/res/manual/page31-32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a2e40e315f428c09060bde33470e6ef6e705df308d29c763730b121058f00a3 +size 3951770 diff --git a/res/manual/page33-34.png b/res/manual/page33-34.png new file mode 100644 index 0000000..aca024d --- /dev/null +++ b/res/manual/page33-34.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ba356537e5fa98157dec72d905de411bd7cf4b5d8858880db76152dbe1e6a6a +size 3764411 diff --git a/res/manual/page35-36.png b/res/manual/page35-36.png new file mode 100644 index 0000000..17cc05e --- /dev/null +++ b/res/manual/page35-36.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f80060ab271a9c420a53fe244f9003eedb87fb8b04cae19ae6138e351f2ff525 +size 3666749 diff --git a/res/manual/page37-38.png b/res/manual/page37-38.png new file mode 100644 index 0000000..64a9905 --- /dev/null +++ b/res/manual/page37-38.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fd31244af8c36538a3ed28878bbf723ad31886fe99dbdb45f449e5203e0cbdd +size 3744877 diff --git a/res/manual/page39-40.png b/res/manual/page39-40.png new file mode 100644 index 0000000..3646c11 --- /dev/null +++ b/res/manual/page39-40.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:effd67749d0c2c75a1b81250035722f9bfc6d0489311d6cd10ca3be92a49dad8 +size 3464287 diff --git a/res/manual/page41-42.png b/res/manual/page41-42.png new file mode 100644 index 0000000..5d84884 --- /dev/null +++ b/res/manual/page41-42.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c674c19f03c62b8c2d50ae9b1856d4e7ab4fe54bdee711cae63812b408bf0fb1 +size 3883708 diff --git a/res/manual/page43-44.png b/res/manual/page43-44.png new file mode 100644 index 0000000..2597533 --- /dev/null +++ b/res/manual/page43-44.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20b5221fb44b5153522b60a50e576924985d3f41bce962662d60d20dfb8629b3 +size 3945721 diff --git a/res/manual/page45-56.png b/res/manual/page45-56.png new file mode 100644 index 0000000..b9a6ecb --- /dev/null +++ b/res/manual/page45-56.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d3624928f8bbc860bfd8163070334414e812a522d5aba5ced2886cf888c0715 +size 4137327 diff --git a/res/manual/page47-48.png b/res/manual/page47-48.png new file mode 100644 index 0000000..b09d01e --- /dev/null +++ b/res/manual/page47-48.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5d7605de58ed1b11a5a820c5eefa0c2128bff21bf80a7b79f703796ac283c5a +size 3436462 diff --git a/res/manual/page49-50.png b/res/manual/page49-50.png new file mode 100644 index 0000000..8b318c6 --- /dev/null +++ b/res/manual/page49-50.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb005c0cf49677953de0117b0610a88603c2d93266809cb09c1e55aad714fd9f +size 4086110 diff --git a/res/manual/page51-52.png b/res/manual/page51-52.png new file mode 100644 index 0000000..765ac42 --- /dev/null +++ b/res/manual/page51-52.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5073eb7244f18b173d93b207dc6d2c3f55e4ddb4268cddec7f6d2913a81167f +size 1695848 diff --git a/res/manual/page53-54.png b/res/manual/page53-54.png new file mode 100644 index 0000000..ec643be --- /dev/null +++ b/res/manual/page53-54.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f439d108735e23e0b790dbb5fb7856c9bef778ec670db7b385701b9e8e50b8 +size 1777449 diff --git a/res/manual/page55.png b/res/manual/page55.png new file mode 100644 index 0000000..65e2df2 --- /dev/null +++ b/res/manual/page55.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7583e2f833c566c42cde48efb8ee46de17ff1a91f70cb6b10ae89f18471010d9 +size 1745333 diff --git a/res/script.js b/res/script.js new file mode 100644 index 0000000..dabd89e --- /dev/null +++ b/res/script.js @@ -0,0 +1,549 @@ +// SPDX-FileCopyrightText: Copyright 2024 Fabio Iotti +// SPDX-License-Identifier: AGPL-3.0-only + +// @ts-check + +const page = /** @type {HTMLDivElement} */(document.getElementById('page')); +const pageImg = /** @type {HTMLImageElement} */(page.querySelector('img')); +const pageWordTemplate = /** @type {HTMLDivElement} */(page.querySelector('.word')); +const pageSymbolTemplate = /** @type {HTMLDivElement} */(page.querySelector('.symbol')); +const pageLinkTemplate = /** @type {HTMLDivElement} */(page.querySelector('.link')); + +const nav = /** @type {HTMLDivElement} */(document.getElementById('nav')); + +const prevPageButton = /** @type {HTMLButtonElement} */(document.getElementById('prev_page')); +const nextPageButton = /** @type {HTMLButtonElement} */(document.getElementById('next_page')); +const goToTocButton = /** @type {HTMLButtonElement} */(document.getElementById('go_to_toc')); +const pageSelect = /** @type {HTMLSelectElement} */(document.getElementById('page_select')); +const pageSelectOptionTemplate = /** @type {HTMLOptionElement} */(pageSelect.querySelector('option')); + +const modeViewButton = /** @type {HTMLButtonElement} */(document.getElementById('mode_view')); +const modeWordButton = /** @type {HTMLButtonElement} */(document.getElementById('mode_word')); +const modeSymbolButton = /** @type {HTMLButtonElement} */(document.getElementById('mode_symbol')); +const modeLinkButton = /** @type {HTMLButtonElement} */(document.getElementById('mode_link')); + +//#region Page selection + +const TOC_PAGE_ID = "pg02"; + +/** @type {{ [id: string]: { src: string, label: string } }} */ +const PAGES = { + "pg02": { src: "res/manual/page02.png", label: "02" }, + "pg03": { src: "res/manual/page03-04.png", label: "03-04" }, + "pg05": { src: "res/manual/page05-06.png", label: "05-06" }, + "pg07": { src: "res/manual/page07-08.png", label: "07-08" }, + "pg09": { src: "res/manual/page09-10.png", label: "09-10" }, + "pg11": { src: "res/manual/page11-12.png", label: "11-12" }, + "pg13": { src: "res/manual/page13-14.png", label: "13-14" }, + "pg15": { src: "res/manual/page15-16.png", label: "15-16" }, + "pg17": { src: "res/manual/page17-18.png", label: "17-18" }, + "pg19": { src: "res/manual/page19-20.png", label: "29-20" }, + "pg21": { src: "res/manual/page21-22.png", label: "21-22" }, + "pg23": { src: "res/manual/page23-24.png", label: "23-24" }, + "pg25": { src: "res/manual/page25-26.png", label: "25-26" }, + "pg27": { src: "res/manual/page27-28.png", label: "27-28" }, + "pg29": { src: "res/manual/page29-30.png", label: "29-30" }, + "pg31": { src: "res/manual/page31-32.png", label: "31-32" }, + "pg33": { src: "res/manual/page33-34.png", label: "33-34" }, + "pg35": { src: "res/manual/page35-36.png", label: "35-36" }, + "pg37": { src: "res/manual/page37-38.png", label: "37-38" }, + "pg39": { src: "res/manual/page39-40.png", label: "39-40" }, + "pg41": { src: "res/manual/page41-42.png", label: "41-42" }, + "pg43": { src: "res/manual/page43-44.png", label: "43-44" }, + "pg45": { src: "res/manual/page45-46.png", label: "45-46" }, + "pg47": { src: "res/manual/page47-48.png", label: "47-48" }, + "pg49": { src: "res/manual/page49-50.png", label: "49-50" }, + "pg51": { src: "res/manual/page51-52.png", label: "51-52" }, + "pg53": { src: "res/manual/page53-54.png", label: "53-54" }, + "pg55": { src: "res/manual/page55.png", label: "55" }, +}; + +const goToPage = (id) => { + pageImg.src = PAGES[id]?.src; + + if (pageSelect.value != id) + pageSelect.value = id; + + localStorage.setItem('tunic_manual_translation.page', id); + + loadPageElements(pageSelect.value); +}; + +for (const id in PAGES) { + const opt = /** @type {HTMLOptionElement} */(pageSelectOptionTemplate.cloneNode(true)); + opt.value = id; + opt.textContent = PAGES[id].label; + + pageSelectOptionTemplate.parentElement?.insertBefore(opt, pageSelectOptionTemplate); +} +pageSelectOptionTemplate.remove(); + +pageSelect.addEventListener('change', ev => { + ev.preventDefault(); + goToPage(pageSelect.value); +}); + +const goToPrevPage = () => { + const pageIds = Object.keys(PAGES); + const pageIndex = pageIds.indexOf(pageSelect.value); + goToPage((pageIndex == 0 || pageIndex == -1) ? pageIds[pageIds.length - 1] : pageIds[pageIndex - 1]); +}; + +const goToNextPage = () => { + const pageIds = Object.keys(PAGES); + const pageIndex = pageIds.indexOf(pageSelect.value); + goToPage((pageIndex == pageIds.length - 1 || pageIndex == -1) ? pageIds[0] : pageIds[pageIndex + 1]); +}; + +prevPageButton.addEventListener('click', ev => { + ev.preventDefault(); + goToPrevPage(); +}); + +nextPageButton.addEventListener('click', ev => { + ev.preventDefault(); + goToNextPage(); +}); + +goToTocButton.addEventListener('click', ev => { + ev.preventDefault(); + goToPage(TOC_PAGE_ID); +}); + +pageSelect.value = localStorage.getItem('tunic_manual_translation.page') ?? Object.keys(PAGES)[0]; + +//#endregion + +//#region Edit mode selection + +/** @typedef {'VIEW' | 'WORD' | 'SYMBOL' | 'LINK'} EditMode */ +/** @type {EditMode} */ +let currentEditMode = 'VIEW'; + +/** @type {Record} */ +const MODE_BUTTON = { + 'VIEW': modeViewButton, + 'WORD': modeWordButton, + 'SYMBOL': modeSymbolButton, + 'LINK': modeLinkButton, +}; + +/** @type {Record} */ +const MODE_CLASSES = { + 'VIEW': 'mode-view', + 'WORD': 'mode-word', + 'SYMBOL': 'mode-symbol', + 'LINK': 'mode-link', +}; + +const setEditMode = (/** @type {EditMode} */mode) => { + MODE_BUTTON[currentEditMode]?.classList.remove('active'); + MODE_BUTTON[mode]?.classList.add('active'); + + document.body.classList.remove(MODE_CLASSES[currentEditMode]); + document.body.classList.add(MODE_CLASSES[mode]); + + currentEditMode = mode; + + localStorage.setItem('tunic_manual_translation.edit_mode', currentEditMode); +}; + +modeViewButton.addEventListener('click', ev => { + ev.preventDefault(); + setEditMode('VIEW'); +}); + +modeWordButton.addEventListener('click', ev => { + ev.preventDefault(); + setEditMode('WORD'); +}); + +modeSymbolButton.addEventListener('click', ev => { + ev.preventDefault(); + setEditMode('SYMBOL'); +}); + +modeLinkButton.addEventListener('click', ev => { + ev.preventDefault(); + setEditMode('LINK'); +}); + +currentEditMode = /** @type {EditMode|null} */(localStorage.getItem('tunic_manual_translation.edit_mode')) ?? 'VIEW'; +setEditMode(currentEditMode); + +//#endregion + +//#region Element placement + +/** @type {{ lastPos: { clientX: number, clientY: number }, start: { clientX: number, clientY: number }, end: { clientX: number, clientY: number }, el: HTMLDivElement } | null} */ +let currentDrawingElement = null; + +/** @satisfies {Partial>} */ +const DRAWING_ELEMENT_TEMPLATES = { + 'WORD': pageWordTemplate, + 'SYMBOL': pageSymbolTemplate, + 'LINK': pageLinkTemplate, +}; + +/** @satisfies {Partial>} */ +const DRAWING_ELEMENT_CLASSES = { + 'WORD': 'word', + 'SYMBOL': 'symbol', + 'LINK': 'link', +}; + +/** + * @param {EditMode} type + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ +const addNewElement = (type, x, y, width, height) => { + const id = uuidv4(); + + const pages = data.pages ??= {}; + const page = pages[pageSelect.value] ??= { }; + + /** @type {page[keyof page]} */ + let element; + switch (type) { + case 'WORD': + element = page.words ??= {}; + break; + + case 'SYMBOL': + element = page.symbols ??= {}; + break; + + case 'LINK': + element = page.links ??= {}; + break; + + default: + throw new Error(`Invalid type: ${type}`); + } + + element[id] = { x, y, w: width, h: height }; + + saveUpdatedData(); + + // Refresh page. + loadPageElements(pageSelect.value); +}; + +/** + * @param {EditMode} type + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {(() => void) | null} onRemove + * @param {(() => void) | null} onClick + */ +const createDrawingElement = (type, x, y, width, height, onRemove = null, onClick = null) => { + const el = /** @type {HTMLDivElement} */(DRAWING_ELEMENT_TEMPLATES[type].cloneNode(true)); + el.classList.add(DRAWING_ELEMENT_CLASSES[type]); + setElPosition(el, x, y, width, height); + + el.addEventListener('mousedown', ev => { + ev.preventDefault(); + }); + + if (onRemove) { + /** @type {HTMLDivElement} */(el.querySelector('.remove')).addEventListener('click', ev => { + if (ev.defaultPrevented || ev.button != 0) + return; + + ev.preventDefault(); + onRemove(); + }); + } + + if (onClick) { + el.addEventListener('click', ev => { + if (ev.defaultPrevented || ev.button != 0) + return; + + ev.preventDefault(); + onClick(); + }); + } + + page.appendChild(el); + + return el; +}; + +const setElPosition = (el, x, y, width, height) => { + el.style.setProperty('--left', String(x)); + el.style.setProperty('--top', String(y)); + el.style.setProperty('--width', String(width)); + el.style.setProperty('--height', String(height)); +} + +const updateCurrentDrawingElPosition = (place = false) => { + if (!currentDrawingElement) + return; + + let x = currentDrawingElement.start.clientX - currentPan.x; + let y = currentDrawingElement.start.clientY - currentPan.y; + let w = currentDrawingElement.end.clientX - currentDrawingElement.start.clientX; + let h = currentDrawingElement.end.clientY - currentDrawingElement.start.clientY; + + if (w < 0) { + x += w; + w = -w; + } + + if (h < 0) { + y += h; + h = -h; + } + + setElPosition(currentDrawingElement.el, x, y, w, h); + + if (place && w + h > 16 && w > 4 && h > 4) + addNewElement(currentEditMode, x, y, w, h); +}; + +page.addEventListener('mousedown', ev => { + if (ev.defaultPrevented || ev.button != 0 || !Object.hasOwn(DRAWING_ELEMENT_TEMPLATES, currentEditMode)) + return; + + ev.preventDefault(); + + currentDrawingElement = { + lastPos: { clientX: ev.clientX, clientY: ev.clientY}, + start: { clientX: ev.clientX, clientY: ev.clientY }, + end: { clientX: ev.clientX, clientY: ev.clientY }, + el: createDrawingElement(currentEditMode, 0, 0, 0, 0), + }; + + updateCurrentDrawingElPosition(); +}); + +window.addEventListener('mousemove', ev => { + if (ev.defaultPrevented || ev.button != 0 || !currentDrawingElement) + return; + + ev.preventDefault(); + + currentDrawingElement.end.clientX += ev.clientX - currentDrawingElement.lastPos.clientX; + currentDrawingElement.end.clientY += ev.clientY - currentDrawingElement.lastPos.clientY; + updateCurrentDrawingElPosition(); + + currentDrawingElement.lastPos = { clientX: ev.clientX, clientY: ev.clientY }; +}); + +window.addEventListener('mouseup', ev => { + if (ev.defaultPrevented || ev.button != 0 || !currentDrawingElement) + return; + + ev.preventDefault(); + + currentDrawingElement.end.clientX += ev.clientX - currentDrawingElement.lastPos.clientX; + currentDrawingElement.end.clientY += ev.clientY - currentDrawingElement.lastPos.clientY; + + updateCurrentDrawingElPosition(true); + + currentDrawingElement.el.remove(); + + currentDrawingElement = null; +}); + +for (const el of Object.values(DRAWING_ELEMENT_TEMPLATES)) + el.remove(); + +//#endregion + +//#region View mode (page panning) + +/** @type {{ clientX: number, clientY: number } | null} */ +let panningLastPos = null; + +let currentPan = { x: 0, y: 0 }; + +const panBy = (x, y) => { + currentPan.x += x; + currentPan.y += y; + + page.style.setProperty('--left', String(currentPan.x)); + page.style.setProperty('--top', String(currentPan.y)); + + localStorage.setItem('tunic_manual_translation.pan_x', String(currentPan.x)); + localStorage.setItem('tunic_manual_translation.pan_y', String(currentPan.y)); +}; + +page.addEventListener('mousedown', ev => { + if (ev.defaultPrevented || ev.button != 0 || currentEditMode != 'VIEW') + return; + + ev.preventDefault(); + + panningLastPos = { clientX: ev.clientX, clientY: ev.clientY }; +}); + +window.addEventListener('mousemove', ev => { + if (ev.defaultPrevented || ev.button != 0 || !panningLastPos) + return; + + ev.preventDefault(); + panBy(ev.clientX - panningLastPos.clientX, ev.clientY - panningLastPos.clientY); + panningLastPos = { clientX: ev.clientX, clientY: ev.clientY }; +}); + +window.addEventListener('mouseup', ev => { + if (ev.defaultPrevented || ev.button != 0 || !panningLastPos) + return; + + ev.preventDefault(); + panBy(ev.clientX - panningLastPos.clientX, ev.clientY - panningLastPos.clientY); + panningLastPos = null; +}); + +currentPan.x = Number(localStorage.getItem('tunic_manual_translation.pan_x')) || 0; +currentPan.y = Number(localStorage.getItem('tunic_manual_translation.pan_y')) || 0; +panBy(0, 0); + +//#endregion + +//#region Database + +/** @typedef {string} GUID */ + +/** @typedef {{ x: number, y: number, w: number, h: number }} WordData */ + +/** @typedef {{ x: number, y: number, w: number, h: number }} SymbolData */ + +/** @typedef {{ x: number, y: number, w: number, h: number, ref?: string }} LinkData */ + +/** @typedef {{ words?: Record, symbols?: Record, links?: Record }} PageData */ + +/** @typedef {{ pages?: Record }} Database */ + +/** @type {Database} */ +let data = {}; + +/** @type {Record} */ +let currentPageElements = {}; + +const uuidv4 = () => self.crypto.randomUUID(); + +const loadPageElements = (pageId) => { + for (const id in currentPageElements) + currentPageElements[id].remove(); + + if (!data.pages) + return; + + /** @type {PageData} */ + const pageData = Object.hasOwn(data.pages, pageId) ? data.pages[pageId] : {}; + + const pageWords = pageData.words; + for (const id in pageWords) { + const data = pageWords[id]; + const el = createDrawingElement('WORD', data.x, data.y, data.w, data.h, () => { + // When removed... + delete currentPageElements[id]; + delete pageWords[id]; + saveUpdatedData(); + el.remove(); + }, () => { + // When clicked... + // TODO + console.log("clicked word", id); + }); + + currentPageElements[id] = el; + } + + const pageSymbols = pageData.symbols; + for (const id in pageSymbols) { + const data = pageSymbols[id]; + const el = createDrawingElement('SYMBOL', data.x, data.y, data.w, data.h, () => { + // When removed... + delete currentPageElements[id]; + delete pageSymbols[id]; + saveUpdatedData(); + el.remove(); + }, () => { + // When clicked... + // TODO + console.log("clicked symbol", id); + }); + + currentPageElements[id] = el; + } + + const pageLinks = pageData.links; + for (const id in pageLinks) { + const data = pageLinks[id]; + const el = createDrawingElement('LINK', data.x, data.y, data.w, data.h, () => { + // When removed... + delete currentPageElements[id]; + delete pageLinks[id]; + saveUpdatedData(); + el.remove(); + }, () => { + // When clicked... + // TODO + console.log("clicked link", id); + }); + + currentPageElements[id] = el; + } +}; + +const saveUpdatedData = () => { + localStorage.setItem('tunic_manual_translation.data', JSON.stringify(data)); + console.debug("Updated data:", JSON.stringify(data)); +}; + +data = /** @type {Database | null}*/(JSON.parse(localStorage.getItem('tunic_manual_translation.data') || 'null')) ?? data; + +//#endregion + +//#region Keyboard shortcuts + +window.addEventListener('keyup', ev => { + console.log(ev.key); + switch (ev.key) { + case 'ArrowLeft': + goToPrevPage(); + break; + + case 'ArrowRight': + goToNextPage(); + break; + + case 'i': + case 'I': + goToPage(TOC_PAGE_ID); + break; + + case 'v': + case 'V': + setEditMode('VIEW'); + break; + + case 'w': + case 'W': + setEditMode('WORD'); + break; + + case 's': + case 'S': + setEditMode('SYMBOL'); + break; + + case 'l': + case 'L': + setEditMode('LINK'); + break; + } +}); + +//#endregion + +goToPage(pageSelect.value); diff --git a/res/styles.css b/res/styles.css new file mode 100644 index 0000000..ca55bc8 --- /dev/null +++ b/res/styles.css @@ -0,0 +1,198 @@ +/* SPDX-FileCopyrightText: Copyright 2024 Fabio Iotti */ +/* SPDX-License-Identifier: AGPL-3.0-only */ + +:root { + color-scheme: dark; + font-family: system-ui, sans-serif; +} + +html { + width: 100vw; + height: 100vh; + + overflow: hidden; +} + +#page { + --top: 0; + --left: 0; + + position: absolute; + top: calc(var(--top) * 1px); + left: calc(var(--left) * 1px); + + min-width: 60rem; + min-height: 37rem; + + background: black; +} + +.ui { + z-index: 100; + position: fixed; + + padding: 1rem; + + background: #0008; + border-radius: 1rem; + + &.hidden { + display: none; + } + + .section { + display: flex; + gap: .2em; + + > * { + flex-grow: 1; + } + } + + .center { + text-align: center; + } + + hr { + border: 0; + border-top: 1px solid #8888; + } +} + +#nav { + top: 1rem; + left: 1rem; + + width: 15rem; +} + +#props_word, #props_symbol, #props_link { + top: 1rem; + right: 1rem; + + width: 15rem; +} + +.button { + padding: .2em .6em; + border: 0; + border-radius: .2em; + + + &.active { + background: #088; + } +} + +.word, .symbol, .link { + --top: 0; + --left: 0; + --width: 0; + --height: 0; + + --element-base-color: gray; + + position: absolute; + top: calc(var(--top) * 1px); + left: calc(var(--left) * 1px); + width: calc(var(--width) * 1px); + height: calc(var(--height) * 1px); + + pointer-events: none; + + --element-color: color(from var(--element-base-color) srgb r g b / .5); + border: 1px dashed var(--element-color); + + &.active { + margin: -1px; + border-width: 2px; + } + + .remove { + display: none; + + position: absolute; + cursor: pointer; + right: .05rem; + top: .05rem; + + margin: 0; + padding: 0; + border: 0; + border-radius: .2rem; + line-height: 1; + width: .8rem; + height: .8rem; + + background: color(from var(--element-base-color) srgb r g b / .5); + + &:hover { + background: var(--element-base-color); + } + } +} + +.word { + --element-base-color: #f00; + + body.mode-word & { + .remove { + display: initial; + } + } + + body.mode-view &, body.mode-word & { + z-index: 2; + cursor: pointer; + pointer-events: auto; + --element-color: var(--element-base-color); + } + + .word-meaning { + position: absolute; + font-size: .6rem; + color: var(--element-color); + bottom: 0; + left: 0; + right: 0; + margin: auto; + + text-align: center; + + text-transform: uppercase; + } +} + +.symbol { + --element-base-color: #f80; + + body.mode-symbol & { + .remove { + display: initial; + } + } + + body.mode-symbol & { + z-index: 1; + cursor: pointer; + pointer-events: auto; + --element-color: var(--element-base-color); + } +} + +.link { + --element-base-color: #08f; + + body.mode-link & { + .remove { + display: initial; + } + } + + body.mode-view &, body.mode-link & { + z-index: 3; + cursor: pointer; + pointer-events: auto; + --element-color: var(--element-base-color); + } +}