commit 341bd8c5557820682667791f84796ea6fa248aeb parent b6d5aef833c60fed29ba23593cde3eb718ca2998 Author: Tyson Lupul <triesap@radroots.dev> Date: Mon, 22 Dec 2025 15:49:04 +0000 Merge pull request #2 from radrootslabs/migrate/apps-lib-pwa Migrate @radroots/apps-lib-pwa Diffstat:
| A | apps-lib-pwa/.gitignore | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/LICENSE | | | 675 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/package.json | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte | | | 25 | +++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-glyph.svelte | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-label-dashed.svelte | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-layout-bottom.svelte | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-layout-pair.svelte | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-layout.svelte | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-round-nav.svelte | | | 26 | ++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/button/button-simple.svelte | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/farm/farms-add-detail.svelte | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/farm/farms-add-map.svelte | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte | | | 111 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/form/entry-line.svelte | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/form/entry-wrap.svelte | | | 36 | ++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/form/form-line-ledger-label-select-label.svelte | | | 36 | ++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/form/form-line-ledger-select.svelte | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/form/form-line-ledger.svelte | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/layout/layout-page.svelte | | | 18 | ++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/layout/layout-trellis.svelte | | | 20 | ++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/layout/layout-view.svelte | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/layout/layout-window.svelte | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/carousel-container.svelte | | | 21 | +++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/carousel-item.svelte | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/css.svelte | | | 3 | +++ |
| A | apps-lib-pwa/src/lib/components/lib/float-page.svelte | | | 11 | +++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/input-pwa.svelte | | | 120 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/input-value.svelte | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/label-swap.svelte | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/load-circle.svelte | | | 30 | ++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/load-symbol.svelte | | | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/logo-circle-sm.svelte | | | 14 | ++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/logo-circle.svelte | | | 18 | ++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/logo-letters.svelte | | | 3 | +++ |
| A | apps-lib-pwa/src/lib/components/lib/select-menu.svelte | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/select-pwa.svelte | | | 126 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/lib/wrap-border.svelte | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/map/map-marker-area.svelte | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/map/map.svelte | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/navigation/navigation-tabs.svelte | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/navigation/page-header.svelte | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-end.svelte | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-input.svelte | | | 91 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-line.svelte | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-select.svelte | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-title.svelte | | | 79 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/components/trellis/trellis.svelte | | | 167 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/index.ts | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/stores/app.ts | | | 33 | +++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/types/app.ts | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/types/components/lib.ts | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/types/components/trellis.ts | | | 128 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/types/views.ts | | | 9 | +++++++++ |
| A | apps-lib-pwa/src/lib/types/views/farms.ts | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/types/views/profile.ts | | | 12 | ++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/app.ts | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/context.ts | | | 22 | ++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/farm/schema.ts | | | 29 | +++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/map.ts | | | 25 | +++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/profile/lib.ts | | | 13 | +++++++++++++ |
| A | apps-lib-pwa/src/lib/utils/styles.ts | | | 10 | ++++++++++ |
| A | apps-lib-pwa/src/lib/views/farms/farms-add.svelte | | | 246 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/views/farms/farms.svelte | | | 92 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/views/profile/profile-edit.svelte | | | 103 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/views/profile/profile.svelte | | | 355 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/views/root/home.svelte | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/src/lib/views/root/settings.svelte | | | 116 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | apps-lib-pwa/svelte.config.js | | | 11 | +++++++++++ |
| A | apps-lib-pwa/tsconfig.json | | | 13 | +++++++++++++ |
| A | apps-lib-pwa/vite.config.ts | | | 6 | ++++++ |
80 files changed, 5310 insertions(+), 0 deletions(-)
diff --git a/apps-lib-pwa/.gitignore b/apps-lib-pwa/.gitignore @@ -0,0 +1,43 @@ +node_modules +dist +.turbo +.svelte-kit + +# Logs +logs/ +*.log + +# Env +.env +.env.* +!.env.example +!.env.test + +# OS +.DS_Store +Thumbs.db + +# Secrets +*.pem +*.crt +*.key + +# Testing +test*.json + +# Editors +.vscode/ +.idea/ +*.iml + +# Notes +notes*.txt +notes*.md +notes*.json +tree*.txt +diff*.txt +prompt*.txt + +# Dev +.local* +justfile +\ No newline at end of file diff --git a/apps-lib-pwa/LICENSE b/apps-lib-pwa/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + 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. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + 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 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. Use with the GNU Affero General Public License. + + 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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 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 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 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + 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 GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. +\ No newline at end of file diff --git a/apps-lib-pwa/package.json b/apps-lib-pwa/package.json @@ -0,0 +1,99 @@ +{ + "name": "@radroots/apps-lib-pwa", + "version": "0.0.1", + "private": true, + "license": "GPLv3", + "scripts": { + "dev": "svelte-package -w", + "prebuild": "npm run clean && npm run check", + "build": "svelte-package", + "preview": "vite preview", + "clean": "rimraf dist", + "prepare": "svelte-kit sync || echo ''", + "prepack": "svelte-kit sync && svelte-package && publint", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "files": [ + "dist", + "!dist/**/*.test.*", + "!dist/**/*.spec.*" + ], + "sideEffects": [ + "**/*.css" + ], + "svelte": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./dist/index.js" + }, + "./stores/*": { + "types": "./dist/stores/*.d.ts", + "import": "./dist/stores/*.js", + "default": "./dist/stores/*.js" + }, + "./types/*": { + "types": "./dist/types/*.d.ts", + "import": "./dist/types/*.js", + "default": "./dist/types/*.js" + }, + "./utils/*": { + "types": "./dist/utils/*.d.ts", + "import": "./dist/utils/*.js", + "default": "./dist/utils/*.js" + } + }, + "typesVersions": { + "*": { + "stores/*": [ + "dist/stores/*" + ], + "types/*": [ + "dist/types/*" + ], + "utils/*": [ + "dist/utils/*" + ] + } + }, + "peerDependencies": { + "@sveltejs/kit": "^2.22.0", + "svelte": "^5.0.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/kit": "^2.22.0", + "@sveltejs/package": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@types/node": "^22.5.0", + "publint": "^0.3.2", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "5.8.3", + "vite": "7.0.6" + }, + "dependencies": { + "@radroots/locales": "*", + "@radroots/apps-lib": "*", + "@radroots/events-bindings": "*", + "@radroots/client": "*", + "@radroots/geo": "*", + "@radroots/tangle-schema-bindings": "*", + "@radroots/themes": "*", + "@radroots/utils": "*", + "@radroots/utils-nostr": "*", + "@nostr-dev-kit/ndk": "2.14.33", + "@nostr-dev-kit/ndk-cache-dexie": "2.6.34", + "@nostr-dev-kit/ndk-svelte": "2.4.38", + "@sveltekit-i18n/base": "^1.3.7", + "@sveltekit-i18n/parser-icu": "^1.0.8", + "luxon": "^3.5.0", + "sveltekit-i18n": "^2.4.2", + "svelte-maplibre": "1.2.0", + "sveltekit-search-params": "^3.0.0", + "zod": "^4.0.5" + } +} diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte @@ -0,0 +1,25 @@ +<script lang="ts"> + import type { IButtonGlyphCircle } from "$lib/types/components/lib"; + import { fmt_cl, glyph_style_map } from "@radroots/apps-lib"; + import GlyphButton from "./button-glyph.svelte"; + + let { basis }: { basis: IButtonGlyphCircle } = $props(); + + const styles = $derived( + basis?.glyph?.dim + ? glyph_style_map.get(basis?.glyph?.dim) + : glyph_style_map.get(`sm`), + ); +</script> + +{#if styles?.dim_1} + <div + class={`${fmt_cl(basis?.classes_wrap)} flex flex-col h-[${ + styles?.dim_1 + }px] w-[${ + styles?.dim_1 + }px] justify-center items-center rounded-full el-re`} + > + <GlyphButton basis={basis?.glyph} /> + </div> +{/if} diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import { + type GlyphKey, + type IClOpt, + fmt_cl, + Glyph, + } from "@radroots/apps-lib"; + import { type CallbackPromise } from "@radroots/utils"; + + let { + basis, + }: { + basis: IClOpt & { + kind?: `primary` | `neutral`; + label: string; + callback: CallbackPromise; + glyph?: GlyphKey; + }; + } = $props(); + + const classes_kind = $derived( + basis.kind === `neutral` ? `text-ly0-gl-shade` : `text-ly0-gl-hl`, + ); +</script> + +<button + class={`${fmt_cl(basis.classes)} group flex flex-row justify-center items-center`} + onclick={basis.callback} +> + {#if basis.glyph} + <Glyph + basis={{ + classes: `${classes_kind}`, + dim: `sm+`, + + key: basis.glyph, + }} + /> + {/if} + <p + class={`font-sans font-[600] text-line_label ${classes_kind} opacity-active`} + > + {basis.label} + </p> +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import { type IGlyph, fmt_cl, glyph_style_map } from "@radroots/apps-lib"; + + let { basis }: { basis: IGlyph } = $props(); + const styles = $derived( + basis?.dim ? glyph_style_map.get(basis.dim) : glyph_style_map.get(`sm`), + ); +</script> + +<!-- svelte-ignore a11y_consider_explicit_label --> +<button + class={`${fmt_cl( + basis.classes, + )} flex flex-col justify-center items-center text-[${ + styles?.gl_1 + }px] el-re`} + onclick={async () => { + if (basis.callback) await basis.callback(); + }} +> + <i class={`ph-bold ph-${basis.key}`}></i> +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-label-dashed.svelte b/apps-lib-pwa/src/lib/components/button/button-label-dashed.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import type { ICb } from "@radroots/apps-lib"; + + let { + basis, + }: { + basis: ICb & { + label: string; + }; + } = $props(); +</script> + +<button + class={`flex flex-row h-line_button w-full justify-center items-center`} + onclick={async () => { + await basis.callback(); + }} +> + <p class={`font-sans font-[500] text-lg text-ly0-gl-hl tracking-wide`}> + {`- ${basis.label} -`} + </p> +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-layout-bottom.svelte b/apps-lib-pwa/src/lib/components/button/button-layout-bottom.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import { app_lo } from "$lib/stores/app"; + import type { Snippet } from "svelte"; + + let { + basis, + children, + }: { + basis?: { + hidden: boolean; + }; + children: Snippet; + } = $props(); +</script> + +{#if !basis?.hidden} + <div + class={`z-10 absolute bottom-0 h-lo_bottom_button_${$app_lo} flex flex-col w-full px-4 gap-1 justify-start items-center`} + > + {@render children()} + </div> +{/if} diff --git a/apps-lib-pwa/src/lib/components/button/button-layout-pair.svelte b/apps-lib-pwa/src/lib/components/button/button-layout-pair.svelte @@ -0,0 +1,66 @@ +<script lang="ts"> + import { app_lo } from "$lib/stores/app"; + import { + Flex, + fmt_cl, + type IClOpt, + type IDisabledOpt, + type ILoadingOpt, + } from "@radroots/apps-lib"; + import { type CallbackPromise } from "@radroots/utils"; + import ButtonLayout from "./button-layout.svelte"; + + let { + basis, + }: { + basis: IClOpt & { + continue: IDisabledOpt & + ILoadingOpt & { + label: string; + callback: CallbackPromise; + }; + back?: IDisabledOpt & { + visible: boolean; + label?: string; + callback: CallbackPromise; + }; + }; + } = $props(); +</script> + +<div + class={`${fmt_cl(basis.classes)} flex flex-col gap-1 justify-center items-center ${basis?.back?.visible ? `-translate-y-8` : ``} el-re`} +> + <ButtonLayout + basis={{ + disabled: basis.continue.disabled, + label: basis.continue.label, + callback: basis.continue.callback, + }} + /> + {#if basis.back} + <div class={`flex flex-col justify-center items-center el-re`}> + {#if basis.back.visible} + <button + class={`group flex flex-row h-12 w-lo_${$app_lo} justify-center items-center fade-in el-re`} + onclick={async (ev) => { + ev.stopPropagation(); + if (!basis.back?.disabled) await basis.back?.callback(); + }} + > + <p + class={`font-sans font-[600] tracking-wide text-ly1-gl-shade ${basis.back?.disabled ? `` : `group-active:text-ly1-gl/40`} el-re`} + > + {basis.back.label || ``} + </p> + </button> + {:else} + <div + class={`flex flex-row h-4 w-full justify-start items-center`} + > + <Flex /> + </div> + {/if} + </div> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/button/button-layout.svelte b/apps-lib-pwa/src/lib/components/button/button-layout.svelte @@ -0,0 +1,53 @@ +<script lang="ts"> + import { app_lo } from "$lib/stores/app"; + import { + type IClOpt, + type IDisabledOpt, + type ILoadingOpt, + type ILyOpt, + fmt_cl, + parse_layer, + } from "@radroots/apps-lib"; + import type { CallbackPromise } from "@radroots/utils"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + let { + basis, + }: { + basis: ILyOpt & + IClOpt & + IDisabledOpt & + ILoadingOpt & { + classes_inner?: string; + hide_active?: boolean; + label: string; + callback: CallbackPromise; + }; + } = $props(); + + const layer = $derived(parse_layer(basis.layer, 1)); + + const classes_active = $derived( + !basis.hide_active + ? `ly1-active-surface ly1-active-raise-less ly1-active-ring-less` + : ``, + ); +</script> + +<button + class={`${fmt_cl(basis.classes)} group flex flex-row h-touch_guide w-lo_${$app_lo} justify-center items-center bg-ly${layer} rounded-touch ${basis.disabled ? `opacity-60` : classes_active} el-re`} + onclick={async (ev) => { + ev.stopPropagation(); + if (!basis.disabled) await basis.callback(); + }} +> + {#if basis.loading} + <LoadSymbol basis={{ dim: `md` }} /> + {:else} + <p + class={`${fmt_cl(basis.classes_inner)} font-sans font-[600] tracking-wide text-ly${layer}-gl-shade ${basis.disabled ? `` : `group-active:text-ly${layer}-gl/40 `} el-re`} + > + {basis.label || ``} + </p> + {/if} +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-round-nav.svelte b/apps-lib-pwa/src/lib/components/button/button-round-nav.svelte @@ -0,0 +1,26 @@ +<script lang="ts"> + import type { IButtonNavRound } from "$lib/types/components/lib"; + import { Glyph } from "@radroots/apps-lib"; + import LoadCircle from "../lib/load-circle.svelte"; + + let { basis }: { basis: IButtonNavRound } = $props(); +</script> + +<button + class={`flex flex-row h-12 w-12 justify-center items-center bg-ly1 rounded-full el-re`} + disabled={!!basis.disabled} + onclick={basis.callback} +> + {#if basis.loading} + <LoadCircle /> + {:else} + <Glyph + basis={{ + classes: `text-ly0-gl`, + dim: `sm+`, + + key: basis.glyph, + }} + /> + {/if} +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-simple.svelte b/apps-lib-pwa/src/lib/components/button/button-simple.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import type { IButtonSimple } from "$lib/types/components/lib"; + import { parse_layer } from "@radroots/apps-lib"; + + let { basis }: { basis: IButtonSimple } = $props(); + + const layer = $derived(parse_layer(basis.layer || 1)); +</script> + +<button + class={`group flex flex-row h-line_button w-full justify-center items-center rounded-touch bg-ly${layer} ly${layer}-active-surface ly${layer}-active-ring`} + onclick={async (ev) => { + if (!basis.allow_propogation) ev.stopPropagation(); + await basis.callback(); + }} +> + <p + class={`font-sans font-[600] text-xl text-ly0-gl capitalize tracking-wider opacity-active`} + > + {basis.label} + </p> +</button> diff --git a/apps-lib-pwa/src/lib/components/farm/farms-add-detail.svelte b/apps-lib-pwa/src/lib/components/farm/farms-add-detail.svelte @@ -0,0 +1,98 @@ +<script lang="ts"> + import FormLineLedger from "$lib/components/form/form-line-ledger.svelte"; + import { get_context } from "@radroots/apps-lib"; + import { area_units, form_fields } from "@radroots/utils"; + + const { ls } = get_context(`lib`); + + let { + val_farmname = $bindable(``), + val_farmaddress = $bindable(``), + val_farmarea = $bindable(``), + val_farmarea_unit = $bindable(``), + val_farmcontact = $bindable(``), + farm_geop_lat, + farm_geop_lng, + }: { + val_farmname: string; + val_farmaddress: string; + val_farmarea: string; + val_farmarea_unit: string; + val_farmcontact: string; + farm_geop_lat: string; + farm_geop_lng: string; + } = $props(); +</script> + +<div + class={`flex flex-col h-[100vh] w-full px-6 pt-2 gap-4 justify-start items-center`} +> + <FormLineLedger + bind:value={val_farmaddress} + basis={{ + id: `farm_location`, + label: `${$ls(`common.farm_location`)}`, + input: { + placeholder: `${$ls(`icu.enter_*`, { + value: `${$ls(`common.farm_location`)}`.toLowerCase(), + })}`, + }, + }} + /> + + <FormLineLedger + basis={{ + id: `farm_coordinates`, + label: `${$ls(`common.farm_coordinates`)}`, + display_value: + farm_geop_lat && farm_geop_lng + ? `${farm_geop_lat}, ${farm_geop_lng}` + : undefined, + input: + farm_geop_lat && farm_geop_lng + ? undefined + : { + placeholder: `${$ls(`icu.enter_*`, { + value: `${$ls( + `common.farm_coordinates`, + )}`.toLowerCase(), + })}`, + }, + }} + /> + <FormLineLedger + bind:value={val_farmname} + basis={{ + id: `farm_name`, + label: `${$ls(`common.farm_name`)}`, + input: { + placeholder: `${$ls(`icu.enter_*`, { + value: `${$ls(`common.farm_name`)}`.toLowerCase(), + })}`, + }, + }} + /> + <FormLineLedger + bind:value={val_farmarea} + bind:value_label_sel={val_farmarea_unit} + basis={{ + id: `farm_size`, + label: `${$ls(`common.farm_size`)}`, + label_select: { + label: `${$ls(`units.area.${val_farmarea_unit}_ab`)}`, + entries: area_units.map((i) => ({ + value: i, + label: `${$ls(`units.area.${i}`)}`, + })), + }, + input: { + placeholder: `${`${$ls(`icu.enter_*`, { + value: `${$ls(`common.farm_size`)}`.toLowerCase(), + })}`} ${`${$ls( + `units.area.${val_farmarea_unit}_pl`, + )}`.toLowerCase()}`, + field: form_fields.farm_size, + }, + }} + /> +</div> diff --git a/apps-lib-pwa/src/lib/components/farm/farms-add-map.svelte b/apps-lib-pwa/src/lib/components/farm/farms-add-map.svelte @@ -0,0 +1,88 @@ +<script lang="ts"> + import WrapBorder from "$lib/components/lib/wrap-border.svelte"; + import MapMarkerArea from "$lib/components/map/map-marker-area.svelte"; + import Map from "$lib/components/map/map.svelte"; + import { app_lo } from "$lib/stores/app"; + import { focus_map_marker } from "$lib/utils/map"; + import { Fade, geop_is_valid, get_context } from "@radroots/apps-lib"; + import { + type GeocoderReverseResult, + type GeolocationPoint, + } from "@radroots/geo"; + import { handle_err } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { lc_geop_current, lc_geocode } = get_context(`lib`); + + let { + map_geoc = $bindable(undefined), + map_geop = $bindable(undefined), + farm_geop_lat, + farm_geop_lng, + }: { + map_geoc: GeocoderReverseResult | undefined; + map_geop: GeolocationPoint | undefined; + farm_geop_lat: string; + farm_geop_lng: string; + } = $props(); + + let map: maplibregl.Map | undefined = $state(undefined); + + const is_valid_geop = $derived(geop_is_valid(map_geop)); + + onMount(async () => { + try { + const geop = await lc_geop_current(); + if (!geop) return; + map_geop = { ...geop }; + const geoc = await lc_geocode(geop); + if (!geoc) return; + map_geoc = geoc; + if (map && map_geop) map.setCenter([map_geop.lng, map_geop.lat]); + map?.setZoom(13); + focus_map_marker(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +<div + class={`flex flex-col h-[100vh] w-full px-6 gap-4 justify-start items-center`} +> + <WrapBorder basis={{ classes: `h-lo_view_main_${$app_lo}` }}> + <Map bind:map> + {#if map_geop} + <MapMarkerArea + bind:map_geop + bind:map_geoc + basis={{ + show_display: true, + }} + /> + {/if} + </Map> + </WrapBorder> + {#if is_valid_geop} + <Fade> + <div + class={`flex flex-col w-full gap-1 justify-center items-center`} + > + <div + class={`flex flex-row w-full gap-2 justify-center items-center`} + > + <p + class={`font-sans font-[500] text-ly0-gl tracking-tightest`} + > + {farm_geop_lat} + </p> + <p + class={`font-sans font-[500] text-ly0-gl tracking-tightest`} + > + {farm_geop_lng} + </p> + </div> + </div> + </Fade> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte b/apps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte @@ -0,0 +1,111 @@ +<script lang="ts"> + import MapMarkerArea from "$lib/components/map/map-marker-area.svelte"; + import Map from "$lib/components/map/map.svelte"; + import type { FarmExtended } from "$lib/types/views/farms"; + import { get_context } from "@radroots/apps-lib"; + import { + fmt_geolocation_address, + geol_lat_fmt, + geol_lng_fmt, + parse_geol_point_tup, + parse_tup_geop_point, + type GeolocationPointTuple, + } from "@radroots/geo"; + import type { CallbackPromiseGeneric } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls, locale } = get_context(`lib`); + + let { + basis, + on_handle_farm_view, + }: { + basis: FarmExtended; + on_handle_farm_view: CallbackPromiseGeneric<string>; + } = $props(); + + let map: maplibregl.Map | undefined = $state(undefined); + let map_center: GeolocationPointTuple = $state([0, 0]); + + onMount(async () => { + map?.setZoom(13); + if (basis.location?.point) { + map_center = parse_geol_point_tup(basis.location.point); + if (map_center && map) map.setCenter(map_center); + } + }); + + const map_geop = $derived(parse_tup_geop_point(map_center)); + + const farm_addr_fmt = $derived( + basis.location?.address + ? fmt_geolocation_address(basis.location.address) + : ``, + ); + + const farm_geop_lat = $derived( + basis.location?.point + ? geol_lat_fmt(basis.location.point.lat, `dms`, $locale, 3) + : ``, + ); + + const farm_geop_lng = $derived( + basis.location?.point + ? geol_lng_fmt(basis.location.point.lng, `dms`, $locale, 3) + : ``, + ); +</script> + +<button + class={`z-10 relative flex flex-col w-full p-4 gap-3 justify-start items-center bg-ly1 ly1-active-raise-less ly1-active-ring rounded-3xl el-re`} + onclick={async () => { + if (basis.farm.id) await on_handle_farm_view(basis.farm.id); + }} +> + <div class={`flex flex-col w-full gap-2 justify-center items-center`}> + <div class={`flex flex-row w-full justify-between items-center`}> + <p class={`font-sans font-[500] text-3xl text-ly0-gl`}> + {basis.farm.name} + </p> + + <div + class={`flex flex-row h-6 px-2 py-1 justify-center items-center bg-lime-400 rounded-lg`} + > + <p class={`font-sans font-[700] text-white`}> + {`${$ls(`common.farm`)}`} + </p> + </div> + </div> + <div class={`flex flex-col w-full justify-center items-center`}> + <div class={`flex flex-row w-full justify-start items-center`}> + <p class={`font-sans font-[500] text-lg text-ly0-gl`}> + {farm_addr_fmt} + </p> + </div> + <div class={`flex flex-row w-full justify-start items-center`}> + <p class={`font-sans font-[500] text-lg text-ly0-gl`}> + {farm_geop_lat && farm_geop_lng + ? `${farm_geop_lat}, ${farm_geop_lng}` + : ``} + </p> + </div> + </div> + </div> + <div + class={`flex flex-col h-[16rem] w-full justify-center items-center rounded-2xl overflow-hidden`} + > + <Map + bind:map + basis={{ + interactive: false, + }} + > + <MapMarkerArea + {map_geop} + basis={{ + no_drag: true, + }} + /> + </Map> + </div> +</button> diff --git a/apps-lib-pwa/src/lib/components/form/entry-line.svelte b/apps-lib-pwa/src/lib/components/form/entry-line.svelte @@ -0,0 +1,70 @@ +<script lang="ts"> + import { + Glyph, + parse_layer, + type IEntryLine, + type LoadingDimension, + } from "@radroots/apps-lib"; + import InputValue from "../lib/input-value.svelte"; + import LoadCircle from "../lib/load-circle.svelte"; + import EntryWrap from "./entry-wrap.svelte"; + + let { + basis, + value = $bindable(``), + }: { + basis: IEntryLine; + value: string; + } = $props(); + + const layer = $derived( + parse_layer( + typeof basis.wrap?.layer === `boolean` ? 0 : basis.wrap?.layer, + ), + ); + + const classes_layer = $derived( + typeof basis.wrap?.layer === `boolean` ? `` : `text-ly${layer}-gl`, + ); + + const loading_dim: LoadingDimension = $derived( + basis.wrap?.style === `guide` ? `md` : `sm`, + ); +</script> + +<EntryWrap basis={basis?.wrap}> + <InputValue + bind:value + basis={{ + ...basis.el, + classes: `h-entry_line ${basis.el.classes}`, + }} + /> + {#if basis.loading} + <div + class={`z-5 absolute right-0 top-0 flex flex-row h-full pr-4 justify-end items-center fade-in el-re`} + > + <LoadCircle + basis={{ + dim: loading_dim, + }} + /> + </div> + {:else if basis.notify_inline} + {#if `glyph` in basis.notify_inline} + <div + class={`z-5 absolute el-re right-0 top-0 flex flex-row h-full pr-3 justify-end items-center translate-x-[34px] fade-in`} + > + <Glyph + basis={typeof basis.notify_inline.glyph === `string` + ? { + key: basis.notify_inline.glyph, + dim: `xs+`, + classes: `${classes_layer}`, + } + : basis.notify_inline.glyph} + /> + </div> + {/if} + {/if} +</EntryWrap> diff --git a/apps-lib-pwa/src/lib/components/form/entry-wrap.svelte b/apps-lib-pwa/src/lib/components/form/entry-wrap.svelte @@ -0,0 +1,36 @@ +<script lang="ts"> + import { + fmt_cl, + type IBasisOpt, + type IEntryWrap, + parse_layer, + } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis = undefined, + children, + }: { + basis?: IBasisOpt<IEntryWrap>; + children: Snippet; + } = $props(); + + const layer = $derived( + typeof basis?.layer === `boolean` + ? parse_layer(0) + : parse_layer(basis?.layer), + ); + + const classes_layer = $derived( + typeof basis?.layer === `boolean` + ? `bg-transparent` + : `bg-ly${layer} ${basis?.style_a ? `active:bg-ly${layer}-a` : ``}`, + ); +</script> + +<button + id={basis?.id || null} + class={`${fmt_cl(basis?.classes)} relative entry-line-wrap ${!basis?.no_pad ? ` pl-6 pr-4` : ``} h-entry_${basis?.style ? basis.style : `line`} rounded-touch ${classes_layer} el-re`} +> + {@render children()} +</button> diff --git a/apps-lib-pwa/src/lib/components/form/form-line-ledger-label-select-label.svelte b/apps-lib-pwa/src/lib/components/form/form-line-ledger-label-select-label.svelte @@ -0,0 +1,36 @@ +<script lang="ts"> + import { symbols } from "@radroots/apps-lib"; + + let { + basis, + }: { + basis: { + label: string; + }; + } = $props(); +</script> + +<div class={`flex flex-row justify-start items-center`}> + <p + class={`pr-[13px] font-sansd text-trellis_ti text-ly0-gl-label uppercase`} + > + {`(${basis.label}`} + </p> + <div + class={`relative flex flex-row justify-start items-center -translate-x-[10px] -translate-y-[1px]`} + > + <p + class={`absolute font-sansd text-trellis_ti text-ly0-gl-label uppercase scale-y-[70%] scale-x-[80%] -translate-y-[1px]`} + > + {`${symbols.up}`} + </p> + <p + class={`absolute font-sansd text-trellis_ti text-ly0-gl-label uppercase scale-y-[70%] scale-x-[80%] translate-y-[2px]`} + > + {`${symbols.down}`} + </p> + </div> + <p class={`font-sansd text-trellis_ti text-ly0-gl-label uppercase`}> + {`)`} + </p> +</div> diff --git a/apps-lib-pwa/src/lib/components/form/form-line-ledger-select.svelte b/apps-lib-pwa/src/lib/components/form/form-line-ledger-select.svelte @@ -0,0 +1,86 @@ +<script lang="ts"> + import { + type ElementCallbackValueKeydown, + type IIdOpt, + type ISelectCallback, + type ISelectOption, + fmt_id, + } from "@radroots/apps-lib"; + import InputPwa from "../lib/input-pwa.svelte"; + import SelectPwa from "../lib/select-pwa.svelte"; + + let { + basis, + value_input = $bindable(``), + value_sel = $bindable(``), + }: { + basis: IIdOpt & { + display_value?: string; + label?: string; + input: { + placeholder?: string; + callback_keydown?: + | ElementCallbackValueKeydown<HTMLInputElement> + | undefined; + }; + select: { + entries: ISelectOption<string>[]; + callback?: ISelectCallback; + }; + }; + value_input?: string; + value_sel?: string; + } = $props(); + + const id = $derived(basis.id || ``); +</script> + +<div class={`flex flex-col w-full gap-2 justify-start items-start`}> + {#if basis.label} + <div class={`flex flex-row w-full justify-start items-center`}> + <p class={`font-sansd text-trellis_ti text-ly0-gl-label uppercase`}> + {basis.label} + </p> + </div> + {/if} + <div + class={`relative flex flex-row h-12 w-full justify-start items-center border-y-line border-ly0-edge`} + > + {#if basis.display_value} + <p class={`font-sans font-[400] text-ly0-gl text-form_base`}> + {basis.display_value} + </p> + {:else} + <InputPwa + bind:value={value_input} + basis={{ + id: id ? fmt_id(`${id}_input`) : undefined, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + placeholder: basis.input.placeholder || ``, + callback_keydown: basis.input.callback_keydown, + }} + /> + <div + class={`absolute right-0 flex flex-row justify-center items-center`} + > + <SelectPwa + bind:value={value_sel} + basis={{ + classes: `w-fit text-ly1-gl`, + id: id ? fmt_id(`${id}_sel`) : undefined, + sync: true, + layer: 1, + show_arrows: `r`, + options: [ + { + entries: basis.select.entries, + }, + ], + callback: basis.select.callback, + }} + /> + </div> + {/if} + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/form/form-line-ledger.svelte b/apps-lib-pwa/src/lib/components/form/form-line-ledger.svelte @@ -0,0 +1,88 @@ +<script lang="ts"> + import { + type ElementCallbackValueKeydown, + type IIdOpt, + type ISelectOption, + fmt_id, + } from "@radroots/apps-lib"; + import { type FormField } from "@radroots/utils"; + import InputPwa from "../lib/input-pwa.svelte"; + import SelectMenu from "../lib/select-menu.svelte"; + import FormLineLedgerLabelSelectLabel from "./form-line-ledger-label-select-label.svelte"; + + let { + basis, + value = $bindable(``), + value_label_sel = $bindable(``), + }: { + basis: IIdOpt & { + display_value?: string; + label?: string; + label_select?: { + label: string; + entries: ISelectOption<string>[]; + }; + input?: { + placeholder?: string; + field?: FormField; + callback_keydown?: + | ElementCallbackValueKeydown<HTMLInputElement> + | undefined; + }; + }; + value?: string; + value_label_sel?: string; + } = $props(); + + const id = $derived(basis.id || ``); +</script> + +<div class={`flex flex-col w-full gap-2 justify-start items-start`}> + {#if basis.label} + <div class={`flex flex-row w-full justify-start gap-1 items-center`}> + <p class={`font-sansd text-trellis_ti text-ly0-gl-label uppercase`}> + {basis.label} + </p> + {#if basis.label_select} + <SelectMenu + bind:value={value_label_sel} + basis={{ + layer: 0, + options: [ + { + entries: basis.label_select.entries, + }, + ], + }} + > + <FormLineLedgerLabelSelectLabel + basis={{ + label: basis.label_select.label, + }} + /> + </SelectMenu> + {/if} + </div> + {/if} + <div + class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-ly0-edge`} + > + {#if basis.display_value} + <p class={`font-sans font-[400] text-ly1-gl text-form_base`}> + {basis.display_value} + </p> + {:else if basis.input} + <InputPwa + bind:value + basis={{ + id: id ? fmt_id(id) : undefined, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + field: basis.input?.field || undefined, + placeholder: basis.input?.placeholder || ``, + callback_keydown: basis.input?.callback_keydown, + }} + /> + {/if} + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/layout/layout-page.svelte b/apps-lib-pwa/src/lib/components/layout/layout-page.svelte @@ -0,0 +1,18 @@ +<script lang="ts"> + import { type IBasisOpt, type IClOpt, fmt_cl } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis = undefined, + children, + }: { + basis?: IBasisOpt<IClOpt>; + children: Snippet; + } = $props(); +</script> + +<div + class={`${fmt_cl(basis?.classes)} flex flex-col w-full pt-4 px-4 pb-24 gap-4 justify-center items-center`} +> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/layout/layout-trellis.svelte b/apps-lib-pwa/src/lib/components/layout/layout-trellis.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import { type IBasisOpt, type IClOpt, fmt_cl } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis = undefined, + children, + }: { + basis?: IBasisOpt<IClOpt>; + children: Snippet; + } = $props(); +</script> + +<div + class={`${fmt_cl( + basis?.classes, + )} flex flex-col pb-12 gap-6 justify-center items-center scroll-hide`} +> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/layout/layout-view.svelte b/apps-lib-pwa/src/lib/components/layout/layout-view.svelte @@ -0,0 +1,50 @@ +<script lang="ts"> + import { nav_blur, ph_blur, tabs_blur } from "$lib/stores/app"; + import { fmt_cl, type IBasisOpt, type IClOpt } from "@radroots/apps-lib"; + import { onDestroy, onMount, type Snippet } from "svelte"; + + let { + basis = undefined, + el = $bindable(null), + children, + }: { + el?: HTMLDivElement | null; + basis?: IBasisOpt<IClOpt & { fade?: boolean }>; + children: Snippet; + } = $props(); + + onMount(async () => { + try { + el?.addEventListener("scroll", on_scroll_change); + } catch (e) { + } finally { + } + }); + + onDestroy(async () => { + try { + el?.removeEventListener("scroll", on_scroll_change); + } catch (e) { + } finally { + } + }); + + const on_scroll_change = (): void => { + if (Math.max(el?.scrollTop || 0, 0) > 10) nav_blur.set(true); + else nav_blur.set(false); + if (Math.max(el?.scrollTop || 0, 0) > 10) tabs_blur.set(true); + else tabs_blur.set(false); + if (Math.max(el?.scrollTop || 0, 0) > 30) ph_blur.set(true); + else ph_blur.set(false); + }; +</script> + +<div + bind:this={el} + class={`${fmt_cl( + basis?.classes, + )} absolute top-0 left-0 flex flex-col h-[100vh] w-full justify-start items-center scroll-hide overflow-auto`} + class:fade-in={basis?.fade} +> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/layout/layout-window.svelte b/apps-lib-pwa/src/lib/components/layout/layout-window.svelte @@ -0,0 +1,57 @@ +<script lang="ts"> + import { + app_lo, + app_tilt, + envelope_tilt, + envelope_visible, + } from "$lib/stores/app"; + import { handle_err, window_set } from "@radroots/apps-lib"; + import { onMount } from "svelte"; + import LogoCircle from "../lib/logo-circle.svelte"; + + let { children } = $props(); + + onMount(async () => { + try { + window_set(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + envelope_visible.subscribe(async (_envelope_visible) => { + if (_envelope_visible && $envelope_tilt) app_tilt.set(true); + else app_tilt.set(false); + }); +</script> + +<div + class={`relative lg:hidden flex flex-col h-[100vh] w-full bg-ly0 ${ + $app_tilt ? `scale-y-[96%] translate-y-4 rounded-t-[3rem]` : `` + } overflow-x-hidden overflow-y-scroll scroll-hide delay-75 duration-200 el-re cursor-default`} +> + {#if $app_lo} + <div class={`flex flex-col h-full w-full`}> + {@render children()} + </div> + {/if} +</div> +<div + class={`max-lg:hidden flex flex-col h-[100vh] w-full justify-center items-center bg-ly0 cursor-default el-re`} +> + <div class={`flex flex-col justify-center items-center`}> + <LogoCircle /> + <div class={`flex flex-col w-full gap-1 justify-center items-center`}> + <p class={`font-sans font-[400] text-base text-ly0-gl italic`}> + {`Welcome to the`} + <span class={`font-[600]`}> + {`Rad Roots`} + </span> + {`app!`} + </p> + <p class={`font-sans font-[400] text-base text-ly0-gl`}> + {`Please view this application on mobile device`} + </p> + </div> + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/carousel-container.svelte b/apps-lib-pwa/src/lib/components/lib/carousel-container.svelte @@ -0,0 +1,21 @@ +<script lang="ts"> + import type { ICarouselContainer } from "$lib/types/components/lib"; + import { fmt_cl } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis, + children, + }: { + basis: ICarouselContainer<string>; + children: Snippet; + } = $props(); + + const classes = $derived( + `${fmt_cl(basis.classes)} carousel-container flex h-full w-full`, + ); +</script> + +<div data-carousel-container={basis.view} class={classes}> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/carousel-item.svelte b/apps-lib-pwa/src/lib/components/lib/carousel-item.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import type { + CarouselKeyboardEvent, + CarouselMouseEvent, + ICarouselItem, + } from "$lib/types/components/lib"; + import { fmt_cl } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis, + children, + }: { + basis: ICarouselItem<string>; + children: Snippet; + } = $props(); + + const classes = $derived( + `${fmt_cl(basis.classes)} carousel-item flex flex-col h-full w-full`, + ); + + const handle_click = async (ev: MouseEvent): Promise<void> => { + if (!basis.callback_click) return; + const event_cast = ev as CarouselMouseEvent; + await basis.callback_click(event_cast); + }; + + const handle_keydown = async (ev: KeyboardEvent): Promise<void> => { + if (!basis.callback_keydown) return; + const event_cast = ev as CarouselKeyboardEvent; + await basis.callback_keydown(event_cast); + }; +</script> + +<!-- svelte-ignore a11y_no_noninteractive_tabindex --> +<div + data-carousel-item={basis.view} + class={classes} + role={basis.role ?? undefined} + tabindex={basis.tabindex ?? undefined} + onclick={handle_click} + onkeydown={handle_keydown} +> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/css.svelte b/apps-lib-pwa/src/lib/components/lib/css.svelte @@ -0,0 +1,3 @@ +<div + class="hidden -bottom-lo_bottom_button_ios0 -bottom-lo_bottom_button_ios1 -bottom-lo_bottom_button_webm0 -bottom-lo_bottom_button_webm1 -bottom-lo_view_main_ios0 -bottom-lo_view_main_ios1 -bottom-lo_view_main_webm0 -bottom-lo_view_main_webm1 -bottom-nav_page_header_ios0 -bottom-nav_page_header_ios1 -bottom-nav_page_header_webm0 -bottom-nav_page_header_webm1 -bottom-nav_page_toolbar_ios0 -bottom-nav_page_toolbar_ios1 -bottom-nav_page_toolbar_webm0 -bottom-nav_page_toolbar_webm1 -bottom-nav_tabs_ios0 -bottom-nav_tabs_ios1 -bottom-nav_tabs_webm0 -bottom-nav_tabs_webm1 -left-lo_ios0 -left-lo_ios1 -left-lo_line_entry_ios0 -left-lo_line_entry_ios1 -left-lo_line_entry_webm0 -left-lo_line_entry_webm1 -left-lo_textdesc_ios0 -left-lo_textdesc_ios1 -left-lo_textdesc_webm0 -left-lo_textdesc_webm1 -left-lo_webm0 -left-lo_webm1 -right-lo_ios0 -right-lo_ios1 -right-lo_line_entry_ios0 -right-lo_line_entry_ios1 -right-lo_line_entry_webm0 -right-lo_line_entry_webm1 -right-lo_textdesc_ios0 -right-lo_textdesc_ios1 -right-lo_textdesc_webm0 -right-lo_textdesc_webm1 -right-lo_webm0 -right-lo_webm1 -top-lo_bottom_button_ios0 -top-lo_bottom_button_ios1 -top-lo_bottom_button_webm0 -top-lo_bottom_button_webm1 -top-lo_view_main_ios0 -top-lo_view_main_ios1 -top-lo_view_main_webm0 -top-lo_view_main_webm1 -top-nav_page_header_ios0 -top-nav_page_header_ios1 -top-nav_page_header_webm0 -top-nav_page_header_webm1 -top-nav_page_toolbar_ios0 -top-nav_page_toolbar_ios1 -top-nav_page_toolbar_webm0 -top-nav_page_toolbar_webm1 -top-nav_tabs_ios0 -top-nav_tabs_ios1 -top-nav_tabs_webm0 -top-nav_tabs_webm1 -translate-x-w_lo_ios0 -translate-x-w_lo_ios1 -translate-x-w_lo_line_entry_ios0 -translate-x-w_lo_line_entry_ios1 -translate-x-w_lo_line_entry_webm0 -translate-x-w_lo_line_entry_webm1 -translate-x-w_lo_textdesc_ios0 -translate-x-w_lo_textdesc_ios1 -translate-x-w_lo_textdesc_webm0 -translate-x-w_lo_textdesc_webm1 -translate-x-w_lo_webm0 -translate-x-w_lo_webm1 -translate-y-h_lo_bottom_button_ios0 -translate-y-h_lo_bottom_button_ios1 -translate-y-h_lo_bottom_button_webm0 -translate-y-h_lo_bottom_button_webm1 -translate-y-h_lo_view_main_ios0 -translate-y-h_lo_view_main_ios1 -translate-y-h_lo_view_main_webm0 -translate-y-h_lo_view_main_webm1 -translate-y-h_nav_page_header_ios0 -translate-y-h_nav_page_header_ios1 -translate-y-h_nav_page_header_webm0 -translate-y-h_nav_page_header_webm1 -translate-y-h_nav_page_toolbar_ios0 -translate-y-h_nav_page_toolbar_ios1 -translate-y-h_nav_page_toolbar_webm0 -translate-y-h_nav_page_toolbar_webm1 -translate-y-h_nav_tabs_ios0 -translate-y-h_nav_tabs_ios1 -translate-y-h_nav_tabs_webm0 -translate-y-h_nav_tabs_webm1 active:bg-ly0-gl active:bg-ly0-gl-hl active:bg-ly0-gl-hl_a active:bg-ly0-gl-label active:bg-ly0-gl-shade active:bg-ly0-gl-a active:bg-ly0-gl-pl active:bg-ly0 active:bg-ly0-blur active:bg-ly0-edge active:bg-ly0-a active:bg-ly0-w active:bg-ly1-gl active:bg-ly1-gl-hl active:bg-ly1-gl-hl_a active:bg-ly1-gl-label active:bg-ly1-gl-shade active:bg-ly1-gl-a active:bg-ly1-gl-d active:bg-ly1-gl-pl active:bg-ly1 active:bg-ly1-edge active:bg-ly1-err active:bg-ly1-focus active:bg-ly1-a active:bg-ly2-gl active:bg-ly2-gl-hl active:bg-ly2-gl-hl_a active:bg-ly2-gl-shade active:bg-ly2-gl-a active:bg-ly2-gl-d active:bg-ly2-gl-pl active:bg-ly2 active:bg-ly2-edge active:bg-ly2-a active:bg-radroots-accent-focus active:border-ly0-gl active:border-ly0-gl-hl active:border-ly0-gl-hl_a active:border-ly0-gl-label active:border-ly0-gl-shade active:border-ly0-gl-a active:border-ly0-gl-pl active:border-ly0 active:border-ly0-blur active:border-ly0-edge active:border-ly0-a active:border-ly0-w active:border-ly1-gl active:border-ly1-gl-hl active:border-ly1-gl-hl_a active:border-ly1-gl-label active:border-ly1-gl-shade active:border-ly1-gl-a active:border-ly1-gl-d active:border-ly1-gl-pl active:border-ly1 active:border-ly1-edge active:border-ly1-err active:border-ly1-focus active:border-ly1-a active:border-ly2-gl active:border-ly2-gl-hl active:border-ly2-gl-hl_a active:border-ly2-gl-shade active:border-ly2-gl-a active:border-ly2-gl-d active:border-ly2-gl-pl active:border-ly2 active:border-ly2-edge active:border-ly2-a active:border-radroots-accent-focus active:text-ly0-gl active:text-ly0-gl-hl active:text-ly0-gl-hl_a active:text-ly0-gl-label active:text-ly0-gl-shade active:text-ly0-gl-a active:text-ly0-gl-pl active:text-ly0 active:text-ly0-blur active:text-ly0-edge active:text-ly0-a active:text-ly0-w active:text-ly1-gl active:text-ly1-gl-hl active:text-ly1-gl-hl_a active:text-ly1-gl-label active:text-ly1-gl-shade active:text-ly1-gl-a active:text-ly1-gl-d active:text-ly1-gl-pl active:text-ly1 active:text-ly1-edge active:text-ly1-err active:text-ly1-focus active:text-ly1-a active:text-ly2-gl active:text-ly2-gl-hl active:text-ly2-gl-hl_a active:text-ly2-gl-shade active:text-ly2-gl-a active:text-ly2-gl-d active:text-ly2-gl-pl active:text-ly2 active:text-ly2-edge active:text-ly2-a active:text-radroots-accent-focus bg-ly0-gl bg-ly0-gl-hl bg-ly0-gl-hl_a bg-ly0-gl-label bg-ly0-gl-shade bg-ly0-gl-a bg-ly0-gl-pl bg-ly0 bg-ly0-blur bg-ly0-edge bg-ly0-a bg-ly0-w bg-ly1-gl bg-ly1-gl-hl bg-ly1-gl-hl_a bg-ly1-gl-label bg-ly1-gl-shade bg-ly1-gl-a bg-ly1-gl-d bg-ly1-gl-pl bg-ly1 bg-ly1-edge bg-ly1-err bg-ly1-focus bg-ly1-a bg-ly2-gl bg-ly2-gl-hl bg-ly2-gl-hl_a bg-ly2-gl-shade bg-ly2-gl-a bg-ly2-gl-d bg-ly2-gl-pl bg-ly2 bg-ly2-edge bg-ly2-a bg-radroots-accent-focus border-ly0-gl border-ly0-gl-hl border-ly0-gl-hl_a border-ly0-gl-label border-ly0-gl-shade border-ly0-gl-a border-ly0-gl-pl border-ly0 border-ly0-blur border-ly0-edge border-ly0-a border-ly0-w border-ly1-gl border-ly1-gl-hl border-ly1-gl-hl_a border-ly1-gl-label border-ly1-gl-shade border-ly1-gl-a border-ly1-gl-d border-ly1-gl-pl border-ly1 border-ly1-edge border-ly1-err border-ly1-focus border-ly1-a border-ly2-gl border-ly2-gl-hl border-ly2-gl-hl_a border-ly2-gl-shade border-ly2-gl-a border-ly2-gl-d border-ly2-gl-pl border-ly2 border-ly2-edge border-ly2-a border-radroots-accent-focus bottom-lo_bottom_button_ios0 bottom-lo_bottom_button_ios1 bottom-lo_bottom_button_webm0 bottom-lo_bottom_button_webm1 bottom-lo_view_main_ios0 bottom-lo_view_main_ios1 bottom-lo_view_main_webm0 bottom-lo_view_main_webm1 bottom-nav_page_header_ios0 bottom-nav_page_header_ios1 bottom-nav_page_header_webm0 bottom-nav_page_header_webm1 bottom-nav_page_toolbar_ios0 bottom-nav_page_toolbar_ios1 bottom-nav_page_toolbar_webm0 bottom-nav_page_toolbar_webm1 bottom-nav_tabs_ios0 bottom-nav_tabs_ios1 bottom-nav_tabs_webm0 bottom-nav_tabs_webm1 focus:bg-ly0-gl focus:bg-ly0-gl-hl focus:bg-ly0-gl-hl_a focus:bg-ly0-gl-label focus:bg-ly0-gl-shade focus:bg-ly0-gl-a focus:bg-ly0-gl-pl focus:bg-ly0 focus:bg-ly0-blur focus:bg-ly0-edge focus:bg-ly0-a focus:bg-ly0-w focus:bg-ly1-gl focus:bg-ly1-gl-hl focus:bg-ly1-gl-hl_a focus:bg-ly1-gl-label focus:bg-ly1-gl-shade focus:bg-ly1-gl-a focus:bg-ly1-gl-d focus:bg-ly1-gl-pl focus:bg-ly1 focus:bg-ly1-edge focus:bg-ly1-err focus:bg-ly1-focus focus:bg-ly1-a focus:bg-ly2-gl focus:bg-ly2-gl-hl focus:bg-ly2-gl-hl_a focus:bg-ly2-gl-shade focus:bg-ly2-gl-a focus:bg-ly2-gl-d focus:bg-ly2-gl-pl focus:bg-ly2 focus:bg-ly2-edge focus:bg-ly2-a focus:bg-radroots-accent-focus focus:border-ly0-gl focus:border-ly0-gl-hl focus:border-ly0-gl-hl_a focus:border-ly0-gl-label focus:border-ly0-gl-shade focus:border-ly0-gl-a focus:border-ly0-gl-pl focus:border-ly0 focus:border-ly0-blur focus:border-ly0-edge focus:border-ly0-a focus:border-ly0-w focus:border-ly1-gl focus:border-ly1-gl-hl focus:border-ly1-gl-hl_a focus:border-ly1-gl-label focus:border-ly1-gl-shade focus:border-ly1-gl-a focus:border-ly1-gl-d focus:border-ly1-gl-pl focus:border-ly1 focus:border-ly1-edge focus:border-ly1-err focus:border-ly1-focus focus:border-ly1-a focus:border-ly2-gl focus:border-ly2-gl-hl focus:border-ly2-gl-hl_a focus:border-ly2-gl-shade focus:border-ly2-gl-a focus:border-ly2-gl-d focus:border-ly2-gl-pl focus:border-ly2 focus:border-ly2-edge focus:border-ly2-a focus:border-radroots-accent-focus focus:text-ly0-gl focus:text-ly0-gl-hl focus:text-ly0-gl-hl_a focus:text-ly0-gl-label focus:text-ly0-gl-shade focus:text-ly0-gl-a focus:text-ly0-gl-pl focus:text-ly0 focus:text-ly0-blur focus:text-ly0-edge focus:text-ly0-a focus:text-ly0-w focus:text-ly1-gl focus:text-ly1-gl-hl focus:text-ly1-gl-hl_a focus:text-ly1-gl-label focus:text-ly1-gl-shade focus:text-ly1-gl-a focus:text-ly1-gl-d focus:text-ly1-gl-pl focus:text-ly1 focus:text-ly1-edge focus:text-ly1-err focus:text-ly1-focus focus:text-ly1-a focus:text-ly2-gl focus:text-ly2-gl-hl focus:text-ly2-gl-hl_a focus:text-ly2-gl-shade focus:text-ly2-gl-a focus:text-ly2-gl-d focus:text-ly2-gl-pl focus:text-ly2 focus:text-ly2-edge focus:text-ly2-a focus:text-radroots-accent-focus group-active:bg-ly0-gl group-active:bg-ly0-gl-hl group-active:bg-ly0-gl-hl_a group-active:bg-ly0-gl-label group-active:bg-ly0-gl-shade group-active:bg-ly0-gl-a group-active:bg-ly0-gl-pl group-active:bg-ly0 group-active:bg-ly0-blur group-active:bg-ly0-edge group-active:bg-ly0-a group-active:bg-ly0-w group-active:bg-ly1-gl group-active:bg-ly1-gl-hl group-active:bg-ly1-gl-hl_a group-active:bg-ly1-gl-label group-active:bg-ly1-gl-shade group-active:bg-ly1-gl-a group-active:bg-ly1-gl-d group-active:bg-ly1-gl-pl group-active:bg-ly1 group-active:bg-ly1-edge group-active:bg-ly1-err group-active:bg-ly1-focus group-active:bg-ly1-a group-active:bg-ly2-gl group-active:bg-ly2-gl-hl group-active:bg-ly2-gl-hl_a group-active:bg-ly2-gl-shade group-active:bg-ly2-gl-a group-active:bg-ly2-gl-d group-active:bg-ly2-gl-pl group-active:bg-ly2 group-active:bg-ly2-edge group-active:bg-ly2-a group-active:bg-radroots-accent-focus group-active:border-ly0-gl group-active:border-ly0-gl-hl group-active:border-ly0-gl-hl_a group-active:border-ly0-gl-label group-active:border-ly0-gl-shade group-active:border-ly0-gl-a group-active:border-ly0-gl-pl group-active:border-ly0 group-active:border-ly0-blur group-active:border-ly0-edge group-active:border-ly0-a group-active:border-ly0-w group-active:border-ly1-gl group-active:border-ly1-gl-hl group-active:border-ly1-gl-hl_a group-active:border-ly1-gl-label group-active:border-ly1-gl-shade group-active:border-ly1-gl-a group-active:border-ly1-gl-d group-active:border-ly1-gl-pl group-active:border-ly1 group-active:border-ly1-edge group-active:border-ly1-err group-active:border-ly1-focus group-active:border-ly1-a group-active:border-ly2-gl group-active:border-ly2-gl-hl group-active:border-ly2-gl-hl_a group-active:border-ly2-gl-shade group-active:border-ly2-gl-a group-active:border-ly2-gl-d group-active:border-ly2-gl-pl group-active:border-ly2 group-active:border-ly2-edge group-active:border-ly2-a group-active:border-radroots-accent-focus group-active:text-ly0-gl group-active:text-ly0-gl-hl group-active:text-ly0-gl-hl_a group-active:text-ly0-gl-label group-active:text-ly0-gl-shade group-active:text-ly0-gl-a group-active:text-ly0-gl-pl group-active:text-ly0 group-active:text-ly0-blur group-active:text-ly0-edge group-active:text-ly0-a group-active:text-ly0-w group-active:text-ly1-gl group-active:text-ly1-gl-hl group-active:text-ly1-gl-hl_a group-active:text-ly1-gl-label group-active:text-ly1-gl-shade group-active:text-ly1-gl-a group-active:text-ly1-gl-d group-active:text-ly1-gl-pl group-active:text-ly1 group-active:text-ly1-edge group-active:text-ly1-err group-active:text-ly1-focus group-active:text-ly1-a group-active:text-ly2-gl group-active:text-ly2-gl-hl group-active:text-ly2-gl-hl_a group-active:text-ly2-gl-shade group-active:text-ly2-gl-a group-active:text-ly2-gl-d group-active:text-ly2-gl-pl group-active:text-ly2 group-active:text-ly2-edge group-active:text-ly2-a group-active:text-radroots-accent-focus group-focus:bg-ly0-gl group-focus:bg-ly0-gl-hl group-focus:bg-ly0-gl-hl_a group-focus:bg-ly0-gl-label group-focus:bg-ly0-gl-shade group-focus:bg-ly0-gl-a group-focus:bg-ly0-gl-pl group-focus:bg-ly0 group-focus:bg-ly0-blur group-focus:bg-ly0-edge group-focus:bg-ly0-a group-focus:bg-ly0-w group-focus:bg-ly1-gl group-focus:bg-ly1-gl-hl group-focus:bg-ly1-gl-hl_a group-focus:bg-ly1-gl-label group-focus:bg-ly1-gl-shade group-focus:bg-ly1-gl-a group-focus:bg-ly1-gl-d group-focus:bg-ly1-gl-pl group-focus:bg-ly1 group-focus:bg-ly1-edge group-focus:bg-ly1-err group-focus:bg-ly1-focus group-focus:bg-ly1-a group-focus:bg-ly2-gl group-focus:bg-ly2-gl-hl group-focus:bg-ly2-gl-hl_a group-focus:bg-ly2-gl-shade group-focus:bg-ly2-gl-a group-focus:bg-ly2-gl-d group-focus:bg-ly2-gl-pl group-focus:bg-ly2 group-focus:bg-ly2-edge group-focus:bg-ly2-a group-focus:bg-radroots-accent-focus group-focus:border-ly0-gl group-focus:border-ly0-gl-hl group-focus:border-ly0-gl-hl_a group-focus:border-ly0-gl-label group-focus:border-ly0-gl-shade group-focus:border-ly0-gl-a group-focus:border-ly0-gl-pl group-focus:border-ly0 group-focus:border-ly0-blur group-focus:border-ly0-edge group-focus:border-ly0-a group-focus:border-ly0-w group-focus:border-ly1-gl group-focus:border-ly1-gl-hl group-focus:border-ly1-gl-hl_a group-focus:border-ly1-gl-label group-focus:border-ly1-gl-shade group-focus:border-ly1-gl-a group-focus:border-ly1-gl-d group-focus:border-ly1-gl-pl group-focus:border-ly1 group-focus:border-ly1-edge group-focus:border-ly1-err group-focus:border-ly1-focus group-focus:border-ly1-a group-focus:border-ly2-gl group-focus:border-ly2-gl-hl group-focus:border-ly2-gl-hl_a group-focus:border-ly2-gl-shade group-focus:border-ly2-gl-a group-focus:border-ly2-gl-d group-focus:border-ly2-gl-pl group-focus:border-ly2 group-focus:border-ly2-edge group-focus:border-ly2-a group-focus:border-radroots-accent-focus group-focus:text-ly0-gl group-focus:text-ly0-gl-hl group-focus:text-ly0-gl-hl_a group-focus:text-ly0-gl-label group-focus:text-ly0-gl-shade group-focus:text-ly0-gl-a group-focus:text-ly0-gl-pl group-focus:text-ly0 group-focus:text-ly0-blur group-focus:text-ly0-edge group-focus:text-ly0-a group-focus:text-ly0-w group-focus:text-ly1-gl group-focus:text-ly1-gl-hl group-focus:text-ly1-gl-hl_a group-focus:text-ly1-gl-label group-focus:text-ly1-gl-shade group-focus:text-ly1-gl-a group-focus:text-ly1-gl-d group-focus:text-ly1-gl-pl group-focus:text-ly1 group-focus:text-ly1-edge group-focus:text-ly1-err group-focus:text-ly1-focus group-focus:text-ly1-a group-focus:text-ly2-gl group-focus:text-ly2-gl-hl group-focus:text-ly2-gl-hl_a group-focus:text-ly2-gl-shade group-focus:text-ly2-gl-a group-focus:text-ly2-gl-d group-focus:text-ly2-gl-pl group-focus:text-ly2 group-focus:text-ly2-edge group-focus:text-ly2-a group-focus:text-radroots-accent-focus h-[12px] h-[16px] h-[17px] h-[18px] h-[20px] h-[22px] h-[24px] h-[28px] h-[36px] h-lo_bottom_button_ios0 h-lo_bottom_button_ios1 h-lo_bottom_button_webm0 h-lo_bottom_button_webm1 h-lo_view_main_ios0 h-lo_view_main_ios1 h-lo_view_main_webm0 h-lo_view_main_webm1 h-nav_page_header_ios0 h-nav_page_header_ios1 h-nav_page_header_webm0 h-nav_page_header_webm1 h-nav_page_toolbar_ios0 h-nav_page_toolbar_ios1 h-nav_page_toolbar_webm0 h-nav_page_toolbar_webm1 h-nav_tabs_ios0 h-nav_tabs_ios1 h-nav_tabs_webm0 h-nav_tabs_webm1 left-lo_ios0 left-lo_ios1 left-lo_line_entry_ios0 left-lo_line_entry_ios1 left-lo_line_entry_webm0 left-lo_line_entry_webm1 left-lo_textdesc_ios0 left-lo_textdesc_ios1 left-lo_textdesc_webm0 left-lo_textdesc_webm1 left-lo_webm0 left-lo_webm1 max-h-lo_bottom_button_ios0 max-h-lo_bottom_button_ios1 max-h-lo_bottom_button_webm0 max-h-lo_bottom_button_webm1 max-h-lo_view_main_ios0 max-h-lo_view_main_ios1 max-h-lo_view_main_webm0 max-h-lo_view_main_webm1 max-h-nav_page_header_ios0 max-h-nav_page_header_ios1 max-h-nav_page_header_webm0 max-h-nav_page_header_webm1 max-h-nav_page_toolbar_ios0 max-h-nav_page_toolbar_ios1 max-h-nav_page_toolbar_webm0 max-h-nav_page_toolbar_webm1 max-h-nav_tabs_ios0 max-h-nav_tabs_ios1 max-h-nav_tabs_webm0 max-h-nav_tabs_webm1 max-w-lo_ios0 max-w-lo_ios1 max-w-lo_line_entry_ios0 max-w-lo_line_entry_ios1 max-w-lo_line_entry_webm0 max-w-lo_line_entry_webm1 max-w-lo_textdesc_ios0 max-w-lo_textdesc_ios1 max-w-lo_textdesc_webm0 max-w-lo_textdesc_webm1 max-w-lo_webm0 max-w-lo_webm1 min-h-lo_bottom_button_ios0 min-h-lo_bottom_button_ios1 min-h-lo_bottom_button_webm0 min-h-lo_bottom_button_webm1 min-h-lo_view_main_ios0 min-h-lo_view_main_ios1 min-h-lo_view_main_webm0 min-h-lo_view_main_webm1 min-h-nav_page_header_ios0 min-h-nav_page_header_ios1 min-h-nav_page_header_webm0 min-h-nav_page_header_webm1 min-h-nav_page_toolbar_ios0 min-h-nav_page_toolbar_ios1 min-h-nav_page_toolbar_webm0 min-h-nav_page_toolbar_webm1 min-h-nav_tabs_ios0 min-h-nav_tabs_ios1 min-h-nav_tabs_webm0 min-h-nav_tabs_webm1 min-w-lo_ios0 min-w-lo_ios1 min-w-lo_line_entry_ios0 min-w-lo_line_entry_ios1 min-w-lo_line_entry_webm0 min-w-lo_line_entry_webm1 min-w-lo_textdesc_ios0 min-w-lo_textdesc_ios1 min-w-lo_textdesc_webm0 min-w-lo_textdesc_webm1 min-w-lo_webm0 min-w-lo_webm1 pb-h_lo_bottom_button_ios0 pb-h_lo_bottom_button_ios1 pb-h_lo_bottom_button_webm0 pb-h_lo_bottom_button_webm1 pb-h_lo_view_main_ios0 pb-h_lo_view_main_ios1 pb-h_lo_view_main_webm0 pb-h_lo_view_main_webm1 pb-h_nav_page_header_ios0 pb-h_nav_page_header_ios1 pb-h_nav_page_header_webm0 pb-h_nav_page_header_webm1 pb-h_nav_page_toolbar_ios0 pb-h_nav_page_toolbar_ios1 pb-h_nav_page_toolbar_webm0 pb-h_nav_page_toolbar_webm1 pb-h_nav_tabs_ios0 pb-h_nav_tabs_ios1 pb-h_nav_tabs_webm0 pb-h_nav_tabs_webm1 pl-w_lo_ios0 pl-w_lo_ios1 pl-w_lo_line_entry_ios0 pl-w_lo_line_entry_ios1 pl-w_lo_line_entry_webm0 pl-w_lo_line_entry_webm1 pl-w_lo_textdesc_ios0 pl-w_lo_textdesc_ios1 pl-w_lo_textdesc_webm0 pl-w_lo_textdesc_webm1 pl-w_lo_webm0 pl-w_lo_webm1 pr-w_lo_ios0 pr-w_lo_ios1 pr-w_lo_line_entry_ios0 pr-w_lo_line_entry_ios1 pr-w_lo_line_entry_webm0 pr-w_lo_line_entry_webm1 pr-w_lo_textdesc_ios0 pr-w_lo_textdesc_ios1 pr-w_lo_textdesc_webm0 pr-w_lo_textdesc_webm1 pr-w_lo_webm0 pr-w_lo_webm1 pt-h_lo_bottom_button_ios0 pt-h_lo_bottom_button_ios1 pt-h_lo_bottom_button_webm0 pt-h_lo_bottom_button_webm1 pt-h_lo_view_main_ios0 pt-h_lo_view_main_ios1 pt-h_lo_view_main_webm0 pt-h_lo_view_main_webm1 pt-h_nav_page_header_ios0 pt-h_nav_page_header_ios1 pt-h_nav_page_header_webm0 pt-h_nav_page_header_webm1 pt-h_nav_page_toolbar_ios0 pt-h_nav_page_toolbar_ios1 pt-h_nav_page_toolbar_webm0 pt-h_nav_page_toolbar_webm1 pt-h_nav_tabs_ios0 pt-h_nav_tabs_ios1 pt-h_nav_tabs_webm0 pt-h_nav_tabs_webm1 right-lo_ios0 right-lo_ios1 right-lo_line_entry_ios0 right-lo_line_entry_ios1 right-lo_line_entry_webm0 right-lo_line_entry_webm1 right-lo_textdesc_ios0 right-lo_textdesc_ios1 right-lo_textdesc_webm0 right-lo_textdesc_webm1 right-lo_webm0 right-lo_webm1 text-[12px] text-[15px] text-[16px] text-[18px] text-[19px] text-[20px] text-[21px] text-[23px] text-[24px] text-[26px] text-[27px] text-[28px] text-[30px] text-[36px] text-[40px] text-ly0-gl text-ly0-gl-hl text-ly0-gl-hl_a text-ly0-gl-label text-ly0-gl-shade text-ly0-gl-a text-ly0-gl-pl text-ly0 text-ly0-blur text-ly0-edge text-ly0-a text-ly0-w text-ly1-gl text-ly1-gl-hl text-ly1-gl-hl_a text-ly1-gl-label text-ly1-gl-shade text-ly1-gl-a text-ly1-gl-d text-ly1-gl-pl text-ly1 text-ly1-edge text-ly1-err text-ly1-focus text-ly1-a text-ly2-gl text-ly2-gl-hl text-ly2-gl-hl_a text-ly2-gl-shade text-ly2-gl-a text-ly2-gl-d text-ly2-gl-pl text-ly2 text-ly2-edge text-ly2-a text-radroots-accent-focus top-lo_bottom_button_ios0 top-lo_bottom_button_ios1 top-lo_bottom_button_webm0 top-lo_bottom_button_webm1 top-lo_view_main_ios0 top-lo_view_main_ios1 top-lo_view_main_webm0 top-lo_view_main_webm1 top-nav_page_header_ios0 top-nav_page_header_ios1 top-nav_page_header_webm0 top-nav_page_header_webm1 top-nav_page_toolbar_ios0 top-nav_page_toolbar_ios1 top-nav_page_toolbar_webm0 top-nav_page_toolbar_webm1 top-nav_tabs_ios0 top-nav_tabs_ios1 top-nav_tabs_webm0 top-nav_tabs_webm1 translate-x-w_lo_ios0 translate-x-w_lo_ios1 translate-x-w_lo_line_entry_ios0 translate-x-w_lo_line_entry_ios1 translate-x-w_lo_line_entry_webm0 translate-x-w_lo_line_entry_webm1 translate-x-w_lo_textdesc_ios0 translate-x-w_lo_textdesc_ios1 translate-x-w_lo_textdesc_webm0 translate-x-w_lo_textdesc_webm1 translate-x-w_lo_webm0 translate-x-w_lo_webm1 translate-y-h_lo_bottom_button_ios0 translate-y-h_lo_bottom_button_ios1 translate-y-h_lo_bottom_button_webm0 translate-y-h_lo_bottom_button_webm1 translate-y-h_lo_view_main_ios0 translate-y-h_lo_view_main_ios1 translate-y-h_lo_view_main_webm0 translate-y-h_lo_view_main_webm1 translate-y-h_nav_page_header_ios0 translate-y-h_nav_page_header_ios1 translate-y-h_nav_page_header_webm0 translate-y-h_nav_page_header_webm1 translate-y-h_nav_page_toolbar_ios0 translate-y-h_nav_page_toolbar_ios1 translate-y-h_nav_page_toolbar_webm0 translate-y-h_nav_page_toolbar_webm1 translate-y-h_nav_tabs_ios0 translate-y-h_nav_tabs_ios1 translate-y-h_nav_tabs_webm0 translate-y-h_nav_tabs_webm1 w-[12px] w-[16px] w-[17px] w-[18px] w-[20px] w-[22px] w-[24px] w-[28px] w-[36px] w-lo_ios0 w-lo_ios1 w-lo_line_entry_ios0 w-lo_line_entry_ios1 w-lo_line_entry_webm0 w-lo_line_entry_webm1 w-lo_textdesc_ios0 w-lo_textdesc_ios1 w-lo_textdesc_webm0 w-lo_textdesc_webm1 w-lo_webm0 w-lo_webm1" +></div> diff --git a/apps-lib-pwa/src/lib/components/lib/float-page.svelte b/apps-lib-pwa/src/lib/components/lib/float-page.svelte @@ -0,0 +1,11 @@ +<script lang="ts"> + import type { IFloatPage } from "$lib/types/components/lib"; + import type { Snippet } from "svelte"; + + let { basis, children }: { basis: IFloatPage; children: Snippet } = + $props(); +</script> + +<div class={`absolute top-10 ${basis.posx}-6 flex flex-row`}> + {@render children()} +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/input-pwa.svelte b/apps-lib-pwa/src/lib/components/lib/input-pwa.svelte @@ -0,0 +1,120 @@ +<script lang="ts"> + import { browser } from "$app/environment"; + import { + fmt_cl, + idb_kv, + type IInput, + parse_layer, + value_constrain, + } from "@radroots/apps-lib"; + import { handle_err } from "@radroots/utils"; + import { onMount } from "svelte"; + + let { + basis, + el = $bindable(null), + value = $bindable(``), + }: { + basis: IInput<string>; + el?: HTMLInputElement | null; + value?: string; + } = $props(); + + const id = $derived(basis?.id ? basis.id : null); + const layer = $derived( + typeof basis?.layer === `boolean` ? 0 : parse_layer(basis?.layer), + ); + const classes_layer = $derived( + typeof basis?.layer === `boolean` || typeof basis?.layer === `undefined` + ? `` + : `bg-ly${layer} text-ly${layer}-gl_d placeholder:text-ly${layer}-gl_pl caret-ly${layer}-gl`, + ); + + const sync_from_idb = async (): Promise<void> => { + if (!browser || !id) return; + try { + const kv_val = await idb_kv.get(id); + if (kv_val !== null && kv_val !== undefined && kv_val !== value) { + value = kv_val; + } else if (kv_val === null || kv_val === undefined) { + value = ``; + await idb_kv.set(id, ``); + } + } catch (e) { + handle_err(e, `sync_from_idb`); + } + }; + + const sync_to_idb = async (): Promise<void> => { + if (!browser || !id) return; + try { + await idb_kv.set(id, value || ``); + } catch (e) { + handle_err(e, `input_idb_sync`); + } + }; + + onMount(async () => { + await sync_from_idb(); + if (basis?.callback_mount && el) { + try { + await basis.callback_mount({ el }); + } catch (e) { + handle_err(e, `callback_mount`); + } + } + }); + + $effect(() => { + if (id && basis?.sync && browser) { + (async () => { + await sync_to_idb(); + })(); + } + }); + + const handle_on_input = async (): Promise<void> => { + try { + let val_cur = value; + let pass = true; + if (basis?.field) { + val_cur = value_constrain(basis.field?.charset, val_cur); + if (val_cur !== value) { + value = val_cur; + } + pass = basis.field?.validate.test(val_cur); + } + if (basis?.callback) { + await basis.callback({ value: val_cur, pass }); + } + } catch (e) { + handle_err(e, `handle_on_input`); + } + }; +</script> + +<input + bind:this={el} + bind:value + disabled={!!basis.disabled} + oninput={handle_on_input} + onblur={async ({ currentTarget: el }) => { + if (basis.callback_blur) await basis.callback_blur({ el }); + }} + onfocus={async ({ currentTarget: el }) => { + if (id && basis.sync && browser) await sync_from_idb(); + if (basis.callback_focus) await basis.callback_focus({ el }); + }} + onkeydown={async (ev) => { + if (basis?.callback_keydown) + await basis.callback_keydown({ + key: ev.key, + key_s: ev.key === `Enter`, + el: ev.currentTarget, + }); + }} + {id} + type="text" + class={`${fmt_cl(basis?.classes)} el-input ${classes_layer} el-re`} + placeholder={basis?.placeholder || ``} +/> diff --git a/apps-lib-pwa/src/lib/components/lib/input-value.svelte b/apps-lib-pwa/src/lib/components/lib/input-value.svelte @@ -0,0 +1,71 @@ +<script lang="ts"> + import { + fmt_cl, + type IInputValue, + parse_layer, + value_constrain, + } from "@radroots/apps-lib"; + + let { + basis, + el = $bindable(null), + value = $bindable(``), + }: { + basis: IInputValue<string>; + el?: HTMLInputElement | null; + value: string; + } = $props(); + + const id = $derived(basis?.id ? basis.id : null); + const layer = $derived( + typeof basis?.layer === `boolean` + ? parse_layer(0) + : parse_layer(basis?.layer), + ); + + const classes_layer = $derived( + typeof basis?.layer === `boolean` || typeof basis?.layer === `undefined` + ? `` + : `bg-ly${layer} text-ly${layer}-gl placeholder:text-ly${layer}-gl_pl caret-ly${layer}-gl`, + ); + + const handle_on_input = async (): Promise<void> => { + try { + let val_cur = value; + let pass = true; + if (basis?.field) { + val_cur = value_constrain(basis.field.charset, val_cur); + if (val_cur !== value) value = val_cur; + pass = basis.field.validate.test(val_cur); + } + if (basis?.callback) await basis.callback({ value: val_cur, pass }); + } catch (e) { + console.error(`(error) handle_on_input`, e); + } + }; +</script> + +<input + bind:this={el} + bind:value + disabled={!!basis.disabled} + oninput={handle_on_input} + onblur={async ({ currentTarget: el }) => { + if (basis.callback_blur) await basis.callback_blur({ el }); + }} + onfocus={async ({ currentTarget: el }) => { + if (basis.callback_focus) await basis.callback_focus({ el }); + }} + onkeydown={async (ev) => { + if (basis?.callback_keydown) + await basis.callback_keydown({ + key: ev.key, + key_s: ev.key === `Enter`, + el: ev.currentTarget, + }); + }} + {id} + type="text" + class={`${fmt_cl(basis?.classes)} el-input ${classes_layer} el-re`} + placeholder={basis?.placeholder || ``} +/> diff --git a/apps-lib-pwa/src/lib/components/lib/label-swap.svelte b/apps-lib-pwa/src/lib/components/lib/label-swap.svelte @@ -0,0 +1,47 @@ +<script lang="ts"> + import { + fmt_cl, + type ILabelSwap, + type ILyOpt, + parse_layer, + } from "@radroots/apps-lib"; + + let { + basis, + el = $bindable(null), + }: { + basis: ILabelSwap & ILyOpt; + el?: HTMLLabelElement | null; + } = $props(); + + const layer = $derived(parse_layer(basis?.layer ? basis.layer : 1)); +</script> + +<div class={`flex flex-row justify-start items-center`}> + <!-- svelte-ignore a11y_label_has_associated_control --> + <label + bind:this={el} + class={`swap${basis.swap.toggle ? ` swap-active` : ``}`} + > + <div class="swap-on"> + <p + class={`${fmt_cl( + basis.swap.on.classes || + `text-nav_prev text-ly${layer}-gl-hl group-active:opacity-60`, + )} font-sans -translate-y-[1px] el-re`} + > + {basis.swap.on.value} + </p> + </div> + <div class="swap-off"> + <p + class={`${fmt_cl( + basis.swap.off.classes || + `text-nav_prev text-ly${layer}-gl-hl group-active:opacity-60`, + )} font-sans -translate-y-[1px] el-re`} + > + {basis.swap.off.value} + </p> + </div> + </label> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/load-circle.svelte b/apps-lib-pwa/src/lib/components/lib/load-circle.svelte @@ -0,0 +1,30 @@ +<script lang="ts"> + import type { ILoadCircle } from "$lib/types/components/lib"; + import { loading_style_map } from "$lib/utils/styles"; + import { fmt_cl, Glyph } from "@radroots/apps-lib"; + + let { + basis = undefined, + }: { + basis?: ILoadCircle; + } = $props(); + + const styles = $derived( + basis?.dim + ? loading_style_map.get(basis?.dim) + : loading_style_map.get("sm"), + ); +</script> + +<div + class={`relative flex flex-row justify-center items-center h-[${styles?.dim_1}px] w-[${styles?.dim_1}px] fade-in el-re`} +> + <Glyph + basis={{ + classes: `${fmt_cl( + basis?.classes, + )} text-base text-ly0-gl-a animate-spin-slow`, + key: "circle-notch", + }} + /> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/load-symbol.svelte b/apps-lib-pwa/src/lib/components/lib/load-symbol.svelte @@ -0,0 +1,101 @@ +<script lang="ts"> + import { loading_style_map } from "$lib/utils/styles"; + import type { ILoadSymbol } from "@radroots/apps-lib"; + + let { + basis = undefined, + }: { + basis?: ILoadSymbol; + } = $props(); + + const styles = $derived( + basis?.dim + ? loading_style_map.get(basis?.dim) + : loading_style_map.get("sm"), + ); + + const num_blades = $derived(basis?.blades || 8); +</script> + +<div + class={`relative flex flex-row justify-center items-center h-[${styles?.dim_1}px] w-[${styles?.dim_1}px] fade-in el-re`} +> + <div + class={`${ + num_blades === 12 ? `spinner12 center` : `spinner8 center` + } text-[${styles?.gl_2 || styles?.dim_1}px]`} + > + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + {#if num_blades === 12} + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + <div + class={`${ + num_blades === 12 ? `spinner12-blade` : `spinner8-blade` + }`} + ></div> + {/if} + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/logo-circle-sm.svelte b/apps-lib-pwa/src/lib/components/lib/logo-circle-sm.svelte @@ -0,0 +1,14 @@ +<div + class={`relative flex flex-row h-12 w-12 justify-center items-center bg-ly2 rounded-full`} +> + <p + class={`font-sans font-[900] text-[1.5rem] text-ly0-gl -tracking-[0.4rem] -translate-x-[2px]`} + > + {"»`,"} + </p> + <p + class={`font-sans font-[900] text-[1.5rem] text-ly0-gl translate-x-[3px]`} + > + {"-"} + </p> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/logo-circle.svelte b/apps-lib-pwa/src/lib/components/lib/logo-circle.svelte @@ -0,0 +1,18 @@ +<div + class={`relative flex flex-col h-[196px] w-full justify-center items-center`} +> + <div + class={`relative flex flex-row h-36 w-36 justify-center items-center bg-ly2 rounded-full`} + > + <p + class={`font-sans font-[900] text-6xl text-ly0-gl -tracking-[0.4rem] -translate-x-[6px]`} + > + {"»`,"} + </p> + <p + class={`font-sans font-[900] text-6xl text-ly0-gl translate-x-[8px]`} + > + {"-"} + </p> + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/logo-letters.svelte b/apps-lib-pwa/src/lib/components/lib/logo-letters.svelte @@ -0,0 +1,3 @@ +<p class={`font-sansd italic font-[700] text-[1.7rem] text-ly0-gl lowercase`}> + {`radroots`} +</p> diff --git a/apps-lib-pwa/src/lib/components/lib/select-menu.svelte b/apps-lib-pwa/src/lib/components/lib/select-menu.svelte @@ -0,0 +1,81 @@ +<script lang="ts"> + import { fmt_cl, type ISelect, parse_layer } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis, + value = $bindable(``), + el_wrap = $bindable(null), + el_select = $bindable(null), + children, + }: { + basis: ISelect; + value?: string; + el_wrap?: HTMLDivElement | null; + el_select?: HTMLSelectElement | null; + children?: Snippet; + } = $props(); + + const layer = $derived( + parse_layer(typeof basis?.layer === `boolean` ? basis.layer : 0), + ); + + const classes_layer = $derived( + typeof basis?.layer === `boolean` ? `` : `text-ly${layer}-gl`, + ); +</script> + +<div + class={`relative flex flex-row h-max w-auto justify-center items-center`} + bind:this={el_wrap} +> + <div + class={`${fmt_cl( + basis.classes, + )} z-20 absolute top-0 left-0 flex flex-row h-full w-full justify-end items-center ${classes_layer}`} + > + <select + class={`select select-ghost h-full w-full bg-transparent focus:border-0 focus:outline-0 text-transparent focus:text-transparent`} + bind:this={el_select} + bind:value + onchange={async (e) => { + const opt = basis.options + .map((i) => i.entries) + .reduce((_, j) => j, []) + .find( + (k: { value: string }) => + k.value === e.currentTarget?.value, + ); + if (basis.callback && opt) await basis.callback(opt); + if (el_select) el_select.value = value; + }} + > + {#each basis.options as opt_g} + {#if opt_g.group} + <optgroup> + {#each opt_g.entries as opt} + <option + label={opt_g.group === true + ? `-`.repeat(21) + : opt_g.group || ``} + > + {opt.label} + </option> + {/each} + </optgroup> + {:else} + {#each opt_g.entries as opt} + <option value={opt.value} disabled={!!opt.disabled}> + {opt.label} + </option> + {/each} + {/if} + {/each} + </select> + </div> + {#if children} + <div class={`z-10 flex flex-row h-full w-full`}> + {@render children()} + </div> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/lib/select-pwa.svelte b/apps-lib-pwa/src/lib/components/lib/select-pwa.svelte @@ -0,0 +1,126 @@ +<script lang="ts"> + import { browser } from "$app/environment"; + import { + Glyph, + type ISelect, + fmt_cl, + idb_kv, + parse_layer, + } from "@radroots/apps-lib"; + import { handle_err } from "@radroots/utils"; + import { onMount } from "svelte"; + + let { + basis, + value = $bindable(``), + el = $bindable(null), + }: { + basis: ISelect; + value: string; + el?: HTMLSelectElement | null; + } = $props(); + + const id = $derived(basis?.id ? basis.id : null); + + const layer = $derived( + typeof basis?.layer === `boolean` + ? parse_layer(0) + : parse_layer(basis.layer), + ); + + const classes_layer = $derived( + typeof basis?.layer === `boolean` + ? `` + : !value + ? `text-ly${layer}-gl/60` + : `text-ly${layer}-gl_d`, + ); + + onMount(async () => { + try { + if (id && basis?.sync_init && browser) { + const sync_val = await idb_kv.get(id); + await idb_kv.set(id, sync_val || ``); + } + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + $effect(() => { + if (browser && id && basis?.sync) { + (async () => { + await idb_kv.set(id, value); + })(); + } + }); + + const handle_on_change = async (el: HTMLSelectElement): Promise<void> => { + try { + const opt = basis.options + .map((i) => i.entries) + .reduce((_, j) => j, []) + .find((k) => k.value === el?.value); + if (el) el.value = value; + if (basis?.sync && id && browser) await idb_kv.set(id, value); + if (basis.callback && opt) await basis.callback(opt); + } catch (e) { + console.log(`(error) handle_on_change `, e); + } + }; +</script> + +{#if basis?.show_arrows === "l"} + <div class={`flex flex-row pr-[2px] justify-center items-center`}> + <Glyph + basis={{ + key: `caret-up-down`, + dim: `xs`, + + classes: `text-ly${layer}-gl translate-y-[1px]`, + }} + /> + </div> +{/if} +<select + bind:this={el} + bind:value + onchange={async ({ currentTarget: el }) => { + handle_on_change(el); + }} + {id} + class={`${fmt_cl(basis.classes)} z-10 el-select ${classes_layer}`} +> + {#each basis.options as opt_g} + {#if opt_g.group} + <optgroup> + {#each opt_g.entries as opt} + <option + label={opt_g.group === true + ? `-`.repeat(21) + : opt_g.group || ``} + > + {opt.label} + </option> + {/each} + </optgroup> + {:else} + {#each opt_g.entries as opt} + <option value={opt.value} disabled={!!opt.disabled}> + {opt.label} + </option> + {/each} + {/if} + {/each} +</select> +{#if basis?.show_arrows === "r"} + <div class={`flex flex-row pl-[2px] justify-center items-center`}> + <Glyph + basis={{ + key: `caret-up-down`, + dim: `xs`, + classes: `text-ly${layer}-gl`, + }} + /> + </div> +{/if} diff --git a/apps-lib-pwa/src/lib/components/lib/wrap-border.svelte b/apps-lib-pwa/src/lib/components/lib/wrap-border.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import { type IClOpt, fmt_cl } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + + let { + basis, + children, + }: { + basis: IClOpt; + children: Snippet; + } = $props(); +</script> + +<div + class={`${fmt_cl(basis.classes)} relative flex flex-col w-full py-[4px] px-[4px] justify-start items-center rounded-[32px] overflow-hidden bg-white shadow-sm`} +> + <div + class={`flex flex-row h-full w-full justify-center items-center bg-white/30 overflow-hidden rounded-[28px]`} + > + {@render children()} + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte b/apps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte @@ -0,0 +1,57 @@ +<script lang="ts"> + import { Fade, Glyph, type IBasisOpt } from "@radroots/apps-lib"; + + let { + basis = undefined, + }: { + basis?: IBasisOpt<{ + primary: string; + admin: string; + country: string; + }>; + } = $props(); +</script> + +{#if basis} + <Fade + basis={{ + classes: `flex-col w-full justify-center items-start`, + }} + > + <div + class={`flex flex-col w-fit px-5 py-[10px] justify-start items-start bg-ly1 rounded-3xl shadow-lg`} + > + <div class={`flex flex-col w-full gap-1 justify-start items-start`}> + <div class={`flex flex-row gap-1 justify-start items-center`}> + <p + class={`font-sans font-[600] text-[0.95rem] text-ly2-gl`} + > + {basis.primary} + </p> + <Glyph + basis={{ + classes: `text-ly2-gl -translate-y-[2px]`, + dim: `xs`, + + key: `map-pin-simple`, + }} + /> + </div> + <div + class={`flex flex-row w-full gap-1 justify-start items-center`} + > + <p + class={`font-sans font-[600] text-[0.95rem] tracking-tight text-ly2-gl`} + > + {`${basis.admin},`} + </p> + <p + class={`font-sans font-[600] text-[0.95rem] tracking-tight text-ly2-gl`} + > + {`${basis.country}`} + </p> + </div> + </div> + </div> + </Fade> +{/if} diff --git a/apps-lib-pwa/src/lib/components/map/map-marker-area.svelte b/apps-lib-pwa/src/lib/components/map/map-marker-area.svelte @@ -0,0 +1,47 @@ +<script lang="ts"> + import type { IMapMarkerArea } from "$lib/types/components/lib"; + import { get_context } from "@radroots/apps-lib"; + import { + type GeocoderReverseResult, + type GeolocationPoint, + } from "@radroots/geo"; + import { Marker, Popup } from "svelte-maplibre"; + import MapMarkerAreaDisplay from "./map-marker-area-display.svelte"; + + const { lc_geocode } = get_context(`lib`); + + let { + basis, + map_geop = $bindable(), + map_geoc = $bindable(undefined), + }: { + basis: IMapMarkerArea; + map_geop: GeolocationPoint; + map_geoc?: GeocoderReverseResult | undefined; + } = $props(); +</script> + +<Marker + bind:lngLat={map_geop} + draggable={!basis.no_drag} + class={`flex flex-row h-[100px] w-[100px] bg-blue-400/20 border-[2px] border-white justify-center items-center rounded-full shadow-lg`} + ondragend={async () => { + if (!map_geop) return; + const geoc = await lc_geocode(map_geop); + if (geoc) map_geoc = geoc; + }} +> + {#if basis.show_display} + <Popup open={basis.show_display} offset={[0, -55]}> + <MapMarkerAreaDisplay + basis={map_geoc + ? { + primary: map_geoc.name, + admin: map_geoc.admin1_name, + country: map_geoc.country_name, + } + : undefined} + /> + </Popup> + {/if} +</Marker> diff --git a/apps-lib-pwa/src/lib/components/map/map.svelte b/apps-lib-pwa/src/lib/components/map/map.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + import { cfg_map } from "$lib/utils/map"; + import { type IClOpt, fmt_cl, theme_mode } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + import { MapLibre } from "svelte-maplibre"; + + let { + basis = undefined, + map = $bindable(undefined), + children, + }: { + basis?: IClOpt & { + interactive?: boolean; + zoom_click_off?: boolean; + }; + map?: maplibregl.Map; + interactive?: boolean; + children: Snippet; + } = $props(); + + const interactive = $derived( + typeof basis?.interactive === `boolean` ? basis?.interactive : true, + ); + + const double_click_zoom = $derived( + typeof basis?.zoom_click_off === `boolean` + ? basis?.zoom_click_off + : true, + ); +</script> + +<MapLibre + bind:map + class="{fmt_cl(basis?.classes)} relative h-full w-full" + zoom={10} + style={cfg_map.styles.base[$theme_mode ?? "light"]} + attributionControl={false} + interactive={!!interactive} + zoomOnDoubleClick={double_click_zoom} +> + {@render children()} +</MapLibre> diff --git a/apps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte b/apps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte @@ -0,0 +1,46 @@ +<script lang="ts"> + import { get_context, Glyph } from "@radroots/apps-lib"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + const { ls, lc_photos_add } = get_context(`lib`); + + let { + basis, + photo_path = $bindable(``), + }: { + basis: { + loading?: boolean; + }; + photo_path: string; + } = $props(); +</script> + +<div class={`relative flex flex-row w-full justify-center items-center`}> + <button + class={`flex flex-row h-[5rem] w-[5rem] justify-center items-center bg-ly1/60 rounded-full`} + onclick={async () => { + const photo_paths_add = await lc_photos_add(); + if (photo_paths_add) photo_path = photo_paths_add[0]; + }} + > + {#if basis.loading} + <LoadSymbol basis={{ dim: `md` }} /> + {:else} + <Glyph + basis={{ + classes: `text-[40px] text-ly2-gl`, + dim: `sm`, + key: `camera`, + }} + /> + {/if} + + <div + class={`absolute -bottom-[1.8rem] flex flex-row justify-start items-center`} + > + <p class={`font-arch font-[600] text-sm text-ly0-gl capitalize`}> + {`${$ls(`icu.add_*`, { value: `${$ls(`common.photo`)}` })}`} + </p> + </div> + </button> +</div> diff --git a/apps-lib-pwa/src/lib/components/navigation/navigation-tabs.svelte b/apps-lib-pwa/src/lib/components/navigation/navigation-tabs.svelte @@ -0,0 +1,100 @@ +<script lang="ts"> + import { goto } from "$app/navigation"; + import { page } from "$app/state"; + import { app_lo } from "$lib/stores/app"; + import { Flex, Glyph } from "@radroots/apps-lib"; +</script> + +<div + class={`fixed bottom-0 left-0 h-nav_tabs_${$app_lo} flex flex-row w-full pt-2 justify-center items-start`} +> + <div class={`flex flex-row justify-between gap-10 items-center`}> + <div + class={`grid grid-cols-4 flex flex-row h-[3.1rem] px-6 gap-6 justify-start items-center bg-ly1 rounded-full backdrop-blur-lg`} + > + <button + class={`col-span-1 flex flex-row justify-center items-center`} + onclick={async () => { + await goto(`/`); + }} + > + <Glyph + basis={{ + classes: `text-[26px] text-ly0-gl/80 rotate-90`, + key: `columns`, + weight: page.url.pathname === `/` ? `fill` : `bold`, + }} + /> + </button> + <button + class={`relative col-span-1 flex flex-row justify-center items-center`} + onclick={async () => { + await goto(`/search`); + }} + > + <Glyph + basis={{ + classes: `text-[24px] text-ly0-gl/80`, + key: `magnifying-glass`, + weight: page.url.pathname.includes(`search`) + ? `fill` + : `bold`, + }} + /> + </button> + <button + class={`relative col-span-1 flex flex-row justify-center items-center`} + onclick={async () => { + goto(`/profile`); + }} + > + <Glyph + basis={{ + classes: `text-[24px] text-ly0-gl/80`, + key: `user`, + weight: page.url.pathname.includes(`profile`) + ? `fill` + : `bold`, + }} + /> + </button> + <button + class={`relative col-span-1 flex flex-row h-full justify-center items-center`} + onclick={async () => { + await goto(`/notifications`); + }} + > + <Glyph + basis={{ + classes: `text-[24px] text-ly0-gl/80`, + key: `bell`, + weight: page.url.pathname.includes(`notifications`) + ? `fill` + : `bold`, + }} + /> + <div + class={`absolute top-2 -right-1 flex flex-row justify-start items-center`} + > + <div + class={`flex flex-row h-2 w-2 justify-start items-center bg-yellow-400 rounded-full`} + > + <Flex /> + </div> + </div> + </button> + </div> + <button + class={`flex flex-row h-[3.1rem] w-[3.1rem] justify-center items-center bg-ly1 rounded-full backdrop-blur-lg`} + onclick={async () => {}} + > + <Glyph + basis={{ + classes: `text-[22px] text-ly0-gl/80`, + + key: `plus`, + }} + /> + </button> + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/navigation/page-header.svelte b/apps-lib-pwa/src/lib/components/navigation/page-header.svelte @@ -0,0 +1,55 @@ +<script lang="ts"> + import { app_lo, ph_blur } from "$lib/stores/app"; + import type { IPageHeader } from "$lib/types/components/lib"; + import { callback_route, Flex } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + import { fade } from "svelte/transition"; + + let { + basis, + children, + }: { + basis: IPageHeader<string>; + children?: Snippet; + } = $props(); +</script> + +{#if $ph_blur} + <div + in:fade={{ duration: 50 }} + out:fade={{ delay: 50, duration: 200 }} + class={`z-20 fixed top-0 left-0 flex flex-row h-nav_page_header_${$app_lo} w-full justify-center items-center bg-ly0-blur/30 backdrop-blur-lg`} + > + <Flex /> + </div> +{/if} +<div + class={`z-20 sticky top-0 flex flex-row min-h-nav_page_header_${$app_lo} h-nav_page_header_${$app_lo} w-full px-6 justify-between items-center`} +> + <div class={`flex flex-row justify-start items-center`}> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + if (basis.callback_route) + await callback_route(basis.callback_route); + }} + > + <p + class={`font-sansd font-[700] text-2xl text-ly0-gl capitalize max-w-lo_${$app_lo} truncate`} + > + {basis.label || ``} + </p> + </button> + </div> + {#if children} + {#if !$ph_blur} + <div + in:fade={{ duration: 50 }} + out:fade={{ delay: 50, duration: 200 }} + class={`flex flex-row justify-center items-center`} + > + {@render children()} + </div> + {/if} + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte b/apps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte @@ -0,0 +1,57 @@ +<script lang="ts"> + import { goto } from "$app/navigation"; + import { app_lo } from "$lib/stores/app"; + import type { IPageToolbar } from "$lib/types/components/lib"; + import { type IBasisOpt, Glyph } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + import LogoCircleSm from "../lib/logo-circle-sm.svelte"; + import LogoLetters from "../lib/logo-letters.svelte"; + import PageHeader from "./page-header.svelte"; + + let { + basis = undefined, + header_option, + }: { + basis?: IBasisOpt<IPageToolbar<string>>; + header_option?: Snippet; + } = $props(); +</script> + +<div + class={`flex flex-row min-h-nav_page_toolbar_${$app_lo} h-nav_page_toolbar_${$app_lo} w-full px-6 justify-between items-end`} +> + <div class={`flex flex-row w-full justify-between items-center`}> + <button + class={`flex flex-row gap-2 justify-start items-center`} + onclick={async () => { + if (basis?.callback) await basis.callback(); + else await goto(`/`); + }} + > + <LogoCircleSm /> + <LogoLetters /> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + await goto(`/settings`); + }} + > + <Glyph + basis={{ + classes: `text-ly0-gl`, + dim: `lg`, + + key: `gear`, + }} + /> + </button> + </div> +</div> +{#if basis?.header} + <PageHeader basis={basis.header}> + {#if header_option} + {@render header_option()} + {/if} + </PageHeader> +{/if} diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte @@ -0,0 +1,37 @@ +<script lang="ts"> + import type { ITrellisDefaultLabel } from "$lib/types/components/trellis"; + import { fmt_cl } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + + let { + layer, + labels, + classes = ``, + }: { + layer: ThemeLayer; + labels: ITrellisDefaultLabel[]; + classes?: string; + } = $props(); +</script> + +<div class={`${fmt_cl(classes)} flex flex-row`}> + <p class={`font-sans text-trellis_ti text-ly${layer}-gl-shade`}> + {#each labels as label} + <span class={`${fmt_cl(label.classes)} font-sans text-trellis_ti`}> + {#if `callback` in label} + <button + class={``} + onclick={async () => { + if (`callback` in label && label.callback) + await label.callback(); + }} + > + {label.label} + </button> + {:else} + {label.label} + {/if} + </span> + {/each} + </p> +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-end.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-end.svelte @@ -0,0 +1,38 @@ +<script lang="ts"> + import type { ITrellisBasisTouchEnd } from "$lib/types/components/trellis"; + import { Glyph } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + + let { + basis, + layer, + hide_active, + }: { + basis: ITrellisBasisTouchEnd; + layer: ThemeLayer; + hide_active: boolean; + } = $props(); +</script> + +<div + class={`absolute top-0 right-0 h-full w-max flex flex-row justify-center items-center`} +> + <button + class={`flex pr-3`} + onclick={async (ev) => { + if (basis.callback) await basis.callback(ev); + }} + > + {#if basis.glyph} + <Glyph + basis={{ + classes: `text-ly${layer}-gl-shade ${ + hide_active ? `` : `group-active:text-ly${layer}-gl_a` + } translate-y-[1px] opacity-70`, + dim: `xs+`, + ...basis.glyph, + }} + /> + {/if} + </button> +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-input.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-input.svelte @@ -0,0 +1,91 @@ +<script lang="ts"> + import type { ITrellisBasisInput } from "$lib/types/components/trellis"; + import { fmt_trellis } from "$lib/utils/app"; + import { fmt_cl, Glyph } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + import InputPwa from "../lib/input-pwa.svelte"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + let { + basis, + layer, + hide_border_b, + hide_border_t, + }: { + basis: ITrellisBasisInput; + layer: ThemeLayer; + hide_border_b: boolean; + hide_border_t: boolean; + } = $props(); +</script> + +<div class={`flex flex-row flex-grow h-full w-full`}> + <div + class={`${fmt_trellis( + hide_border_b, + hide_border_t, + )} flex flex-row h-line w-full justify-start items-center border-t-line border-ly${layer}-edge overflow-hidden`} + > + {#if basis.line_label && basis.line_label.value} + <div + class={`${fmt_cl( + basis.line_label.classes, + )} flex flex-row h-full justify-start items-center overflow-x-hidden`} + > + <p class={`font-sans text-ly${layer}-gl_b`}> + {basis.line_label.value} + </p> + </div> + {/if} + <div + class={`relative flex flex-row flex-grow h-full pr-12 justify-start items-center`} + > + <InputPwa + basis={{ + ...basis.basis, + layer: layer, + }} + /> + {#if basis.action} + {#if basis.action.visible} + <div + class={`absolute top-0 right-0 flex flex-row h-full w-12 pr-4 justify-end items-center fade-in`} + > + {#if basis.action.loading} + <div class={`flex flex-row fade-in`}> + <LoadSymbol + basis={{ + dim: `glyph-send-button`, + blades: 8, + classes: `text-ly${layer}-gl el-re`, + }} + /> + </div> + {:else} + <button + class={`group fade-in-long`} + onclick={async () => { + if (basis.action?.callback) + await basis.action.callback(); + }} + > + <Glyph + basis={basis.action.glyph + ? { + dim: `md-`, + ...basis.action.glyph, + } + : { + key: `plus`, + classes: `text-ly${layer}-gl`, + dim: `md-`, + }} + /> + </button> + {/if} + </div> + {/if} + {/if} + </div> + </div> +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-line.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-line.svelte @@ -0,0 +1,60 @@ +<script lang="ts"> + import { fmt_trellis } from "$lib/utils/app"; + import type { ThemeLayer } from "@radroots/themes"; + import type { CallbackPromiseGeneric } from "@radroots/utils"; + import type { Snippet } from "svelte"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + let { + loading = false, + layer, + callback, + hide_border_b, + hide_border_t, + children, + el_end, + }: { + loading?: boolean; + layer: ThemeLayer; + callback?: CallbackPromiseGeneric<MouseEvent>; + hide_border_b: boolean; + hide_border_t: boolean; + children: Snippet; + el_end?: Snippet; + } = $props(); +</script> + +<button + class={`flex flex-row flex-grow overflow-hidden`} + onclick={async (ev) => { + if (callback) await callback(ev); + }} +> + <div + class={`${fmt_trellis( + hide_border_b, + hide_border_t, + )} flex flex-row h-full w-full justify-center items-center border-t-line border-ly${layer}-edge el-re`} + > + {#if loading} + <div + class={`flex flex-row h-full w-full justify-center items-center`} + > + <LoadSymbol basis={{ dim: `sm` }} /> + </div> + {:else} + <div + class={`relative group flex flex-row h-line w-full pr-[2px] justify-between items-center el-re`} + > + <div + class={`flex flex-row h-full w-trellis_display justify-between items-center`} + > + {@render children()} + </div> + {#if el_end} + {@render el_end()} + {/if} + </div> + {/if} + </div> +</button> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte @@ -0,0 +1,72 @@ +<script lang="ts"> + import type { + ITrellisBasisOffset, + ITrellisBasisOffsetMod, + } from "$lib/types/components/trellis"; + import { Flex, fmt_cl, Glyph } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + import ButtonGlyphCircle from "../button/button-glyph-circle.svelte"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + let { + basis = undefined, + layer, + }: { + basis?: ITrellisBasisOffset; + layer: ThemeLayer; + } = $props(); + + const mod: ITrellisBasisOffsetMod = $derived(basis?.mod ? basis.mod : `sm`); +</script> + +<div class={`flex flex-row h-full`}> + {#if mod === `sm`} + <div class={`${fmt_cl(``)} flex flex-row h-full w-[22px]`}> + <Flex /> + </div> + {:else if mod === `glyph`} + <div class={`flex flex-row pr-[2px]`}> + <div class={`${fmt_cl(``)} flex flex-row h-full w-trellisOffset`}> + <Flex /> + </div> + </div> + {:else if typeof mod === `object`} + <div + class={`flex flex-row h-full min-w-[20px] w-trellisOffset justify-center items-center pr-3`} + > + <button + class={`fade-in pl-2 translate-x-[3px] translate-y-[1px]`} + onclick={async (ev) => { + if (mod.loading) return; + else if (typeof basis !== `boolean` && basis?.callback) + await basis.callback(ev); + }} + > + {#if mod.loading} + <LoadSymbol basis={{ blades: 8, dim: `xs` }} /> + {:else if `glyph` in mod} + <Glyph + basis={{ + classes: mod.glyph.classes + ? mod.glyph.classes + : `text-ly${layer}-gl`, + ...mod.glyph, + }} + /> + {:else if `glyph_circle` in mod} + <ButtonGlyphCircle + basis={{ + classes_wrap: mod.glyph_circle?.classes_wrap, + glyph: { + classes: mod.glyph_circle?.glyph?.classes + ? mod.glyph_circle?.glyph?.classes + : `text-ly${layer}-gl`, + ...mod.glyph_circle?.glyph, + }, + }} + /> + {/if} + </button> + </div> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte @@ -0,0 +1,46 @@ +<script lang="ts"> + import type { ITrellisKindDisplayValue } from "$lib/types/components/trellis"; + import { get_label_classes_kind } from "$lib/utils/app"; + import { Glyph, fmt_cl } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + + let { + basis, + layer, + hide_active, + }: { + basis: ITrellisKindDisplayValue; + layer: ThemeLayer; + hide_active: boolean; + } = $props(); +</script> + +<button + class={`z-10 flex flex-grow justify-end`} + onclick={async (ev) => { + ev.stopPropagation(); + if (basis.callback) await basis.callback(ev); + }} +> + {#if `icon` in basis} + <Glyph + basis={{ + classes: + basis.icon.classes || + `${get_label_classes_kind(layer, `shade`, hide_active)}`, + key: basis.icon.key, + dim: `sm`, + }} + /> + {:else if basis.label} + {#if `value` in basis.label} + <p + class={`${fmt_cl( + basis.label.classes, + )} font-sans text-line_d_e line-clamp-1 text-ly0-gl-label el-re`} + > + {basis.label.value} + </p> + {/if} + {/if} +</button> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte @@ -0,0 +1,81 @@ +<script lang="ts"> + import { get_label_classes_kind } from "$lib/utils/app"; + import { type ILabelTupFields, fmt_cl } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + import ButtonGlyph from "../button/button-glyph.svelte"; + + let { + basis, + layer, + hide_active, + }: { + basis: ILabelTupFields; + layer: ThemeLayer; + hide_active: boolean; + } = $props(); +</script> + +<div class={`flex flex-row h-full items-center justify-between`}> + {#if basis.left && basis.left.length} + <div class={`flex flex-row h-full items-center truncate`}> + {#each basis.left as title_l} + <div + class={`${fmt_cl( + title_l.classes_wrap, + )} flex flex-row h-full items-center ${get_label_classes_kind( + layer, + undefined, + hide_active, + )} ${title_l.hide_truncate ? `` : `truncate`}`} + > + {#if `glyph` in title_l} + <div + class={`flex flex-row justify-start items-center pr-2`} + > + <ButtonGlyph basis={{ ...title_l.glyph }} /> + </div> + {:else if `value` in title_l} + <p + class={`${fmt_cl( + title_l.classes, + )} font-sans text-line_d ${ + title_l.hide_truncate ? `` : `truncate` + } el-re`} + > + {title_l.value || ``} + </p> + {/if} + </div> + {/each} + </div> + {/if} + {#if basis.right && basis.right.length} + <div + class={`flex flex-row h-full w-content items-center justify-end pr-4`} + > + {#each basis.right.reverse() as title_r} + <div + class={`${fmt_cl( + title_r.classes_wrap, + )} flex flex-row h-full gap-1 items-center ${ + title_r.hide_truncate ? `` : `truncate` + }`} + > + {#if `glyph` in title_r} + <ButtonGlyph basis={{ ...title_r.glyph }} /> + {:else if `value` in title_r} + <p + class={`${fmt_cl( + title_r.classes, + )} font-sans text-line_d text-ly${layer}-gl_d ${ + title_r.hide_truncate ? `` : `truncate` + } el-re`} + > + {title_r.value || ``} + </p> + {/if} + </div> + {/each} + </div> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-select.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-select.svelte @@ -0,0 +1,65 @@ +<script lang="ts"> + import type { ITrellisBasisSelect } from "$lib/types/components/trellis"; + import type { ThemeLayer } from "@radroots/themes"; + import LoadSymbol from "../lib/load-symbol.svelte"; + import SelectMenu from "../lib/select-menu.svelte"; + import TrellisEnd from "./trellis-end.svelte"; + import TrellisLine from "./trellis-line.svelte"; + import TrellisRowDisplayValue from "./trellis-row-display-value.svelte"; + import TrellisRowLabel from "./trellis-row-label.svelte"; + + let { + basis, + layer, + hide_active, + hide_border_b, + hide_border_t, + }: { + basis: ITrellisBasisSelect; + layer: ThemeLayer; + hide_active: boolean; + hide_border_b: boolean; + hide_border_t: boolean; + } = $props(); + + const loading = $derived( + typeof basis?.loading === `boolean` ? basis.loading : false, + ); + + const value = $derived(basis.el.value); +</script> + +<TrellisLine + {layer} + {loading} + {hide_border_b} + {hide_border_t} + callback={basis.callback} +> + <TrellisRowLabel basis={basis.label} {layer} {hide_active} /> + {#if basis.display} + <div class={`flex flex-row pr-3 justify-center items-end`}> + <SelectMenu {value} basis={basis.el}> + {#if basis.display.loading} + <div + class={`flex flex-row h-full w-full justify-end items-center`} + > + <LoadSymbol basis={{ dim: `sm` }} /> + </div> + {:else} + <TrellisRowDisplayValue + basis={{ ...basis.display }} + {layer} + {hide_active} + /> + {/if} + </SelectMenu> + </div> + {/if} + + {#snippet el_end()} + {#if basis.end} + <TrellisEnd basis={basis.end} {layer} {hide_active} /> + {/if} + {/snippet} +</TrellisLine> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-title.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-title.svelte @@ -0,0 +1,79 @@ +<script lang="ts"> + import type { ITrellisTitle } from "$lib/types/components/trellis"; + import { Flex, fmt_cl, Glyph } from "@radroots/apps-lib"; + import type { ThemeLayer } from "@radroots/themes"; + import LabelSwap from "../lib/label-swap.svelte"; + + let { + basis, + layer = 0, + }: { + basis: ITrellisTitle; + layer: ThemeLayer; + } = $props(); + + const mod = $derived(basis?.mod ? basis.mod : `sm`); +</script> + +<div + class={`${fmt_cl( + basis.classes, + )} flex flex-row h-[24px] w-full pl-[2px] gap-1 items-center`} +> + <button + class={`flex flex-row h-full w-max items-center gap-1 ${ + mod === `glyph` ? `pl-[36px]` : mod === `sm` ? `pl-[16px]` : `` + }`} + onclick={async () => { + if (basis && basis.callback) await basis.callback(); + }} + > + {#if basis.value === true} + <Flex /> + {:else} + <p + class={`font-sans text-trellis_ti text-ly${layer}-gl-label uppercase`} + > + {basis.value || ``} + </p> + {/if} + </button> + {#if basis.link} + <button + class={`${fmt_cl( + basis.link.classes, + )} group flex flex-row h-full w-max items-center`} + onclick={async () => { + if (basis.link && basis.link.callback) + await basis.link.callback(); + }} + > + {#if basis.link.label} + {#if `swap` in basis.link.label} + <LabelSwap basis={basis.link.label} /> + {:else if `value` in basis.link.label} + <p + class={`${fmt_cl( + basis.link.label.classes, + )} font-sans text-trellis_ti uppercase fade-in`} + > + {basis.link.label.value || ``} + </p> + {/if} + {/if} + {#if basis.link.glyph} + <div class={`flex flex-row w-max`}> + <Glyph + basis={{ + ...basis.link.glyph, + dim: `xs-`, + classes: `${fmt_cl( + basis.link.glyph.classes, + )} fade-in`, + }} + /> + </div> + {/if} + </button> + {/if} +</div> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte @@ -0,0 +1,40 @@ +<script lang="ts"> + import type { ITrellisBasisTouch } from "$lib/types/components/trellis"; + import type { ThemeLayer } from "@radroots/themes"; + import TrellisEnd from "./trellis-end.svelte"; + import TrellisLine from "./trellis-line.svelte"; + import TrellisRowDisplayValue from "./trellis-row-display-value.svelte"; + import TrellisRowLabel from "./trellis-row-label.svelte"; + + let { + basis, + layer, + hide_active, + hide_border_b, + hide_border_t, + }: { + basis: ITrellisBasisTouch; + layer: ThemeLayer; + hide_active: boolean; + hide_border_b: boolean; + hide_border_t: boolean; + } = $props(); +</script> + +<TrellisLine {layer} {hide_border_b} {hide_border_t} callback={basis.callback}> + <TrellisRowLabel basis={basis.label} {layer} {hide_active} /> + {#if basis.display} + <TrellisRowDisplayValue + basis={{ + ...basis.display, + }} + {layer} + {hide_active} + /> + {/if} + {#snippet el_end()} + {#if basis.end} + <TrellisEnd basis={basis.end} {layer} {hide_active} /> + {/if} + {/snippet} +</TrellisLine> diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis.svelte @@ -0,0 +1,167 @@ +<script lang="ts"> + import { app_lo } from "$lib/stores/app"; + import type { ITrellis } from "$lib/types/components/trellis"; + import { fmt_cl, get_context, parse_layer } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + import TrellisDefaultLabel from "./trellis-default-label.svelte"; + import TrellisInput from "./trellis-input.svelte"; + import TrellisOffset from "./trellis-offset.svelte"; + import TrellisSelect from "./trellis-select.svelte"; + import TrellisTitle from "./trellis-title.svelte"; + import TrellisTouch from "./trellis-touch.svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + el_default, + el_offset, + el_append, + }: { + basis: ITrellis; + el_default?: Snippet; + el_offset?: Snippet; + el_append?: Snippet; + } = $props(); + + const hide_border_t = $derived( + typeof basis.hide_border_top === `boolean` + ? basis.hide_border_top + : true, + ); + + const hide_border_b = $derived( + typeof basis.hide_border_bottom === `boolean` + ? basis.hide_border_bottom + : true, + ); + + const hide_rounded = $derived( + typeof basis.hide_rounded === `boolean` ? basis.hide_rounded : false, + ); + + const set_title_background = $derived( + typeof basis.set_title_background === `boolean` + ? basis.set_title_background + : false, + ); + + const set_default_background = $derived( + typeof basis.set_default_background === `boolean` + ? basis.set_default_background + : false, + ); +</script> + +<div + id={basis.id || ``} + class={`${fmt_cl(basis.classes)} flex flex-col`} + data-view={basis.view || ``} +> + <div + class={`relative flex flex-col h-auto w-lo_${$app_lo} gap-[3px] ${ + set_title_background ? `bg-ly${basis.layer}` : `` + }`} + > + {#if basis.title && (!basis.default_el || (basis.default_el && basis.default_el.show_title))} + <TrellisTitle + basis={basis.title} + layer={parse_layer(basis.layer - 1)} + /> + {/if} + {#if basis.default_el} + <div + class={`flex flex-col h-auto w-full justify-center items-center`} + > + {#if el_default} + {@render el_default()} + {:else if basis.default_el} + <TrellisDefaultLabel + layer={parse_layer(basis.layer - 1)} + labels={basis.default_el.labels + ? basis.default_el.labels + : [ + { + label: `${$ls( + `common.no_items_to_display`, + )}.`, + }, + ]} + /> + {/if} + </div> + {:else if basis.list} + <div class={`flex flex-col w-full justify-center items-center`}> + {#each basis.list as li} + {#if li} + <div + class={`${ + li.hide_field ? `hidden` : `` + } group flex flex-row h-full w-full justify-end items-center bg-ly${ + basis.layer + } ${li.full_rounded ? `rounded-touch` : ``} ${ + hide_rounded + ? `` + : `first:rounded-t-2xl last:rounded-b-2xl` + } ${ + !li.hide_active + ? `active:bg-ly${basis.layer}_a` + : `` + } el-re`} + > + <div + class={`flex flex-row h-full w-full gap-1 items-center overflow-y-hidden`} + > + {#if !basis.hide_offset} + <TrellisOffset + basis={li.offset} + layer={basis.layer} + /> + {/if} + {#if el_offset} + {@render el_offset()} + {/if} + {#if `touch` in li && li.touch} + <TrellisTouch + basis={li.touch} + layer={basis.layer} + {hide_border_b} + {hide_border_t} + hide_active={!!li.hide_active} + /> + {:else if `input` in li && li.input} + <TrellisInput + basis={li.input} + layer={basis.layer} + {hide_border_b} + {hide_border_t} + /> + {:else if `select` in li && li.select} + <TrellisSelect + basis={li.select} + layer={basis.layer} + {hide_border_b} + {hide_border_t} + hide_active={!!li.hide_active} + /> + {/if} + </div> + </div> + {/if} + {/each} + </div> + {/if} + </div> + {#if el_append} + <div + class={`flex flex-col w-full ${ + set_default_background ? `bg-ly${basis.layer}` : `` + }`} + > + {@render el_append()} + </div> + {/if} +</div> +<div + class={`hidden group-first:border-t-0 group-first:border-t-line group-first:border-b-0 group-first:border-b-line`} +></div> diff --git a/apps-lib-pwa/src/lib/index.ts b/apps-lib-pwa/src/lib/index.ts @@ -0,0 +1,60 @@ +export { default as ButtonGlyphCircle } from "./components/button/button-glyph-circle.svelte"; +export { default as ButtonGlyphSimple } from "./components/button/button-glyph-simple.svelte"; +export { default as ButtonGlyph } from "./components/button/button-glyph.svelte"; +export { default as ButtonLabelDashed } from "./components/button/button-label-dashed.svelte"; +export { default as ButtonLayoutBottom } from "./components/button/button-layout-bottom.svelte"; +export { default as ButtonLayoutPair } from "./components/button/button-layout-pair.svelte"; +export { default as ButtonLayout } from "./components/button/button-layout.svelte"; +export { default as ButtonRoundNav } from "./components/button/button-round-nav.svelte"; +export { default as ButtonSimple } from "./components/button/button-simple.svelte"; +export { default as FarmsAddDetail } from "./components/farm/farms-add-detail.svelte"; +export { default as FarmsAddMap } from "./components/farm/farms-add-map.svelte"; +export { default as FarmsPreviewCard } from "./components/farm/farms-preview-card.svelte"; +export { default as EntryLine } from "./components/form/entry-line.svelte"; +export { default as EntryWrap } from "./components/form/entry-wrap.svelte"; +export { default as FormLineLedgerLabelSelectLabel } from "./components/form/form-line-ledger-label-select-label.svelte"; +export { default as FormLineLedgerSelect } from "./components/form/form-line-ledger-select.svelte"; +export { default as FormLineLedger } from "./components/form/form-line-ledger.svelte"; +export { default as LayoutPage } from "./components/layout/layout-page.svelte"; +export { default as LayoutTrellis } from "./components/layout/layout-trellis.svelte"; +export { default as LayoutView } from "./components/layout/layout-view.svelte"; +export { default as LayoutWindow } from "./components/layout/layout-window.svelte"; +export { default as CarouselContainer } from "./components/lib/carousel-container.svelte"; +export { default as CarouselItem } from "./components/lib/carousel-item.svelte"; +export { default as Css } from "./components/lib/css.svelte"; +export { default as FloatPage } from "./components/lib/float-page.svelte"; +export { default as InputPwa } from "./components/lib/input-pwa.svelte"; +export { default as InputValue } from "./components/lib/input-value.svelte"; +export { default as LabelSwap } from "./components/lib/label-swap.svelte"; +export { default as LoadCircle } from "./components/lib/load-circle.svelte"; +export { default as LoadSymbol } from "./components/lib/load-symbol.svelte"; +export { default as LogoCircleSm } from "./components/lib/logo-circle-sm.svelte"; +export { default as LogoCircle } from "./components/lib/logo-circle.svelte"; +export { default as LogoLetters } from "./components/lib/logo-letters.svelte"; +export { default as SelectMenu } from "./components/lib/select-menu.svelte"; +export { default as SelectPwa } from "./components/lib/select-pwa.svelte"; +export { default as WrapBorder } from "./components/lib/wrap-border.svelte"; +export { default as MapMarkerAreaDisplay } from "./components/map/map-marker-area-display.svelte"; +export { default as MapMarkerArea } from "./components/map/map-marker-area.svelte"; +export { default as Map } from "./components/map/map.svelte"; +export { default as ImageUploadPhotoAdd } from "./components/media/image-upload-photo-add.svelte"; +export { default as NavigationTabs } from "./components/navigation/navigation-tabs.svelte"; +export { default as PageHeader } from "./components/navigation/page-header.svelte"; +export { default as PageToolbar } from "./components/navigation/page-toolbar.svelte"; +export { default as TrellisDefaultLabel } from "./components/trellis/trellis-default-label.svelte"; +export { default as TrellisEnd } from "./components/trellis/trellis-end.svelte"; +export { default as TrellisInput } from "./components/trellis/trellis-input.svelte"; +export { default as TrellisLine } from "./components/trellis/trellis-line.svelte"; +export { default as TrellisOffset } from "./components/trellis/trellis-offset.svelte"; +export { default as TrellisRowDisplayValue } from "./components/trellis/trellis-row-display-value.svelte"; +export { default as TrellisRowLabel } from "./components/trellis/trellis-row-label.svelte"; +export { default as TrellisSelect } from "./components/trellis/trellis-select.svelte"; +export { default as TrellisTitle } from "./components/trellis/trellis-title.svelte"; +export { default as TrellisTouch } from "./components/trellis/trellis-touch.svelte"; +export { default as Trellis } from "./components/trellis/trellis.svelte"; +export { default as FarmsAdd } from "./views/farms/farms-add.svelte"; +export { default as Farms } from "./views/farms/farms.svelte"; +export { default as ProfileEdit } from "./views/profile/profile-edit.svelte"; +export { default as Profile } from "./views/profile/profile.svelte"; +export { default as Home } from "./views/root/home.svelte"; +export { default as Settings } from "./views/root/settings.svelte"; diff --git a/apps-lib-pwa/src/lib/stores/app.ts b/apps-lib-pwa/src/lib/stores/app.ts @@ -0,0 +1,33 @@ +import type { BrowserPlatformInfo, NavigationPreviousParam, NavigationRouteParamField, NavigationRouteParamId, NavigationRouteParamLat, NavigationRouteParamLng, NavigationRouteParamNostrPublicKey, NavigationRouteParamRef } from "@radroots/apps-lib"; +import { writable } from "svelte/store"; +import { queryParam } from "sveltekit-search-params"; + +export const app_tilt = writable<boolean>(false); +export const app_lo = writable<string>(""); +export const app_notify = writable<string>(``); +export const app_splash = writable<boolean>(true); +export const app_loading = writable<boolean>(false); +export const app_platform = writable<BrowserPlatformInfo | undefined>(undefined); + +export const cfg_role = writable<string>(); +export const cfg_setup = writable<boolean | undefined>(undefined); + +export const envelope_visible = writable<boolean>(false); +export const envelope_tilt = writable<boolean>(true); + +export const nav_visible = writable<boolean>(false); +export const nav_blur = writable<boolean>(false); +export const nav_prev = writable<NavigationPreviousParam<string>[]>([]); + +export const ph_blur = writable<boolean>(false); + +export const tabs_visible = writable<boolean>(false); +export const tabs_blur = writable<boolean>(false); +export const tabs_active = writable<number>(0); + +export const qp_id = queryParam<NavigationRouteParamId>("id"); +export const qp_field = queryParam<NavigationRouteParamField>("field"); +export const qp_ref = queryParam<NavigationRouteParamRef>("ref"); +export const qp_lat = queryParam<NavigationRouteParamLat>("lat"); +export const qp_lng = queryParam<NavigationRouteParamLng>("lng"); +export const qp_keynostr = queryParam<NavigationRouteParamNostrPublicKey>("key_nostr"); diff --git a/apps-lib-pwa/src/lib/types/app.ts b/apps-lib-pwa/src/lib/types/app.ts @@ -0,0 +1,60 @@ +import type { TangleDatabaseBackup } from "@radroots/client/tangle"; +import type { IdbClientConfig } from "@radroots/utils"; + +export type AppConfigRole = `farmer` | `personal` + +export type AppLayoutKeyIOS = `ios0` | `ios1`; +export type AppLayoutKeyWeb = `webm0` | `webm1`; +export type AppLayoutKey = AppLayoutKeyIOS | AppLayoutKeyWeb; + +export type AppLayoutIOS<T extends string> = `${T}_${AppLayoutKeyIOS}`; +export type AppLayoutWeb<T extends string> = `${T}_${AppLayoutKeyWeb}`; + +export type AppLayoutKeyHeight = + | `lo_view_main` + | `lo_bottom_button` + | `nav_tabs` + | `nav_page_header` + | `nav_page_toolbar`; + +export type AppLayoutKeyWidth = + | `lo` + | `lo_line_entry` + | `lo_textdesc`; + +export type AppHeightsResponsiveIOS = AppLayoutIOS<AppLayoutKeyHeight>; +export type AppHeightsResponsiveWeb = AppLayoutWeb<AppLayoutKeyHeight>; + +export type AppWidthsResponsiveIOS = AppLayoutIOS<AppLayoutKeyWidth>; +export type AppWidthsResponsiveWeb = AppLayoutWeb<AppLayoutKeyWidth>; + +export type LabelFieldKind = `link` | `on` | `shade`; + +export type BackupVersions = { + app: string; + tangle_sql: string; + backup_format: string; +}; + +export type ExportedAppState = { + backup_version: string; + exported_at: string; + versions: BackupVersions; + datastore: { + config: IdbClientConfig; + entries: Record<string, unknown>; + }; + nostr_keystore: { + config: IdbClientConfig; + keys: { + public_key: string; + secret_key: string; + }[]; + }; + database: { + store_key: string; + backup: TangleDatabaseBackup; + }; +}; + +export type ImportableAppState = ExportedAppState; diff --git a/apps-lib-pwa/src/lib/types/components/lib.ts b/apps-lib-pwa/src/lib/types/components/lib.ts @@ -0,0 +1,57 @@ +import type { CallbackRoute, GeometryScreenPositionHorizontal, ICb, ICbOpt, IClOpt, IDisabledOpt, IGlyph, IGlyphKey, ILoadingOpt, ILyOpt, LoadingDimension } from "@radroots/apps-lib"; +import type { CallbackPromise, CallbackPromiseGeneric } from "@radroots/utils"; + +export type IButtonSimple = ILyOpt & { + label: string; + callback: CallbackPromise; + allow_propogation?: boolean; +}; + +export type IPageHeader<T extends string> = { + label: string; + callback_route?: CallbackRoute<T>; +}; + +export type IPageToolbar<T extends string> = ICbOpt & { + header?: IPageHeader<T>; +}; + +export type IMapMarkerArea = { + show_display?: boolean; + no_drag?: boolean; +} + +export type IButtonGlyphCircle = { + classes_wrap: string; + glyph: IGlyph +}; + +export type IButtonNavRound = ICb & IDisabledOpt & ILoadingOpt & IGlyphKey; + +export type CarouselMouseEvent = MouseEvent & { + currentTarget: EventTarget & HTMLDivElement; +}; + +export type CarouselKeyboardEvent = KeyboardEvent & { + currentTarget: EventTarget & HTMLDivElement; +}; + +export type ICarouselContainer<T extends string> = IClOpt & { + view: T; +}; + +export type ICarouselItem<T extends string> = IClOpt & { + view: T; + role?: string; + tabindex?: number; + callback_click?: CallbackPromiseGeneric<CarouselMouseEvent>; + callback_keydown?: CallbackPromiseGeneric<CarouselKeyboardEvent>; +}; + +export type ILoadCircle = IClOpt & { + dim?: LoadingDimension; +}; + +export type IFloatPage = { + posx: Omit<GeometryScreenPositionHorizontal, "center">; +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/types/components/trellis.ts b/apps-lib-pwa/src/lib/types/components/trellis.ts @@ -0,0 +1,127 @@ +import type { GlyphKey, ICbGOpt, ICbOpt, IClOpt, IGl, IGlOpt, IGlyph, IInput, ILabel, ILabelOpt, ILabelTup, ILoadingOpt, ILy, ISelect } from "@radroots/apps-lib"; +import type { CallbackPromise } from "@radroots/utils"; +import type { IButtonGlyphCircle } from "./lib"; + +export type ITrellisExtList = { + list: (ITrellisKind | undefined)[]; +} + +export type ITrellis = ILy & + IClOpt & + ITrellisStyles & { + id?: string; + view?: string; + title?: ITrellisTitle; + description?: ITrellisDescription; + default_el?: ITrellisDefault; + list?: (ITrellisKind | undefined)[]; + hide_offset?: true; + }; + +export type ITrellisStyles = { + hide_rounded?: boolean; + hide_border_top?: boolean; + hide_border_bottom?: boolean; + set_title_background?: boolean; + set_default_background?: boolean; +}; + +export type ITrellisTitle = ICbOpt & + IClOpt & { + mod?: ITrellisBasisOffsetMod, + value: string | true; + link?: ICbOpt & + IClOpt & + IGlOpt & ILabelOpt; + }; + +export type ITrellisDescription = string | true; + +export type ITrellisBasisOffsetModKey = 'sm' | 'glyph'; +export type ITrellisBasisOffsetMod = ITrellisBasisOffsetModKey | (({ glyph: IGlyph } | { glyph_circle: IButtonGlyphCircle }) & { + loading?: boolean; +}); + +export type ITrellisDefault = { + labels?: ITrellisDefaultLabel[]; + show_title?: boolean; +}; + +export type ITrellisDefaultLabel = ICbOpt & { + label: string; + classes?: string; +}; + +export type ITrellisKind = ( + | ITrellisKindTouch + | ITrellisKindInput + | ITrellisKindSelect +); + +export type ITrellisBasis = { + loading?: boolean; + hide_active?: boolean; + hide_field?: boolean; + offset?: ITrellisBasisOffset; + full_rounded?: boolean; +}; + +export type ITrellisBasisOffset = ICbGOpt<MouseEvent> & + IClOpt & { + mod?: ITrellisBasisOffsetMod; + classes?: string; + hide_space?: boolean; + hide_offset?: boolean; + }; + +export type ITrellisKindDisplay = { + display?: ITrellisKindDisplayValue; +} +export type ITrellisKindDisplayValue = ICbGOpt<MouseEvent> & ILoadingOpt & + (ITrellisKindDisplayValueIcon | ILabel); + + +export type ITrellisKindDisplayValueIcon = { + icon: { + classes?: string; + key: GlyphKey; + }; +}; +export type ITrellisKindTouch = ITrellisBasis & { + touch: ITrellisBasisTouch; +}; + +export type ITrellisBasisTouch = ICbGOpt<MouseEvent> & + ILabelTup & ITrellisKindDisplay & { + end?: ITrellisBasisTouchEnd; + }; + +export type ITrellisKindInput = ITrellisBasis & { + input: ITrellisBasisInput; +}; + +export type ITrellisBasisInput = { + basis: IInput<string>; + line_label?: { + classes?: string; + value: string; + }; + action?: { + visible: boolean; + loading?: boolean; + callback?: CallbackPromise; + glyph?: IGlyph + }; +}; + +export type ITrellisKindSelect = ITrellisBasis & { + select: ITrellisBasisSelect; +}; + +export type ITrellisBasisSelect = ICbGOpt<MouseEvent> & + ILabelTup & ITrellisKindDisplay & ILoadingOpt & { + end?: ITrellisBasisTouchEnd; + el: ISelect & { value: string; }; + }; + +export type ITrellisBasisTouchEnd = ICbGOpt<MouseEvent> & IGl; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/types/views.ts b/apps-lib-pwa/src/lib/types/views.ts @@ -0,0 +1,9 @@ +import type { CallbackPromise } from "@radroots/utils"; + +export type IViewBasis<T extends object> = { + kv_init_prevent?: boolean; + on_mount?: CallbackPromise; + on_destroy?: CallbackPromise; +} & T; + +export type IViewHomeData = {}; diff --git a/apps-lib-pwa/src/lib/types/views/farms.ts b/apps-lib-pwa/src/lib/types/views/farms.ts @@ -0,0 +1,41 @@ +import type { Farm } from "@radroots/tangle-schema-bindings"; +import type { GeocoderReverseResult, GeolocationPoint, LocationBasis } from "@radroots/geo"; + +export type FarmExtended = { + farm: Farm; + location?: LocationBasis; + lots?: FarmLotBasis[]; +}; + + +export type FarmLotBasis = { + id: string; + location?: LocationBasis; +}; +export type IViewFarmsData = { + list: FarmExtended[]; +}; + +export type IViewFarmsAddSubmission = { + farm_name: string; + farm_area?: number; + farm_area_unit?: string; + farm_contact_name?: string; + geolocation_point: GeolocationPoint; + geocode_result: GeocoderReverseResult; +}; + +export type IViewFarmsProductsAddSubmitPayload = { + product: string; + process: string; + description: string; + price_amount: number; + price_currency: string; + price_quantity_unit: string; + photos: string[]; + quantity_amount: number; + quantity_unit: string; + quantity_label: string; + geolocation_point: GeolocationPoint; + geocode_result: GeocoderReverseResult; +}; diff --git a/apps-lib-pwa/src/lib/types/views/profile.ts b/apps-lib-pwa/src/lib/types/views/profile.ts @@ -0,0 +1,12 @@ +import type { NostrProfile } from "@radroots/tangle-schema-bindings"; + +export type IViewProfileData = { + profile: NostrProfile; +}; + +export type ViewProfileEditFieldKey = `name` | `display_name` | `about`; + +export type IViewProfileEditData = { + public_key: string; + field: ViewProfileEditFieldKey; +}; diff --git a/apps-lib-pwa/src/lib/utils/app.ts b/apps-lib-pwa/src/lib/utils/app.ts @@ -0,0 +1,39 @@ +import type { AppLayoutKey, LabelFieldKind } from "$lib/types/app"; +import type { ThemeLayer } from "@radroots/themes"; + +type ConfigWindow = { + layout: Record<AppLayoutKey, { + h: number; + }>; + debounce: { + search: number; + } +}; + +export const cfg_app: ConfigWindow = { + layout: { + ios0: { + h: 600 + }, + ios1: { + h: 750 + }, + webm0: { + h: 600 + }, + webm1: { + h: 750 + } + }, + debounce: { + search: 200 + }, +}; + +export const fmt_trellis = (hide_border_t: boolean, hide_border_b: boolean): string => { + return `${hide_border_t ? `group-first:border-t-0` : `group-first:border-t-line`} ${hide_border_b ? `group-last:border-b-0` : `group-last:border-b-line`}`; +}; + +export const get_label_classes_kind = (layer: ThemeLayer, label_kind: LabelFieldKind | undefined, hide_active: boolean): string => { + return `text-ly${layer}-gl${label_kind ? `-${label_kind}` : ``} ${hide_active ? `` : `group-active:text-ly${layer}-gl${label_kind ? `-${label_kind}_a` : `_a`}`}` +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/utils/context.ts b/apps-lib-pwa/src/lib/utils/context.ts @@ -0,0 +1,21 @@ +import type { I18nTranslateFunction, I18nTranslateLocale, LocalCallbackColorMode, LocalCallbackGeocode, LocalCallbackGeocodeCurrent, LocalCallbackGuiAlert, LocalCallbackGuiConfirm, LocalCallbackImgBin, LocalCallbackPhotosAddMultiple, LocalCallbackPhotosUpload } from "@radroots/apps-lib"; + +export type ContextKeys = + | `lib`; + +export type ContextMap = { + lib: LibContext; +}; + +export type LibContext = { + ls: I18nTranslateFunction; + locale: I18nTranslateLocale; + lc_color_mode: LocalCallbackColorMode; + lc_gui_alert: LocalCallbackGuiAlert; + lc_gui_confirm: LocalCallbackGuiConfirm; + lc_geocode: LocalCallbackGeocode; + lc_photos_add: LocalCallbackPhotosAddMultiple; + lc_img_bin: LocalCallbackImgBin; + lc_geop_current: LocalCallbackGeocodeCurrent; + lc_photos_upload: LocalCallbackPhotosUpload; +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/utils/farm/schema.ts b/apps-lib-pwa/src/lib/utils/farm/schema.ts @@ -0,0 +1,28 @@ +import { dev } from "$app/environment"; +import type { IViewFarmsAddSubmission, IViewFarmsProductsAddSubmitPayload } from "$lib/types/views/farms"; +import { form_fields, schema_geocode_result, schema_geolocation_point, util_rxp, zf_numf_pos, zf_numi_pos, zf_price } from "@radroots/utils"; +import { z } from "zod"; + +export const schema_view_farms_add_submission: z.ZodSchema<IViewFarmsAddSubmission> = z.object({ + farm_name: z.string().regex(form_fields.farm_name.validate), + farm_area: zf_numf_pos.optional(), + farm_area_unit: z.string().regex(form_fields.area_unit.validate).optional(), + farm_contact_name: z.string().regex(form_fields.contact_name.validate).optional(), + geolocation_point: schema_geolocation_point, + geocode_result: schema_geocode_result, +}); + +export const schema_view_farms_products_add_submission: z.ZodSchema<IViewFarmsProductsAddSubmitPayload> = z.object({ + product: z.string().regex(form_fields.product_key.validate), + process: z.string().regex(form_fields.product_process.validate), + description: z.string().regex(form_fields.product_description.validate), + price_amount: zf_price, + price_currency: z.string().regex(form_fields.price_currency.validate), + price_quantity_unit: z.string().regex(form_fields.quantity_unit.validate), + photos: z.array(z.string().regex(dev ? util_rxp.url_image_upload_dev : util_rxp.url_image_upload)), + quantity_amount: zf_numi_pos, + quantity_unit: z.string().regex(form_fields.quantity_unit.validate), + quantity_label: z.string().regex(form_fields.quantity_label.validate), + geolocation_point: schema_geolocation_point, + geocode_result: schema_geocode_result, +}); +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/utils/map.ts b/apps-lib-pwa/src/lib/utils/map.ts @@ -0,0 +1,24 @@ +export const cfg_map = { + styles: { + base: { + light: `https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json`, + dark: `https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json` + } + }, + popup: { + dot: { + offset: [0, -10] as [number, number] + } + }, + coords: { + default: { + lat: 0, + lng: 0, + } + } +}; + +export const focus_map_marker = (): void => { + const el = document.querySelector(".maplibregl-marker"); + if (el instanceof HTMLElement) el.click(); +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/utils/profile/lib.ts b/apps-lib-pwa/src/lib/utils/profile/lib.ts @@ -0,0 +1,12 @@ +import type { ViewProfileEditFieldKey } from "$lib/types/views/profile"; + +export const parse_view_profile_field_key = (val?: string | null): ViewProfileEditFieldKey | undefined => { + switch (val) { + case `name`: + case `display_name`: + case `about`: + return val; + default: + return undefined; + } +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/utils/styles.ts b/apps-lib-pwa/src/lib/utils/styles.ts @@ -0,0 +1,10 @@ +import type { LoadingDimension } from "@radroots/apps-lib"; + +export const loading_style_map: Map<LoadingDimension, { dim_1: number; gl_2: number }> = new Map([ + ["glyph-send-button", { dim_1: 20, gl_2: 20 }], + ["xs", { dim_1: 12, gl_2: 12 }], + ["sm", { dim_1: 16, gl_2: 16 }], + ["md", { dim_1: 20, gl_2: 20 }], + ["lg", { dim_1: 28, gl_2: 28 }], + ["xl", { dim_1: 36, gl_2: 36 }], +]); diff --git a/apps-lib-pwa/src/lib/views/farms/farms-add.svelte b/apps-lib-pwa/src/lib/views/farms/farms-add.svelte @@ -0,0 +1,246 @@ +<script lang="ts"> + import ButtonLayoutBottom from "$lib/components/button/button-layout-bottom.svelte"; + import ButtonLayoutPair from "$lib/components/button/button-layout-pair.svelte"; + import FarmsAddDetails from "$lib/components/farm/farms-add-detail.svelte"; + import FarmsAddMap from "$lib/components/farm/farms-add-map.svelte"; + import LayoutView from "$lib/components/layout/layout-view.svelte"; + import CarouselContainer from "$lib/components/lib/carousel-container.svelte"; + import CarouselItem from "$lib/components/lib/carousel-item.svelte"; + import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; + import { app_platform } from "$lib/stores/app"; + import type { IViewFarmsAddSubmission } from "$lib/types/views/farms"; + import { schema_view_farms_add_submission } from "$lib/utils/farm/schema"; + import { focus_map_marker } from "$lib/utils/map"; + import { + carousel_dec, + carousel_inc, + carousel_init, + casl_i, + el_id, + fmt_id, + geop_init, + geop_is_valid, + get_context, + type CallbackRoute, + } from "@radroots/apps-lib"; + import { + geol_lat_fmt, + geol_lng_fmt, + parse_geocode_address, + type GeocoderReverseResult, + type GeolocationAddress, + type GeolocationPoint, + } from "@radroots/geo"; + import { + handle_err, + parse_float, + type CallbackPromiseGeneric, + } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls, locale, lc_gui_alert, lc_geop_current, lc_geocode } = + get_context(`lib`); + + let { + basis, + }: { + basis: { + callback_route?: CallbackRoute<string>; + on_submit: CallbackPromiseGeneric<{ + payload: IViewFarmsAddSubmission; + }>; + }; + } = $props(); + + let map_geop: GeolocationPoint = $state(geop_init()); + let map_geoc: GeocoderReverseResult | undefined = $state(undefined); + + let val_farmname = $state(``); + let val_farmaddress = $state(``); + let val_farmcontact = $state(``); + let val_farmarea = $state(``); + let val_farmarea_unit = $state(`ac`); + + const carousel_view: "farms_add" = "farms_add"; + + const disabled_submit = $derived($casl_i === 1 && !val_farmname); + + onMount(async () => { + try { + await carousel_init(carousel_view, 1); + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + const farm_geop_lat = $derived( + geop_is_valid(map_geop) + ? geol_lat_fmt(map_geop.lat, `dms`, $locale, 3) + : ``, + ); + + const farm_geop_lng = $derived( + geop_is_valid(map_geop) + ? geol_lng_fmt(map_geop.lng, `dms`, $locale, 3) + : ``, + ); + + const farm_geolocation_address: GeolocationAddress | undefined = $derived( + parse_geocode_address(map_geoc), + ); + + $effect(() => { + if (farm_geolocation_address) + val_farmaddress = `${farm_geolocation_address.primary}, ${farm_geolocation_address.admin}, ${farm_geolocation_address.country}`; + }); + + const handle_enter_location = async (): Promise<void> => { + map_geoc = undefined; + map_geop = geop_init(); + val_farmaddress = ``; + await handle_continue(); + el_id(fmt_id(`farm_location`))?.focus(); + }; + + const handle_continue_1 = async (): Promise<void> => { + if (!map_geop || !map_geoc) + return void lc_gui_alert(`No farm location provided.`); // @todo + const farms_add_submission = schema_view_farms_add_submission.safeParse( + { + farm_name: val_farmname, + farm_area: val_farmarea ? parse_float(val_farmarea) : undefined, + farm_area_unit: + val_farmarea && val_farmarea_unit + ? val_farmarea_unit + : undefined, + farm_contact_name: val_farmcontact + ? val_farmcontact + : undefined, + geolocation_point: map_geop, + geocode_result: map_geoc, + } satisfies IViewFarmsAddSubmission, + ); + + if (!farms_add_submission.success) { + return void lc_gui_alert( + `Request invalid: ${farms_add_submission.error}`, + ); // @todo + } + await basis.on_submit({ payload: farms_add_submission.data }); + }; + + const handle_continue = async (): Promise<void> => { + switch ($casl_i) { + case 1: + return await handle_continue_1(); + default: + await carousel_inc(carousel_view); + } + }; + + const handle_back = async (): Promise<void> => { + switch ($casl_i) { + case 1: { + if (!geop_is_valid(map_geop)) { + const geop_cur = await lc_geop_current(); + if (geop_cur) { + map_geop = geop_cur; + const geoc_cur = await lc_geocode(geop_cur); + if (geoc_cur) map_geoc = geoc_cur; + focus_map_marker(); + } + } + } + default: + return await carousel_dec(carousel_view); + } + }; +</script> + +<LayoutView> + <PageToolbar + basis={{ + header: { + label: `${$ls(`common.farms`)} / ${`${$ls(`common.add`)}`}`, + callback_route: basis.callback_route, + }, + }} + > + {#snippet header_option()} + <!-- {#if $casl_i === 0} + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + await handle_enter_location(); + }} + > + <p + class={`font-sans font-[600] text-[18px] text-ly0-gl-hl`} + > + {`${$ls(`common.enter_location`)}`} + </p> + <Glyph + basis={{ + classes: `text-ly0-gl-hl`, + dim: `md`, + key: `caret-right`, + }} + /> + </button> + {/if}--> + {/snippet} + </PageToolbar> + <CarouselContainer + basis={{ + view: carousel_view, + }} + > + <CarouselItem + basis={{ + view: carousel_view, + classes: `justify-start items-center`, + }} + > + <FarmsAddMap + bind:map_geop + bind:map_geoc + {farm_geop_lat} + {farm_geop_lng} + /> + </CarouselItem> + <CarouselItem + basis={{ + view: carousel_view, + classes: `justify-start items-center`, + }} + > + <FarmsAddDetails + bind:val_farmname + bind:val_farmaddress + bind:val_farmcontact + bind:val_farmarea + bind:val_farmarea_unit + {farm_geop_lat} + {farm_geop_lng} + /> + </CarouselItem> + </CarouselContainer> +</LayoutView> +{#if $app_platform?.browser !== `safari`} + <ButtonLayoutBottom> + <ButtonLayoutPair + basis={{ + continue: { + label: `${$ls(`common.continue`)}`, + disabled: disabled_submit, + callback: handle_continue, + }, + back: { + label: `${$ls(`common.back`)}`, + visible: $casl_i > 0, + callback: handle_back, + }, + }} + /> + </ButtonLayoutBottom> +{/if} diff --git a/apps-lib-pwa/src/lib/views/farms/farms.svelte b/apps-lib-pwa/src/lib/views/farms/farms.svelte @@ -0,0 +1,92 @@ +<script lang="ts"> + import ButtonGlyphSimple from "$lib/components/button/button-glyph-simple.svelte"; + import ButtonLabelDashed from "$lib/components/button/button-label-dashed.svelte"; + import FarmsPreviewCard from "$lib/components/farm/farms-preview-card.svelte"; + import LayoutPage from "$lib/components/layout/layout-page.svelte"; + import LayoutView from "$lib/components/layout/layout-view.svelte"; + import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; + import type { IViewBasis } from "$lib/types/views"; + import type { IViewFarmsData } from "$lib/types/views/farms"; + import { + Fade, + get_context, + idb_kv_init_page, + type CallbackRoute, + } from "@radroots/apps-lib"; + import { + handle_err, + type CallbackPromise, + type CallbackPromiseGeneric, + } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + }: { + basis: IViewBasis<{ + data?: IViewFarmsData; + callback_route?: CallbackRoute<string>; + on_handle_farm_add: CallbackPromise; + on_handle_farm_view: CallbackPromiseGeneric<string>; + }>; + } = $props(); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +<LayoutView> + <PageToolbar + basis={{ + header: { + label: `${$ls(`common.farms`)}`, + callback_route: basis.callback_route, + }, + }} + > + {#snippet header_option()} + {#if basis.data?.list.length} + <Fade> + <ButtonGlyphSimple + basis={{ + label: `${$ls(`icu.add_*`, { + value: `${$ls(`common.farm`)}`, + })}`, + callback: async () => { + await basis.on_handle_farm_add(); + }, + }} + /> + </Fade> + {/if} + {/snippet} + </PageToolbar> + <LayoutPage> + {#if basis.data} + {#if basis.data?.list.length} + {#each basis.data?.list || [] as li} + <FarmsPreviewCard + basis={li} + on_handle_farm_view={basis.on_handle_farm_view} + /> + {/each} + {:else} + <ButtonLabelDashed + basis={{ + label: `Add farm`, + callback: async () => { + await basis.on_handle_farm_add(); + }, + }} + /> + {/if} + {/if} + </LayoutPage> +</LayoutView> diff --git a/apps-lib-pwa/src/lib/views/profile/profile-edit.svelte b/apps-lib-pwa/src/lib/views/profile/profile-edit.svelte @@ -0,0 +1,103 @@ +<script lang="ts"> + import { FloatPage, LayoutPage, LayoutView, NavigationTabs } from "$lib"; + import ButtonRoundNav from "$lib/components/button/button-round-nav.svelte"; + import type { IViewBasis } from "$lib/types/views"; + import type { + IViewProfileEditData, + ViewProfileEditFieldKey, + } from "$lib/types/views/profile"; + import { + type ElementCallbackValue, + Flex, + InputExt, + fmt_id, + get_context, + idb_kv_init_page, + } from "@radroots/apps-lib"; + import { type CallbackPromiseGeneric, handle_err } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + val_field = $bindable(``), + }: { + basis: IViewBasis<{ + data?: IViewProfileEditData; + on_handle_back: CallbackPromiseGeneric<{ + field: ViewProfileEditFieldKey; + public_key: string; + }>; + on_handle_input: ElementCallbackValue; + }>; + val_field: string; + } = $props(); + + const param: Record<ViewProfileEditFieldKey, { placeholder: string }> = { + name: { + placeholder: `${$ls(`icu.enter_*`, { value: `profile username` })}`, // @todo + }, + display_name: { + placeholder: `${$ls(`icu.enter_*`, { + value: `profile display name`, + })}`, // @todo + }, + about: { + placeholder: `${$ls(`icu.enter_*`, { value: `profile bio` })}`, // @todo + }, + }; + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + const input_placeholder = $derived( + basis.data?.field ? param[basis.data.field]?.placeholder : ``, + ); +</script> + +{#if basis.data} + {@const { data: basis_data } = basis} + <LayoutView> + <LayoutPage> + <div class={`flex flex-row h-20 w-full justify-start items-center`}> + <Flex /> + </div> + {#if basis.data.field} + <InputExt + bind:value={val_field} + basis={{ + id: fmt_id(`field`), + sync: true, + classes: `pl-6 h-entry_line text-ly1-gl bg-ly1 rounded-2xl`, + placeholder: input_placeholder, + callback: basis.on_handle_input, + }} + /> + {/if} + </LayoutPage> + </LayoutView> + <FloatPage + basis={{ + posx: `left`, + }} + > + <ButtonRoundNav + basis={{ + glyph: `arrow-left`, + callback: async () => { + await basis.on_handle_back({ + field: basis_data.field, + public_key: basis_data.public_key, + }); + }, + }} + /> + </FloatPage> + <NavigationTabs /> +{/if} diff --git a/apps-lib-pwa/src/lib/views/profile/profile.svelte b/apps-lib-pwa/src/lib/views/profile/profile.svelte @@ -0,0 +1,355 @@ +<script lang="ts"> + import { NavigationTabs, SelectMenu } from "$lib"; + import ButtonRoundNav from "$lib/components/button/button-round-nav.svelte"; + import FloatPage from "$lib/components/lib/float-page.svelte"; + import ImageUploadPhotoAdd from "$lib/components/media/image-upload-photo-add.svelte"; + import type { IViewBasis } from "$lib/types/views"; + import type { + IViewProfileData, + ViewProfileEditFieldKey, + } from "$lib/types/views/profile"; + import { + get_context, + Glyph, + idb_kv_init_page, + ImagePath, + symbols, + type IViewOnDestroy, + } from "@radroots/apps-lib"; + import { + handle_err, + type CallbackPromise, + type CallbackPromiseGeneric, + } from "@radroots/utils"; + import { onDestroy, onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + photo_path = $bindable(``), + }: { + basis: IViewBasis<{ + data?: IViewProfileData; + loading_photo_upload: boolean; + loading_photo_upload_open: boolean; + on_handle_back: CallbackPromiseGeneric<{ + is_photo_existing: boolean; + }>; + on_handle_photo_options: CallbackPromise; + on_handle_edit_profile_field: CallbackPromiseGeneric<{ + field: ViewProfileEditFieldKey; + }>; + }> & + IViewOnDestroy<{ public_key: string }>; + photo_path: string; + } = $props(); + + type ViewDisplay = `photos` | `following` | `followers`; + let view_display: ViewDisplay = $state(`photos`); + + let val_sel_options_button = $state(``); + + $effect(() => { + console.log(JSON.stringify(basis.data, null, 4), `data`); + }); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + onDestroy(async () => { + try { + if (basis.data?.profile.public_key) + await basis.on_destroy({ + public_key: basis.data.profile.public_key, + }); + } catch (e) { + handle_err(e, `on_destroy`); + } + }); + + const photo_overlay_visible = $derived( + !!(basis.data?.profile.picture || photo_path), + ); + + const classes_photo_overlay_glyph = $derived( + photo_overlay_visible ? `text-white` : `text-ly0-gl`, + ); + + const classes_photo_overlay_glyph_opt = $derived( + photo_overlay_visible ? `text-gray-300` : `text-ly0-gl`, + ); + + const classes_photo_overlay_glyph_opt_selected = $derived( + photo_overlay_visible ? `text-white` : `text-ly1-gl_d`, + ); +</script> + +{#if basis.data} + <div + class={`relative flex flex-col min-h-[525px] h-[525px] w-full justify-center items-center bg-ly2 fade-in`} + > + <FloatPage + basis={{ + posx: `left`, + }} + > + <ButtonRoundNav + basis={{ + glyph: `arrow-left`, + loading: basis.loading_photo_upload, + callback: async () => { + await basis.on_handle_back({ + is_photo_existing: photo_overlay_visible, + }); + }, + }} + /> + </FloatPage> + {#if photo_path} + <button + class={`absolute top-12 flex flex-row h-7 px-6 justify-center items-center bg-ly1 active:bg-ly1-a rounded-full`} + onclick={async () => {}} + > + <p class={`font-sans font-[400] text-sm text-ly0-gl`}> + {`Post Photo`} + </p> + </button> + {/if} + <FloatPage + basis={{ + posx: `right`, + }} + > + <SelectMenu + bind:value={val_sel_options_button} + basis={{ + layer: 0, + options: [ + { + entries: [ + { + value: `*add-new`, + label: `Add new photo`, + }, + ], + }, + ], + }} + > + <ButtonRoundNav + basis={{ + glyph: `images-square`, + callback: basis.on_handle_photo_options, + }} + /> + </SelectMenu> + </FloatPage> + {#if basis.data.profile.picture || photo_path} + {@const img_path = photo_path || basis.data.profile.picture || ``} + <ImagePath basis={{ path: img_path }} /> + {:else} + <div + class={`flex flex-row justify-start items-center -translate-y-8`} + > + <ImageUploadPhotoAdd + bind:photo_path + basis={{ + loading: basis.loading_photo_upload_open, + }} + /> + </div> + {/if} + <div + class={`absolute bottom-0 left-0 flex flex-col h-[calc(100%-100%/1.618)] w-full px-6 gap-2 justify-end items-center`} + > + <div + class={`flex flex-col w-full gap-[2px] justify-center items-center`} + > + <div + class={`flex flex-row h-10 w-full justify-start items-center`} + > + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `display_name`, + }); + }} + > + <p + class={`font-sansd font-[600] text-[2rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.name + ? `` + : `capitalize opacity-active` + } el-re`} + > + {#if basis.data?.profile.display_name} + {`${basis.data?.profile.display_name}`} + {:else if basis.data?.profile.name} + {`${ + basis.data?.profile.display_name || + basis.data?.profile.name || + `` + }`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.profile_name`)}`, + })}`}`} + {/if} + </p> + </button> + </div> + <div + class={`flex flex-row w-full gap-[6px] justify-start items-center`} + > + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `name`, + }); + }} + > + <p + class={`font-sansd font-[600] text-[1.1rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.name + ? `` + : `capitalize opacity-active` + } el-re`} + > + {#if basis.data?.profile.name} + {`@${basis.data.profile.name}`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.username`)}`, + })}`}`} + {/if} + </p> + </button> + <p + class={`font-sans font-[400] ${classes_photo_overlay_glyph}`} + > + {symbols.bullet} + </p> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + alert(`@todo!`); + }} + > + <Glyph + basis={{ + classes: `${classes_photo_overlay_glyph}`, + dim: `xs`, + + key: `link-simple`, + }} + /> + </button> + </div> + <div class={`flex flex-row w-full justify-start items-center`}> + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `about`, + }); + }} + > + <p + class={`font-sansd font-[400] text-[1.1rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.about + ? `` + : `capitalize opacity-active` + }`} + > + {#if basis.data?.profile.about} + {`${basis.data.profile.about}`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.bio`)}`, + })}`}`} + {/if} + </p> + </button> + </div> + </div> + <div + class={`flex flex-row w-full pt-2 pb-6 gap-2 justify-start items-center`} + > + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `photos`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `photos` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.photos`)}`} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `following`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `following` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.following`)}`} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `followers`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `followers` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.followers`)}`} + </p> + </button> + </div> + </div> + </div> + <div + class={`flex flex-col w-full min-h-[500px] justify-start items-center`} + > + {#if view_display === `photos`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {:else if view_display === `following`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {:else if view_display === `followers`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {/if} + </div> + <NavigationTabs /> +{/if} diff --git a/apps-lib-pwa/src/lib/views/root/home.svelte b/apps-lib-pwa/src/lib/views/root/home.svelte @@ -0,0 +1,56 @@ +<script lang="ts"> + import ButtonSimple from "$lib/components/button/button-simple.svelte"; + import LayoutPage from "$lib/components/layout/layout-page.svelte"; + import LayoutView from "$lib/components/layout/layout-view.svelte"; + import NavigationTabs from "$lib/components/navigation/navigation-tabs.svelte"; + import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; + import type { IViewBasis, IViewHomeData } from "$lib/types/views"; + import { get_context, idb_kv_init_page } from "@radroots/apps-lib"; + + import { handle_err, type CallbackPromise } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + }: { + basis: IViewBasis<{ + data?: IViewHomeData; + on_handle_farms: CallbackPromise; + on_handle_products: CallbackPromise; + }>; + } = $props(); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +{#if basis.data} + {@const { data: basis_data } = basis} + <LayoutView> + <PageToolbar + basis={{ + header: { + label: `${$ls(`common.general`)}`, + }, + }} + /> + <LayoutPage> + <ButtonSimple + basis={{ + label: `${$ls(`common.farms`)}`, + callback: async () => { + await basis.on_handle_farms(); + }, + }} + /> + </LayoutPage> + </LayoutView> + <NavigationTabs /> +{/if} diff --git a/apps-lib-pwa/src/lib/views/root/settings.svelte b/apps-lib-pwa/src/lib/views/root/settings.svelte @@ -0,0 +1,116 @@ +<script lang="ts"> + import { LayoutView, PageToolbar } from "$lib"; + import LayoutTrellis from "$lib/components/layout/layout-trellis.svelte"; + import Trellis from "$lib/components/trellis/trellis.svelte"; + import type { ITrellisExtList } from "$lib/types/components/trellis"; + import type { IViewBasis } from "$lib/types/views"; + import { + get_context, + idb_kv_init_page, + symbols, + theme_mode, + } from "@radroots/apps-lib"; + import { handle_err } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls, lc_color_mode } = get_context(`lib`); + + let { + basis, + }: { + basis: IViewBasis<{ + trellis_ext?: ITrellisExtList[]; + }>; + } = $props(); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +<LayoutView> + <PageToolbar + basis={{ + header: { + label: `${$ls(`common.settings`)}`, + }, + }} + /> + <LayoutTrellis> + <Trellis + basis={{ + layer: 1, + title: { + value: `Appearance`, + }, + list: [ + { + hide_active: true, + select: { + label: { + left: [ + { + value: `${$ls(`common.color_mode`)}`, + classes: `capitalize`, + }, + ], + }, + display: { + label: { + value: `${$theme_mode}`, + classes: `capitalize`, + }, + }, + el: { + value: $theme_mode, + options: [ + { + entries: [ + { + value: symbols.bullet, + label: `${$ls(`icu.choose_*`, { + value: `${$ls( + `common.color_mode`, + )}`.toLowerCase(), + })}`, + disabled: true, + }, + { + value: `light`, + label: `${$ls(`common.light`)}`, + }, + { + value: `dark`, + label: `${$ls(`common.dark`)}`, + }, + ], + }, + ], + callback: lc_color_mode, + }, + end: { + glyph: { + key: `caret-right`, + }, + }, + }, + }, + ], + }} + /> + {#if basis.trellis_ext?.length} + {#each basis.trellis_ext as trellis_ext} + <Trellis + basis={{ + layer: 1, + list: trellis_ext.list, + }} + /> + {/each} + {/if} + </LayoutTrellis> +</LayoutView> diff --git a/apps-lib-pwa/svelte.config.js b/apps-lib-pwa/svelte.config.js @@ -0,0 +1,11 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + }, +}; + +export default config; diff --git a/apps-lib-pwa/tsconfig.json b/apps-lib-pwa/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + } +} diff --git a/apps-lib-pwa/vite.config.ts b/apps-lib-pwa/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit() as any] +});