Update dependencies

Migrate to Vue 3 + Vite + Pinia

- Vue 2 class components → Vue 3 defineComponent + Composition API
- Vuex + vuex-module-decorators → Pinia
- vue-cli/webpack → Vite 8 with vite-plugin-glsl
- vue-router@3 → vue-router@4, vue-i18n@8 → vue-i18n@9
- require.context → import.meta.glob
- require('./shader.glsl') → static import with vite-plugin-glsl
- vue-color → native <input type="color">
- All TSX components migrated, build passes with 0 TypeScript errors
main
Warinyourself 3 years ago
parent 23472d0279
commit 2099183f29

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

@ -1,5 +0,0 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

@ -1,44 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
camelcase: 'off',
indent: 'off',
'multiline-ternary': 'off',
'space-before-function-paren': [2, 'never'],
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'warn',
'vue/array-bracket-spacing': 'error',
'vue/arrow-spacing': 'error',
'vue/block-spacing': 'error',
'vue/brace-style': 'error',
'vue/camelcase': 'error',
'vue/comma-dangle': 'error',
'vue/component-name-in-template-casing': 'error',
'vue/eqeqeq': 'error',
'vue/key-spacing': 'error',
'vue/match-component-file-name': 'error',
'vue/object-curly-spacing': 'error',
'no-useless-constructor': 'off',
'@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/no-useless-constructor': 'error'
},
parserOptions: {
ecmaVersion: 2020
},
extends: [
'eslint:recommended',
'plugin:vue/recommended',
'@vue/typescript',
'plugin:vue/essential',
'@vue/typescript/recommended'
]
}

39
.gitignore vendored

@ -1,24 +1,39 @@
.DS_Store
node_modules
/dist
*.tar.gz
# local env files
.env
.env.local
.env.*.local
# Log files
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/
# Vite
*.timestamp-*-*.mjs

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

@ -1,674 +0,0 @@
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>.

@ -1,83 +1,42 @@
# [Osmos greeter theme](https://warinyourself.github.io/lightdm-webkit-theme-osmos/)
# osmos-v3
![INTRO](https://user-images.githubusercontent.com/83131232/154862858-3acede10-9987-4d58-bae4-d173e51dc833.gif)
This template should help get you started developing with Vue 3 in Vite.
<br />
## Recommended IDE Setup
## Installation
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
### Arch Linux:
## Recommended Browser Setup
``` sh
yay -S lightdm-webkit-theme-osmos
```
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
### Manual installation:
## Type Support for `.vue` Imports in TS
Set `greeter-session = lightdm-webkit2-greeter` in `/etc/lightdm/lightdm.conf`:
```sh
sudo sed -i 's/^\(#\?greeter\)-session\s*=\s*\(.*\)/greeter-session = lightdm-webkit2-greeter/g' /etc/lightdm/lightdm.conf
```
Then edit `/etc/lightdm/lightdm-webkit2-greeter.conf` to set `webkit_theme=osmos`.
``` sh
sudo sed -i 's/^webkit_theme\s*=\s*\(.*\)/webkit_theme = osmos/g' /etc/lightdm/lightdm-webkit2-greeter.conf
```
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
<br />
## Customize configuration
## Control brightness and battery:
![image](https://user-images.githubusercontent.com/83131232/206694253-865022cd-5406-45f2-9bf1-6307816adcf7.png)
See [Vite Configuration Reference](https://vite.dev/config/).
If you want to control brightness and battery - install [node-greeter](https://github.com/JezerM/nody-greeter#installation).
## Project Setup
Set `greeter-session = nody-greeter` in `/etc/lightdm/lightdm.conf`:
``` sh
sudo sed -i 's/^\(#\?greeter\)-session\s*=\s*\(.*\)/greeter-session = nody-greeter/g' /etc/lightdm/lightdm.conf
```sh
npm install
```
Then edit `/etc/lightdm/web-greeter.yml` to set `theme: osmos`:
### Compile and Hot-Reload for Development
``` sh
sudo sed -i 's/\stheme:\s*\(.*\)/ theme: osmos/g' /etc/lightdm/web-greeter.yml
```
Link webkit theme with nody greeter themes:
```sh
ln -s /usr/share/lightdm-webkit/themes/osmos /usr/share/web-greeter/themes/osmos
```
To control battery install the [acpid](https://archlinux.org/packages/?name=acpid) package and enable/start acpid.service.
``` sh
sudo systemctl enable acpid
sudo systemctl start acpid
npm run dev
```
<br />
## Check the themes:
[![Random](https://user-images.githubusercontent.com/83131232/153943224-2264f687-7c81-4d06-8424-3f31f8aefd66.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&animation-speed=5&symmetry=0.01&thickness=0.1&hue=360&brightness=1&invert=false&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Random)
[![Sphere](https://user-images.githubusercontent.com/83131232/153943231-0523905f-66e7-41a5-980b-1246a3ec2438.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&invert=false&hue=21&brightness=1&animation-speed=7.32&size=1.75&blur=false&no-transition=false&show-framerate=false&only-ui=false&themeName=Sphere)
### Type-Check, Compile and Minify for Production
[![Planet](https://user-images.githubusercontent.com/83131232/153943216-0f5da865-e8e1-4d79-8df3-56db406a5de2.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&animation-speed=5&position=7.02&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Planet)
[![Destruction](https://user-images.githubusercontent.com/83131232/153943194-bd44a36b-7aec-4fa1-974a-93ab39cb5947.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&position=1&perspective=0.02&animation-speed=10&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Destruction)
[![Rings](https://user-images.githubusercontent.com/83131232/153943227-ba025898-4141-4d00-9db0-7f1b951aa05a.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?animation-speed=5&blur=false&hue=0.03&no-transition=false&only-ui=true&pxratio=0.8&show-framerate=false&themeName=Rings&zoom=74.3)
[![Tenderness](https://user-images.githubusercontent.com/83131232/153943232-b6989a78-5871-4844-b2c0-8b1202ac53f2.png)]((https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&animation-speed=10&blur=false&no-transition=false&show-framerate=false&only-ui=false&themeName=Tenderness))
[![Plasma](https://user-images.githubusercontent.com/83131232/153943346-c09fcbaa-0b57-43d6-98fc-5b43d0f65f25.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&hue=81&animation-speed=10&blur=false&no-transition=false&show-framerate=false&only-ui=false&themeName=Plasma)
[![Flow](https://user-images.githubusercontent.com/83131232/153943203-cfa24684-c94f-4763-945d-3875cd2f0a69.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&animation-speed=10&size=1&blur=false&no-transition=false&show-framerate=false&only-ui=false&themeName=Flow)
[![Infinity](https://user-images.githubusercontent.com/83131232/153943210-e4cc3bc3-3ade-4323-a216-acf787b61d76.png)](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?palette=3&size=11&amount=50&animation-speed=20&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Infinity)
</br>
## ROADMAP
- Integrates with nody-greeter [x]
- Add possibility to guest login []
- Add possibility to pick the wallpaper []
- Add zoom slider (for hight DPI screens) [x]
- Add CHANGELOG []
```sh
npm run build
```

@ -1,65 +0,0 @@
const child_process = require('child_process')
// import * as child_process from 'child_process'
// import { logger } from '../logger'
const logger = {
error: console.error,
debug: console.log
}
type Callback = (data: string) => void;
class ACPIController {
public constructor() {
if (this.checkAcpi()) this.listen()
else logger.error('ACPI: acpi_listen does not exists')
}
protected tries = 0;
protected callbacks: Callback[] = [];
public connect(cb: Callback): void {
this.callbacks.push(cb)
}
public disconnect(cb: Callback): void {
const ind = this.callbacks.findIndex((c) => {
return c === cb
})
if (ind == -1) return
this.callbacks.splice(ind, 1)
}
private checkAcpi(): boolean {
const res = child_process.spawnSync('which', ['acpi_listen'], {
encoding: 'utf-8'
})
if (res.status == 0) return true
else return false
}
private listen(): void {
const acpi = child_process.spawn('acpi_listen')
acpi.on('error', (err) => {
logger.error('ACPI: ' + err.message)
})
acpi.on('close', () => {
if (this.tries < 5) {
this.tries++
logger.debug('Restarting acpi_listen')
return this.listen()
}
})
acpi.stdout.addListener('data', (d: Buffer) => {
const data = d.toString().trim()
this.callbacks.forEach((cb) => {
if (cb !== undefined) cb(data)
})
})
}
}
const ACPI = new ACPIController()
export { ACPI }

@ -1,7 +0,0 @@
module.exports = {
presets: [
'@babel/preset-env',
'@vue/cli-plugin-babel/preset'
],
plugins: ['transform-vue-jsx']
}

@ -1,9 +0,0 @@
#!/bin/bash
echo Building LightDM Webkit theme Osmos v$(cat ./version)...
echo
rm lightdm-webkit-theme-osmos*.tar.gz
rm -r dist/
yarn build
pushd dist && tar zcvf ../lightdm-webkit-theme-osmos-$(cat ../version).tar.gz ./* && popd

14
env.d.ts vendored

@ -0,0 +1,14 @@
/// <reference types="vite/client" />
declare module '*.glsl' {
const content: string
export default content
}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AppIcon: typeof import('./src/components/app/AppIcon.vue')['default']
}
}
export {}

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class="lightdm">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Osmos</title>
<link rel="icon" href="/favicon.ico">
<style>
html { background-color: black !important; }
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

@ -1,5 +0,0 @@
# npm run build
sudo chmod 777 -R dist
rm -Rf /usr/share/lightdm-webkit/themes/osmos/*
cp -r ./dist/* /usr/share/lightdm-webkit/themes/osmos
# ls /usr/share/web-greeter/themes/

3964
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,92 +1,44 @@
{
"name": "osmos",
"version": "2.0.3",
"description": "Lightdm greeter theme based on GLSL.",
"private": false,
"name": "osmos-v3",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:github": "VUE_APP_VIEW=github vue-cli-service build",
"lint": "vue-cli-service lint",
"dev": "vue-cli-service serve --open",
"dev:github": "VUE_APP_VIEW=github vue-cli-service serve --open"
},
"author": {
"name": "Warinyourself",
"email": "warinyourself@gmail.com"
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"core-js": "^3.6.5",
"css-doodle": "^0.12.0",
"lodash": "^4.17.21",
"css-doodle": "^0.51.0",
"lodash": "^4.18.1",
"mousetrap": "^1.6.5",
"nody-greeter-types": "^1.0.7",
"nouislider": "^15.0.0",
"nody-greeter-types": "^1.1.0",
"nouislider": "^15.8.1",
"parallax-js": "^3.1.0",
"vue": "2.7.14",
"vue-color": "^2.8.1",
"vue-i18n": "^8.22.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
"pinia": "^3.0.4",
"vue": "^3.5.32",
"vue-i18n": "^11.4.5",
"vue-router": "^5.0.4"
},
"license": "GPL-3.0",
"devDependencies": {
"@babel/helper-module-imports": "^7.16.0",
"@types/lodash": "^4.14.178",
"@types/mousetrap": "^1.6.9",
"@types/node": "14.10.0",
"@types/vue-color": "^2.4.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.2.45",
"@vue/eslint-config-airbnb": "7.0.0",
"@vue/eslint-config-prettier": "7.0.0",
"@vue/eslint-config-standard": "8.0.1",
"@vue/eslint-config-typescript": "11.0.2",
"babel-eslint": "^10.0.3",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-vue-jsx": "3.3.0",
"babel-preset-env": "^1.7.0",
"eslint": "^7.1.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^9.7.0",
"husky": "^4.2.5",
"lint-staged": "^10.2.2",
"prettier": "2.7.1",
"stylus": "0.59.0",
"stylus-loader": "3.0.2",
"typescript": "4.8.4",
"vue-class-component": "7.2.6",
"vue-property-decorator": "9.1.2",
"vue-template-compiler": "2.7.14",
"vuex-module-decorators": "2.0.0",
"webpack-glsl-loader": "1.0.1"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
"@tsconfig/node24": "^24.0.4",
"@types/lodash": "^4.17.24",
"@types/mousetrap": "^1.6.15",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"@vitejs/plugin-vue-jsx": "^5.1.5",
"@vue/tsconfig": "^0.9.1",
"npm-run-all2": "^8.0.4",
"stylus": "^0.64.0",
"typescript": "~6.0.0",
"vite": "^8.0.8",
"vite-plugin-glsl": "^1.6.0",
"vite-plugin-vue-devtools": "^8.1.1",
"vue-tsc": "^3.2.6"
},
"lint-staged": {
"*.{js,vue}": [
"eslint --quiet"
],
"*.{js,jsx,vue,ts,tsx}": [
"vue-cli-service lint",
"git add"
]
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html class="lightdm">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title> Osmos </title>
<link rel="icon" href="/favicon.ico">
<style>
html {
background-color: black !important;
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<!-- built files will be auto injected -->
</body>
</html>

@ -1,75 +1,64 @@
import { Component, Vue, Watch } from 'vue-property-decorator'
import { defineComponent, watch } from 'vue'
import { debounce } from 'lodash'
import Mousetrap from 'mousetrap'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { Debounce, focusInputPassword, setCSSVariable } from '@/utils/helper'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import { focusInputPassword, setCSSVariable } from '@/utils/helper'
import { hotkeys } from '@/utils/hotkeys'
import { initTimer } from '@/utils/time'
@Component
export default class MainApp extends Vue {
get bodyClass() {
return AppModule.bodyClass
}
export default defineComponent({
name: 'MainApp',
get getMainSettings() {
return AppModule.getMainSettings
}
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
@Debounce(100)
@Watch('getMainSettings', { deep: true })
handleSettingsThemes() {
AppModule.syncSettingsWithCache()
}
const initZoom = () => {
setCSSVariable('--zoom', appStore.zoom + '' || '1')
}
/**
* INFO: For High-DPI screens settings
*/
initZoom() {
setCSSVariable('--zoom', AppModule.zoom + '' || '1')
}
const initKeybinds = () => {
hotkeys.forEach(({ keys, callback }) => Mousetrap.bind(keys.join('+'), callback))
created() {
AppModule.setUpSettings()
this.initKeybinds()
initTimer()
this.initZoom()
}
Mousetrap.bind('escape', () => {
const isFocusPassword = document.querySelector('#password:focus') as HTMLInputElement
if (pageStore.dialog) {
pageStore.closeDialog()
} else if (pageStore.menu.view) {
pageStore.assignMenu({ view: false })
} else if (isFocusPassword) {
isFocusPassword.blur()
} else if (pageStore.activeBlock) {
pageStore.closeBlock()
}
})
initKeybinds() {
hotkeys.forEach(({ keys, callback }) => Mousetrap.bind(keys.join('+'), callback))
Mousetrap.bind('enter', () => {
const isFocusPassword = document.querySelector('#password:focus')
if (isFocusPassword) {
appStore.login()
} else {
focusInputPassword()
}
})
}
Mousetrap.bind('escape', () => {
const isFocusPassword = document.querySelector('#password:focus') as HTMLInputElement
const isShowModal = !!PageModule.dialog
if (isShowModal) {
PageModule.closeDialog()
} else if (PageModule.menu.view) {
PageModule.ASSIGN_MENU({ view: false })
} else if (isFocusPassword) {
isFocusPassword.blur()
} else if (PageModule.activeBlock) {
PageModule.closeBlock()
}
})
Mousetrap.bind('enter', () => {
const isFocusPassword = document.querySelector('#password:focus')
appStore.setUpSettings()
initKeybinds()
initTimer()
initZoom()
if (isFocusPassword) {
AppModule.login()
} else {
focusInputPassword()
}
})
}
watch(
() => appStore.getMainSettings,
debounce(() => appStore.syncSettingsWithCache(), 100),
{ deep: true }
)
render() {
return (
<div id="app" class={ this.bodyClass }>
return () => (
<div id="app" class={appStore.bodyClass}>
<router-view />
</div>
)
}
}
})

@ -0,0 +1,85 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<RouterView />
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

@ -0,0 +1,95 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
+
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener"
>Vue - Official</a
>. If you need to test your components and web pages, check out
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
and
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
/
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
<br />
More instructions are available in
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
(our official Discord server), or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also follow the official
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
Bluesky account or the
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
X account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

@ -0,0 +1,87 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

@ -1,33 +1,23 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from './AppIcon.vue'
import { defineComponent } from 'vue'
import { useAppStore } from '@/store/app'
import timer from '@/utils/time'
import BatteryIcon from '../base/BatteryIcon'
import BrightIcon from '../base/BrightIcon'
import BatteryIcon from '@/components/base/BatteryIcon'
import BrightIcon from '@/components/base/BrightIcon'
@Component({
components: { AppIcon, BatteryIcon, BrightIcon }
})
export default class AppBar extends Vue {
get showBattery() {
return AppModule.battery
}
get showBright() {
return !!AppModule.brightness
}
export default defineComponent({
name: 'AppBar',
get currentTime() {
return timer.longTime
}
setup() {
const appStore = useAppStore()
render() {
return <div class="app-bar active-interface">
<div class="app-bar__time"> { timer.longTime } </div>
<div class="app-bar__info">
{ this.showBattery && <BatteryIcon /> }
{ this.showBright && <BrightIcon /> }
return () => (
<div class="app-bar active-interface">
<div class="app-bar__time">{timer.longTime}</div>
<div class="app-bar__info">
{appStore.battery && <BatteryIcon />}
{!!appStore.brightness && <BrightIcon />}
</div>
</div>
</div>
)
}
}
})

@ -1,109 +1,64 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { defineComponent, computed, h } from 'vue'
import AppIcon from '@/components/app/AppIcon.vue'
import { Route } from 'vue-router'
import { CreateElement, VNode, VNodeData } from 'vue'
const prefix = 'app-button'
interface AppButtonPropsInterface {
label: string;
icon?: string;
fab?: boolean;
tag?: string;
link?: boolean;
nuxt?: boolean;
block?: boolean;
target?: string;
disabled?: boolean;
loading?: boolean;
type?: string;
to?: Route | string;
href?: Route | string;
}
@Component({
components: { AppIcon }
})
export default class AppButton extends Vue implements AppButtonPropsInterface {
@Prop({ type: String }) label!: string
@Prop({ type: String }) icon!: string
@Prop({ type: String }) type!: string
@Prop({ type: Boolean }) fab!: boolean
@Prop({ type: Boolean }) link!: boolean
@Prop({ type: Boolean }) block!: boolean
@Prop({ type: String }) target!: string
@Prop({ type: Boolean }) loading!: boolean
@Prop({ type: Boolean }) nuxt!: boolean
@Prop({ type: String, default: 'transparent' }) color!: string
@Prop({ type: Boolean }) disabled!: boolean
@Prop({ type: String, default: 'button' }) tag!: string
@Prop({ type: [Object, String] }) to!: Route | string
@Prop({ type: [Object, String] }) href!: Route | string
get classes() {
const classes: Record<string, boolean> = { [prefix]: true, [`${prefix}--${this.color}`]: true }
const propertyList: Array<keyof AppButtonPropsInterface> = ['fab', 'block']
propertyList.forEach(property => {
classes[`${prefix}--${property}`] = !!this[property]
})
return classes
}
get isLink() {
return this.to || this.href || this.link
}
generateRouteLink() {
let tag
const data: VNodeData = {
attrs: {
tabindex: 'tabindex' in this.$attrs ? this.$attrs.tabindex : undefined
},
class: this.classes,
[this.to ? 'nativeOn' : 'on']: {
...this.$listeners
},
ref: 'link'
}
if (this.to) {
tag = this.nuxt ? 'nuxt-link' : 'router-link'
} else {
tag = (this.href && 'a') || this.tag || 'div'
if (tag === 'a' && this.href) data.attrs!.href = this.href
export default defineComponent({
name: 'AppButton',
emits: ['click'],
props: {
label: String,
icon: String,
type: String,
fab: Boolean,
link: Boolean,
block: Boolean,
target: String,
loading: Boolean,
nuxt: Boolean,
color: { type: String, default: 'transparent' },
disabled: Boolean,
tag: { type: String, default: 'button' },
to: [Object, String],
href: [Object, String]
},
setup(props, { slots, attrs }) {
const classes = computed(() => ({
[prefix]: true,
[`${prefix}--${props.color}`]: true,
[`${prefix}--fab`]: !!props.fab,
[`${prefix}--block`]: !!props.block
}))
return () => {
let tag = props.tag || 'button'
if (props.to) tag = 'router-link'
else if (props.href) tag = 'a'
const btnProps: Record<string, any> = {
class: classes.value,
...attrs
}
if (tag === 'button') {
btnProps.type = props.type
btnProps.disabled = props.disabled
}
if (tag === 'router-link') btnProps.to = props.to
if (tag === 'a') btnProps.href = props.href
if (props.target) btnProps.target = props.target
const content = <span class={`${prefix}__content`}>{slots.default?.()}</span>
const loader = props.loading && (
<span class={`${prefix}__loader`}>
{slots.loader?.() || <AppIcon name="loader" />}
</span>
)
return h(tag, btnProps, [content, loader])
}
if (this.target) data.attrs!.target = this.target
return { tag, data }
}
buildContent() {
return <span class={ `${prefix}__content` }> { this.$slots.default } </span>
}
buildLoader() {
return <span class={ `${prefix}__loader` }>
{ this.$slots.loader || <AppIcon name='loader' /> }
</span>
}
render(h: CreateElement): VNode {
const children = [
this.buildContent(),
this.loading && this.buildLoader()
]
const { tag, data } = this.generateRouteLink()
if (tag === 'button') {
data.attrs!.type = this.type
data.attrs!.disabled = this.disabled
}
return h(tag, data, children)
}
}
})

@ -1,45 +1,40 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent, computed } from 'vue'
@Component({
components: { AppIcon }
})
export default class AppCheckbox extends Vue {
@Prop({ default: false }) value!: boolean
@Prop({ default: '' }) label!: string
export default defineComponent({
name: 'AppCheckbox',
get classes(): Record<string, boolean> {
return {
checkbox: true,
'checkbox--active': this.value
}
}
props: {
modelValue: { type: Boolean, default: false },
label: { type: String, default: '' },
inline: { type: Boolean, default: false }
},
get idCheckbox(): string {
return `input-${(this as any)._uid}`
}
emits: ['update:modelValue'],
changeState(): void {
this.$emit('input', !this.value)
}
setup(props, { emit }) {
const classes = computed(() => ({
checkbox: true,
'checkbox--active': props.modelValue
}))
render() {
return <label class={ this.classes }>
<div class="checkbox-control">
<div class="checkbox-control-box">
<AppIcon name="checkbox"></AppIcon>
const changeState = () => emit('update:modelValue', !props.modelValue)
return () => (
<label class={classes.value}>
<div class="checkbox-control">
<div class="checkbox-control-box">
<AppIcon name="checkbox" />
</div>
<input
type="checkbox"
checked={props.modelValue}
aria-checked={props.modelValue}
onInput={changeState}
/>
</div>
<input
type="checkbox"
value={ this.value }
aria-checked={ this.value }
id={ this.idCheckbox }
onInput={ this.changeState }
/>
</div>
<p class="input-label">
{ this.label }
</p>
</label>
<p class="input-label">{props.label}</p>
</label>
)
}
}
})

@ -1,33 +1,28 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { Chrome } from 'vue-color'
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
@Component({
components: { Chrome }
})
export default class AppColorSelector extends Vue {
@Prop({ type: String, default: '#fff' }) value!: string
@Prop({ default: '' }) label!: string
export default defineComponent({
name: 'AppColorSelector',
changeState(event: Event) {
event.stopPropagation()
props: {
modelValue: { type: String, default: '#fff' },
label: { type: String, default: '' }
},
this.$emit('input', !this.value)
}
emits: ['update:modelValue'],
render() {
return <div class="color-selector">
<p> { this.$t(this.label) } </p>
<Chrome
{ ...{
props: {
disableAlpha: true,
disableFields: true,
value: this.value
},
on: this.$listeners
}}
>
</Chrome>
</div>
setup(props, { emit }) {
const { t } = useI18n()
return () => (
<div class="color-selector">
<p>{t(props.label)}</p>
<input
type="color"
value={props.modelValue}
onInput={(e) => emit('update:modelValue', (e.target as HTMLInputElement).value)}
/>
</div>
)
}
}
})

@ -1,45 +1,32 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import AppButton from './AppButton'
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePageStore } from '@/store/page'
import { stopPropagation } from '@/utils/helper'
import AppButton from './AppButton'
@Component({
components: { AppIcon }
})
export default class AppDialog extends Vue {
@Prop({ type: Boolean, default: false }) value!: boolean
@Prop({ default: '' }) label!: string
get dialog() {
return PageModule.dialog
}
get showDialog() {
return !!this.dialog
}
generateDialog() {
return <div class="dialog-overlay active-block" onClick={ PageModule.closeDialog }>
<div class="dialog-body" onClick={ stopPropagation }>
<h5 class="dialog-title"> { this.$t(this.dialog?.title + '') } </h5>
<p class="dialog-text"> { this.$t(this.dialog?.text + '') } </p>
{ this.generateButtons() }
</div>
</div>
}
generateButtons() {
const buttons = this.dialog?.actions.map(({ title, callback }) => {
return <AppButton onClick={ callback }> { this.$t(title + '') } </AppButton>
})
export default defineComponent({
name: 'AppDialog',
return <div class="dialog-buttons"> { buttons } </div>
}
setup() {
const pageStore = usePageStore()
const { t } = useI18n()
render() {
return <transition name="fade">
{ this.showDialog && this.generateDialog() }
</transition>
return () => (
<transition name="fade">
{pageStore.dialog && (
<div class="dialog-overlay active-block" onClick={pageStore.closeDialog}>
<div class="dialog-body" onClick={stopPropagation}>
<h5 class="dialog-title">{t(pageStore.dialog.title)}</h5>
<p class="dialog-text">{t(pageStore.dialog.text)}</p>
<div class="dialog-buttons">
{pageStore.dialog.actions.map(({ title, callback }) => (
<AppButton onClick={callback}>{t(title)}</AppButton>
))}
</div>
</div>
</div>
)}
</transition>
)
}
}
})

@ -40,7 +40,7 @@
style="isolation:isolate"
viewBox="1303.7 727.244 33.56 33.464"
fill="currentColor"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M 1320.75 751.23 C 1318.75 751.23 1316.83 750.43 1315.42 749.02 C 1314 747.6 1313.21 745.68 1313.21 743.68 C 1313.21 741.68 1314 739.76 1315.42 738.35 C 1316.83 736.93 1318.75 736.14 1320.75 736.14 C 1322.75 736.14 1324.68 736.93 1326.09 738.35 C 1327.5 739.76 1328.3 741.68 1328.3 743.68 C 1328.3 745.68 1327.5 747.6 1326.09 749.02 C 1324.68 750.43 1322.75 751.23 1320.75 751.23 L 1320.75 751.23 Z M 1328.594 757.292 L 1327.093 756.466 C 1325.288 755.474 1323.006 756.128 1322.002 757.926 L 1321.166 759.422 C 1320.161 761.22 1318.74 761.121 1317.996 759.2 L 1317.376 757.603 C 1316.631 755.682 1314.463 754.717 1312.538 755.449 L 1310.936 756.057 C 1309.01 756.789 1307.986 755.8 1308.65 753.85 L 1309.202 752.228 C 1309.866 750.278 1308.826 748.145 1306.88 747.467 L 1305.262 746.904 C 1303.317 746.227 1303.168 744.81 1304.93 743.743 L 1306.396 742.856 C 1308.158 741.789 1308.732 739.486 1307.677 737.717 L 1306.8 736.245 C 1305.745 734.476 1306.541 733.295 1308.577 733.611 L 1310.27 733.873 C 1312.306 734.188 1314.226 732.793 1314.555 730.759 L 1314.829 729.068 C 1315.158 727.035 1316.527 726.642 1317.884 728.192 L 1319.012 729.481 C 1320.369 731.032 1322.736 731.197 1324.296 729.851 L 1325.593 728.731 C 1327.152 727.385 1328.453 727.964 1328.496 730.024 L 1328.531 731.737 C 1328.574 733.796 1330.282 735.445 1332.341 735.416 L 1334.055 735.392 C 1336.114 735.363 1336.739 736.643 1335.448 738.248 L 1334.374 739.584 C 1333.083 741.189 1333.331 743.549 1334.928 744.851 L 1336.255 745.934 C 1337.852 747.236 1337.507 748.617 1335.487 749.017 L 1333.806 749.35 C 1331.785 749.75 1330.458 751.718 1330.844 753.741 L 1331.165 755.424 C 1331.551 757.448 1330.399 758.285 1328.594 757.292 L 1328.594 757.292 Z "
@ -62,41 +62,16 @@
/>
</svg>
<svg
v-else-if="name === 'brightness'"
width="128"
height="128"
viewBox="0 0 128 128"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M64 103.226C60.5795 103.226 57.8065 105.999 57.8065 109.419V121.806C57.8065 125.227 60.5795 128 64 128C67.4205 128 70.1935 125.227 70.1935 121.807V109.42C70.1935 105.999 67.4205 103.226 64 103.226Z"
/>
<path
d="M64 0C60.5795 0 57.8065 2.773 57.8065 6.1935V18.5805C57.8065 22.001 60.5795 24.774 64 24.774C67.4205 24.774 70.1935 22.001 70.1935 18.5805V6.1935C70.1935 2.773 67.4205 0 64 0Z"
/>
<path
d="M64.0007 37.1613C49.2027 37.1618 37.163 49.2015 37.163 64C37.163 78.7985 49.202 90.838 64 90.8388C78.7992 90.8388 90.8402 78.799 90.8402 64C90.8402 49.201 78.8 37.1613 64.0007 37.1613Z"
/>
<path
d="M128 64C128 60.5795 125.227 57.8065 121.807 57.8065H109.42C105.999 57.8065 103.226 60.5795 103.226 64C103.226 67.4205 105.999 70.1935 109.42 70.1935H121.807C125.227 70.1935 128 67.4205 128 64Z"
/>
<path
d="M0 64C0 67.4205 2.773 70.1935 6.1935 70.1935H18.5805C22.001 70.1935 24.774 67.4205 24.774 64C24.774 60.5795 22.001 57.8065 18.5805 57.8065H6.1935C2.773 57.8065 0 60.5795 0 64Z"
/>
<path
d="M96.116 38.077C97.7007 38.077 99.2862 37.472 100.495 36.2627L109.255 27.5037C111.673 25.085 111.673 21.1635 109.255 18.7447C106.836 16.326 102.915 16.326 100.495 18.7447L91.7363 27.5037C89.3175 29.9225 89.3175 33.844 91.7363 36.2627C92.9463 37.4722 94.531 38.077 96.116 38.077Z"
/>
<path
d="M23.1248 111.069C24.71 111.069 26.295 110.464 27.504 109.255L36.2632 100.496C38.682 98.0775 38.682 94.1555 36.2632 91.7367C33.8447 89.318 29.923 89.318 27.5042 91.7367L18.745 100.496C16.3263 102.914 16.3263 106.836 18.745 109.255C19.9543 110.464 21.5395 111.069 23.1248 111.069Z"
/>
<path
d="M100.496 91.7367C98.0775 89.318 94.1555 89.318 91.7367 91.7367C89.318 94.155 89.318 98.077 91.7367 100.496L100.496 109.255C101.706 110.464 103.291 111.069 104.875 111.069C106.461 111.069 108.046 110.464 109.255 109.255C111.673 106.837 111.673 102.915 109.255 100.496L100.496 91.7367Z"
/>
<path
d="M27.5042 36.2627C28.7137 37.4722 30.2988 38.077 31.884 38.077C33.4693 38.077 35.0542 37.472 36.2632 36.2627C38.682 33.844 38.682 29.9225 36.2632 27.5037L27.504 18.7445C25.0857 16.326 21.1638 16.326 18.745 18.7445C16.3263 21.1632 16.3263 25.0847 18.745 27.5035L27.5042 36.2627Z"
/>
<svg v-else-if="name === 'brightness'" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00002 12.9033C7.57246 12.9033 7.22583 13.2499 7.22583 13.6774V15.2258C7.22583 15.6534 7.57246 16 8.00002 16C8.42758 16 8.7742 15.6534 8.7742 15.2259V13.6775C8.7742 13.2499 8.42758 12.9033 8.00002 12.9033Z" />
<path d="M8.00002 0C7.57246 0 7.22583 0.346625 7.22583 0.774188V2.32256C7.22583 2.75012 7.57246 3.09675 8.00002 3.09675C8.42758 3.09675 8.7742 2.75012 8.7742 2.32256V0.774188C8.7742 0.346625 8.42758 0 8.00002 0Z" />
<path d="M8.0001 4.64517C6.15035 4.64523 4.64539 6.1502 4.64539 8.00001C4.64539 9.84982 6.15026 11.3548 8.00001 11.3549C9.84991 11.3549 11.355 9.84988 11.355 8.00001C11.355 6.15013 9.85001 4.64517 8.0001 4.64517Z" />
<path d="M16 7.99999C16 7.57242 15.6534 7.2258 15.2259 7.2258H13.6775C13.2499 7.2258 12.9033 7.57242 12.9033 7.99999C12.9033 8.42755 13.2499 8.77417 13.6775 8.77417H15.2259C15.6534 8.77417 16 8.42755 16 7.99999Z" />
<path d="M0 7.99999C0 8.42755 0.346625 8.77417 0.774188 8.77417H2.32256C2.75012 8.77417 3.09675 8.42755 3.09675 7.99999C3.09675 7.57242 2.75012 7.2258 2.32256 7.2258H0.774188C0.346625 7.2258 0 7.57242 0 7.99999Z" />
<path d="M12.0145 4.75962C12.2126 4.75962 12.4108 4.684 12.5619 4.53284L13.6569 3.43796C13.9591 3.13562 13.9591 2.64544 13.6569 2.34309C13.3545 2.04075 12.8644 2.04075 12.5619 2.34309L11.4671 3.43796C11.1647 3.74031 11.1647 4.2305 11.4671 4.53284C11.6183 4.68402 11.8164 4.75962 12.0145 4.75962Z" />
<path d="M2.89062 13.8836C3.08877 13.8836 3.2869 13.808 3.43802 13.6569L4.53292 12.562C4.83527 12.2597 4.83527 11.7694 4.53292 11.4671C4.23061 11.1647 3.7404 11.1647 3.43805 11.4671L2.34315 12.562C2.04081 12.8642 2.04081 13.3545 2.34315 13.6569C2.49431 13.808 2.69246 13.8836 2.89062 13.8836Z" />
<path d="M12.562 11.4671C12.2597 11.1647 11.7695 11.1647 11.4671 11.4671C11.1648 11.7694 11.1648 12.2596 11.4671 12.562L12.562 13.6569C12.7133 13.808 12.9114 13.8836 13.1094 13.8836C13.3076 13.8836 13.5058 13.808 13.6569 13.6569C13.9591 13.3546 13.9591 12.8644 13.6569 12.562L12.562 11.4671Z" />
<path d="M3.43805 4.53284C3.58923 4.68403 3.78737 4.75963 3.98552 4.75963C4.18368 4.75963 4.3818 4.684 4.53292 4.53284C4.83527 4.2305 4.83527 3.74032 4.53292 3.43797L3.43802 2.34307C3.13573 2.04075 2.6455 2.04075 2.34315 2.34307C2.04081 2.6454 2.04081 3.13559 2.34315 3.43794L3.43805 4.53284Z" />
</svg>
<svg
@ -135,7 +110,7 @@
viewBox="0 0 70.055 70.055"
width="70.055"
height="70.055"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 42.013 35.028 L 68.615 8.426 C 69.532 7.499 70.055 6.246 70.055 4.933 C 70.055 3.621 69.532 2.368 68.615 1.441 C 67.687 0.523 66.434 0 65.122 0 C 63.809 0 62.556 0.523 61.629 1.441 L 35.028 28.042 L 8.426 1.441 C 7.499 0.523 6.246 0 4.933 0 C 3.621 0 2.368 0.523 1.441 1.441 C 0.523 2.368 0 3.621 0 4.933 C 0 6.246 0.523 7.499 1.441 8.426 L 28.042 35.028 L 1.441 61.629 C 0.523 62.556 0 63.809 0 65.122 C 0 66.434 0.523 67.687 1.441 68.615 C 2.368 69.532 3.621 70.055 4.933 70.055 C 6.246 70.055 7.499 69.532 8.426 68.615 L 35.028 42.013 L 61.629 68.615 C 62.556 69.532 63.809 70.055 65.122 70.055 C 66.434 70.055 67.687 69.532 68.615 68.615 C 69.532 67.687 70.055 66.434 70.055 65.122 C 70.055 63.809 69.532 62.556 68.615 61.629 L 42.013 35.028 Z "
@ -149,7 +124,7 @@
viewBox="0 0 92 92"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M38.7935 55.2327V47.2344C33.0391 45.0181 29.0089 39.9177 29.0089 33.9848V14.4284C29.0089 6.46553 36.2714 0 45.2182 0H46.7818C55.7275 0 62.9899 6.46553 62.9899 14.4284V33.9848C62.9899 39.9177 58.9598 45.0181 53.2054 47.2344V55.2327H75.8905C84.7808 55.2327 92 61.6578 92 69.5713V77.6604C92 85.5749 84.7808 92 75.8905 92H16.1095C7.21812 92 0 85.5749 0 77.6604V69.5713C0 61.6578 7.21812 55.2327 16.1095 55.2327H38.7935Z"
@ -189,7 +164,7 @@
height="24"
viewBox="0 0 24 24"
xml:space="preserve"
v-on="$listeners"
v-bind="$attrs"
>
<path
stroke="currentColor"
@ -222,7 +197,7 @@
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -239,7 +214,7 @@
viewBox="0 0 27 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -254,7 +229,7 @@
height="491"
viewBox="0 0 511 491"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M506.575 227.66L453.569 163.874L497.007 103.095C504.153 93.0988 500.157 79.0326 488.82 74.2841L314.93 1.48904C306.681 -1.96514 297.145 0.744401 291.944 8.01923L255.499 59.0093L219.051 8.01923C213.854 0.744401 204.318 -1.96514 196.069 1.48904L22.1787 74.2841C10.8415 79.0326 6.84148 93.0949 13.9916 103.095L57.43 163.874L4.42434 227.66C-3.79786 237.551 -0.10976 252.603 11.7615 257.574L62.4475 278.794V398.47C62.4475 406.193 67.0869 413.16 74.2097 416.142L248.1 488.941C252.833 490.922 258.166 490.922 262.895 488.941L436.789 416.142C443.912 413.16 448.547 406.193 448.547 398.47V278.794L499.237 257.574C511.105 252.607 514.801 237.554 506.575 227.66ZM314.232 42.7326L451.873 100.354L422.696 141.181L285.047 83.5589L314.232 42.7326ZM255.499 216.783L131.215 164.755L255.499 112.725L379.784 164.755L255.499 216.783ZM196.771 42.7326L225.948 83.5589L88.3071 141.181L59.1259 100.354L196.771 42.7326ZM87.2817 187.898L224.217 245.223L187.375 289.554C174.159 284.022 63.5664 237.726 50.4437 232.229L87.2817 187.898ZM410.236 385.725L255.499 450.501L100.763 385.725V294.833L185.652 330.373C193.422 333.624 202.404 331.422 207.784 324.946L255.499 267.527L303.215 324.946C308.606 331.433 317.593 333.616 325.347 330.373L410.236 294.833V385.725ZM323.624 289.554L286.782 245.223L423.717 187.898L460.559 232.229C447.425 237.73 336.86 284.014 323.624 289.554Z"
@ -268,7 +243,7 @@
viewBox="0 0 60 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -285,7 +260,7 @@
viewBox="0 0 106.903 106.903"
width="106.903"
height="106.903"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 24.85 34.532 C 21.923 31.605 21.923 26.852 24.85 23.925 C 27.777 20.998 32.529 20.998 35.456 23.925 C 38.383 26.852 38.383 31.605 35.456 34.532 C 32.529 37.459 27.777 37.459 24.85 34.532 L 24.85 34.532 L 24.85 34.532 L 24.85 34.532 Z M 2.195 57.186 C -0.732 54.259 -0.732 49.507 2.195 46.58 C 5.122 43.653 9.875 43.653 12.802 46.58 C 15.729 49.507 15.729 54.259 12.802 57.186 C 9.875 60.113 5.122 60.113 2.195 57.186 L 2.195 57.186 L 2.195 57.186 L 2.195 57.186 Z M 46.58 12.802 C 43.653 9.875 43.653 5.122 46.58 2.195 C 49.507 -0.732 54.259 -0.732 57.186 2.195 C 60.113 5.122 60.113 9.875 57.186 12.802 C 54.259 15.729 49.507 15.729 46.58 12.802 L 46.58 12.802 L 46.58 12.802 Z M 74.871 64.258 C 76.278 65.665 77.063 67.581 77.063 69.561 C 77.063 71.555 76.271 73.464 74.871 74.864 C 73.464 76.272 71.555 77.064 69.561 77.064 C 67.581 77.064 65.672 76.272 64.264 74.864 L 38.851 49.451 C 37.444 48.044 36.652 46.135 36.652 44.155 C 36.652 42.161 37.444 40.252 38.851 38.844 C 40.251 37.444 42.16 36.652 44.154 36.652 C 46.134 36.652 48.05 37.437 49.458 38.844 L 74.871 64.258 L 74.871 64.258 Z M 61.04 27.262 C 59.633 25.855 58.841 23.946 58.848 21.959 C 58.841 19.972 59.633 18.063 61.04 16.655 C 62.447 15.248 64.356 14.456 66.343 14.463 C 68.33 14.456 70.239 15.248 71.647 16.655 L 95.519 40.527 C 102.809 47.818 106.903 57.71 106.903 68.02 C 106.903 78.329 102.802 88.229 95.519 95.512 C 88.228 102.802 78.329 106.903 68.019 106.903 C 57.71 106.903 47.817 102.809 40.527 95.519 L 16.655 71.647 C 15.248 70.24 14.456 68.331 14.463 66.344 C 14.456 64.357 15.248 62.448 16.655 61.041 C 18.062 59.633 19.971 58.841 21.958 58.848 C 23.945 58.841 25.854 59.633 27.261 61.041 L 51.133 84.912 C 55.609 89.388 61.691 91.906 68.026 91.906 C 74.348 91.906 80.429 89.388 84.912 84.905 C 89.388 80.429 91.905 74.348 91.905 68.027 C 91.905 61.691 89.388 55.61 84.912 51.134 L 61.04 27.262 Z "
@ -301,7 +276,7 @@
viewBox="0 0 81.359 100"
width="81.359"
height="100"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 72.781 0 C 51.978 0 48.075 29.63 59.778 29.63 C 71.48 29.63 93.585 0 72.781 0 L 72.781 0 L 72.781 0 L 72.781 0 Z M 38.238 25.959 C 44.469 26.339 51.305 2.179 39.594 3.699 C 27.887 5.218 32.004 25.578 38.238 25.959 L 38.238 25.959 L 38.238 25.959 L 38.238 25.959 Z M 9.68 40.972 C 14.112 39.021 10.251 19.94 2.715 25.144 C -4.818 30.348 5.248 42.922 9.68 40.972 L 9.68 40.972 L 9.68 40.972 Z M 22.167 30.984 C 27.443 29.909 27.732 8.268 18.324 12.15 C 8.914 16.033 16.896 32.062 22.167 30.984 L 22.167 30.984 L 22.167 30.984 Z M 49.716 79.412 C 50.652 86.561 44.471 90.091 38.419 85.483 C 19.15 70.813 70.322 63.494 66.949 43.452 C 64.149 26.816 13.105 31.937 7.292 57.968 C 3.358 75.574 23.488 100 44.493 100 C 54.826 100 66.744 90.67 68.974 78.85 C 70.677 69.837 48.934 73.448 49.716 79.412 L 49.716 79.412 Z "
@ -316,7 +291,7 @@
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M0 24V8.25H16.5V7.5H0V0H24V24H16.5V15.75H7.5V16.5H15.75V24H0Z"
@ -331,7 +306,7 @@
viewBox="0 0 100 100"
width="100"
height="100"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 50 0 C 22.386 0 0 22.386 0 50 C 0 77.614 22.386 100 50 100 C 77.614 100 100 77.614 100 50 C 100 22.386 77.614 0 50 0 Z M 50 4.167 C 70.995 4.167 88.692 18.283 94.12 37.542 C 89.445 47.184 82.793 55.865 74.693 62.88 C 70.801 66.25 66.553 69.254 61.859 71.37 C 57.166 73.485 52.004 74.685 46.865 74.391 C 43.003 74.169 39.189 73.094 35.807 71.219 C 44.769 66.363 52.855 59.888 59.474 52.135 C 63.819 47.047 67.62 41.263 69.083 34.734 C 69.815 31.47 69.939 28.051 69.255 24.776 C 68.571 21.501 67.057 18.38 64.766 15.943 C 62.527 13.561 59.588 11.874 56.437 11.005 C 53.287 10.136 49.934 10.069 46.719 10.656 C 40.288 11.831 34.547 15.569 29.943 20.208 C 21.826 28.387 16.809 39.926 17.729 51.411 C 18.189 57.154 20.114 62.797 23.411 67.521 C 24.148 68.575 24.957 69.58 25.823 70.531 C 21.019 72.213 16.045 73.409 11 74.083 C 6.669 67.085 4.167 58.835 4.167 50 C 4.167 24.687 24.687 4.167 50 4.167 Z M 50.292 13.427 C 53.514 13.431 56.757 14.407 59.328 16.349 C 61.644 18.099 63.36 20.588 64.333 23.323 C 65.306 26.058 65.556 29.028 65.224 31.911 C 64.56 37.679 61.651 42.968 58.135 47.589 C 51.911 55.77 43.638 62.317 34.438 66.901 C 33.496 67.37 32.539 67.816 31.578 68.245 C 29.576 66.493 27.86 64.415 26.552 62.099 C 23.819 57.26 22.918 51.511 23.448 45.979 C 23.978 40.447 25.877 35.115 28.427 30.177 C 31.384 24.452 35.415 19.029 41.057 15.917 C 43.879 14.361 47.07 13.423 50.292 13.427 Z M 95.422 43.865 C 95.69 45.872 95.833 47.919 95.833 50 C 95.833 75.313 75.313 95.833 50 95.833 C 35.784 95.833 23.079 89.362 14.672 79.203 C 19.861 78.025 24.932 76.332 29.797 74.177 C 31.924 75.797 34.273 77.126 36.771 78.083 C 43.963 80.841 52.128 80.442 59.406 77.922 C 66.685 75.402 73.139 70.88 78.766 65.62 C 85.468 59.354 91.12 51.969 95.422 43.865 Z "
@ -346,7 +321,7 @@
viewBox="0 0 90 87.407"
width="90"
height="87.407"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 79.536 54.583 C 80.721 51.087 81.327 47.412 81.327 43.703 C 81.327 39.029 80.364 34.406 78.491 30.13 C 76.652 25.95 74.024 22.16 70.737 18.994 C 67.398 15.768 63.478 13.209 59.185 11.448 C 54.683 9.61 49.861 8.664 44.996 8.673 C 40.139 8.664 35.317 9.61 30.815 11.448 C 26.522 13.209 22.602 15.768 19.263 18.994 C 15.976 22.16 13.339 25.95 11.509 30.121 C 9.636 34.406 8.673 39.029 8.673 43.703 L 8.673 43.703 C 8.673 48.378 9.636 53.001 11.509 57.277 C 12.556 59.668 13.867 61.932 15.413 64.025 L 46.401 28.959 L 50.737 37.632 L 42.931 55.846 L 61.584 34.163 L 79.536 54.583 Z M 0 43.703 C 0 37.832 1.214 32.03 3.565 26.652 C 5.854 21.431 9.141 16.713 13.235 12.758 C 17.372 8.76 22.22 5.594 27.537 3.417 C 33.079 1.154 39.011 -0.009 44.996 0 C 50.989 -0.009 56.921 1.154 62.463 3.417 C 67.78 5.594 72.628 8.76 76.756 12.758 C 80.859 16.713 84.146 21.431 86.435 26.644 C 88.786 32.03 90 37.832 90 43.703 C 90 49.575 88.786 55.377 86.435 60.763 C 84.146 65.976 80.859 70.694 76.756 74.649 C 72.628 78.647 67.78 81.813 62.463 83.99 C 56.921 86.253 50.989 87.416 44.996 87.407 C 39.011 87.416 33.079 86.253 27.537 83.99 C 22.22 81.813 17.372 78.647 13.235 74.649 C 9.141 70.694 5.854 65.976 3.565 60.763 C 1.214 55.377 0 49.575 0 43.703 L 0 43.703 Z "
@ -362,7 +337,7 @@
viewBox="0 0 89.753 100"
width="89.753"
height="100"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 41.1 56.122 L 41.1 6.412 C 41.1 4.58 41.698 3.053 42.892 1.832 C 44.086 0.611 45.628 0 47.516 0 C 49.405 0 50.932 0.569 52.099 1.707 C 53.293 2.845 53.89 4.413 53.89 6.412 L 53.89 24.98 L 74.887 2.415 C 76.359 0.805 78.011 0 79.844 0 C 81.538 0 83.01 0.583 84.26 1.748 C 85.538 2.886 86.176 4.385 86.176 6.245 C 86.176 7.3 85.843 8.34 85.177 9.367 C 84.51 10.367 83.441 11.602 81.969 13.073 L 65.846 29.102 L 85.552 49.877 C 86.69 50.959 87.565 52.014 88.176 53.041 C 88.815 54.04 89.134 55.15 89.134 56.372 C 89.134 58.315 88.496 59.799 87.218 60.826 C 85.94 61.826 84.357 62.325 82.469 62.325 C 81.358 62.325 80.344 62.02 79.428 61.409 C 78.539 60.799 77.4 59.772 76.011 58.328 L 53.89 34.181 L 53.89 56.122 C 53.89 57.87 53.293 59.342 52.099 60.535 C 50.904 61.729 49.377 62.325 47.516 62.325 C 45.655 62.325 44.114 61.756 42.892 60.618 C 41.698 59.48 41.1 57.981 41.1 56.122 L 41.1 56.122 Z M 9.756 80.339 L 17.951 88.919 L 31.219 79.949 L 41.755 84.629 L 45.267 100 L 56.584 99.896 L 59.705 84.125 L 70.242 79.949 L 83.51 88.529 L 89.753 79.949 L 74.696 65.748 C 36.102 101.124 2.752 46.602 35.901 23.792 L 35.901 15.602 L 31.999 17.552 L 19.902 9.866 L 11.545 16.724 L 19.121 29.641 L 14.829 40.171 L 0.046 42.625 L 0 53.82 L 15.219 57.72 L 19.414 67.957 L 9.756 80.339 L 9.756 80.339 Z "
@ -378,7 +353,7 @@
viewBox="0 0 97.338 94.68"
width="97.338"
height="94.68"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 0 78.895 L 0 48.096 L 0 17.297 L 26.673 32.697 L 53.345 48.096 L 26.673 63.496 L 0 78.895 L 0 78.895 Z M 20.588 21.19 L 18.548 23.88 L 11.958 18.85 L 14.618 15.86 C 15.578 14.79 16.578 13.76 17.628 12.78 C 22.048 8.63 27.228 5.37 32.878 3.18 C 38.338 1.08 44.138 0 49.988 0 C 56.348 0 62.648 1.27 68.518 3.75 C 74.588 6.33 80.048 10.15 84.558 14.97 C 88.698 19.39 91.958 24.57 94.148 30.22 C 96.258 35.68 97.338 41.48 97.338 47.33 C 97.338 53.69 96.058 59.99 93.578 65.86 C 90.998 71.93 87.178 77.39 82.358 81.9 C 77.938 86.04 72.768 89.3 67.108 91.49 C 61.658 93.6 55.858 94.68 50.008 94.68 C 43.638 94.68 37.338 93.4 31.478 90.92 C 25.408 88.34 19.938 84.52 15.438 79.7 L 12.708 76.78 L 19.438 72.01 L 21.278 74.24 C 25.018 78.24 29.558 81.41 34.598 83.56 C 39.478 85.62 44.718 86.68 50.008 86.68 C 54.868 86.68 59.688 85.78 64.228 84.03 C 68.918 82.21 73.218 79.5 76.898 76.06 C 80.898 72.31 84.068 67.78 86.218 62.73 C 88.278 57.86 89.338 52.62 89.338 47.33 C 89.338 42.47 88.438 37.65 86.688 33.11 C 84.868 28.41 82.158 24.11 78.718 20.44 C 74.968 16.44 70.438 13.26 65.388 11.12 C 60.518 9.06 55.278 8 49.988 8 C 45.128 8 40.298 8.89 35.768 10.64 C 31.068 12.47 26.768 15.17 23.098 18.62 C 22.228 19.44 21.388 20.29 20.588 21.19 L 20.588 21.19 Z M 31.148 29.39 L 29.178 31.36 L 22.598 26.78 L 25.358 23.88 C 31.778 17.13 40.708 13.3 50.028 13.3 C 58.758 13.3 67.158 16.66 73.478 22.67 C 80.238 29.09 84.058 38.02 84.058 47.34 C 84.058 56.07 80.708 64.47 74.698 70.8 C 68.268 77.55 59.338 81.38 50.018 81.37 C 41.288 81.37 32.888 78.02 26.568 72.01 L 23.668 69.25 L 30.689 64.971 L 32.078 66.21 C 36.918 70.81 43.348 73.38 50.018 73.37 C 57.148 73.38 63.978 70.45 68.898 65.28 C 73.498 60.44 76.058 54.02 76.058 47.34 C 76.058 40.21 73.138 33.38 67.968 28.47 C 63.128 23.87 56.708 21.3 50.028 21.3 C 42.898 21.3 36.068 24.23 31.148 29.39 Z "
@ -394,7 +369,7 @@
viewBox="0 0 67.156 67.383"
width="67.156"
height="67.383"
v-on="$listeners"
v-bind="$attrs"
>
<path
color="active"
@ -409,7 +384,7 @@
viewBox="0 0 63 65"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M27.5314 16.5725C24.0072 22.0321 32.6483 23.7106 33.9712 22.715C39.6749 18.4223 29.0841 14.1671 27.5314 16.5725Z"
@ -433,7 +408,7 @@
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M2.49998 14.5C3.88069 14.5 4.99997 13.3807 4.99997 12C4.99997 10.6193 3.88069 9.50002 2.49998 9.50002C1.11928 9.50002 0 10.6193 0 12C0 13.3807 1.11928 14.5 2.49998 14.5Z"
@ -462,7 +437,7 @@
height="56"
viewBox="0 0 56 56"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -478,7 +453,7 @@
fill="currentColor"
viewBox="0 0 19 16"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M0.515625 0.078125V4.76562H2.85938V9.45312C2.85938 12.5781 4.8125 15.3125 7.9375 15.3125H18.0938V5.54688C18.0938 2.33359 15.4476 0.0935957 12.2344 0.078125H0.515625ZM1.6875 1.25H12.2344C14.8184 1.25 16.9219 2.96289 16.9219 5.54688V14.1406H7.9375C5.59375 14.1406 4.03125 11.7969 4.03125 9.45312V5.15625C4.03125 3.99816 3.58116 3.59375 2.46875 3.59375H1.6875V1.25Z"
@ -496,7 +471,7 @@
viewBox="535.623 183 294.755 294.755"
width="294.755"
height="294.755"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 683 183 C 669.879 215.166 661.911 236.236 647.307 267.447 C 656.26 276.94 667.267 287.955 685.124 300.442 C 665.927 292.544 652.842 284.636 643.061 276.405 C 624.366 315.414 595.058 370.961 535.623 477.755 C 582.323 450.795 618.531 434.159 652.273 427.812 C 650.825 421.59 650.056 414.829 650.112 407.805 L 650.149 406.332 C 650.89 376.406 666.461 353.391 684.906 354.953 C 703.348 356.514 717.708 382.052 716.966 411.984 C 716.824 417.621 716.17 423.037 715.06 428.066 C 748.436 434.596 784.299 451.154 830.377 477.755 C 821.291 461.028 813.153 445.954 805.406 431.589 C 793.206 422.133 780.485 409.832 754.529 396.509 C 772.371 401.147 785.155 406.507 795.112 412.485 C 716.368 265.868 710.002 246.372 683 183 L 683 183 L 683 183 Z "
@ -511,7 +486,7 @@
viewBox="179 14 90 90"
width="90"
height="90"
v-on="$listeners"
v-bind="$attrs"
>
<path
d=" M 179 59 C 179 34.164 199.164 14 224 14 C 248.836 14 269 34.164 269 59 C 269 83.836 248.836 104 224 104 C 199.164 104 179 83.836 179 59 Z M 216.5 74.479 L 216.5 78.479 L 225.5 82.479 L 216.5 87.479 L 216.5 91.479 L 229.5 84.479 L 229.5 80.479 L 216.5 74.479 L 216.5 74.479 L 216.5 74.479 L 216.5 74.479 L 216.5 74.479 Z M 203.5 74.479 L 203.5 78.479 L 212.5 82.479 L 203.5 87.479 L 203.5 91.479 L 216.5 84.479 L 216.5 80.479 L 203.5 74.479 L 203.5 74.479 L 203.5 74.479 L 203.5 74.479 L 203.5 74.479 Z M 205.5 26.521 L 212.5 26.521 L 223.5 44.479 L 234.5 26.521 L 242.5 26.521 L 227.5 49.479 L 244.5 74.479 L 237.5 74.479 L 223.5 54.479 L 210.5 74.479 L 203.5 74.479 L 219.5 49.479 L 205.5 26.521 L 205.5 26.521 L 205.5 26.521 L 205.5 26.521 Z M 232.5 78.479 L 244.5 78.479 L 244.5 81.479 L 232.5 81.479 L 232.5 78.479 L 232.5 78.479 L 232.5 78.479 Z M 232.5 85.479 L 244.5 85.479 L 244.5 88.479 L 232.5 88.479 L 232.5 85.479 L 232.5 85.479 Z "
@ -524,7 +499,7 @@
xmlns="http://www.w3.org/2000/svg"
style="isolation:isolate"
viewBox="0 0 43 40"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M20 4c-2-2-5-4-9-4C5 0 0 5 0 12q0 9 20 23 20-13 20-23c0-9-5-12-11-12-4 0-7 2-9 4z"
@ -538,7 +513,7 @@
viewBox="0 0 37 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
d="M3 16.0213L11.9362 24.9574L33.8936 3"
@ -962,7 +937,7 @@
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -978,7 +953,7 @@
height="26"
viewBox="0 0 26 26"
xmlns="http://www.w3.org/2000/svg"
v-on="$listeners"
v-bind="$attrs"
>
<path
fill-rule="evenodd"
@ -994,15 +969,11 @@
/>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class AppIcon extends Vue {
@Prop({ type: String, default: 'love' }) name!: string
<script setup lang="ts">
defineProps({
name: { type: String, default: 'love' }
})
colorDark = 'var(--primary-dark-1)'
colorDarker = 'var(--primary-dark-2)'
colorDarkest = 'var(--primary-dark-3)'
}
defineEmits(['click'])
defineOptions({ inheritAttrs: false })
</script>

@ -1,123 +1,97 @@
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import { AppMenuItem, AppMenuPosition } from '@/models/page'
import { AppModule } from '@/store/app'
@Component({
components: { AppIcon }
})
export default class AppMenu extends Vue {
innerPositioned: AppMenuPosition = { left: 0, top: 0, width: 199 }
get position() {
return this.menu.position ?? this.innerPositioned
}
get menu() {
return PageModule.menu
}
get isShow() {
return this.menu.view
}
@Watch('menu.view')
updatePosition(isOpen: boolean) {
if (isOpen) {
this.calculatePosition()
import { defineComponent, ref, watch, computed, onMounted, onBeforeUnmount } from 'vue'
import { usePageStore } from '@/store/page'
import { useAppStore } from '@/store/app'
import type { AppMenuItem, AppMenuPosition } from '@/models/page'
export default defineComponent({
name: 'AppMenu',
setup() {
const pageStore = usePageStore()
const appStore = useAppStore()
const menuRef = ref<HTMLElement | null>(null)
const innerPositioned = ref<AppMenuPosition>({ left: 0, top: 0, width: 199 })
const position = computed(() => (pageStore.menu as any).position ?? innerPositioned.value)
const style = computed(() =>
Object.entries(position.value)
.map(([prop, val]) => `${prop}: ${val}px`)
.join(';')
)
const calculatePosition = () => {
const node = (pageStore.menu as any).node as HTMLElement | undefined
if (!node || !pageStore.menu.view) return
const { bottom, left, top, height, width } = node.getBoundingClientRect()
const { zoom } = appStore
const pos: AppMenuPosition = { left: left * zoom, width: width * zoom }
if (bottom > top) {
pos.top = (top + height) * zoom
} else {
pos.bottom = (window.innerHeight - bottom + height) * zoom
}
innerPositioned.value = pos
}
}
get style() {
return Object.entries(this.position).reduce((acc, [property, value]) => {
acc += `${property}: ${value}px;`
return acc
}, '')
}
get menuNode() {
return this.$refs.menu as Element
}
async handleCallback(event: MouseEvent, item: AppMenuItem | string) {
if (this.menu.handler) {
await this.menu.handler(item)
}
this.closeMenu()
}
handleClickOutside(event: MouseEvent) {
if (!this.isShow) return false
const closeMenu = () => pageStore.assignMenu({ view: false })
const clickOnMenu = this.$el.contains(event.target as Node)
!clickOnMenu && this.closeMenu()
}
mounted() {
window.addEventListener('resize', this.handleResize, { passive: true })
window.addEventListener('click', this.handleClickOutside)
}
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
window.removeEventListener('click', this.handleClickOutside)
}
handleResize() {
this.calculatePosition()
}
calculatePosition() {
const node = this.menu.node
const { zoom } = AppModule
if (!node || !this.menu.view) return
const { bottom, left, top, height, width } = node.getBoundingClientRect()
const allHeight = window.innerHeight
const positionY = bottom > top ? 'bottom' : 'top'
const position: AppMenuPosition = {
left: left * zoom,
width: width * zoom
const handleClickOutside = (event: MouseEvent) => {
if (!pageStore.menu.view) return
const clickOnMenu = menuRef.value?.contains(event.target as Node)
if (!clickOnMenu) closeMenu()
}
if (positionY === 'bottom') {
position.top = (top + height) * zoom
} else {
position.bottom = (allHeight - bottom + height) * zoom
const handleResize = () => calculatePosition()
watch(() => pageStore.menu.view, (isOpen) => {
if (isOpen) calculatePosition()
})
onMounted(() => {
window.addEventListener('resize', handleResize, { passive: true })
window.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
window.removeEventListener('click', handleClickOutside)
})
const buildItem = (item: AppMenuItem | string, index: number) => {
const isStr = typeof item === 'string'
const { text, icon } = isStr ? { text: item, icon: null } : (item as AppMenuItem)
return (
<li
class="menu-list-item"
key={index}
onClick={async (event) => {
if ((pageStore.menu as any).handler) {
await (pageStore.menu as any).handler(item)
}
closeMenu()
}}
>
{text}
{icon && <AppIcon class="menu-icon" name={icon} />}
</li>
)
}
this.innerPositioned = position
return () => (
<div class="menu-wrapper" ref={menuRef}>
<transition name="fade-menu">
{pageStore.menu.view && (
<ul class="menu-list active-block" id="menu" style={style.value}>
{pageStore.menu.items.map(buildItem)}
</ul>
)}
</transition>
</div>
)
}
closeMenu() {
PageModule.ASSIGN_MENU({ view: false })
}
buildElementItem(item: AppMenuItem | string, index: number) {
const isSting = typeof item === 'string'
const { text, icon } = isSting ? { text: item, icon: null } : item
return <li
class='menu-list-item'
key={index}
onClick={(event) => { this.handleCallback(event, item) }}
>
{ text }
{ icon && <AppIcon class='menu-icon' name={icon} /> }
</li>
}
render() {
return <div class='menu-wrapper' ref='menu'>
<transition name='fade-menu'>
{ this.menu.view && <ul class='menu-list active-block' id='menu' style={ this.style }>
{ this.menu.items.map(this.buildElementItem) }
</ul>
}
</transition>
</div>
}
}
})

@ -1,32 +1,30 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent } from 'vue'
@Component({
components: { AppIcon }
})
export default class AppPaletteSelector extends Vue {
@Prop({ type: Number, default: 0 }) value!: number
@Prop({ type: Array, required: true }) values!: string[][]
@Prop({ default: '' }) label!: string
activatePalette(index: number) {
this.$emit('input', index)
}
export default defineComponent({
name: 'AppPaletteSelector',
generatePaletteItem(colors: string[], index: number) {
const buildColor = (color: string) => <div class="palette-block-color" style={ `background-color: ${color}` }/>
const classes = `palette-block ${index === this.value ? 'active' : ''}`
props: {
modelValue: { type: Number, default: 0 },
values: { type: Array as () => string[][], required: true },
label: { type: String, default: '' }
},
return <div onClick={ () => this.activatePalette(index) } class={ classes }> { colors.map(buildColor) } </div>
}
emits: ['update:modelValue'],
generatePaletteItems() {
return this.values.map(this.generatePaletteItem)
setup(props, { emit }) {
return () => (
<div class="palette-blocks">
{props.values.map((colors, index) => {
const classes = `palette-block ${index === props.modelValue ? 'active' : ''}`
return (
<div onClick={() => emit('update:modelValue', index)} class={classes}>
{colors.map((color) => (
<div class="palette-block-color" style={`background-color: ${color}`} />
))}
</div>
)
})}
</div>
)
}
render() {
return <div class="palette-blocks">
{ this.generatePaletteItems() }
</div>
}
}
})

@ -1,90 +1,83 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import { AppMenuItem } from '@/models/page'
import { defineComponent, computed, ref, type PropType } from 'vue'
import { usePageStore } from '@/store/page'
import type { AppMenuItem } from '@/models/page'
export interface AppSelectorProps {
value: AppMenuItem | string | undefined;
items: AppMenuItem[];
label: string;
}
@Component({
components: { AppIcon }
})
export default class AppSelector extends Vue implements AppSelectorProps {
@Prop({ type: [Object, String], default: undefined }) value!: AppMenuItem | string | undefined
@Prop({ type: [Array], default: () => [] }) items!: AppMenuItem[]
@Prop({ default: '' }) label!: string
export default defineComponent({
name: 'AppSelector',
selectedValue: null | AppMenuItem | string = null
props: {
modelValue: { type: [Object, String, Number] as PropType<AppMenuItem | string | number | null | undefined>, default: null },
items: { type: Array as PropType<(AppMenuItem | string)[]>, default: () => [] },
label: { type: String, default: '' }
},
get fullSelectedItem() {
const selected = this.value !== null ? this.value : this.selectedValue
const finalSelectedValue = typeof selected === 'object' ? selected?.value : selected
emits: ['update:modelValue'],
return this.items.find((item) => {
const finalValue = typeof item === 'object' ? item?.value : item
return finalValue === finalSelectedValue
})
}
setup(props, { emit }) {
const pageStore = usePageStore()
const selectorRef = ref<HTMLElement | null>(null)
const selectedValue = ref<AppMenuItem | string | null>(null)
get menu() {
return PageModule.menu
}
get isActive() {
return this.menu?.view && this.menu?.node === this.$refs.selector
}
const fullSelectedItem = computed(() => {
const selected = props.modelValue !== null ? props.modelValue : selectedValue.value
const finalSelectedValue = typeof selected === 'object' ? (selected as AppMenuItem)?.value : selected
return props.items.find((item) => {
const finalValue = typeof item === 'object' ? item?.value : item
return finalValue === finalSelectedValue
})
})
get currentValueLabel() {
const selected = this.fullSelectedItem ?? this.selectedValue
return typeof selected === 'object' ? selected?.text : selected
}
const isActive = computed(
() => pageStore.menu.view && (pageStore.menu as any).node === selectorRef.value
)
openSelector(event: Event) {
event.preventDefault()
event.stopPropagation()
const currentValueLabel = computed(() => {
const selected = fullSelectedItem.value ?? selectedValue.value
return typeof selected === 'object' ? (selected as AppMenuItem)?.text : selected
})
if (this.menu.view) {
return PageModule.ASSIGN_MENU({ view: false })
const callback = (item: AppMenuItem | string) => {
const finalValue = typeof item === 'object' ? item.value : item
selectedValue.value = item
emit('update:modelValue', finalValue)
}
PageModule.ASSIGN_MENU({
node: this.$refs.selector as HTMLElement,
view: true,
items: this.items,
handler: this.callback
})
}
const openSelector = (event: Event) => {
event.preventDefault()
event.stopPropagation()
callback(item: AppMenuItem | string) {
const finalValue = typeof item === 'object' ? item.value : item
if (pageStore.menu.view) {
return pageStore.assignMenu({ view: false })
}
this.selectedValue = item
this.$emit('input', finalValue)
}
pageStore.assignMenu({
node: selectorRef.value as HTMLElement,
view: true,
items: props.items as AppMenuItem[],
handler: callback
})
}
render() {
const selectorIcon = this.fullSelectedItem?.icon ? <AppIcon {...{
class: 'menu-icon selector-icon',
props: {
name: this.fullSelectedItem.icon
}
}}/> : null
return () => {
const iconName = (fullSelectedItem.value as AppMenuItem)?.icon
const selectorIcon = iconName ? (
<AppIcon class="menu-icon selector-icon" name={iconName} />
) : null
return <div
onClick={ this.openSelector }
ref="selector"
class={['selector', this.isActive ? 'active' : '']}
>
<h2 class='selector-label'>
{ selectorIcon }
<p class="selector-label-text">
{ this.currentValueLabel || this.label }
</p>
</h2>
<AppIcon name='arrow' class='icon selector-arrow' />
</div>
return (
<div
onClick={openSelector}
ref={selectorRef}
class={['selector', isActive.value ? 'active' : '']}
>
<h2 class="selector-label">
{selectorIcon}
<p class="selector-label-text">{currentValueLabel.value || props.label}</p>
</h2>
<AppIcon name="arrow" class="icon selector-arrow" />
</div>
)
}
}
}
})

@ -1,68 +1,63 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { defineComponent, onMounted, onUpdated, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import noUiSlider from 'nouislider'
import { Debounce } from '@/utils/helper'
@Component
export default class AppSlider extends Vue {
@Prop({ type: [Number], default: '' }) value!: number | string
@Prop({ default: '' }) label!: string
@Prop({ type: Number, default: 0 }) from!: number
@Prop({ type: Number, default: 100 }) to!: number
@Prop({ type: Number, default: 1 }) step!: number
@Prop({ type: Boolean, default: false }) changeOnUpdate!: boolean
preValue = 0
@Debounce(100)
syncSliderValue() {
const slider = (this.$refs.slider as any).noUiSlider
const sliderValue = parseFloat(slider.get())
const isDifferentValues = sliderValue !== this.value
if (isDifferentValues) {
slider.set(this.value)
}
}
updated() {
this.syncSliderValue()
}
mounted() {
const slider = this.$refs.slider as any
const isValidValue = this.value !== 'undefined'
noUiSlider.create(slider, {
start: isValidValue ? this.value : this.to,
connect: 'lower',
step: this.step,
range: {
min: this.from,
max: this.to
import { debounce } from 'lodash'
export default defineComponent({
name: 'AppSlider',
props: {
modelValue: { type: Number, default: 0 },
label: { type: String, default: '' },
from: { type: Number, default: 0 },
to: { type: Number, default: 100 },
step: { type: Number, default: 1 },
changeOnUpdate: { type: Boolean, default: false }
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const { t } = useI18n()
const sliderRef = ref<HTMLElement | null>(null)
const syncSliderValue = debounce(() => {
const slider = sliderRef.value as any
if (!slider?.noUiSlider) return
const sliderValue = parseFloat(slider.noUiSlider.get())
if (sliderValue !== props.modelValue) {
slider.noUiSlider.set(props.modelValue)
}
}, 100)
onMounted(() => {
const slider = sliderRef.value as any
const isValidValue = props.modelValue !== undefined
noUiSlider.create(slider, {
start: isValidValue ? props.modelValue : props.to,
connect: 'lower',
step: props.step,
range: { min: props.from, max: props.to }
})
const eventChange = props.changeOnUpdate ? 'update' : 'set'
slider.noUiSlider.on(eventChange, (values: string[]) => {
emit('update:modelValue', parseFloat(values[0] ?? '0'))
})
})
slider.noUiSlider.on('slide', (values: string[]) => {
this.preValue = parseFloat(values[0])
})
const eventChange = this.changeOnUpdate ? 'update' : 'set'
slider.noUiSlider.on(eventChange, (values: string[]) => {
const value = parseFloat(values[0])
onUpdated(syncSliderValue)
this.$emit('input', value)
})
}
render() {
return <div class="app-slider">
<div class="app-slider__content">
<p class="mb-2"> { this.$t(this.label) } </p>
</div>
<div class="center-x">
<div ref="slider" class="slider-input"></div>
return () => (
<div class="app-slider">
<div class="app-slider__content">
<p class="mb-2">{t(props.label)}</p>
</div>
<div class="center-x">
<div ref={sliderRef} class="slider-input" />
</div>
</div>
</div>
)
}
}
})

@ -1,44 +1,48 @@
import { Component, Vue } from 'vue-property-decorator'
import { CreateElement } from 'vue/types/umd'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { AppTheme } from '@/models/app'
const components: { [k: string]: Vue } = {}
const requireComponent = require.context(
'@/components/themes', true, /\.(vue|tsx)$/
)
requireComponent.keys().forEach(fileName => {
let componentName = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
const isInFolder = componentName.includes('/')
import { defineComponent, computed, resolveComponent, h } from 'vue'
import { useAppStore } from '@/store/app'
import flow from '@/components/themes/flow'
import sphere from '@/components/themes/sphere'
import rings from '@/components/themes/rings'
import plasma from '@/components/themes/plasma'
import destruction from '@/components/themes/destruction'
import tenderness from '@/components/themes/tenderness'
import planet from '@/components/themes/planet'
import random from '@/components/themes/random'
import infinity from '@/components/themes/infinity'
import osmos from '@/components/themes/osmos'
import space from '@/components/themes/space'
import suprematism from '@/components/themes/suprematism'
const themeMap: Record<string, any> = {
flow,
sphere,
rings,
plasma,
destruction,
tenderness,
planet,
random,
infinity,
osmos,
space,
suprematism
}
if (isInFolder) {
componentName = componentName.split('/')[0]
export default defineComponent({
name: 'BackgroundImage',
setup() {
const appStore = useAppStore()
const theme = computed(() => appStore.activeTheme)
return () => {
const component = themeMap[theme.value.component ?? 'random'] ?? themeMap.random
return (
<div class="background-image">
<div class="mask-background" />
{h(component)}
</div>
)
}
}
const componentConfig = requireComponent(fileName)
components[componentName] = componentConfig.default || componentConfig
})
@Component({ components })
export default class BackgroundImage extends Vue {
get theme(): AppTheme {
return AppModule.activeTheme
}
get isOpenLogin(): boolean {
return PageModule.isOpenBlock('login')
}
render(h: CreateElement): JSX.Element {
const body = h(components[this.theme.component ?? 'random'])
return <div class='background-image'>
<div class="mask-background"></div>
{ body }
</div>
}
}

@ -1,29 +1,22 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent } from 'vue'
import { useAppStore } from '@/store/app'
@Component({
components: { AppIcon }
})
export default class BatteryIcon extends Vue {
get batteryLevel() {
return AppModule.batteryLevel
}
get isCharging() {
return AppModule.isCharging
}
render() {
return <div class="app-bar-battery">
<div class="app-bar-battery-icon">
<div
class={`app-bar-battery-icon__fill ${this.isCharging ? 'charging' : ''}`}
style={{ width: `${this.batteryLevel}%` }}
/>
{ this.isCharging && <AppIcon name="charge" class="app-bar-battery-icon__charge"/>}
export default defineComponent({
name: 'BatteryIcon',
setup() {
const appStore = useAppStore()
return () => (
<div class="app-bar-battery">
<div class="app-bar-battery-icon">
<div
class={`app-bar-battery-icon__fill ${appStore.isCharging ? 'charging' : ''}`}
style={{ width: `${appStore.batteryLevel}%` }}
/>
{appStore.isCharging && <AppIcon name="charge" class="app-bar-battery-icon__charge" />}
</div>
<span class="app-bar-battery__percent">{appStore.batteryLevel}%</span>
</div>
<span class="app-bar-battery__percent"> { this.batteryLevel }% </span>
</div>
)
}
}
})

@ -1,19 +1,16 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent } from 'vue'
import { useAppStore } from '@/store/app'
@Component({
components: { AppIcon }
})
export default class BrightIcon extends Vue {
get brightLevel() {
return AppModule.brightness
}
render() {
return <div class="app-bar__bright">
<AppIcon name="brightness" class="brightness-icon"/>
{ this.brightLevel }
</div>
export default defineComponent({
name: 'BrightIcon',
setup() {
const appStore = useAppStore()
return () => (
<div class="app-bar__bright">
<AppIcon name="brightness" class="brightness-icon" />
{appStore.brightness}
</div>
)
}
}
})

@ -1,46 +1,40 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted } from 'vue'
import { useAppStore } from '@/store/app'
let elapsedTime = 0
let frameCount = 0
let lastTime = new Date().getTime()
@Component
export default class FrameRateBlock extends Vue {
FPS = 0
get isShow() {
return AppModule.showFrameRate
}
mounted() {
this.FPS = 0
this.drawScene()
lastTime = new Date().getTime()
}
drawScene() {
const now = new Date().getTime()
frameCount++
elapsedTime += (now - lastTime)
lastTime = now
if (elapsedTime >= 1000) {
const fps = frameCount
frameCount = 0
elapsedTime -= 1000
this.FPS = fps
export default defineComponent({
name: 'FrameRateBlock',
setup() {
const appStore = useAppStore()
const FPS = ref(0)
const isShow = computed(() => appStore.showFrameRate)
const drawScene = () => {
const now = new Date().getTime()
frameCount++
elapsedTime += now - lastTime
lastTime = now
if (elapsedTime >= 1000) {
FPS.value = frameCount
frameCount = 0
elapsedTime -= 1000
}
if (isShow.value) {
requestAnimationFrame(drawScene)
}
}
if (this.isShow) {
requestAnimationFrame(this.drawScene)
}
}
onMounted(() => {
FPS.value = 0
drawScene()
lastTime = new Date().getTime()
})
render() {
return <div class="frame-rate-block"> FPS: { this.FPS } </div>
return () => <div class="frame-rate-block">FPS: {FPS.value}</div>
}
}
})

@ -1,21 +1,14 @@
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent } from 'vue'
import AppButton from '@/components/app/AppButton'
@Component({
components: { AppIcon, AppButton },
funtional: true
})
export default class GithubButton extends Vue {
render() {
return <AppButton
fab
href="https://github.com/Warinyourself"
class="github-link active-block"
target="_blank"
>
<AppIcon name="github"/>
</AppButton>
export default defineComponent({
name: 'GithubButton',
setup() {
return () => (
<AppButton fab href="https://github.com/Warinyourself" class="github-link active-block" target="_blank">
<AppIcon name="github" />
</AppButton>
)
}
}
})

@ -1,84 +1,42 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import UserAvatar from '@/components/base/UserAvatar'
import UserInput from '@/components/base/UserInput'
import SettingsView from '@/components/base/settings/SettingsView'
import { defineComponent, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import UserAvatar from './UserAvatar'
import UserInput from './UserInput'
import SettingsView from './settings/SettingsView'
import { focusInputPassword, getDesktopIcon } from '@/utils/helper'
@Component({
components: {
AppIcon,
UserInput,
UserAvatar,
SettingsView
}
})
export default class LoginComponent extends Vue {
get theme() {
return AppModule.activeTheme
}
get classObject() {
return {
'login-menu': true
}
}
get activeBlock() {
return PageModule.activeBlock
}
get currentDesktop() {
return AppModule.currentDesktop
}
get currentDesktopIcon() {
return getDesktopIcon(this.currentDesktop?.key)
}
get mainTabIndex() {
return PageModule.mainTabIndex
}
export default defineComponent({
name: 'LoginComponent',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
get tabs() {
const tabs = [this.$t('settings.choice-themes'), this.$t('settings.general')]
const hasThemeSettings = AppModule.activeTheme?.settings?.length !== undefined
const activeBlock = computed(() => pageStore.activeBlock)
const currentDesktopIcon = computed(() => getDesktopIcon(appStore.currentDesktop?.key))
if (hasThemeSettings) {
tabs.splice(1, 0, this.$t('settings.customize-theme'))
const openSettingsTab = (event: Event) => {
event.preventDefault()
event.stopPropagation()
pageStore.openBlock({ id: 'settings' })
pageStore.openTab({ type: 'settings' })
}
return tabs
}
mounted() {
this.$nextTick(focusInputPassword)
}
activateTab(index: number) {
PageModule.SET_STATE_PAGE({ key: 'mainTabIndex', value: index })
}
openSettingsTab(event: Event) {
event.preventDefault()
event.stopPropagation()
PageModule.openBlock({ id: 'settings' })
PageModule.openTab({ type: 'settings' })
}
render() {
return <div class={ `block-${this.activeBlock?.id}` }>
<div class={ `active-interface login-view login-view--${PageModule.loginPosition}` }>
<AppIcon name={ this.currentDesktopIcon } onClick={ this.openSettingsTab } class='desktop-icon'/>
<AppIcon name={ AppModule.currentOs } onClick={ this.openSettingsTab } class='system-icon'/>
<UserAvatar />
<UserInput />
onMounted(() => {
void Promise.resolve().then(focusInputPassword)
})
return () => (
<div class={`block-${activeBlock.value?.id}`}>
<div class={`active-interface login-view login-view--${pageStore.loginPosition}`}>
<AppIcon name={currentDesktopIcon.value} onClick={openSettingsTab} class="desktop-icon" />
<AppIcon name={appStore.currentOs} onClick={openSettingsTab} class="system-icon" />
<UserAvatar />
<UserInput />
</div>
</div>
</div>
)
}
}
})

@ -1,89 +1,57 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import UserAvatar from '@/components/base/UserAvatar'
import SettingsView from '@/components/base/settings/SettingsView'
@Component({
components: {
AppIcon,
UserAvatar,
SettingsView
}
})
export default class SettingsComponent extends Vue {
get theme() {
return AppModule.activeTheme
}
get classObject() {
return {
'login-menu': true
import { defineComponent, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import UserAvatar from './UserAvatar'
import SettingsView from './settings/SettingsView'
export default defineComponent({
name: 'SettingsComponent',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const { t } = useI18n()
const isViewThemeOnly = computed(() => appStore.viewThemeOnly)
const mainTabIndex = computed(() => pageStore.mainTabIndex)
const tabs = computed(() => {
const list = [t('settings.choice-themes'), t('settings.general')]
if (appStore.activeTheme?.settings?.length !== undefined) {
list.splice(1, 0, t('settings.customize-theme'))
}
return list
})
const openLogin = (event: Event) => {
event.preventDefault()
event.stopPropagation()
pageStore.openBlock({ id: 'login' })
}
}
get activeBlock() {
return PageModule.activeBlock
}
get currentDesktop() {
return AppModule.currentDesktop
}
get mainTabIndex() {
return PageModule.mainTabIndex
}
get tabs() {
const tabs = [this.$t('settings.choice-themes').toString(), this.$t('settings.general').toString()]
const hasThemeSettings = AppModule.activeTheme?.settings?.length !== undefined
if (hasThemeSettings) {
tabs.splice(1, 0, this.$t('settings.customize-theme').toString())
const activateTab = (index: number) => {
pageStore.mainTabIndex = index
}
return tabs
}
get isViewThemeOnly() {
return AppModule.viewThemeOnly
}
activateTab(index: number) {
PageModule.SET_STATE_PAGE({ key: 'mainTabIndex', value: index })
}
openLogin(event: Event) {
event.preventDefault()
event.stopPropagation()
PageModule.openBlock({ id: 'login' })
}
generateTab(tab: string, index: number) {
const classes = `user-settings-tab ${this.mainTabIndex === index ? 'active' : ''}`
return <div class={ classes } onClick={() => this.activateTab(index)}> { tab } </div>
}
generateTabs() {
return <div class='user-settings-tabs'>
{ this.tabs.map(this.generateTab) }
</div>
}
render() {
return <div class="block-settings login-content-settings">
<div class="login-view active-interface">
<AppIcon onClick={ this.openLogin } name='collapse' class='system-icon' />
const generateTab = (tab: string, index: number) => {
const classes = `user-settings-tab ${mainTabIndex.value === index ? 'active' : ''}`
return (
<div class={classes} onClick={() => activateTab(index)}>
{tab}
</div>
)
}
{ !this.isViewThemeOnly && <UserAvatar /> }
{ this.generateTabs() }
<SettingsView />
return () => (
<div class="block-settings login-content-settings">
<div class="login-view active-interface">
<AppIcon onClick={openLogin} name="collapse" class="system-icon" />
{!isViewThemeOnly.value && <UserAvatar />}
<div class="user-settings-tabs">{tabs.value.map(generateTab)}</div>
<SettingsView />
</div>
</div>
</div>
)
}
}
})

@ -1,19 +1,18 @@
import { Component, Vue } from 'vue-property-decorator'
import { PageModule } from '@/store/page'
import AppIcon from '@/components/app/AppIcon.vue'
import ShutdownMenu from '@/components/base/ShutdownMenu'
import { defineComponent } from 'vue'
import { systemActionsObject } from '@/utils/helper'
import ShutdownMenu from './ShutdownMenu'
@Component({
components: { AppIcon, ShutdownMenu }
})
export default class ShutdownBlock extends Vue {
render() {
return <div class="shutdown-block active-block">
<ShutdownMenu />
<div class="shutdown-button active-interface" onClick={ systemActionsObject.shutdown }>
<AppIcon name="shutdown" />
export default defineComponent({
name: 'ShutdownButton',
setup() {
return () => (
<div class="shutdown-block active-block">
<ShutdownMenu />
<div class="shutdown-button active-interface" onClick={systemActionsObject.shutdown}>
<AppIcon name="shutdown" />
</div>
</div>
</div>
)
}
}
})

@ -1,40 +1,27 @@
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent, computed } from 'vue'
import { systemActionsObject } from '@/utils/helper'
import { LightdmHandler } from '@/utils/lightdm'
@Component({
components: { AppIcon }
})
export default class ShutdownMenu extends Vue {
get actions() {
return [
{
icon: 'restart',
show: LightdmHandler.canRestart,
callback: systemActionsObject.restart
},
{
icon: 'suspend',
show: LightdmHandler.canSuspend,
callback: systemActionsObject.suspend
},
{
icon: 'hibernate',
show: LightdmHandler.canHibernate,
callback: systemActionsObject.hibernate
}
].filter(({ show }) => show)
}
export default defineComponent({
name: 'ShutdownMenu',
setup() {
const actions = computed(() =>
[
{ icon: 'restart', show: LightdmHandler.canRestart, callback: systemActionsObject.restart },
{ icon: 'suspend', show: LightdmHandler.canSuspend, callback: systemActionsObject.suspend },
{ icon: 'hibernate', show: LightdmHandler.canHibernate, callback: systemActionsObject.hibernate }
].filter(({ show }) => show)
)
render() {
return <div class="shutdown-menu active-interface">
{ this.actions.map((action) => {
return <div class="shutdown-item" onClick={ action.callback }>
<AppIcon name={ action.icon } />
</div>
})}
</div>
return () => (
<div class="shutdown-menu active-interface">
{actions.value.map((action) => (
<div class="shutdown-item" onClick={action.callback}>
<AppIcon name={action.icon} />
</div>
))}
</div>
)
}
}
})

@ -1,57 +1,36 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import { LightdmUsers } from '@/models/lightdm'
import { PageModule } from '@/store/page'
import { defineComponent, computed } from 'vue'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import timer from '@/utils/time'
@Component({
components: { AppIcon }
})
export default class UserAvatar extends Vue {
get currentTime() {
return this.isOpenSettings ? timer.longTime : timer.shortTime
}
get isOpenSettings() {
return PageModule.isOpenBlock('settings')
}
get isSupportFullApi() {
return AppModule.isSupportFullApi
}
get locale() {
return PageModule.locale
}
get user() {
return AppModule.currentUser
import type { LightdmUsers } from '@/models/lightdm'
export default defineComponent({
name: 'UserAvatar',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const isOpenSettings = computed(() => pageStore.isOpenBlock('settings'))
const currentTime = computed(() => isOpenSettings.value ? timer.longTime : timer.shortTime)
const buildUserAvatar = (image: string | undefined) =>
image
? <img class="user-avatar" src={image} />
: <AppIcon name="user" />
const buildUser = (user: LightdmUsers) => (
<div class="user-choice" key={user.username}>
<p class="time">{appStore.isSupportFullApi ? '' : currentTime.value}</p>
{buildUserAvatar(user?.image)}
<div class="user-name">{user?.display_name}</div>
</div>
)
return () => (
<transition-group tag="div" name="fade-bottom" class="transition-group">
{appStore.users.filter(({ username }) => username === appStore.currentUser?.username).map(buildUser)}
</transition-group>
)
}
get users() {
return AppModule.users
}
buildUserAvatar(image: string | undefined) {
const defaultAvatar = <AppIcon name='user'/>
const userAvatar = <img class='user-avatar' src={image} />
return image ? userAvatar : defaultAvatar
}
buildUser(user: LightdmUsers) {
return <div class='user-choice' key={ user.username }>
<p class='time'> { this.isSupportFullApi ? '' : this.currentTime } </p>
{ this.buildUserAvatar(user?.image) }
<div class='user-name'> { user?.display_name } </div>
</div>
}
render() {
return <transition-group tag="div" name="fade-bottom" class="transition-group">
{ this.users.filter(({ username }) => username === this.user?.username).map(this.buildUser) }
</transition-group>
}
}
})

@ -1,72 +1,52 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import { PageModule } from '@/store/page'
@Component({
components: { AppIcon }
})
export default class UserInput extends Vue {
logging = false
get user() {
return AppModule.currentUser
}
get passwordValue(): string {
return AppModule.password
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
export default defineComponent({
name: 'UserInput',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const { t } = useI18n()
const openSettings = (event: Event) => {
event.preventDefault()
event.stopPropagation()
pageStore.openBlock({ id: 'settings' })
}
const handleKeyup = (event: KeyboardEvent) => {
appStore.password = (event.target as HTMLInputElement)?.value || ''
}
return () => (
<div class="user-input">
<AppIcon class="settings-button" name="settings" onClick={openSettings} />
<input
id="password"
type={appStore.showPassword ? 'text' : 'password'}
name="password"
autocomplete="on"
autofocus
class="mousetrap"
placeholder={t('text.password')}
onKeyup={handleKeyup}
value={appStore.password}
/>
<AppIcon
class={['icon icon-eye', { hide: !appStore.showPassword }]}
name="eye"
onClick={() => appStore.toggleShowPassword()}
/>
<button class="user-input-login" onClick={() => appStore.login()}>
<AppIcon name="arrow" />
</button>
</div>
)
}
get showPassword(): boolean {
return AppModule.showPassword
}
login(): void {
AppModule.login()
}
handleKeyup(event: KeyboardEvent): void {
AppModule.SET_STATE_APP({ key: 'password', value: (event.target as HTMLInputElement)?.value || '' })
}
openSettings(event: Event): void {
event.preventDefault()
event.stopPropagation()
PageModule.openBlock({ id: 'settings' })
}
render() {
return <div class='user-input'>
<AppIcon class='settings-button' name='settings' onClick={ this.openSettings } />
<input
id='password'
type={ this.showPassword ? 'text' : 'password'}
name='password'
autocomplete='on'
autofocus
ref='password'
class='mousetrap'
placeholder={ this.$t('text.password').toString() }
onKeyup={this.handleKeyup}
value={this.passwordValue}
readonly={this.logging}
/>
<AppIcon
class={ ['icon icon-eye', { hide: !this.showPassword }] }
name='eye'
onClick={ AppModule.toggleShowPassword }
/>
<button
class='user-input-login'
onClick={this.login}
>
<AppIcon name='arrow' />
</button>
</div>
}
}
})

@ -1,130 +1,128 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import { AppInputButton, AppInputColor, AppInputThemeGeneral, AppInputThemePalette, AppInputThemeSelector, AppInputThemeSlider, AppTheme } from '@/models/app'
import { defineComponent, computed, onUpdated, type VNode } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import AppSlider from '@/components/app/AppSlider'
import AppButton from '@/components/app/AppButton'
import AppCheckbox from '@/components/app/AppCheckbox'
import AppSelector from '@/components/app/AppSelector'
import AppColorSelector from '@/components/app/AppColorSelector'
import AppPaletteSelector from '@/components/app/AppPaletteSelector'
import { Debounce } from '@/utils/helper'
import { Route } from 'vue-router/types/router'
@Component({
components: {
AppSlider,
AppButton,
AppSelector,
AppColorSelector,
AppPaletteSelector
}
})
export default class SettingsCustom extends Vue {
syncThemeWithQuery = true
get theme() {
return AppModule.activeTheme as AppTheme
}
get inputs() {
return this.theme.settings || []
}
@Debounce()
updateQuery() {
if (this.syncThemeWithQuery) {
AppModule.syncStoreWithQuery()
import { createDebounce } from '@/utils/helper'
import type {
AppInputButton,
AppInputColor,
AppInputThemeGeneral,
AppInputThemePalette,
AppInputThemeSelector,
AppInputThemeSlider
} from '@/models/app'
export default defineComponent({
name: 'SettingsCustom',
setup() {
const appStore = useAppStore()
const { t } = useI18n()
const inputs = computed(() => appStore.activeTheme?.settings || [])
const debouncedSync = createDebounce(() => appStore.syncStoreWithQuery(), 300)
onUpdated(() => debouncedSync())
const buildSlider = (input: AppInputThemeSlider) => {
const { label, value, options: { step, min: from, max: to } } = input
return (
<AppSlider
to={to}
step={step}
from={from}
label={label}
modelValue={value as number}
onUpdate:modelValue={(v: number) => {
appStore.changeSettingsThemeInput({ key: input.name, value: v })
input.callback?.(v)
}}
/>
)
}
}
buildSlider(input: AppInputThemeSlider) {
const { label, value, options: { step, min: from, max: to, changeOnUpdate } } = input
const props = { to, step, from, label, value, changeOnUpdate }
const handler = (value: number) => {
AppModule.changeSettingsThemeInput({ key: input.name, value })
if (input.callback) {
input.callback(value)
}
const buildSelector = (input: AppInputThemeSelector) => {
const { label, value, values } = input
return (
<AppSelector
label={label}
items={[...values]}
modelValue={value as string}
onUpdate:modelValue={(v: string) => {
appStore.changeSettingsThemeInput({ key: input.name, value: v })
}}
/>
)
}
return <AppSlider {...{ props, on: { input: handler } } } />
}
buildSelector(input: AppInputThemeSelector) {
const { label, value, values } = input
const props = { label, value, items: values }
const handler = (value: number) => {
AppModule.changeSettingsThemeInput({ key: input.name, value })
const buildColor = (input: AppInputThemeGeneral) => {
const { label, value, options } = input
return (
<AppColorSelector
class={options?.class}
label={label}
modelValue={value as string}
onUpdate:modelValue={(color: AppInputColor) => {
appStore.changeSettingsThemeInput({ key: input.name, value: color.hex })
input.callback?.(color.hex)
}}
/>
)
}
return <AppSelector {...{ props, on: { input: handler } } } />
}
buildColor(input: AppInputThemeGeneral) {
const { label, value, options } = input
const props = { label, value }
const handler = (color: AppInputColor) => {
AppModule.changeSettingsThemeInput({ key: input.name, value: color.hex })
if (input.callback) {
input.callback(color.hex)
}
const buildCheckbox = (input: AppInputThemeGeneral) => {
const { label, value } = input
return (
<AppCheckbox
label={t(label)}
modelValue={value as boolean}
onUpdate:modelValue={(v: boolean) => {
appStore.changeSettingsThemeInput({ key: input.name, value: v })
input.callback?.(v)
}}
/>
)
}
return <AppColorSelector {...{ props, class: options?.class, on: { input: handler } } } />
}
buildCheckbox(input: AppInputThemeGeneral) {
const { label, value } = input
const props = { label: this.$t(label), value }
const handler = (value: boolean) => {
AppModule.changeSettingsThemeInput({ key: input.name, value })
if (input.callback) {
input.callback(value)
}
}
return <AppCheckbox {...{ props, on: { input: handler } } }/>
}
buildPalette(input: AppInputThemePalette) {
const { label, value, values } = input
const props = { label, value, values }
const handler = (value: boolean) => {
AppModule.changeSettingsThemeInput({ key: input.name, value })
const buildPalette = (input: AppInputThemePalette) => {
const { label, value, values } = input
return (
<AppPaletteSelector
label={label}
modelValue={value as number}
values={values}
onUpdate:modelValue={(v: number) => {
appStore.changeSettingsThemeInput({ key: input.name, value: v })
}}
/>
)
}
return <AppPaletteSelector {...{ props, on: { input: handler } } } />
}
buildButton(input: AppInputButton) {
return <AppButton onClick={ input.callback }> { this.$t(input.label) } </AppButton>
}
buildUnknown(input: AppInputThemeGeneral) {
return <div> Doesn't exist input - { input.type } </div>
}
updated() {
this.updateQuery()
}
render() {
const builderObject = {
color: this.buildColor,
slider: this.buildSlider,
checkbox: this.buildCheckbox,
palette: this.buildPalette,
button: this.buildButton,
selector: this.buildSelector
const buildButton = (input: AppInputButton) => (
<AppButton onClick={input.callback}>{t(input.label)}</AppButton>
)
const buildUnknown = (input: AppInputThemeGeneral) => (
<div>Doesn't exist input - {input.type}</div>
)
const builderObject: Record<string, (input: any) => VNode> = {
color: buildColor,
slider: buildSlider,
checkbox: buildCheckbox,
palette: buildPalette,
button: buildButton,
selector: buildSelector
}
return <div class='user-settings-custom'>
{ this.inputs.map((input) => (builderObject[input.type] || this.buildUnknown)(input as any)) }
</div>
return () => (
<div class="user-settings-custom">
{inputs.value.map((input) => (builderObject[input.type] || buildUnknown)(input))}
</div>
)
}
}
})

@ -1,42 +1,33 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, computed } from 'vue'
import { useAppStore } from '@/store/app'
import { AppModule } from '@/store/app'
const themeImages = import.meta.glob('@/assets/images/themes/*/index.png', { eager: true, import: 'default' }) as Record<string, string>
@Component
export default class SettingsThemes extends Vue {
get themes() {
return AppModule.themes
}
get activeTheme() {
return AppModule.activeTheme
}
setImage(name: string) {
let url
try {
url = require(`@/assets/images/themes/${name}/index.png`)
} catch {
url = 'notFound'
}
return url
}
render() {
return <div class='user-settings-themes'>
{
this.themes.map(theme => {
const isActiveTheme = theme.name === this.activeTheme.name
function getThemeImage(name: string): string {
const key = Object.keys(themeImages).find((k) => k.includes(`/${name.toLowerCase()}/`))
return key ? (themeImages[key] ?? 'notFound') : 'notFound'
}
return <img
class={`user-settings-theme ${isActiveTheme ? 'active' : ''}`}
onClick={() => AppModule.changeTheme(theme.name)}
src={this.setImage(theme.name.toLowerCase())}
/>
})
}
</div>
export default defineComponent({
name: 'SettingsThemes',
setup() {
const appStore = useAppStore()
const themes = computed(() => appStore.themes)
const activeTheme = computed(() => appStore.activeTheme)
return () => (
<div class="user-settings-themes">
{/* {themes.value.map((theme) => {
const isActive = theme.name === activeTheme.value.name
return (
<img
class={`user-settings-theme ${isActive ? 'active' : ''}`}
onClick={() => appStore.changeTheme(theme.name)}
src={getThemeImage(theme.name)}
/>
)
})} */}
</div>
)
}
}
})

@ -1,45 +1,30 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import SettingsThemes from '@/components/base/settings/SettingsThemes'
import SettingsCustom from '@/components/base/settings/SettingsCustom'
import SettingsGeneral from '@/components/base/settings/general/SettingsGeneral'
import { PageModule } from '@/store/page'
@Component({
components: {
AppIcon,
SettingsThemes,
SettingsCustom,
SettingsGeneral
}
})
export default class SettingsView extends Vue {
get mainTabIndex() {
return PageModule.mainTabIndex
}
get user() {
return AppModule.currentUser
}
get users() {
return AppModule.users
}
render() {
const mapTabs = [<SettingsThemes />, <SettingsGeneral />]
const hasThemeSettings = AppModule.activeTheme?.settings?.length
if (hasThemeSettings) {
mapTabs.splice(1, 0, <SettingsCustom />)
import { defineComponent, computed } from 'vue'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import SettingsThemes from './SettingsThemes'
import SettingsCustom from './SettingsCustom'
import SettingsGeneral from './general/SettingsGeneral'
export default defineComponent({
name: 'SettingsView',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const mainTabIndex = computed(() => pageStore.mainTabIndex)
return () => {
const mapTabs = [<SettingsThemes />, <SettingsGeneral />]
const hasThemeSettings = appStore.activeTheme?.settings?.length
if (hasThemeSettings) {
mapTabs.splice(1, 0, <SettingsCustom />)
}
return (
<div class="user-settings">
<div key={mainTabIndex.value}>{mapTabs[mainTabIndex.value]}</div>
</div>
)
}
const activeTab = <div key={this.mainTabIndex}> { mapTabs[this.mainTabIndex] } </div>
return <div class='user-settings'>
{ activeTab }
</div>
}
}
})

@ -1,46 +1,39 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import AppCheckbox from '@/components/app/AppCheckbox'
import { AppModule } from '@/store/app'
@Component({
components: {
AppCheckbox
}
})
export default class SettingsCheckboxes extends Vue {
get bodyClass() {
return AppModule.bodyClass
}
buildCheckbox(name: string) {
return <AppCheckbox
label={ this.$t(`settings.${name}`) }
value={ this.bodyClass[name] }
onInput={ (value: boolean) => {
AppModule.CHANGE_BODY_CLASS({ key: name, value })
AppModule.syncStoreWithQuery()
}}
/>
}
generateRandomTheme(value: boolean) {
AppModule.SET_STATE_APP({ key: 'generateRandomThemes', value })
}
render() {
return <div class="grid-two">
<h2 class="title"> { this.$t('settings.performance') } </h2>
{ this.buildCheckbox('blur') }
{ this.buildCheckbox('show-framerate') }
{ this.buildCheckbox('no-transition') }
{ this.buildCheckbox('only-ui') }
export default defineComponent({
name: 'SettingsCheckboxes',
setup() {
const appStore = useAppStore()
const { t } = useI18n()
const buildCheckbox = (name: string) => (
<AppCheckbox
inline={ true }
label={ this.$t('settings.generate-random-theme') }
value={ AppModule.generateRandomThemes }
onInput={ this.generateRandomTheme }
label={t(`settings.${name}`)}
modelValue={appStore.bodyClass[name]}
onUpdate:modelValue={(value: boolean) => {
appStore.changeBodyClass({ key: name, value })
appStore.syncStoreWithQuery()
}}
/>
</div>
)
return () => (
<div class="grid-two">
<h2 class="title">{t('settings.performance')}</h2>
{buildCheckbox('blur')}
{buildCheckbox('show-framerate')}
{buildCheckbox('no-transition')}
{buildCheckbox('only-ui')}
<AppCheckbox
inline={true}
label={t('settings.generate-random-theme')}
modelValue={appStore.generateRandomThemes}
onUpdate:modelValue={(value: boolean) => { appStore.generateRandomThemes = value }}
/>
</div>
)
}
}
})

@ -1,60 +1,44 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import { useRouter, useRoute } from 'vue-router'
import AppButton from '@/components/app/AppButton'
import SettingsUsers from './SettingsUsers'
import SettingsHotkeys from './SettingsHotkeys'
import SettingsSelectors from './SettingsSelectors'
import SettingsCheckboxes from './SettingsCheckboxes'
@Component({
components: {
AppIcon,
SettingsUsers,
SettingsHotkeys,
SettingsSelectors,
SettingsCheckboxes
}
})
export default class SettingsGeneral extends Vue {
get showFrameRate() {
return AppModule.showFrameRate
}
get isViewThemeOnly() {
return AppModule.viewThemeOnly
}
resetSettings() {
localStorage.clear()
AppModule.setUpSettings()
const hasQyery = Object.keys(this.$route.query).length
if (hasQyery) { this.$router.replace({}) }
window.location.reload()
}
changeTheme() {
throw Error('Change theme')
}
render() {
return <div class='user-settings-general'>
<SettingsCheckboxes />
<SettingsSelectors />
{ !this.isViewThemeOnly && <SettingsUsers /> }
<SettingsHotkeys />
<div class="help-block">
<AppButton onClick={ this.resetSettings } block class="mb-2">
{ this.$t('settings.reset-settings') }
</AppButton>
<AppButton onClick={ this.changeTheme } block>
{ this.$t('settings.change-theme') }
</AppButton>
export default defineComponent({
name: 'SettingsGeneral',
setup() {
const appStore = useAppStore()
const { t } = useI18n()
const router = useRouter()
const route = useRoute()
const isViewThemeOnly = computed(() => appStore.viewThemeOnly)
const resetSettings = () => {
localStorage.clear()
appStore.setUpSettings()
if (Object.keys(route.query).length) {
router.replace({})
}
window.location.reload()
}
return () => (
<div class="user-settings-general">
<SettingsCheckboxes />
<SettingsSelectors />
{!isViewThemeOnly.value && <SettingsUsers />}
<SettingsHotkeys />
<div class="help-block">
<AppButton onClick={resetSettings} block class="mb-2">
{t('settings.reset-settings')}
</AppButton>
</div>
</div>
</div>
)
}
}
})

@ -1,21 +1,27 @@
import { Component, Vue } from 'vue-property-decorator'
import { hotkeys, hotkeysType } from '@/utils/hotkeys'
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { hotkeys } from '@/utils/hotkeys'
import type { hotkeysType } from '@/utils/hotkeys'
@Component
export default class SettingsHotkeys extends Vue {
buildHotKeyBlock({ keys, title }: hotkeysType) {
return <div class="settings-hotkey">
<p class="settings-hotkey-title"> { this.$t(title) }: </p>
<div class="settings-hotkey-blocks">
{ keys.map((key) => <div class="settings-hotkey-block"> { key } </div>) }
export default defineComponent({
name: 'SettingsHotkeys',
setup() {
const { t } = useI18n()
const buildHotKeyBlock = ({ keys, title }: hotkeysType) => (
<div class="settings-hotkey">
<p class="settings-hotkey-title">{t(title)}:</p>
<div class="settings-hotkey-blocks">
{keys.map((key) => <div class="settings-hotkey-block">{key}</div>)}
</div>
</div>
</div>
}
)
render() {
return <div class="hotkeys-section">
<h2 class="title mb-3"> { this.$t('settings.keyboard.title') }: </h2>
{ hotkeys.map(this.buildHotKeyBlock) }
</div>
return () => (
<div class="hotkeys-section">
<h2 class="title mb-3">{t('settings.keyboard.title')}:</h2>
{hotkeys.map(buildHotKeyBlock)}
</div>
)
}
}
})

@ -1,108 +1,107 @@
import { Component, Vue } from 'vue-property-decorator'
import AppSelector, { AppSelectorProps as SProps } from '@/components/app/AppSelector'
import { defineComponent, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import { useRouter } from 'vue-router'
import AppSelector from '@/components/app/AppSelector'
import AppSlider from '@/components/app/AppSlider'
import { Debounce, generateDesktopIcons, languageMap, setCSSVariable } from '@/utils/helper'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { createDebounce, generateDesktopIcons, languageMap, setCSSVariable } from '@/utils/helper'
import { osList } from '@/utils/constant'
import { LoginPosition } from '@/models/page'
import { AppInputThemeSlider, AppInputThemeValue } from '@/models/app'
import type { LoginPosition } from '@/models/page'
@Component
export default class SettingsSelectors extends Vue {
get isViewThemeOnly() {
return AppModule.viewThemeOnly
}
export default defineComponent({
name: 'SettingsSelectors',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const { t, locale } = useI18n()
const router = useRouter()
get languageList() {
return PageModule.languages.map((language) => ({
text: languageMap[language],
value: language
}))
}
const isViewThemeOnly = computed(() => appStore.viewThemeOnly)
const isSupportFullApi = computed(() => appStore.isSupportFullApi)
get menuPositionItems() {
const positions = ['top', 'left', 'right', 'bottom', 'center']
return positions.map(word => ({
value: word,
text: this.$t(`settings.login-position.${word}`).toString()
}))
}
const languageList = computed(() =>
pageStore.languages.map((lang) => ({ text: languageMap[lang] || lang, value: lang }))
)
get menuPositionValue() {
return {
value: PageModule.loginPosition,
text: this.$t(`settings.login-position.${PageModule.loginPosition}`).toString()
}
}
get isSupportFullApi() {
return AppModule.isSupportFullApi
}
get zoomSlider() {
return this.buildSlider({
label: this.$t('input.zoom-interface').toString(),
value: AppModule.zoom,
options: {
min: 0.5,
max: 2,
step: 0.1
},
callback: this.updateZoom
})
}
changeLanguage(value: string) {
this.$i18n.locale = value
localStorage.setItem('language', value)
PageModule.SET_STATE_PAGE({ key: 'language', value })
}
const menuPositionItems = computed(() =>
['top', 'left', 'right', 'bottom', 'center'].map((word) => ({
value: word,
text: t(`settings.login-position.${word}`)
}))
)
changeLoginPosition(position: string) {
localStorage.setItem('loginPosition', position)
PageModule.SET_STATE_PAGE({ key: 'loginPosition', value: position as LoginPosition })
}
changeDesktop(value: string) {
AppModule.SAVE_STATE_APP({ key: 'desktop', value })
}
const menuPositionValue = computed(() => ({
value: pageStore.loginPosition,
text: t(`settings.login-position.${pageStore.loginPosition}`)
}))
changeOs(value: string) {
AppModule.SAVE_STATE_APP({ key: 'currentOs', value })
}
const changeLanguage = (value: string) => {
locale.value = value
localStorage.setItem('language', value)
pageStore.language = value
}
buildSelector(labelI18n: SProps['label'], items: SProps['items'], value: SProps['value'], callback: (value: string) => void) {
const label = this.$t(`settings.${labelI18n}`)
return <AppSelector
label={ label }
items={ items }
value={ value }
onInput={ callback }
/>
}
const changeLoginPosition = (position: string) => {
localStorage.setItem('loginPosition', position)
pageStore.loginPosition = position as LoginPosition
}
buildSlider(input: Omit<AppInputThemeSlider, 'type' | 'name' | 'icon'>) {
const { label, value, callback, options: { step, min: from, max: to } } = input
const props = { to, step, from, label, value }
const changeDesktop = (value: string) => {
appStore.saveStateApp({ key: 'desktop', value })
}
return <AppSlider {...{ props, on: { input: callback } } } />
}
const changeOs = (value: string) => {
appStore.saveStateApp({ key: 'currentOs', value })
}
@Debounce(100)
updateZoom(value: AppInputThemeValue) {
setCSSVariable('--zoom', value + '' || '1')
AppModule.SET_STATE_APP({ key: 'zoom', value: parseFloat(value + '') })
}
const updateZoom = createDebounce((value: number) => {
setCSSVariable('--zoom', value + '' || '1')
appStore.zoom = parseFloat(value + '')
}, 100)
render() {
return <div class="grid-two">
<h2 class="title"> { this.$t('settings.title') } </h2>
{ this.buildSelector('choice-language', this.languageList, PageModule.language, this.changeLanguage) }
{ this.buildSelector('login-position.about', this.menuPositionItems, this.menuPositionValue, this.changeLoginPosition) }
{ !this.isViewThemeOnly && this.buildSelector('choice-desktop', generateDesktopIcons(), AppModule.currentDesktop?.key, this.changeDesktop) }
{ !this.isViewThemeOnly && this.buildSelector('choice-os', osList, AppModule.currentOs, this.changeOs) }
{ !this.isViewThemeOnly && this.isSupportFullApi && this.zoomSlider }
</div>
return () => (
<div class="grid-two">
<h2 class="title">{t('settings.title')}</h2>
<AppSelector
label={t('settings.choice-language')}
items={languageList.value}
modelValue={pageStore.language}
onUpdate:modelValue={changeLanguage}
/>
<AppSelector
label={t('settings.login-position.about')}
items={menuPositionItems.value}
modelValue={menuPositionValue.value}
onUpdate:modelValue={changeLoginPosition}
/>
{!isViewThemeOnly.value && (
<AppSelector
label={t('settings.choice-desktop')}
items={generateDesktopIcons()}
modelValue={appStore.currentDesktop?.key}
onUpdate:modelValue={changeDesktop}
/>
)}
{!isViewThemeOnly.value && (
<AppSelector
label={t('settings.choice-os')}
items={osList}
modelValue={appStore.currentOs}
onUpdate:modelValue={changeOs}
/>
)}
{!isViewThemeOnly.value && isSupportFullApi.value && (
<AppSlider
label={t('input.zoom-interface')}
from={0.5}
to={2}
step={0.1}
modelValue={appStore.zoom}
onUpdate:modelValue={updateZoom}
/>
)}
</div>
)
}
}
})

@ -1,31 +1,32 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import { LightdmUsers } from '@/models/lightdm'
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/store/app'
import type { LightdmUsers } from '@/models/lightdm'
@Component({ components: { AppIcon } })
export default class SettingsUsers extends Vue {
get users() {
return AppModule.users
}
export default defineComponent({
name: 'SettingsUsers',
setup() {
const appStore = useAppStore()
const { t } = useI18n()
buildUserBlock(user: LightdmUsers) {
const isActive = user.username === AppModule.username
const activateUser = () => AppModule.SAVE_STATE_APP({ key: 'username', value: user.username })
const buildUserBlock = (user: LightdmUsers) => {
const isActive = user.username === appStore.username
const activateUser = () => { appStore.username = user.username }
return <div
onClick={ activateUser }
class={`settings-user-block ${isActive ? 'active' : ''}`}
>
<AppIcon class='settings-user-image' name={ user.image || 'user' } />
<p class="settings-user-name"> { user.display_name } </p>
</div>
}
return (
<div onClick={activateUser} class={`settings-user-block ${isActive ? 'active' : ''}`}>
<AppIcon class="settings-user-image" name={user.image || 'user'} />
<p class="settings-user-name">{user.display_name}</p>
</div>
)
}
render() {
return <div>
<h2 class="title mb-1"> { this.$t('settings.users') } </h2>
<div class="users-grid"> { this.users.map(this.buildUserBlock) } </div>
</div>
return () => (
<div>
<h2 class="title mb-1">{t('settings.users')}</h2>
<div class="users-grid">{appStore.users.map(buildUserBlock)}</div>
</div>
)
}
}
})

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

@ -1,291 +0,0 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import { Wavery } from './lib/wave'
import { Debounce } from '@/utils/helper'
import { changeHsl, hexToRgb, rgbToHsl } from '@/utils/color'
import { AppInputThemePalette } from '@/models/app'
import { AgidaTypes } from '@/utils/constant'
export const OPACITY_ARR = [0.265, 0.4, 0.53, 1]
export const topColors = ['#03C79C', '#00A5B2', '#0080A5', '#005A8D']
export const bottomColors = ['#9C1EFF', '#8518E9', '#6F12D3', '#590ABD']
export const MAX_WAVES = 4
@Component
export default class AgidaTheme extends Vue {
iconWidth = 667
iconHeight = 684
wave = {
height: 300,
width: 1200,
segmentCount: 5,
layerCount: 4,
variance: 1
}
screen = {
width: window.innerWidth,
height: window.innerHeight
}
get palette(): string[] {
const input = AppModule.getThemeInput('palette') as AppInputThemePalette
const index = input?.value as number || 0
const values = input?.values || ['#00CC99', '#6600FF']
return values[index] as string[]
}
get topColor(): string {
return this.palette[0]
}
get bottomColor(): string {
return this.palette[1]
}
get animationSpeed(): number {
return AppModule.getThemeInput('animation-speed')?.value as number || 40
}
get type(): AgidaTypes {
return AppModule.getThemeInput('type')?.value as AgidaTypes || 'agida'
}
get topColors(): string[] {
const initHSL = rgbToHsl(hexToRgb(this.topColor))
const second = changeHsl(initHSL, 10, -3, -5)
const third = changeHsl(initHSL, 25, -3, -10)
const fourth = changeHsl(initHSL, 33, -3, -20)
return [initHSL, second, third, fourth]
}
get bottomColors(): string[] {
const initHSL = rgbToHsl(hexToRgb(this.bottomColor))
const second = changeHsl(initHSL, 5, -3, -5)
const third = changeHsl(initHSL, 10, -3, -10)
const fourth = changeHsl(initHSL, 15, -3, -20)
return [initHSL, second, third, fourth]
}
get bottomActiveColor(): string {
return this.bottomColor
}
get knifeSVG(): JSX.Element {
return <g style="transform: scale(0.7) translate(-27%, -7%);transform-origin: center">
<path d="M1233.68 556.289L490.85 201.979L400.013 392.424C1142.84 746.735 1233.68 556.289 1233.68 556.289Z" fill="white"/>
<path d="M432.556 293.998L479.117 196.382L74.764 3.5156C59.8095 -3.61734 41.904 2.72332 34.7711 17.6778L3.55143 83.1312C-3.58151 98.0857 2.75915 115.991 17.7137 123.124L52.0107 139.483C65.6195 145.974 81.9138 140.204 88.4048 126.595C88.4048 126.595 102.294 97.911 145.302 118.425C188.31 138.938 173.495 170.432 173.495 170.432C173.495 170.432 188.648 139.1 231.655 159.613C274.663 180.127 259.849 211.621 259.849 211.621C259.849 211.621 274.692 180.141 318.009 200.802C361.326 221.463 346.203 252.809 346.203 252.809C346.203 252.809 361.355 221.477 404.363 241.991C447.371 262.504 432.556 293.998 432.556 293.998Z" fill="white"/>
</g>
}
get agidaSVG(): JSX.Element {
return <g>
<path d="M236.522 618.424C214.225 622.652 204.614 620.602 184.495 607.788C175.291 601.813 166 593.101 166 593.101C223.5 607.788 267.5 566.618 293.675 571.01C312.903 574.236 339.52 591.001 339.52 591.001L340 607.531C329.492 607.531 303.286 631.11 289.19 653.664C284.577 660.968 278.81 669.041 276.247 671.604C268.687 679.165 253.309 685.059 244.98 683.522C243.8 683.241 243.063 682.984 242.673 682.537C242.173 681.963 242.241 681.076 242.673 679.421C245.364 667.76 258.947 648.282 274.197 633.545C285.217 623.037 288.293 620.987 305.464 612.273C329.171 600.099 330.068 599.586 327.633 596.639C325.968 594.717 324.174 594.46 314.819 595.101C306.105 595.742 301.364 596.895 291.496 600.996C274.709 607.916 249.208 615.989 236.522 618.424Z" fill="white"/>
<path d="M442.401 618.362C464.698 622.591 474.309 620.54 494.428 607.726C503.632 601.752 512.923 593.039 512.923 593.039C455.423 607.726 411.423 566.557 385.248 570.948C366.02 574.174 339.403 590.939 339.403 590.939L338.923 607.47C349.431 607.47 375.637 631.048 389.733 653.602C394.346 660.906 400.113 668.98 402.676 671.542C410.236 679.103 425.614 684.998 433.943 683.46C435.123 683.179 435.86 682.923 436.25 682.475C436.75 681.902 436.682 681.014 436.25 679.359C433.559 667.698 419.975 648.22 404.726 633.483C393.706 622.975 390.63 620.925 373.459 612.211C349.752 600.037 348.855 599.525 351.289 596.577C352.955 594.655 354.749 594.399 364.104 595.039C372.818 595.68 377.559 596.833 387.426 600.934C404.213 607.854 429.714 615.927 442.401 618.362Z" fill="white"/>
<path d="M327 90H302.361L289.29 68.1809L276.86 90H252.128L276.476 43.7891L252 0H276.348L289.29 21.1218L302.746 0H327L303.182 43.7891L327 90Z" fill="white"/>
<path d="M402 90H377.361L364.29 68.1809L351.86 90H327.128L351.476 43.7891L327 0H351.348L364.29 21.1218L377.746 0H402L378.182 43.7891L402 90Z" fill="white"/>
<path d="M426 90H402V0H426V90Z" fill="white"/>
<path d="M336.52 422.712L316.424 445.056L265.131 387.972H305.054L336.52 422.712Z" fill="white"/>
<path d="M371.661 388.636L318.894 447.306L372.412 506.811H412.458L358.939 447.306L411.707 388.636H371.661Z" fill="white"/>
<path d="M336.52 472.217L316.424 449.873L265.131 506.957H305.054L336.52 472.217Z" fill="white"/>
<path d="M428.98 234.452C428.98 210.159 442.862 190.464 473.076 190.464C526.972 190.464 524.818 169.224 524.818 169.224L521.588 210.444C521.588 234.738 501.845 254.432 477.491 254.432C423.596 254.432 426.827 271.175 426.827 271.175L428.98 234.452Z" fill="white"/>
<path d="M249.02 234.452C249.02 210.159 235.138 190.464 204.924 190.464C151.028 190.464 153.182 169.224 153.182 169.224L156.412 210.444C156.412 234.738 176.155 254.432 200.509 254.432C254.404 254.432 251.173 271.175 251.173 271.175L249.02 234.452Z" fill="white"/>
<path d="M175.251 499.349C175.251 499.349 204.006 522.632 220.283 535.811C236.559 548.99 226.17 577.893 226.17 577.893C226.17 577.893 195.851 553.344 182.769 542.751C169.687 532.158 175.251 499.349 175.251 499.349Z" fill="white"/>
<path d="M129 542.962C129 542.962 157.756 566.246 174.032 579.425C190.308 592.604 216.418 576.43 216.418 576.43C216.418 576.43 186.099 551.881 173.016 541.288C159.934 530.695 129 542.962 129 542.962Z" fill="white"/>
<path d="M118.91 444.189C118.91 444.189 144.144 471.249 158.427 486.566C172.71 501.882 158.399 529.057 158.399 529.057C158.399 529.057 131.793 500.526 120.313 488.215C108.832 475.904 118.91 444.189 118.91 444.189Z" fill="white"/>
<path d="M67.038 480.938C67.038 480.938 92.2719 507.998 106.555 523.315C120.838 538.632 148.945 526.251 148.945 526.251C148.945 526.251 122.339 497.719 110.859 485.408C99.3787 473.097 67.038 480.938 67.038 480.938Z" fill="white"/>
<path d="M83.639 377.825C83.639 377.825 100.437 410.792 109.945 429.452C119.453 448.112 98.205 470.29 98.205 470.29C98.205 470.29 80.4939 435.53 72.8519 420.532C65.2098 405.533 83.639 377.825 83.639 377.825Z" fill="white"/>
<path d="M23.6467 398.852C23.6467 398.852 40.4444 431.82 49.9523 450.48C59.4602 469.14 89.8911 464.986 89.8911 464.986C89.8911 464.986 72.18 430.226 64.5379 415.228C56.8959 400.23 23.6467 398.852 23.6467 398.852Z" fill="white"/>
<path d="M69.276 303.224C69.276 303.224 75.701 339.662 79.3377 360.287C82.9745 380.912 56.1713 395.908 56.1713 395.908C56.1713 395.908 49.3969 357.489 46.4739 340.911C43.5509 324.334 69.276 303.224 69.276 303.224Z" fill="white"/>
<path d="M5.75718 305.793C5.75718 305.793 12.1821 342.231 15.8189 362.856C19.4556 383.481 49.7713 388.405 49.7713 388.405C49.7713 388.405 42.9969 349.986 40.0739 333.409C37.1508 316.831 5.75718 305.793 5.75718 305.793Z" fill="white"/>
<path d="M74.8761 228.242C74.8761 228.242 69.7267 264.882 66.812 285.621C63.8973 306.361 33.7719 312.34 33.7719 312.34C33.7719 312.34 39.2013 273.708 41.544 257.038C43.8868 240.369 74.8761 228.242 74.8761 228.242Z" fill="white"/>
<path d="M13.6722 211.057C13.6722 211.057 8.52276 247.697 5.60806 268.436C2.69336 289.175 30.0035 303.227 30.0035 303.227C30.0035 303.227 35.433 264.595 37.7757 247.925C40.1184 231.256 13.6722 211.057 13.6722 211.057Z" fill="white"/>
<path d="M105.903 160.035C105.903 160.035 89.1059 193.002 79.5979 211.662C70.09 230.323 39.6592 226.169 39.6592 226.169C39.6592 226.169 57.3703 191.409 65.0123 176.41C72.6544 161.412 105.903 160.035 105.903 160.035Z" fill="white"/>
<path d="M53.629 123.86C53.629 123.86 36.8314 156.827 27.3234 175.487C17.8155 194.148 39.063 216.325 39.063 216.325C39.063 216.325 56.7742 181.565 64.4162 166.567C72.0582 151.568 53.629 123.86 53.629 123.86Z" fill="white"/>
<path d="M151.744 97.6303C151.744 97.6303 128.965 126.787 116.071 143.29C103.177 159.793 74.0978 149.909 74.0978 149.909C74.0978 149.909 98.116 119.167 108.479 105.903C118.843 92.6379 151.744 97.6303 151.744 97.6303Z" fill="white"/>
<path d="M107.332 52.1456C107.332 52.1456 84.553 81.3019 71.6592 97.8052C58.7654 114.309 75.3909 140.133 75.3909 140.133C75.3909 140.133 99.4091 109.391 109.773 96.1261C120.136 82.8615 107.332 52.1456 107.332 52.1456Z" fill="white"/>
<path d="M208.501 48.9768C208.501 48.9768 178.951 71.244 162.226 83.8478C145.5 96.4516 119.97 79.3779 119.97 79.3779C119.97 79.3779 151.126 55.8999 164.57 45.7695C178.013 35.6391 208.501 48.9768 208.501 48.9768Z" fill="white"/>
<path d="M175.251 499.349C175.251 499.349 204.006 522.632 220.283 535.811C236.559 548.99 226.17 577.893 226.17 577.893C226.17 577.893 195.851 553.344 182.769 542.751C169.687 532.158 175.251 499.349 175.251 499.349Z" fill="white"/>
<path d="M129 542.962C129 542.962 157.756 566.246 174.032 579.425C190.308 592.604 216.418 576.43 216.418 576.43C216.418 576.43 186.099 551.881 173.016 541.288C159.934 530.695 129 542.962 129 542.962Z" fill="white"/>
<path d="M118.91 444.189C118.91 444.189 144.144 471.249 158.427 486.566C172.71 501.882 158.399 529.057 158.399 529.057C158.399 529.057 131.793 500.526 120.313 488.215C108.832 475.904 118.91 444.189 118.91 444.189Z" fill="white"/>
<path d="M67.038 480.938C67.038 480.938 92.2719 507.998 106.555 523.315C120.838 538.632 148.945 526.251 148.945 526.251C148.945 526.251 122.339 497.719 110.859 485.408C99.3787 473.097 67.038 480.938 67.038 480.938Z" fill="white"/>
<path d="M83.639 377.825C83.639 377.825 100.437 410.792 109.945 429.452C119.453 448.112 98.205 470.29 98.205 470.29C98.205 470.29 80.4939 435.53 72.8519 420.532C65.2098 405.533 83.639 377.825 83.639 377.825Z" fill="white"/>
<path d="M23.6467 398.852C23.6467 398.852 40.4444 431.82 49.9523 450.48C59.4602 469.14 89.8911 464.986 89.8911 464.986C89.8911 464.986 72.18 430.226 64.5379 415.228C56.8959 400.23 23.6467 398.852 23.6467 398.852Z" fill="white"/>
<path d="M69.276 303.224C69.276 303.224 75.701 339.662 79.3377 360.287C82.9745 380.912 56.1713 395.908 56.1713 395.908C56.1713 395.908 49.3969 357.489 46.4739 340.911C43.5509 324.334 69.276 303.224 69.276 303.224Z" fill="white"/>
<path d="M5.75718 305.793C5.75718 305.793 12.1821 342.231 15.8189 362.856C19.4556 383.481 49.7713 388.405 49.7713 388.405C49.7713 388.405 42.9969 349.986 40.0739 333.409C37.1508 316.831 5.75718 305.793 5.75718 305.793Z" fill="white"/>
<path d="M74.8761 228.242C74.8761 228.242 69.7267 264.882 66.812 285.621C63.8973 306.361 33.7719 312.34 33.7719 312.34C33.7719 312.34 39.2013 273.708 41.544 257.038C43.8868 240.369 74.8761 228.242 74.8761 228.242Z" fill="white"/>
<path d="M13.6722 211.057C13.6722 211.057 8.52276 247.697 5.60806 268.436C2.69336 289.175 30.0035 303.227 30.0035 303.227C30.0035 303.227 35.433 264.595 37.7757 247.925C40.1184 231.256 13.6722 211.057 13.6722 211.057Z" fill="white"/>
<path d="M105.903 160.035C105.903 160.035 89.1059 193.002 79.5979 211.662C70.09 230.323 39.6592 226.169 39.6592 226.169C39.6592 226.169 57.3703 191.409 65.0123 176.41C72.6544 161.412 105.903 160.035 105.903 160.035Z" fill="white"/>
<path d="M53.629 123.86C53.629 123.86 36.8314 156.827 27.3234 175.487C17.8155 194.148 39.063 216.325 39.063 216.325C39.063 216.325 56.7742 181.565 64.4162 166.567C72.0582 151.568 53.629 123.86 53.629 123.86Z" fill="white"/>
<path d="M151.744 97.6303C151.744 97.6303 128.965 126.787 116.071 143.29C103.177 159.793 74.0978 149.909 74.0978 149.909C74.0978 149.909 98.116 119.167 108.479 105.903C118.843 92.6379 151.744 97.6303 151.744 97.6303Z" fill="white"/>
<path d="M107.332 52.1456C107.332 52.1456 84.553 81.3019 71.6592 97.8052C58.7654 114.309 75.3909 140.133 75.3909 140.133C75.3909 140.133 99.4091 109.391 109.773 96.1261C120.136 82.8615 107.332 52.1456 107.332 52.1456Z" fill="white"/>
<path d="M208.501 48.9768C208.501 48.9768 178.951 71.244 162.226 83.8478C145.5 96.4516 119.97 79.3779 119.97 79.3779C119.97 79.3779 151.126 55.8999 164.57 45.7695C178.013 35.6391 208.501 48.9768 208.501 48.9768Z" fill="white"/>
<path d="M503.169 499.349C503.169 499.349 474.413 522.632 458.137 535.811C441.86 548.99 452.249 577.893 452.249 577.893C452.249 577.893 482.569 553.344 495.651 542.751C508.733 532.158 503.169 499.349 503.169 499.349Z" fill="white"/>
<path d="M549.42 542.962C549.42 542.962 520.664 566.246 504.388 579.425C488.111 592.604 462.002 576.43 462.002 576.43C462.002 576.43 492.321 551.881 505.403 541.288C518.485 530.695 549.42 542.962 549.42 542.962Z" fill="white"/>
<path d="M559.509 444.189C559.509 444.189 534.275 471.249 519.992 486.566C505.709 501.882 520.021 529.057 520.021 529.057C520.021 529.057 546.627 500.526 558.107 488.215C569.587 475.904 559.509 444.189 559.509 444.189Z" fill="white"/>
<path d="M611.382 480.938C611.382 480.938 586.148 507.998 571.865 523.315C557.581 538.632 529.474 526.251 529.474 526.251C529.474 526.251 556.081 497.719 567.561 485.408C579.041 473.097 611.382 480.938 611.382 480.938Z" fill="white"/>
<path d="M594.781 377.825C594.781 377.825 577.983 410.792 568.475 429.452C558.967 448.112 580.215 470.29 580.215 470.29C580.215 470.29 597.926 435.53 605.568 420.532C613.21 405.533 594.781 377.825 594.781 377.825Z" fill="white"/>
<path d="M654.773 398.852C654.773 398.852 637.975 431.82 628.467 450.48C618.959 469.14 588.528 464.986 588.528 464.986C588.528 464.986 606.24 430.226 613.882 415.228C621.524 400.23 654.773 398.852 654.773 398.852Z" fill="white"/>
<path d="M609.144 303.224C609.144 303.224 602.719 339.662 599.082 360.287C595.445 380.912 622.248 395.908 622.248 395.908C622.248 395.908 629.023 357.489 631.946 340.911C634.869 324.334 609.144 303.224 609.144 303.224Z" fill="white"/>
<path d="M672.662 305.793C672.662 305.793 666.237 342.231 662.601 362.856C658.964 383.481 628.648 388.405 628.648 388.405C628.648 388.405 635.423 349.986 638.346 333.409C641.269 316.831 672.662 305.793 672.662 305.793Z" fill="white"/>
<path d="M603.544 228.242C603.544 228.242 608.693 264.882 611.608 285.621C614.522 306.361 644.648 312.34 644.648 312.34C644.648 312.34 639.218 273.708 636.876 257.038C634.533 240.369 603.544 228.242 603.544 228.242Z" fill="white"/>
<path d="M664.747 211.057C664.747 211.057 669.897 247.697 672.812 268.436C675.726 289.175 648.416 303.227 648.416 303.227C648.416 303.227 642.987 264.595 640.644 247.925C638.301 231.256 664.747 211.057 664.747 211.057Z" fill="white"/>
<path d="M572.516 160.035C572.516 160.035 589.314 193.002 598.822 211.662C608.33 230.323 638.76 226.169 638.76 226.169C638.76 226.169 621.049 191.409 613.407 176.41C605.765 161.412 572.516 160.035 572.516 160.035Z" fill="white"/>
<path d="M624.791 123.86C624.791 123.86 641.588 156.827 651.096 175.487C660.604 194.148 639.356 216.325 639.356 216.325C639.356 216.325 621.645 181.565 614.003 166.567C606.361 151.568 624.791 123.86 624.791 123.86Z" fill="white"/>
<path d="M526.676 97.6303C526.676 97.6303 549.455 126.787 562.349 143.29C575.243 159.793 604.322 149.909 604.322 149.909C604.322 149.909 580.304 119.167 569.94 105.903C559.577 92.6379 526.676 97.6303 526.676 97.6303Z" fill="white"/>
<path d="M571.087 52.1456C571.087 52.1456 593.867 81.3019 606.76 97.8052C619.654 114.309 603.029 140.133 603.029 140.133C603.029 140.133 579.01 109.391 568.647 96.1261C558.284 82.8615 571.087 52.1456 571.087 52.1456Z" fill="white"/>
<path d="M469.919 48.9768C469.919 48.9768 499.468 71.244 516.194 83.8478C532.92 96.4516 558.45 79.3779 558.45 79.3779C558.45 79.3779 527.293 55.8999 513.85 45.7695C500.406 35.6391 469.919 48.9768 469.919 48.9768Z" fill="white"/>
<path d="M503.169 499.349C503.169 499.349 474.413 522.632 458.137 535.811C441.86 548.99 452.249 577.893 452.249 577.893C452.249 577.893 482.569 553.344 495.651 542.751C508.733 532.158 503.169 499.349 503.169 499.349Z" fill="white"/>
<path d="M549.42 542.962C549.42 542.962 520.664 566.246 504.388 579.425C488.111 592.604 462.002 576.43 462.002 576.43C462.002 576.43 492.321 551.881 505.403 541.288C518.485 530.695 549.42 542.962 549.42 542.962Z" fill="white"/>
<path d="M559.509 444.189C559.509 444.189 534.275 471.249 519.992 486.566C505.709 501.882 520.021 529.057 520.021 529.057C520.021 529.057 546.627 500.526 558.107 488.215C569.587 475.904 559.509 444.189 559.509 444.189Z" fill="white"/>
<path d="M611.382 480.938C611.382 480.938 586.148 507.998 571.865 523.315C557.581 538.632 529.474 526.251 529.474 526.251C529.474 526.251 556.081 497.719 567.561 485.408C579.041 473.097 611.382 480.938 611.382 480.938Z" fill="white"/>
<path d="M594.781 377.825C594.781 377.825 577.983 410.792 568.475 429.452C558.967 448.112 580.215 470.29 580.215 470.29C580.215 470.29 597.926 435.53 605.568 420.532C613.21 405.533 594.781 377.825 594.781 377.825Z" fill="white"/>
<path d="M654.773 398.852C654.773 398.852 637.975 431.82 628.467 450.48C618.959 469.14 588.528 464.986 588.528 464.986C588.528 464.986 606.24 430.226 613.882 415.228C621.524 400.23 654.773 398.852 654.773 398.852Z" fill="white"/>
<path d="M609.144 303.224C609.144 303.224 602.719 339.662 599.082 360.287C595.445 380.912 622.248 395.908 622.248 395.908C622.248 395.908 629.023 357.489 631.946 340.911C634.869 324.334 609.144 303.224 609.144 303.224Z" fill="white"/>
<path d="M672.662 305.793C672.662 305.793 666.237 342.231 662.601 362.856C658.964 383.481 628.648 388.405 628.648 388.405C628.648 388.405 635.423 349.986 638.346 333.409C641.269 316.831 672.662 305.793 672.662 305.793Z" fill="white"/>
<path d="M603.544 228.242C603.544 228.242 608.693 264.882 611.608 285.621C614.522 306.361 644.648 312.34 644.648 312.34C644.648 312.34 639.218 273.708 636.876 257.038C634.533 240.369 603.544 228.242 603.544 228.242Z" fill="white"/>
<path d="M664.747 211.057C664.747 211.057 669.897 247.697 672.812 268.436C675.726 289.175 648.416 303.227 648.416 303.227C648.416 303.227 642.987 264.595 640.644 247.925C638.301 231.256 664.747 211.057 664.747 211.057Z" fill="white"/>
<path d="M572.516 160.035C572.516 160.035 589.314 193.002 598.822 211.662C608.33 230.323 638.76 226.169 638.76 226.169C638.76 226.169 621.049 191.409 613.407 176.41C605.765 161.412 572.516 160.035 572.516 160.035Z" fill="white"/>
<path d="M624.791 123.86C624.791 123.86 641.588 156.827 651.096 175.487C660.604 194.148 639.356 216.325 639.356 216.325C639.356 216.325 621.645 181.565 614.003 166.567C606.361 151.568 624.791 123.86 624.791 123.86Z" fill="white"/>
<path d="M526.676 97.6303C526.676 97.6303 549.455 126.787 562.349 143.29C575.243 159.793 604.322 149.909 604.322 149.909C604.322 149.909 580.304 119.167 569.94 105.903C559.577 92.6379 526.676 97.6303 526.676 97.6303Z" fill="white"/>
<path d="M571.087 52.1456C571.087 52.1456 593.867 81.3019 606.76 97.8052C619.654 114.309 603.029 140.133 603.029 140.133C603.029 140.133 579.01 109.391 568.647 96.1261C558.284 82.8615 571.087 52.1456 571.087 52.1456Z" fill="white"/>
<path d="M469.919 48.9768C469.919 48.9768 499.468 71.244 516.194 83.8478C532.92 96.4516 558.45 79.3779 558.45 79.3779C558.45 79.3779 527.293 55.8999 513.85 45.7695C500.406 35.6391 469.919 48.9768 469.919 48.9768Z" fill="white"/>
</g>
}
get yinYangSVG(): JSX.Element {
return <g style="transform: translate(30px, 30px);">
<path fill-rule="evenodd" clip-rule="evenodd" d="M289.088 578.175C448.746 578.175 578.175 448.746 578.175 289.088C578.175 129.429 448.746 0 289.088 0C129.429 0 0 129.429 0 289.088C0 448.746 129.429 578.175 289.088 578.175ZM289.087 570.763C214.383 570.763 142.737 541.086 89.9132 488.262C37.0889 435.437 7.41248 363.792 7.41248 289.087C7.41248 214.383 37.0889 142.737 89.9132 89.9132C142.737 37.0889 214.383 7.41248 289.087 7.41248C326.44 7.41248 362.262 22.2507 388.675 48.6628C415.087 75.075 429.925 110.898 429.925 148.25C429.925 185.602 415.087 221.425 388.675 247.837C362.262 274.249 326.44 289.087 289.087 289.087C251.735 289.087 215.912 303.926 189.5 330.338C163.088 356.75 148.25 392.573 148.25 429.925C148.25 467.277 163.088 503.1 189.5 529.512C215.912 555.924 251.735 570.763 289.087 570.763ZM326.15 429.925C326.15 450.394 309.557 466.988 289.088 466.988C268.618 466.988 252.025 450.394 252.025 429.925C252.025 409.456 268.618 392.863 289.088 392.863C309.557 392.863 326.15 409.456 326.15 429.925Z" fill="white"/>
<path d="M289.088 185.312C309.557 185.312 326.15 168.719 326.15 148.25C326.15 127.781 309.557 111.188 289.088 111.188C268.618 111.188 252.025 127.781 252.025 148.25C252.025 168.719 268.618 185.312 289.088 185.312Z" fill="white"/>
</g>
}
get activeFragment() {
const objectMap: Record<AgidaTypes, JSX.Element> = {
agida: this.agidaSVG,
knife: this.knifeSVG,
'yin yang': this.yinYangSVG
}
return objectMap[this.type] || this.agidaSVG
}
get width(): number {
return this.screen.width
}
get height(): number {
return this.screen.height
}
get viewBox(): string {
return `0 0 ${this.width} ${this.height}`
}
get topWaves() {
return this.generateWave('top')
}
get bottomWaves() {
return this.generateWave('bottom')
}
mounted() {
this.updateScreen()
window.addEventListener('resize', this.updateScreen)
}
beforeDestroy() {
window.removeEventListener('resize', this.updateScreen)
}
@Debounce(50)
updateScreen() {
this.screen = {
width: window.innerWidth,
height: window.innerHeight
}
}
generateWave(type: 'bottom' | 'top') {
const wavery = new Wavery(this.wave)
const waveSvg = wavery.generateSvg()
const { height, width, xmlns, paths } = waveSvg.svg
const isTop = type === 'top'
const colorsArray = isTop ? this.topColors : this.bottomColors
const angle = Math.atan(window.innerHeight / window.innerWidth) * (180 / Math.PI) * 1.1
return <svg
class={`waves--${type}`}
style={`width: ${width}px; height: ${height}px; --angle: ${angle}deg`}
viewBox={`0 0 ${width} ${height}`}
xmlns={xmlns}
>
{paths.map((path, index) => {
const pathProps = []
if (path.animatedPath) {
pathProps.push(<style>{wavery.generateAnimationStyle(index)}</style>)
}
pathProps.push(
<path
key={index}
d={path.d}
stroke={path.strokeColor}
stroke-width={path.strokeWidth}
fill={colorsArray[index]}
class={`path-${index}`}
transform={path.transform}
></path>
)
return pathProps
})}
</svg>
}
render() {
return <div>
{ this.topWaves }
{ this.bottomWaves }
<svg
width={this.width}
height={this.height}
viewBox={this.viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width={this.width} height={this.height} fill="#22233D"/>
<mask id="mask-agida" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width={this.width} height={this.height}>
<g id="main" style={`transform: translate(calc(${this.width / 2}px - ${this.iconWidth / 2}px), calc(${this.height / 2}px - ${this.iconHeight / 2 - 30}px))`}>
{this.activeFragment}
</g>
</mask>
<g mask="url(#mask-agida)">
<path d="M1862.93 140.4H-57.0667V1220.4H1862.93V140.4Z" fill={this.bottomActiveColor}/>
<g filter="url(#filter0_f_506_268)">
<path d="M1512.07 1310.6C1932.69 1310.6 2273.67 1022.9 2273.67 668C2273.67 313.102 1932.69 25.4 1512.07 25.4C1091.45 25.4 750.467 313.102 750.467 668C750.467 1022.9 1091.45 1310.6 1512.07 1310.6Z" fill={this.topColor}/>
<path d="M1646.47 889.4C2067.09 889.4 2408.07 601.698 2408.07 246.8C2408.07 -108.098 2067.09 -395.8 1646.47 -395.8C1225.85 -395.8 884.867 -108.098 884.867 246.8C884.867 601.698 1225.85 889.4 1646.47 889.4Z" fill={this.bottomActiveColor}/>
<path d="M272.6 1112.6C693.22 1112.6 1034.2 824.898 1034.2 470C1034.2 115.102 693.22 -172.6 272.6 -172.6C-148.02 -172.6 -489 115.102 -489 470C-489 824.898 -148.02 1112.6 272.6 1112.6Z" fill={this.topColor}/>
<path d="M1145.13 1514C1565.75 1514 1906.73 1226.3 1906.73 871.4C1906.73 516.502 1565.75 228.8 1145.13 228.8C724.513 228.8 383.533 516.502 383.533 871.4C383.533 1226.3 724.513 1514 1145.13 1514Z" fill={this.topColor}/>
<path d="M690.733 855.2C1111.35 855.2 1452.33 567.498 1452.33 212.6C1452.33 -142.298 1111.35 -430 690.733 -430C270.113 -430 -70.8667 -142.298 -70.8667 212.6C-70.8667 567.498 270.113 855.2 690.733 855.2Z" fill={this.bottomActiveColor}/>
<path d="M281.133 1447.4C701.753 1447.4 1042.73 1159.7 1042.73 804.8C1042.73 449.902 701.753 162.2 281.133 162.2C-139.487 162.2 -480.467 449.902 -480.467 804.8C-480.467 1159.7 -139.487 1447.4 281.133 1447.4Z" fill={this.topColor}/>
</g>
</g>
<defs>
<filter id="filter0_f_506_268" x="-811" y="-752" width="3541.07" height="2588" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="161" result="effect1_foregroundBlur_506_268"/>
</filter>
</defs>
</svg>
</div>
}
}

@ -1,62 +0,0 @@
/* bezier-spline.js
*
* computes cubic bezier coefficients to generate a smooth
* line through specified points. couples with SVG graphics
* for interactive processing.
*
* For more info see:
* http://www.particleincell.com/2012/bezier-splines/
*
* Lubos Brieda, Particle In Cell Consulting LLC, 2012
* you may freely use this algorithm in your codes however where feasible
* please include a link/reference to the source article
*/
/* computes control points given knots K, this is the brain of the operation */
export function computeControlPoints(K: number[]) {
const p1 = []
const p2 = []
const n = K.length - 1
/* rhs vector */
const a = []
const b = []
const c = []
const r = []
/* left most segment */
a[0] = 0
b[0] = 2
c[0] = 1
r[0] = K[0] + 2 * K[1]
/* internal segments */
for (let i = 1; i < n - 1; i++) {
a[i] = 1
b[i] = 4
c[i] = 1
r[i] = 4 * K[i] + 2 * K[i + 1]
}
/* right segment */
a[n - 1] = 2
b[n - 1] = 7
c[n - 1] = 0
r[n - 1] = 8 * K[n - 1] + K[n]
/* solves Ax=b with the Thomas algorithm (from Wikipedia) */
for (let i = 1; i < n; i++) {
const m: number = a[i] / b[i - 1]
b[i] = b[i] - m * c[i - 1]
r[i] = r[i] - m * r[i - 1]
}
p1[n - 1] = r[n - 1] / b[n - 1]
for (let i = n - 2; i >= 0; --i) p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i]
/* we have p1, now compute p2 */
for (let i = 0; i < n - 1; i++) p2[i] = 2 * K[i + 1] - p1[i + 1]
p2[n - 1] = 0.5 * (K[n] + p1[n - 1])
return { p1, p2 }
}

@ -1,7 +0,0 @@
import { Wavery, WaveConfigInterface } from './wave'
export function waveInit(data: WaveConfigInterface) {
const wavery = new Wavery(data)
return wavery.generateSvg()
}

@ -1,194 +0,0 @@
import { computeControlPoints } from './bezier-spline'
const svgns = 'http://www.w3.org/2000/svg'
export interface WaveConfigAnimationInterface {
time?: number;
steps?: number;
}
export interface WaveConfigInterface {
height: number;
width: number;
segmentCount: number;
layerCount: number;
variance: number;
strokeWidth?: number;
fillColor?: string;
strokeColor?: string;
transform?: string;
animation?: boolean | WaveConfigAnimationInterface;
}
interface Point {
x: number;
y: number;
}
const defaultAnimationValues = {
steps: 3,
time: 40000,
timingFunction: 'ease',
iteration: 'infinite'
}
export class Wavery {
properties!: WaveConfigInterface
points: Point[][] = []
animationPoints: Point[][][] = [];
constructor(properties: WaveConfigInterface) {
this.properties = properties
this.points = this.generatePoints()
if (this.needAnimation) {
const { steps } = this.animation
Array.from(Array(steps)).forEach(() => {
this.animationPoints.push(this.generatePoints())
})
}
}
get animation() {
const animation = this.properties.animation
const isBoolean = typeof animation === 'boolean'
return isBoolean ? defaultAnimationValues : { ...defaultAnimationValues, ...animation }
}
get cellWidth() {
return this.properties.width / this.properties.segmentCount
}
get cellHeight() {
return this.properties.height / this.properties.layerCount
}
get needAnimation() {
return this.properties.animation
}
get pathList() {
const pathList = []
for (let i = 0; i < this.points.length; i++) {
pathList.push(this.generateClosedPath(i))
}
return pathList
}
generatePoints(): Point[][] {
const { cellWidth, cellHeight } = this
const { width, height, variance } = this.properties
const moveLimitX = cellWidth * variance * 0.2
const moveLimitY = cellHeight * variance
const points = []
for (let y = cellHeight; y <= height; y += cellHeight) {
const pointsPerLayer = []
pointsPerLayer.push({ x: 0, y: Math.floor(y) })
for (let x = cellWidth; x < width; x += cellWidth) {
const varietalY = y - moveLimitY / 2 + Math.random() * moveLimitY
const varietalX = x - moveLimitX / 2 + Math.random() * moveLimitX
pointsPerLayer.push({
x: Math.floor(varietalX),
y: Math.floor(varietalY)
})
}
pointsPerLayer.push({ x: width, y: Math.floor(y) })
points.push(pointsPerLayer)
}
return points
}
generateClosedPath(index: number) {
const animatedPathList: string[] = []
const { fillColor, strokeColor, strokeWidth, transform } = this.properties
const style = { fillColor, strokeColor, strokeWidth, transform }
const path = this.generatePath(this.points[index])
if (this.needAnimation) {
this.animationPoints.forEach((waves) => {
animatedPathList.push(this.generatePath(waves[index]))
})
}
return {
...style,
d: path,
animatedPath: animatedPathList
}
}
generatePath(points: Point[]) {
const xPoints = points.map((p) => p.x)
const yPoints = points.map((p) => p.y)
const leftCornerPoint = { x: 0, y: this.properties.height + this.cellHeight }
const rightCornerPoint = { x: this.properties.width, y: this.properties.height + this.cellHeight }
const xControlPoints = computeControlPoints(xPoints)
const yControlPoints = computeControlPoints(yPoints)
let path =
`M ${leftCornerPoint.x},${leftCornerPoint.y} ` +
`C ${leftCornerPoint.x},${leftCornerPoint.y} ` +
`${xPoints[0]},${yPoints[0]} ` +
`${xPoints[0]},${yPoints[0]} `
for (let i = 0; i < xPoints.length - 1; i++) {
path +=
`C ${xControlPoints.p1[i]},${yControlPoints.p1[i]} ` +
`${xControlPoints.p2[i]},${yControlPoints.p2[i]} ` +
`${xPoints[i + 1]},${yPoints[i + 1]} `
}
path +=
`C ${xPoints[xPoints.length - 1]},${yPoints[xPoints.length - 1]} ` +
`${rightCornerPoint.x},${rightCornerPoint.y} ` +
`${rightCornerPoint.x},${rightCornerPoint.y} Z`
return path
}
private generateKeyframe(percent: number, d: string) {
return `${percent}% {d: path("${d}")}`
}
generateAnimationStyle(index: number) {
const path = this.pathList[index]
const { steps } = this.animation
if (!path) { return }
const animationList = Array.from(Array(steps + 2)).map((_, index, { length }) => {
const isBound = index === 0 || index + 1 === length
const percent = index * (100 / (steps + 1))
return this.generateKeyframe(percent, isBound ? path.d : path.animatedPath[index - 1])
})
return `.path-${index}{
animation:pathAnim-${index} ${this.animation.time}ms;
animation-timing-function: ${this.animation.timingFunction};
animation-iteration-count: ${this.animation.iteration};
}
@keyframes pathAnim-${index}{
${animationList.join('')}
}`
}
generateSvg() {
return {
svg: {
width: this.properties.width,
height: this.properties.height + this.cellHeight,
xmlns: svgns,
paths: this.pathList
}
}
}
}

@ -1,68 +1,56 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class DestructionTheme extends Vue {
get perspective() {
return AppModule.getThemeInput('perspective')?.value as number || 0.2
}
get position() {
return AppModule.getThemeInput('position')?.value as number || 0.2
}
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'position')
gl.programInfo.uniforms.perspective = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'perspective')
export default defineComponent({
name: 'DestructionTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const perspective = computed(() => appStore.getThemeInput('perspective')?.value as number || 0.2)
const position = computed(() => appStore.getThemeInput('position')?.value as number || 0.2)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'position')
gl.programInfo.uniforms.perspective = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'perspective')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.position, position.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.perspective, perspective.value)
}
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.position, vm.position)
gl.ctx.uniform1f(gl.programInfo.uniforms.perspective, vm.perspective)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' />
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,84 +1,60 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class FlowTheme extends Vue {
get hue() {
return AppModule.getThemeInput('hue')?.value as number || 0
}
get brightness() {
return AppModule.getThemeInput('brightness')?.value as number || 0
}
get invert() {
return AppModule.getThemeInput('invert')?.value as boolean || false
}
get filter() {
return `hue-rotate(${this.hue}deg) invert(${Number(this.invert)}) brightness(${this.brightness})`
}
get styleCanvas() {
return {
filter: this.filter
}
}
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
get size() {
return AppModule.getThemeInput('size')?.value as number || 0.2
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
export default defineComponent({
name: 'FlowTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => appStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => appStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => appStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const size = computed(() => appStore.getThemeInput('size')?.value as number || 0.2)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, size.value)
}
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, vm.size)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' style={ this.styleCanvas }/>
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,90 +1,66 @@
import { AppInputThemePalette, AppTheme } from '@/models/app'
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import { CreateElement } from 'vue/types/umd'
import { defineComponent, computed } from 'vue'
import { useAppStore } from '@/store/app'
import type { AppInputThemePalette } from '@/models/app'
@Component
export default class InfinityTheme extends Vue {
get theme() {
return AppModule.activeTheme as AppTheme
}
get palette() {
const input = AppModule.getThemeInput('palette') as AppInputThemePalette
const index = input?.value as number || 0
const values = input?.values || []
return values[index] as string[] || ['#51eaea', '#fffde1', '#ff9d76', '#FB3569']
}
get amount() {
return AppModule.getThemeInput('amount')?.value || 30 || 30
}
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value || 35 || 35
}
export default defineComponent({
name: 'InfinityTheme',
setup() {
const appStore = useAppStore()
get depth() {
return AppModule.getThemeInput('depth')?.value || 200
}
const palette = computed(() => {
const input = appStore.getThemeInput('palette') as AppInputThemePalette
const index = (input?.value as number) || 0
const values = input?.values || []
return (values[index] as string[]) || ['#51eaea', '#fffde1', '#ff9d76', '#FB3569']
})
get size() {
return AppModule.getThemeInput('size')?.value || 17 || 18
}
const amount = computed(() => appStore.getThemeInput('amount')?.value || 30)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value || 35)
const depth = computed(() => appStore.getThemeInput('depth')?.value || 200)
const size = computed(() => appStore.getThemeInput('size')?.value || 17)
get color() {
return `@p(${this.palette.join(', ')});`
}
const color = computed(() => `@p(${palette.value.join(', ')});`)
render(h: CreateElement) {
return h('div', {
class: 'position-center',
domProps: {
innerHTML: `<css-doodle>
:doodle {
@grid: ${this.amount}x1 / ${this.size}vmin;
--deg: @p(-180deg, 180deg);
return () => {
const innerHTML = `<css-doodle>
:doodle {
@grid: ${amount.value}x1 / ${size.value}vmin;
--deg: @p(-180deg, 180deg);
}
:container {
perspective: 10vmin;
}
:after, :before {
content: '';
background: ${color.value};
@place-cell: @r(100%) @r(100%);
@size: @r(6px);
@shape: heart;
}
@place-cell: center;
@size: ${depth.value}%;
box-shadow: @m200(0 0 50px ${color.value});
background: @m100(
radial-gradient(${color.value} 50%, transparent 0)
@r(-20%, 120%) @r(-20%, 100%) / 1px 1px
no-repeat
);
will-change: transform, opacity;
animation: scale-up ${animationSpeed.value}s linear infinite;
animation-delay: calc(-${animationSpeed.value}s / @size() * @i());
@keyframes scale-up {
0%, 95.01%, 100% {
transform: translateZ(0) rotate(0);
opacity: 0;
}
:container {
perspective: 10vmin;
10% { opacity: 1; }
95% {
transform: translateZ(12vmin) rotateZ(@var(--deg));
}
:after, :before {
content: '';
background: ${this.color};
@place-cell: @r(100%) @r(100%);
@size: @r(6px);
@shape: heart;
}
@place-cell: center;
@size: ${this.depth}%;
box-shadow: @m200(0 0 50px ${this.color});
background: @m100(
radial-gradient(${this.color} 50%, transparent 0)
@r(-20%, 120%) @r(-20%, 100%) / 1px 1px
no-repeat
);
will-change: transform, opacity;
animation: scale-up ${this.animationSpeed}s linear infinite;
animation-delay: calc(-${this.animationSpeed}s / @size() * @i());
@keyframes scale-up {
0%, 95.01%, 100% {
transform: translateZ(0) rotate(0);
opacity: 0;
}
10% {
opacity: 1;
}
95% {
transform: translateZ(12vmin) rotateZ(@var(--deg));
}
}
</css-doodle>`
}
})
}
</css-doodle>`
return <div class="position-center" innerHTML={innerHTML} />
}
}
}
})

@ -1,15 +1,24 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
import Parallax from 'parallax-js'
@Component
export default class OsmosTheme extends Vue {
parallax: null | Parallax = null
export default defineComponent({
name: 'OsmosTheme',
setup() {
const sceneRef = ref<HTMLElement | null>(null)
let parallax: Parallax | null = null
render() {
return (
onMounted(() => {
if (sceneRef.value) parallax = new Parallax(sceneRef.value)
})
onBeforeUnmount(() => {
parallax?.destroy()
})
return () => (
<div
class="osmos"
ref="scene"
ref={sceneRef}
data-hover-only="true"
data-friction-x="0.1"
data-friction-y="0.1"
@ -17,35 +26,27 @@ export default class OsmosTheme extends Vue {
data-scalar-y="15"
>
<li class="prllx-block" data-depth="0.0">
<div class="stars"></div>
<div class="stars" />
</li>
<li class="prllx-block" data-depth="0.0">
<div class="sun"></div>
<div class="sun" />
</li>
<li class="prllx-block" data-depth="0.1">
<div class="clouds">
<div></div>
<div></div>
<div></div>
<div /><div /><div />
</div>
<div class="clouds-reflex">
<div></div>
<div></div>
<div></div>
<div /><div /><div />
</div>
</li>
<li class="prllx-block" data-depth="0.0">
<div class="sea">
<div class="rocks">
<div class="rocks_left">
<div></div>
<div></div>
<div></div>
<div /><div /><div />
</div>
<div class="rocks_right">
<div></div>
<div></div>
<div></div>
<div /><div /><div />
</div>
</div>
</div>
@ -53,13 +54,4 @@ export default class OsmosTheme extends Vue {
</div>
)
}
mounted() {
const scene = this.$refs.scene as HTMLElement
if (scene) { this.parallax = new Parallax(scene) }
}
beforeDestroy() {
if (this.parallax) this.parallax.destroy()
}
}
})

@ -1,62 +1,53 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class PlanetTheme extends Vue {
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get position() {
return AppModule.getThemeInput('position')?.value as number || 2.14
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
mounted() {
const vm = this
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'camR')
export default defineComponent({
name: 'PlanetTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const position = computed(() => appStore.getThemeInput('position')?.value as number || 2.14)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.position) {
gl.programInfo.uniforms.position = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'camR')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * animationSpeed.value / 10))
gl.ctx.uniform1f(gl.programInfo.uniforms.position, position.value)
}
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * vm.animationSpeed / 10))
gl.ctx.uniform1f(gl.programInfo.uniforms.position, vm.position)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' />
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,67 +1,53 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class PlasmaTheme extends Vue {
get hue() {
return AppModule.getThemeInput('hue')?.value as number || 0
}
get filter() {
return `hue-rotate(${this.hue}deg)`
}
get styleCanvas() {
return {
filter: this.filter
}
}
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
export default defineComponent({
name: 'PlasmaTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => appStore.getThemeInput('hue')?.value as number || 0)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg)`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
}
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' style={ this.styleCanvas }/>
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,90 +1,63 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class RandomTheme extends Vue {
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
get symmetry() {
return AppModule.getThemeInput('symmetry')?.value as number || 64
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
get thickness() {
return AppModule.getThemeInput('thickness')?.value as number || 64
}
get hue() {
return AppModule.getThemeInput('hue')?.value as number || 0
}
get brightness() {
return AppModule.getThemeInput('brightness')?.value as number || 0
}
get invert() {
return AppModule.getThemeInput('invert')?.value as boolean || false
}
get filter() {
return `hue-rotate(${this.hue}deg) invert(${Number(this.invert)}) brightness(${this.brightness})`
}
get styleCanvas() {
return {
filter: this.filter
}
}
mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.symmetry) {
gl.programInfo.uniforms.symmetry = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'symmetry')
gl.programInfo.uniforms.thickness = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'thickness')
export default defineComponent({
name: 'RandomTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => appStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => appStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => appStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const symmetry = computed(() => appStore.getThemeInput('symmetry')?.value as number || 64)
const thickness = computed(() => appStore.getThemeInput('thickness')?.value as number || 64)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.symmetry) {
gl.programInfo.uniforms.symmetry = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'symmetry')
gl.programInfo.uniforms.thickness = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'thickness')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.symmetry, symmetry.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.thickness, thickness.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * animationSpeed.value / 10))
}
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.symmetry, vm.symmetry)
gl.ctx.uniform1f(gl.programInfo.uniforms.thickness, vm.thickness)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time + (gl.time * vm.animationSpeed / 10))
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' style={ this.styleCanvas } />
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,68 +1,56 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class RingsTheme extends Vue {
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get hue() {
return AppModule.getThemeInput('hue')?.value as number || 0
}
get zoom() {
return AppModule.getThemeInput('zoom')?.value as number || 32
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.hue) {
gl.programInfo.uniforms.hue = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'hue')
gl.programInfo.uniforms.zoom = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'zoom')
export default defineComponent({
name: 'RingsTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => appStore.getThemeInput('hue')?.value as number || 0)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const zoom = computed(() => appStore.getThemeInput('zoom')?.value as number || 32)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.hue) {
gl.programInfo.uniforms.hue = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'hue')
gl.programInfo.uniforms.zoom = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'zoom')
}
gl.time += animationSpeed.value / 500
gl.pxratio = pxratio.value
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.hue, hue.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.zoom, zoom.value)
}
gl.time += vm.animationSpeed / 500
gl.pxratio = vm.pxratio
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.hue, vm.hue)
gl.ctx.uniform1f(gl.programInfo.uniforms.zoom, vm.zoom)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' />
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,37 +1,46 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
import Parallax from 'parallax-js'
@Component
export default class SpaceTheme extends Vue {
parallax: null | Parallax = null
export default defineComponent({
name: 'SpaceTheme',
setup() {
const sceneRef = ref<HTMLElement | null>(null)
let parallax: Parallax | null = null
render() {
return <div class="space" ref="scene" data-hover-only="true" data-friction-x="0.1" data-friction-y="0.1" data-scalar-x="25" data-scalar-y="15">
<li class="prllx-block" data-depth="0.0">
<div class="main-planet position-center"></div>
</li>
<li class="prllx-block" data-depth="0.2">
<div class="ufo"></div>
</li>
<li class="prllx-block" data-depth="0.1">
<div class="meteor"></div>
</li>
<li class="prllx-block" data-depth="0.07">
<div class="hole"></div>
</li>
<li class="prllx-block" data-depth="0.02">
<div class="another-planet"></div>
</li>
</div>
}
onMounted(() => {
if (sceneRef.value) parallax = new Parallax(sceneRef.value)
})
mounted() {
const scene = this.$refs.scene as HTMLElement
if (scene) { this.parallax = new Parallax(scene) }
}
onBeforeUnmount(() => {
parallax?.destroy()
})
beforeDestroy() {
if (this.parallax) this.parallax.destroy()
return () => (
<div
class="space"
ref={sceneRef}
data-hover-only="true"
data-friction-x="0.1"
data-friction-y="0.1"
data-scalar-x="25"
data-scalar-y="15"
>
<li class="prllx-block" data-depth="0.0">
<div class="main-planet position-center" />
</li>
<li class="prllx-block" data-depth="0.2">
<div class="ufo" />
</li>
<li class="prllx-block" data-depth="0.1">
<div class="meteor" />
</li>
<li class="prllx-block" data-depth="0.07">
<div class="hole" />
</li>
<li class="prllx-block" data-depth="0.02">
<div class="another-planet" />
</li>
</div>
)
}
}
})

@ -1,84 +1,60 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class SphereTheme extends Vue {
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get size() {
return AppModule.getThemeInput('size')?.value as number || 0.2
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
get hue() {
return AppModule.getThemeInput('hue')?.value as number || 0
}
get invert() {
return AppModule.getThemeInput('invert')?.value as boolean || false
}
get brightness() {
return AppModule.getThemeInput('brightness')?.value as number || 0
}
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
get filter() {
return `hue-rotate(${this.hue}deg) invert(${Number(this.invert)}) brightness(${this.brightness})`
}
get styleCanvas() {
return {
filter: this.filter
}
}
async mounted() {
const vm = this
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
export default defineComponent({
name: 'SphereTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const hue = computed(() => appStore.getThemeInput('hue')?.value as number || 0)
const brightness = computed(() => appStore.getThemeInput('brightness')?.value as number || 0)
const invert = computed(() => appStore.getThemeInput('invert')?.value as boolean || false)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const size = computed(() => appStore.getThemeInput('size')?.value as number || 0.2)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
const styleCanvas = computed(() => ({
filter: `hue-rotate(${hue.value}deg) invert(${Number(invert.value)}) brightness(${brightness.value})`
}))
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.size) {
gl.programInfo.uniforms.size = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'size')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, size.value)
}
gl.pxratio = vm.pxratio
gl.time += vm.animationSpeed / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.size, vm.size)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} style={styleCanvas.value} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' style={ this.styleCanvas }/>
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,53 +1,59 @@
import { Component, Vue } from 'vue-property-decorator'
import Parallax from 'parallax-js'
import AppIcon from '@/components/app/AppIcon.vue'
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
import Parallax from 'parallax-js'
@Component({
components: {
AppIcon
}
})
export default class SuprematismTheme extends Vue {
parallax: null | Parallax = null
export default defineComponent({
name: 'SuprematismTheme',
setup() {
const sceneRef = ref<HTMLElement | null>(null)
let parallax: Parallax | null = null
render() {
return <div class="suprematism" ref="scene" data-hover-only="true" data-friction-x="0.1" data-friction-y="0.1" data-scalar-x="25" data-scalar-y="15">
<li class="prllx-block" data-depth="0.0">
<div class="parralax-item-cube position-center"></div>
</li>
<li class="prllx-block" data-depth="0.05">
<div class="parralax-item-bottom-left">
<AppIcon name="suprematism-bottom-left"/>
</div>
<div class="parralax-item-bottom-right">
<AppIcon name="suprematism-bottom-right"/>
</div>
<div class="parralax-item-top-right">
<AppIcon name="suprematism-top-right"/>
</div>
<div class="parralax-item-top-left">
<AppIcon name="suprematism-top-left"/>
</div>
</li>
<li class="prllx-block" data-depth=".1">
<div class="parralax-item-bottom-right-triangle"></div>
<div class="parralax-item-top-left-multi"></div>
<div class="parralax-item-bottom-left-pattern">
<AppIcon name="suprematism-bottom-left-pattern"/>
</div>
</li>
<li class="prllx-block" data-depth=".2">
<div class="ps-parralax-item-top-right-circle"></div>
</li>
</div>
}
onMounted(() => {
if (sceneRef.value) parallax = new Parallax(sceneRef.value)
})
mounted() {
const scene = this.$refs.scene as HTMLElement
if (scene) { this.parallax = new Parallax(scene) }
}
onBeforeUnmount(() => {
parallax?.destroy()
})
beforeDestroy() {
if (this.parallax) this.parallax.destroy()
return () => (
<div
class="suprematism"
ref={sceneRef}
data-hover-only="true"
data-friction-x="0.1"
data-friction-y="0.1"
data-scalar-x="25"
data-scalar-y="15"
>
<li class="prllx-block" data-depth="0.0">
<div class="parralax-item-cube position-center" />
</li>
<li class="prllx-block" data-depth="0.05">
<div class="parralax-item-bottom-left">
<AppIcon name="suprematism-bottom-left" />
</div>
<div class="parralax-item-bottom-right">
<AppIcon name="suprematism-bottom-right" />
</div>
<div class="parralax-item-top-right">
<AppIcon name="suprematism-top-right" />
</div>
<div class="parralax-item-top-left">
<AppIcon name="suprematism-top-left" />
</div>
</li>
<li class="prllx-block" data-depth=".1">
<div class="parralax-item-bottom-right-triangle" />
<div class="parralax-item-top-left-multi" />
<div class="parralax-item-bottom-left-pattern">
<AppIcon name="suprematism-bottom-left-pattern" />
</div>
</li>
<li class="prllx-block" data-depth=".2">
<div class="ps-parralax-item-top-right-circle" />
</li>
</div>
)
}
}
})

@ -1,53 +1,48 @@
import { Component, Vue } from 'vue-property-decorator'
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { AppModule } from '@/store/app'
let render: GL
@Component
export default class TendernessTheme extends Vue {
get animationSpeed() {
return AppModule.getThemeInput('animation-speed')?.value as number || 45
}
get pxratio() {
return AppModule.getThemeInput('pxratio')?.value as number || 0.8
}
mounted() {
const vm = this
import { useAppStore } from '@/store/app'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
render = new GL(
this.$refs.canvas as HTMLCanvasElement,
document.querySelector('script#shader-vs')?.textContent || '',
document.querySelector('script#shader-fs')?.textContent || '',
window.innerWidth,
window.innerHeight,
{
renderOptions: {
externalTimeUse: true
},
renderHook() {
const gl = this as unknown as GL
let render: GL
gl.time += vm.animationSpeed / 500
gl.pxratio = vm.pxratio
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
export default defineComponent({
name: 'TendernessTheme',
setup() {
const appStore = useAppStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => appStore.getThemeInput('animation-speed')?.value as number || 45)
const pxratio = computed(() => appStore.getThemeInput('pxratio')?.value as number || 0.8)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
gl.time += animationSpeed.value / 500
gl.pxratio = pxratio.value
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
}
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
render.running = true
}
beforeDestroy() {
render.running = false
}
render() {
return <div>
<canvas ref='canvas' />
<script id="shader-fs" type="x-shader/x-fragment"> { require('./fragment.glsl') } </script>
<script id="shader-vs" type="x-shader/x-vertex"> { require('./vertex.glsl') } </script>
</div>
}
}
})

@ -1,10 +1,11 @@
import Vue from 'vue'
import '@/utils/lightdm'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createI18n } from 'vue-i18n'
import App from './App'
import router from './router'
import store from './store'
import '@/plugins/components'
import VueI18n from 'vue-i18n'
import { registerComponents } from '@/plugins/components'
import '@/utils/lightdm'
import './style/index.styl'
import 'css-doodle'
@ -16,26 +17,25 @@ import de from '@/locales/de.json'
import es from '@/locales/es.json'
let language: string
try {
language = (JSON.parse(localStorage.getItem('settings') || '{}')).language
language = (JSON.parse(localStorage.getItem('settings') || '{}')).language || 'en'
} catch {
language = 'en'
}
const i18n = () => new VueI18n({
locale: language || 'en',
const i18n = createI18n({
legacy: false,
locale: language,
silentTranslationWarn: true,
messages: { ru, en, fr, de, es }
})
Vue.config.productionTip = false
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(i18n)
Vue.use(VueI18n)
registerComponents(app)
new Vue({
i18n: i18n(),
router,
store,
render: h => h(App)
}).$mount('#app')
app.mount('#app')

@ -1,5 +1,6 @@
import Vue from 'vue'
import type { App } from 'vue'
import AppIcon from '@/components/app/AppIcon.vue'
Vue.component('AppIcon', AppIcon)
export function registerComponents(app: App) {
app.component('AppIcon', AppIcon)
}

@ -1,26 +1,12 @@
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home'
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '*',
name: 'Undefined',
component: Home
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', name: 'Home', component: Home },
{ path: '/:pathMatch(.*)*', name: 'Undefined', component: Home }
]
})
export default router

@ -0,0 +1,4 @@
declare module '*.glsl' {
const content: string
export default content
}

@ -1,7 +0,0 @@
import Vue from 'vue'
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
[propName: string]: any;
}
}

18
src/shims-vue.d.ts vendored

@ -1,18 +0,0 @@
import Vue from 'vue'
import VueI18n, { IVueI18n } from 'vue-i18n'
declare module '*.vue' {
export default Vue
}
declare module 'vue/types/vue' {
export interface Vue {
readonly $i18n: VueI18n & IVueI18n;
$t: typeof VueI18n.prototype.t;
$tc: typeof VueI18n.prototype.tc;
$te: typeof VueI18n.prototype.te;
$d: typeof VueI18n.prototype.d;
$n: typeof VueI18n.prototype.n;
}
}

@ -1,360 +1,248 @@
import {
Module,
VuexModule,
getModule,
Mutation,
Action
} from 'vuex-module-decorators'
import { Route } from 'vue-router'
import router from '../router'
import store from '@/store/index'
import {
AppTheme,
AppSettings,
AppInputTheme,
AppInputThemeValue,
AppInputThemeGeneral
} from '@/models/app'
import { LightdmSession, LightdmUsers } from '@/models/lightdm'
import { defineStore } from 'pinia'
import type { RouteLocationNormalized } from 'vue-router'
import router from '@/router'
import type { AppTheme, AppSettings, AppInputTheme, AppInputThemeValue, AppInputThemeGeneral } from '@/models/app'
import type { LightdmSession, LightdmUsers } from '@/models/lightdm'
import type { LightDMBattery } from 'nody-greeter-types'
import { isDifferentRoute, parseQueryValue, randomize, randomizeSettingsTheme, setCSSVariable } from '@/utils/helper'
import { AppThemes, defaultTheme } from '@/utils/constant'
import { version } from '@/../package.json'
import { LightdmHandler } from '@/utils/lightdm'
import { LightDMBattery } from 'nody-greeter-types'
export interface AppState extends AppSettings {
themes: AppTheme[];
getMainSettings: AppSettings;
activeTheme: AppTheme;
battery: LightDMBattery | null;
brightness?: number;
username: string;
desktops: LightdmSession[];
users: LightdmUsers[];
SET_STATE_APP: <S extends this, K extends keyof this>({ key, value }: { key: K; value: S[K] }) => void
}
@Module({ dynamic: true, store, name: 'app' })
class App extends VuexModule implements AppState {
version = version
currentTheme = ''
currentOs = 'arch-linux'
desktop = LightdmHandler.defaultSession
username = LightdmHandler.username
password = ''
defaultColor = '#6BBBED'
battery: LightDMBattery | null = null
brightness = 0
zoom = 1
users = LightdmHandler?.users
desktops = LightdmHandler?.sessions
showPassword = false
generateRandomThemes = false
themes = AppThemes
bodyClass: Record<string, boolean> = {
blur: false,
'no-transition': false,
'show-framerate': false,
'only-ui': false
}
get isCharging() {
return this.battery?.status === 'Charging'
}
get batteryLevel() {
return this.battery?.level || 0
}
get isSupportFullApi() {
return LightdmHandler.isSupportFullApi
}
get getMainSettings(): AppSettings {
const {
zoom,
themes,
desktop,
username,
bodyClass,
currentOs,
currentTheme,
defaultColor,
generateRandomThemes
} = this
return {
zoom,
themes,
desktop,
username,
bodyClass,
currentOs,
currentTheme,
defaultColor,
generateRandomThemes
}
}
get showFrameRate() {
return this.bodyClass['show-framerate']
}
get activeTheme() {
return this.themes.find(({ name }) => name === this.currentTheme) || defaultTheme
}
get currentUser() {
return this.users.find(({ username }) => username === this.username)
}
get currentDesktop() {
return this.desktops.find(({ key }) => key === this.desktop)
}
get getThemeByName() {
return (theme: string) => this.themes.find(({ name }) => name === theme)
}
get getThemeInput() {
return (name: string, theme?: AppTheme) => {
return (theme || this.activeTheme as AppTheme).settings?.find(input => input.name === name)
}
}
get viewThemeOnly() {
return this.bodyClass['only-ui']
}
get isGithubMode() {
return process.env.VUE_APP_VIEW === 'github'
}
get personalInfo() {
const { username, currentTheme, currentOs, desktop, version, defaultColor } = this
return { username, currentTheme, currentOs, desktop, version, defaultColor }
}
get localStorageSettings() {
const { personalInfo, bodyClass, themes } = this
return { personalInfo, bodyClass, themes }
}
@Mutation
SET_STATE_APP<S extends this, K extends keyof this>({ key, value }: { key: K; value: S[K] }) {
this[key] = value
}
@Mutation
SAVE_STATE_APP<S extends this, K extends keyof this>({ key, value }: { key: K; value: S[K] }) {
this[key] = value
const settings = JSON.parse(localStorage.getItem('settings') || '')
settings[key] = value
localStorage.setItem('settings', JSON.stringify(settings))
}
@Mutation
CHANGE_BODY_CLASS({ key, value }: { key: string; value: boolean }) {
this.bodyClass[key] = value
}
@Mutation
CHANGE_THEME_INPUT({ input, value }: { input: AppInputTheme; value: AppInputThemeValue }) {
input.value = value
}
@Mutation
CHANGE_SETTINGS_THEME({ theme, settings }: { theme: string; settings: AppTheme['settings'] }) {
const currentTheme = this.themes.find(({ name }) => name === theme)
if (currentTheme) {
currentTheme.settings = settings
}
}
@Action
randomizeSettingsTheme() {
const theme = this.activeTheme
theme.settings = randomizeSettingsTheme(theme)
this.syncStoreWithQuery()
}
@Action
async changeTheme(themeName: string, themeSettings?: AppTheme['settings']) {
const isExistTheme = this.getThemeByName(themeName)
const finalTheme = isExistTheme ? themeName : this.themes[0].name
this.SET_STATE_APP({ key: 'currentTheme', value: finalTheme })
const needToChangeTheme = isExistTheme && themeSettings
if (needToChangeTheme) {
this.CHANGE_SETTINGS_THEME({ theme: finalTheme, settings: themeSettings })
}
this.syncThemeColor()
this.syncStoreWithQuery()
}
@Action
login() {
LightdmHandler.login(this.username, this.password, this.currentDesktop?.key)
}
@Action
changeSettingsThemeInput({ key, value }: { key: string; value: AppInputThemeValue }) {
const inputs = (this.activeTheme as AppTheme).settings || []
const input = inputs?.find(input => input.name === key)
if (input) {
this.CHANGE_THEME_INPUT({ input, value })
}
}
@Action
toggleShowPassword() {
this.SET_STATE_APP({ key: 'showPassword', value: !this.showPassword })
}
@Action
syncSettingsWithCache() {
localStorage.setItem('settings', JSON.stringify(this.getMainSettings))
}
@Action
syncThemeColor() {
const { color } = this.activeTheme
let activeColor = color.active
if (this.activeTheme.settings) {
const activeColorInput = this.activeTheme.settings.find(({ name }) => name === 'activeColor') as AppInputThemeGeneral
if (activeColorInput) {
activeColor = activeColorInput.value + ''
import { version } from '../../package.json'
export const useAppStore = defineStore('app', {
state: () => ({
version: version as string,
currentTheme: '',
currentOs: 'arch-linux',
desktop: LightdmHandler.defaultSession,
username: LightdmHandler.username,
password: '',
defaultColor: '#6BBBED',
battery: null as LightDMBattery | null,
brightness: 0,
zoom: 1,
users: LightdmHandler.users as LightdmUsers[],
desktops: LightdmHandler.sessions as LightdmSession[],
showPassword: false,
generateRandomThemes: false,
themes: AppThemes as AppTheme[],
bodyClass: {
blur: false,
'no-transition': false,
'show-framerate': false,
'only-ui': false
} as Record<string, boolean>
}),
getters: {
isCharging: (state) => state.battery?.status === 'Charging',
batteryLevel: (state) => state.battery?.level || 0,
isSupportFullApi: () => LightdmHandler.isSupportFullApi,
showFrameRate: (state) => state.bodyClass['show-framerate'],
viewThemeOnly: (state) => state.bodyClass['only-ui'],
isGithubMode: () => import.meta.env.VITE_APP_VIEW === 'github',
activeTheme: (state): AppTheme =>
state.themes.find(({ name }) => name === state.currentTheme) || defaultTheme,
currentUser: (state) => state.users.find(({ username }) => username === state.username),
currentDesktop: (state) => state.desktops.find(({ key }) => key === state.desktop),
getThemeByName: (state) => (theme: string) =>
state.themes.find(({ name }) => name === theme),
getThemeInput: (state) => (name: string, theme?: AppTheme) => {
const active = state.themes.find((t) => t.name === state.currentTheme) || defaultTheme
return (theme || active).settings?.find((input) => input.name === name)
},
getMainSettings: (state): AppSettings => ({
zoom: state.zoom,
themes: state.themes,
desktop: state.desktop,
username: state.username,
bodyClass: state.bodyClass,
currentOs: state.currentOs,
currentTheme: state.currentTheme,
defaultColor: state.defaultColor,
generateRandomThemes: state.generateRandomThemes
}),
personalInfo: (state) => ({
username: state.username,
currentTheme: state.currentTheme,
currentOs: state.currentOs,
desktop: state.desktop,
version: state.version,
defaultColor: state.defaultColor
})
},
actions: {
randomizeSettingsTheme() {
const theme = this.activeTheme
theme.settings = randomizeSettingsTheme(theme as AppTheme)
this.syncStoreWithQuery()
},
async changeTheme(themeName: string, themeSettings?: AppTheme['settings']) {
const isExistTheme = this.getThemeByName(themeName)
const finalTheme = isExistTheme ? themeName : this.themes[0]!.name
this.currentTheme = finalTheme
if (isExistTheme && themeSettings) {
this.changeSettingsTheme({ theme: finalTheme, settings: themeSettings })
}
}
setCSSVariable('--color-active', activeColor)
setCSSVariable('--color-bg', color.background)
}
@Action
syncStoreWithQuery() {
const { app: { $route, $router } } = router
const inputQuery = this.activeTheme.settings?.reduce<Record<string, string>>((query, input) => {
query[input.name] = input.value + ''
return query
}, {})
const bodyClassQuery = Object.entries(this.bodyClass).reduce<Record<string, string>>((query, [key, value]) => {
query[key] = value + ''
return query
}, {})
const query = { ...inputQuery, ...bodyClassQuery, themeName: this.currentTheme }
const routeTo = { name: $route.name || '/', query }
const mayReplace = isDifferentRoute(routeTo)
if (mayReplace) {
$router.replace(routeTo)
}
}
this.syncThemeColor()
this.syncStoreWithQuery()
},
login() {
LightdmHandler.login(this.username, this.password, this.currentDesktop?.key)
},
changeSettingsTheme({ theme, settings }: { theme: string; settings: AppTheme['settings'] }) {
const currentTheme = this.themes.find(({ name }) => name === theme)
if (currentTheme) currentTheme.settings = settings
},
changeThemeInput({ input, value }: { input: AppInputTheme; value: AppInputThemeValue }) {
input.value = value
},
changeSettingsThemeInput({ key, value }: { key: string; value: AppInputThemeValue }) {
const input = (this.activeTheme as AppTheme).settings?.find((i) => i.name === key)
if (input) input.value = value
},
toggleShowPassword() {
this.showPassword = !this.showPassword
},
changeBodyClass({ key, value }: { key: string; value: boolean }) {
this.bodyClass[key] = value
this.syncBodyClassWithStore({ settings: this.getMainSettings, query: router.currentRoute.value.query })
},
saveStateApp({ key, value }: { key: string; value: string }) {
(this as any)[key] = value
localStorage.setItem(key, value)
this.syncStoreWithQuery()
},
syncSettingsWithCache() {
localStorage.setItem('settings', JSON.stringify(this.getMainSettings))
},
syncThemeColor() {
const { color } = this.activeTheme
let activeColor = color.active
if (this.activeTheme.settings) {
const colorInput = this.activeTheme.settings.find(({ name }) => name === 'activeColor') as AppInputThemeGeneral
if (colorInput) activeColor = colorInput.value + ''
}
@Action
syncThemeWithStore({ settings, query }: { settings: AppSettings; query: Route['query'] }) {
let themeName = query.themeName as string || settings.currentTheme
const { generateRandomThemes } = settings
const indexTheme = Math.floor(randomize(0, this.themes.length - 1))
setCSSVariable('--color-active', activeColor)
setCSSVariable('--color-bg', color.background)
},
const syncTheme = this.themes.reduce((themes: AppTheme[], theme, index) => {
const cachedTheme = settings.themes.find(({ name }) => name === theme.name)
const isActiveTheme = generateRandomThemes ? indexTheme === index : theme.name === themeName
const hasCachedTheme = cachedTheme && cachedTheme?.settings
syncStoreWithQuery() {
const route = router.currentRoute.value
if (hasCachedTheme) {
const randomSettings = isActiveTheme && generateRandomThemes
const inputQuery = this.activeTheme.settings?.reduce<Record<string, string>>((query, input) => {
query[input.name] = input.value + ''
return query
}, {})
if (randomSettings) {
themeName = theme.name
}
const bodyClassQuery = Object.entries(this.bodyClass).reduce<Record<string, string>>((query, [key, value]) => {
query[key] = value + ''
return query
}, {})
theme.settings = randomSettings
? randomizeSettingsTheme(theme)
: theme.settings?.map(input => {
const cachedThemeInput = this.getThemeInput(input.name, cachedTheme)
let value = cachedThemeInput?.value ?? input.value
const query = { ...inputQuery, ...bodyClassQuery, themeName: this.currentTheme }
const routeTo = { name: route.name || '/', query }
if (isActiveTheme) {
const queryThemeInput = input.name in query ? parseQueryValue(query[input.name] as string) : null
value = queryThemeInput ?? value
}
return Object.assign(input, { value })
})
if (isDifferentRoute(routeTo)) {
router.replace(routeTo)
}
},
syncThemeWithStore({ settings, query }: { settings: AppSettings; query: RouteLocationNormalized['query'] }) {
let themeName = (query.themeName as string) || settings.currentTheme
const { generateRandomThemes } = settings
const indexTheme = Math.floor(randomize(0, this.themes.length - 1))
const syncTheme = this.themes.reduce((themes: AppTheme[], theme, index) => {
const cachedTheme = settings.themes.find(({ name }) => name === theme.name)
const isActiveTheme = generateRandomThemes ? indexTheme === index : theme.name === themeName
const hasCachedTheme = cachedTheme && cachedTheme.settings
if (hasCachedTheme) {
if (isActiveTheme && generateRandomThemes) {
themeName = theme.name
theme.settings = randomizeSettingsTheme(theme)
} else {
theme.settings = theme.settings?.map((input) => {
const cachedInput = this.getThemeInput(input.name, cachedTheme)
let value = cachedInput?.value ?? input.value
if (isActiveTheme) {
const queryInput = input.name in query ? parseQueryValue(query[input.name] as string) : null
value = queryInput ?? value
}
return { ...input, value }
})
}
}
themes.push(theme)
themes.push(theme)
if (isActiveTheme) this.currentTheme = themeName
if (isActiveTheme) { this.SAVE_STATE_APP({ key: 'currentTheme', value: themeName }) }
return themes
}, [])
return themes
}, [])
this.themes = syncTheme
},
this.SET_STATE_APP({ key: 'themes', value: syncTheme })
}
syncBodyClassWithStore({ settings, query }: { settings: AppSettings; query: RouteLocationNormalized['query'] }) {
const bodyClassKeys = Object.keys(this.bodyClass)
const queryBodyClass = bodyClassKeys.reduce<Record<string, boolean>>((bodyClass, key) => {
if (key in query) bodyClass[key] = query[key] === 'true'
return bodyClass
}, {})
@Action
syncBodyClassWithStore({ settings, query }: { settings: AppSettings; query: Route['query'] }) {
const bodyClassKeys = Object.keys(this.bodyClass)
const queryBodyClass = bodyClassKeys.reduce<Record<string, boolean>>((bodyClass, key) => {
if (key in query) { bodyClass[key] = query[key] === 'true' }
return bodyClass
}, {})
this.SET_STATE_APP({ key: 'bodyClass', value: { ...settings.bodyClass, ...queryBodyClass } })
}
this.bodyClass = { ...settings.bodyClass, ...queryBodyClass }
},
@Action
setUpSettings() {
const { app: { $route } } = router
const { query } = $route
setUpSettings() {
const query = router.currentRoute.value.query
try {
const settings: AppSettings = JSON.parse(localStorage.getItem('settings') || '{}')
this.SET_STATE_APP({ key: 'generateRandomThemes', value: settings.generateRandomThemes || false })
try {
const settings: AppSettings = JSON.parse(localStorage.getItem('settings') || '{}')
this.generateRandomThemes = settings.generateRandomThemes || false
if (settings.themes) {
this.syncThemeWithStore({ settings, query })
}
if (settings.themes) this.syncThemeWithStore({ settings, query })
if (settings.bodyClass) this.syncBodyClassWithStore({ settings, query })
if (settings.bodyClass) {
this.syncBodyClassWithStore({ settings, query })
}
const isExistDE = window.lightdm?.sessions.find(({ key }) => key === settings.desktop)
if (!isExistDE) settings.desktop = window.lightdm?.sessions[0]?.key || 'openbox'
const isExistDE = window.lightdm?.sessions.find(({ key }) => key === settings.desktop)
if (isExistDE === undefined) {
settings.desktop = window.lightdm?.sessions[0].key || 'openbox'
}
const isExistUser = window.lightdm?.users.find(({ username }) => username === settings.username)
if (!isExistUser) settings.username = window.lightdm?.users[0]?.username || 'Warinyourself'
const isExistUser = window.lightdm?.users.find(({ username }) => username === settings.username)
if (isExistUser === undefined) {
settings.username = window.lightdm?.users[0].username || 'Warinyourself'
this.currentOs = settings.currentOs || 'arch-linux'
this.desktop = settings.desktop
this.username = settings.username
this.zoom = settings.zoom || 1
} catch {
this.setUpSettings()
}
this.SET_STATE_APP({ key: 'currentOs', value: settings.currentOs || 'arch-linux' })
this.SET_STATE_APP({ key: 'desktop', value: settings.desktop })
this.SET_STATE_APP({ key: 'username', value: settings.username })
this.SET_STATE_APP({ key: 'zoom', value: settings.zoom || 1 })
} catch (error) {
this.setUpSettings()
}
}
}
export const AppModule = getModule(App)
})

@ -1,9 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { config } from 'vuex-module-decorators'
config.rawError = true
Vue.use(Vuex)
export default new Vuex.Store({})

@ -1,196 +1,150 @@
import {
Module,
VuexModule,
getModule,
Mutation,
Action
} from 'vuex-module-decorators'
import store from '@/store/index'
import { InteractiveBlock, InteractiveBlockIds, AppMenu, LoginPosition, AppMenuMain, DialogInterface } from '@/models/page'
import { AppModule } from './app'
@Module({ dynamic: true, store, name: 'page' })
class Page extends VuexModule {
menu: AppMenuMain | AppMenu = {
view: false,
items: []
}
dialog: DialogInterface | null = null
mainTabIndex = 0
language = ''
loginPosition: LoginPosition = 'center'
languages: string[] = []
activeBlocks: InteractiveBlock[] = []
interactiveBlocks: InteractiveBlock[] = [
{
id: 'login',
closeBeforeMount: ['settings'],
mayOpen: () => !AppModule.viewThemeOnly
import { defineStore } from 'pinia'
import { useAppStore } from '@/store/app'
import type { InteractiveBlock, InteractiveBlockIds, AppMenu, LoginPosition, AppMenuMain, DialogInterface } from '@/models/page'
export const usePageStore = defineStore('page', {
state: () => ({
menu: {
view: false,
items: []
} as AppMenuMain | AppMenu,
dialog: null as DialogInterface | null,
mainTabIndex: 0,
language: '',
loginPosition: 'center' as LoginPosition,
languages: [] as string[],
activeBlocks: [] as InteractiveBlock[],
interactiveBlocks: [
{
id: 'login',
closeBeforeMount: ['settings'],
mayOpen: () => !useAppStore().viewThemeOnly
},
{
id: 'settings',
closeBeforeMount: ['login'],
openAfterDestroy: ['login']
}
] as InteractiveBlock[]
}),
getters: {
locale: (state): string => {
const localesMap: Record<string, string> = {
ru: 'ru-RU',
en: 'en-US',
fr: 'fr-FR',
de: 'de-DE',
es: 'es-ES'
}
return localesMap[state.language] || 'en-US'
},
{
id: 'settings',
closeBeforeMount: ['login'],
openAfterDestroy: ['login']
}
]
get locale() {
const localesMap: Record<string, string> = {
ru: 'ru-RU',
en: 'en-US',
fr: 'fr-FR',
de: 'de-DE',
es: 'es-ES'
}
return localesMap[this.language] || 'en-US'
}
get getBlock() {
return (id: InteractiveBlockIds) => {
return this.activeBlocks.find((activeBlock) => id === activeBlock.id)
}
}
get isOpenBlock() {
return (id: InteractiveBlockIds) => {
return !!this.getBlock(id)
}
}
get activeBlock(): InteractiveBlock | undefined {
return this.activeBlocks.slice(-1)[0]
}
@Mutation
SET_STATE_PAGE<S extends this, K extends keyof this>({ key, value }: { key: K; value: S[K] }) {
this[key] = value
}
getBlock: (state) => (id: InteractiveBlockIds) =>
state.activeBlocks.find((b) => b.id === id),
@Mutation
OPEN_ACTIVE_BLOCK(id: InteractiveBlockIds) {
const activeBlock = this.interactiveBlocks.find((block) => block.id === id)
if (!activeBlock) { return }
const canOpen = activeBlock?.mayOpen ? activeBlock?.mayOpen() : true
if (!canOpen) { return }
this.activeBlocks.push(activeBlock)
}
isOpenBlock: (state) => (id: InteractiveBlockIds) =>
!!state.activeBlocks.find((b) => b.id === id),
@Mutation
CLOSE_ACTIVE_BLOCK(idBlock?: string) {
const index = this.activeBlocks.findIndex(({ id }) => id === idBlock)
activeBlock: (state): InteractiveBlock | undefined =>
state.activeBlocks[state.activeBlocks.length - 1]
},
if (index !== -1) {
this.activeBlocks.splice(index, 1)
} else if (!idBlock) {
this.activeBlocks.pop()
}
}
actions: {
openFirstBlock() {
for (const block of this.interactiveBlocks) {
const canOpen = block.mayOpen ? block.mayOpen() : true
if (canOpen) {
this.openActiveBlock(block.id)
break
}
}
},
@Mutation
CLOSE_ALL_ACTIVE_BLOCK() {
this.activeBlocks = []
}
openActiveBlock(id: InteractiveBlockIds) {
const activeBlock = this.interactiveBlocks.find((b) => b.id === id)
if (!activeBlock) return
@Mutation
ASSIGN_MENU(menu: Partial<AppMenu>) {
this.menu = Object.assign(this.menu, menu)
}
const canOpen = activeBlock.mayOpen ? activeBlock.mayOpen() : true
if (!canOpen) return
@Action
openTab({ type }: { type: 'settings' | 'themes' | 'custom' }) {
const hasCustomCurrentTheme = AppModule.activeTheme?.settings?.length !== undefined
let updatedTabIndex = this.mainTabIndex
this.activeBlocks.push(activeBlock)
},
switch (type) {
case 'settings': {
updatedTabIndex = hasCustomCurrentTheme ? 2 : 1
break
}
case 'custom': {
updatedTabIndex = hasCustomCurrentTheme ? 1 : updatedTabIndex
break
closeActiveBlock(idBlock?: string) {
const index = this.activeBlocks.findIndex(({ id }) => id === idBlock)
if (index !== -1) {
this.activeBlocks.splice(index, 1)
} else if (!idBlock) {
this.activeBlocks.pop()
}
default: {
updatedTabIndex = 0
}
}
},
this.SET_STATE_PAGE({ key: 'mainTabIndex', value: updatedTabIndex })
}
closeAllBlocks() {
this.activeBlocks = []
},
@Action
openFirstBlock() {
for (let i = 0; i < this.interactiveBlocks.length; i++) {
const block = this.interactiveBlocks[i]
const open = block.mayOpen ? block.mayOpen() : true
assignMenu(menu: Partial<AppMenu>) {
this.menu = Object.assign(this.menu, menu)
},
if (open) {
this.OPEN_ACTIVE_BLOCK(block.id)
break
openTab({ type }: { type: 'settings' | 'themes' | 'custom' }) {
const hasCustomCurrentTheme = useAppStore().activeTheme?.settings?.length !== undefined
let updatedTabIndex = this.mainTabIndex
switch (type) {
case 'settings':
updatedTabIndex = hasCustomCurrentTheme ? 2 : 1
break
case 'custom':
updatedTabIndex = hasCustomCurrentTheme ? 1 : updatedTabIndex
break
default:
updatedTabIndex = 0
}
}
}
@Action
async openBlock(settings: { id: InteractiveBlockIds }) {
settings = settings || {}
const { id } = settings
if (!id) { return }
this.mainTabIndex = updatedTabIndex
},
// Already active block
if (this.activeBlocks.find((block) => block.id === id)) { return }
async openBlock(settings: { id: InteractiveBlockIds }) {
const { id } = settings || {}
if (!id) return
const block = this.interactiveBlocks.find((block) => block.id === id)
if (!block) { return }
if (this.activeBlocks.find((b) => b.id === id)) return
const closeBlocks = block.closeBeforeMount
if (closeBlocks) {
closeBlocks.forEach((id) => this.CLOSE_ACTIVE_BLOCK(id))
}
const block = this.interactiveBlocks.find((b) => b.id === id)
if (!block) return
this.OPEN_ACTIVE_BLOCK(id)
}
@Action
async closeBlock(settings?: { id?: InteractiveBlockIds }) {
settings = settings || {}
const lastIdActiveBlock = this.activeBlocks.slice(-1)[0].id
const id = settings.id || lastIdActiveBlock
if (!id) { return }
block.closeBeforeMount?.forEach((bid) => this.closeActiveBlock(bid))
this.openActiveBlock(id)
},
const block = this.activeBlocks.find((block) => block.id === id)
if (!block) { return }
async closeBlock(settings?: { id?: InteractiveBlockIds }) {
const lastId = this.activeBlocks[this.activeBlocks.length - 1]?.id
const id = settings?.id || lastId
if (!id) return
const openBlocks = block.openAfterDestroy
const block = this.activeBlocks.find((b) => b.id === id)
if (!block) return
if (openBlocks) {
openBlocks.forEach((id) => this.OPEN_ACTIVE_BLOCK(id))
}
block.openAfterDestroy?.forEach((bid) => this.openActiveBlock(bid))
let needToClose = true
if (block.callbackBeforeClose) {
needToClose = block.callbackBeforeClose?.map(callback => callback()).reduce((a, b) => a && b, true)
}
let needToClose = true
if (block.callbackBeforeClose) {
needToClose = block.callbackBeforeClose.map((cb) => cb()).every(Boolean)
}
if (needToClose) {
this.CLOSE_ACTIVE_BLOCK(id)
}
}
if (needToClose) this.closeActiveBlock(id)
},
@Action
openDialog(dialog: DialogInterface) {
this.SET_STATE_PAGE({ key: 'dialog', value: dialog })
}
openDialog(dialog: DialogInterface) {
this.dialog = dialog
},
@Action
closeDialog() {
this.SET_STATE_PAGE({ key: 'dialog', value: null })
closeDialog() {
this.dialog = null
}
}
}
export const PageModule = getModule(Page)
})

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

@ -1,4 +1,3 @@
@import './agida.styl'
@import './osmos.styl'
@import './space.styl'
@import './suprematism.styl'

@ -7,9 +7,9 @@ export type ColorArray = [number, number, number];
export const hexToRgb = (color: string): ColorArray => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color)
return result ? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
parseInt(result[1]!, 16),
parseInt(result[2]!, 16),
parseInt(result[3]!, 16)
] : [0, 0, 0]
}

@ -1,4 +1,4 @@
import { AppTheme } from '@/models/app'
import type { AppTheme } from '@/models/app'
import {
pxratio,
hueSlider,
@ -30,43 +30,7 @@ export const defaultTheme: AppTheme = {
]
}
const AGIDA_TYPES = ['agida', 'knife', 'yin yang'] as const
export type AgidaTypes = typeof AGIDA_TYPES[number]
export const AppThemes: AppTheme[] = [
{
name: 'Agida',
component: 'agida',
color: {
active: '#04ded4',
background: '#19102e'
},
settings: [
{
name: 'palette',
type: 'palette',
label: 'input.slider-amount',
value: 0,
values: [
['#00CC99', '#6600FF'],
['#B9E0FF', '#9F73AB'],
['#CBCB68', '#3E9C8F'],
['#51eaea', '#D62E93'],
['#AC3ABB', '#511e78'],
['#B5D115', '#FB044F'],
['#fc5185', '#3fc1c9'],
['#f5f5f5', '#364f6b']
]
},
{
name: 'type',
label: 'type',
type: 'selector',
value: 'agida',
values: AGIDA_TYPES
}
]
},
defaultTheme,
{
name: 'Sphere',

@ -1,11 +1,14 @@
import { AppInputButton, AppInputThemeGeneral, AppInputThemePalette, AppInputThemeSlider, AppInputThemeValue, AppTheme } from '@/models/app'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { debounce, DebounceSettings } from 'lodash'
import { RawLocation } from 'vue-router'
import router from '../router'
import type { AppInputButton, AppInputThemeGeneral, AppInputThemePalette, AppInputThemeSlider, AppTheme } from '@/models/app'
import { debounce, type DebounceSettings } from 'lodash'
import type { RouteLocationRaw } from 'vue-router'
import router from '@/router'
import { LightdmHandler } from '@/utils/lightdm'
// Stores are imported statically but only CALLED inside functions/callbacks
// This is safe thanks to ES module live bindings (no circular dep issue at runtime)
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
export const modKey = 'ctrl'
export const languageMap: Record<string, string> = {
ru: 'Русский',
@ -15,70 +18,44 @@ export const languageMap: Record<string, string> = {
es: 'Español'
}
export function isDifferentRoute(to: RawLocation) {
const { app: { $route, $router } } = router
const resolve = $router.resolve(to)
return $route.fullPath !== resolve.href
export function isDifferentRoute(to: RouteLocationRaw) {
const resolve = router.resolve(to)
return router.currentRoute.value.fullPath !== resolve.fullPath
}
export function Debounce(time = 500, options?: DebounceSettings): MethodDecorator {
const map = new Map<number, any>()
return function(_target, _propertyKey, descriptor: PropertyDescriptor) {
const method = descriptor.value
descriptor.value = function(...args: any[]) {
const _uid = (this as Vue & { _uid: number})._uid
const callback = map.get(_uid)
if (callback) {
return callback(...args)
}
const deb = debounce(method.bind(this), time, options)
map.set(_uid, deb)
return deb(...args)
}
}
export function createDebounce<T extends (...args: any[]) => any>(fn: T, time = 500, options?: DebounceSettings) {
return debounce(fn, time, options) as unknown as T
}
export function parseQueryValue(value: string) {
const isBoolean = ['false', 'true'].includes(value)
if (isBoolean) {
return value === 'true'
}
if (isBoolean) return value === 'true'
const isNumber = !isNaN(parseFloat(value))
if (isNumber) {
return +value
}
if (isNumber) return +value
return value
}
export function randomize(min: number, max: number) {
return (Math.random() * (max - min)) + min
return Math.random() * (max - min) + min
}
export function generateRandomSliderValue(input: AppInputThemeSlider) {
const ignoreSliders = ['pxratio', 'brightness']
if (ignoreSliders.includes(input.name)) { return input.value }
if (ignoreSliders.includes(input.name)) return input.value
const { min, max } = input.options
const decimalPlaces = ((input.options.step + '').split('.')[1] || '').length
const newValue = +(randomize(min, max).toFixed(decimalPlaces))
return newValue
return +(randomize(min, max).toFixed(decimalPlaces))
}
export function generateRandomColor() {
return '#' + (Math.floor(Math.random() * 2 ** 24 - 1)).toString(16)
return '#' + Math.floor(Math.random() * 2 ** 24 - 1).toString(16)
}
export function getDesktopIcon(desktop = '') {
const iconMap = {
const iconMap: Record<string, RegExp> = {
gnome: /gnome/,
openbox: /openbox/,
awesome: /awesome/,
@ -91,68 +68,53 @@ export function getDesktopIcon(desktop = '') {
kodi: /kodi/
}
const [icon] = Object.entries(iconMap).find(([icon, regEx]) => {
return desktop.match(regEx)
}) || ['unknown']
const [icon] = Object.entries(iconMap).find(([, re]) => desktop.match(re)) || ['unknown']
return icon
}
export function generateDesktopIcons() {
return AppModule.desktops.map((desktop) => {
const icon = getDesktopIcon(desktop.key)
return {
text: desktop.name,
value: desktop.key,
icon
}
})
return useAppStore().desktops.map((desktop) => ({
text: desktop.name,
value: desktop.key,
icon: getDesktopIcon(desktop.key)
}))
}
const systemActions = ['hibernate', 'restart', 'shutdown', 'suspend'] as const
type systemActionsType = typeof systemActions[number]
type systemActionsType = (typeof systemActions)[number]
export function buildSystemDialog(callbackName: systemActionsType) {
return () => PageModule.openDialog({
title: `modals.${callbackName}.title`,
text: `modals.${callbackName}.text`,
actions: [
{
title: 'text.yes',
callback: LightdmHandler[callbackName]
},
{
title: 'text.no',
callback: PageModule.closeDialog
}
]
})
return () => {
const pageStore = usePageStore()
pageStore.openDialog({
title: `modals.${callbackName}.title`,
text: `modals.${callbackName}.text`,
actions: [
{ title: 'text.yes', callback: LightdmHandler[callbackName] },
{ title: 'text.no', callback: () => pageStore.closeDialog() }
]
})
}
}
export const systemActionsObject = systemActions.reduce((acc, action) => {
return {
...acc,
[action]: buildSystemDialog(action)
}
}, {} as Record<systemActionsType, () => void>)
export const systemActionsObject = systemActions.reduce(
(acc, action) => ({ ...acc, [action]: buildSystemDialog(action) }),
{} as Record<systemActionsType, () => void>
)
export function preventDefault(event: Event, callback?: () => void): void {
event.preventDefault()
callback && callback()
callback?.()
}
export function focusInputPassword() {
const inputPassword = document.querySelector('#password') as HTMLInputElement
if (inputPassword) {
inputPassword.focus()
}
const el = document.querySelector('#password') as HTMLInputElement | null
el?.focus()
}
export function stopPropagation(event: Event, callback?: () => void): void {
event.stopPropagation()
callback && callback()
callback?.()
}
export function hasSomeParentClass(element: HTMLElement, tag: string): boolean {
@ -160,20 +122,16 @@ export function hasSomeParentClass(element: HTMLElement, tag: string): boolean {
}
export function randomizeSettingsTheme(theme: AppTheme) {
const generateValueObject: Record<string, (input: any) => any> = {
const generateValue: Record<string, (input: any) => any> = {
slider: (input: AppInputThemeSlider) => generateRandomSliderValue(input),
checkbox: () => Math.random() > 0.5,
color: () => generateRandomColor(),
palette: (input: AppInputThemePalette) => Math.floor(randomize(0, (input.values?.length || 2) - 1))
}
return theme.settings?.map(input => {
const changeValueFunction = generateValueObject[input.type]
if (changeValueFunction) {
input.value = changeValueFunction(input)
}
return theme.settings?.map((input) => {
const fn = generateValue[input.type]
if (fn) input.value = fn(input)
return input
})
}
@ -189,7 +147,7 @@ export const randomButton: AppInputButton = {
type: 'button',
icon: 'random',
callback() {
AppModule.randomizeSettingsTheme()
useAppStore().randomizeSettingsTheme()
}
}
@ -202,28 +160,11 @@ export function buildInputSlider({
icon = 'time',
changeOnUpdate = true
} = {}): AppInputThemeSlider {
return {
name,
label: `input.${name}`,
value,
icon,
type: 'slider',
options: { changeOnUpdate, max, step, min }
}
return { name, label: `input.${name}`, value, icon, type: 'slider', options: { changeOnUpdate, max, step, min } }
}
export function buildInputColor({
name = 'active-color',
value = '#00CC99',
...options
} = {}): AppInputThemeGeneral {
return {
name,
value,
label: `input.${name}`,
type: 'color',
...options
}
export function buildInputColor({ name = 'active-color', value = '#00CC99', ...options } = {}): AppInputThemeGeneral {
return { name, value, label: `input.${name}`, type: 'color', ...options }
}
export const pxratio = () => buildInputSlider({ name: 'pxratio', icon: 'pxratio', min: 0.01, max: 1, value: 0.8 })

@ -1,15 +1,17 @@
import { PageModule } from '@/store/page'
import { AppModule } from '@/store/app'
import { usePageStore } from '@/store/page'
import { useAppStore } from '@/store/app'
import { systemActionsObject, modKey } from '@/utils/helper'
const prefixTitle = 'settings.keyboard.'
export const hotkeys = [
{
keys: [modKey, 't'],
title: `${prefixTitle}open-themes`,
callback: () => {
PageModule.openTab({ type: 'themes' })
PageModule.openBlock({ id: 'settings' })
const pageStore = usePageStore()
pageStore.openTab({ type: 'themes' })
pageStore.openBlock({ id: 'settings' })
}
},
{
@ -19,7 +21,7 @@ export const hotkeys = [
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
document.exitFullscreen && document.exitFullscreen()
document.exitFullscreen?.()
}
}
},
@ -27,38 +29,34 @@ export const hotkeys = [
keys: [modKey, 'c'],
title: `${prefixTitle}open-custom`,
callback: () => {
PageModule.openTab({ type: 'custom' })
PageModule.openBlock({ id: 'settings' })
const pageStore = usePageStore()
pageStore.openTab({ type: 'custom' })
pageStore.openBlock({ id: 'settings' })
}
},
{
keys: [modKey, 's'],
title: `${prefixTitle}open-settings`,
callback: () => {
PageModule.openTab({ type: 'settings' })
PageModule.openBlock({ id: 'settings' })
const pageStore = usePageStore()
pageStore.openTab({ type: 'settings' })
pageStore.openBlock({ id: 'settings' })
}
},
{
keys: [modKey, 'h'],
title: `${prefixTitle}hide-windows`,
callback: () => {
PageModule.CLOSE_ALL_ACTIVE_BLOCK()
}
callback: () => usePageStore().closeAllBlocks()
},
{
keys: [modKey, 'r'],
title: `${prefixTitle}randomize-theme`,
callback: () => {
AppModule.randomizeSettingsTheme()
}
callback: () => useAppStore().randomizeSettingsTheme()
},
{
keys: [modKey, 'i'],
title: `${prefixTitle}show-password`,
callback: () => {
AppModule.toggleShowPassword()
}
callback: () => useAppStore().toggleShowPassword()
},
{
keys: [modKey, 'P'],
@ -77,4 +75,4 @@ export const hotkeys = [
}
]
export type hotkeysType = typeof hotkeys[number]
export type hotkeysType = (typeof hotkeys)[number]

@ -1,19 +1,13 @@
import { AppState } from '@/store/app'
/**
* INFO: To avoid recoursive requires modules (lightdm and AppModule)
* @returns AppState
*/
async function getAppModule(): Promise<AppState> {
const module = await import('@/store/app') as any
return module.AppModule
async function getAppStore() {
const { useAppStore } = await import('@/store/app')
return useAppStore()
}
const DEBUG_PASSWORD = 'password'
const lightdmDebug = window.lightdm === undefined
function setIsAuthenticated(value: boolean) {
(window.lightdm as any).is_authenticated = value
;(window.lightdm as any).is_authenticated = value
}
if (lightdmDebug) {
@ -34,68 +28,19 @@ if (lightdmDebug) {
},
brightness: Math.ceil(Math.random() * 99 + 1),
battery_update: {
_callbacks: [],
_emit: () => {
window.lightdm?.battery_update._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.battery_update._callbacks.push(callback)
}
},
authentication_complete: {
_callbacks: [],
_emit: () => {
console.log(window.lightdm?.authentication_complete._callbacks)
window.lightdm?.authentication_complete._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.authentication_complete._callbacks.push(callback)
}
},
brightness_update: {
_callbacks: [],
_emit: () => {
window.lightdm?.brightness_update._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.brightness_update._callbacks.push(callback)
}
},
battery_update: { connect: (callback: () => void) => { console.log('battery update') } },
authentication_complete: { connect: (callback: () => void) => { console.log('authentication complete') } },
brightness_update: { connect: (callback: () => void) => { console.log('brightness update') } },
sessions: [
{
name: 'i3wm',
key: 'i3'
},
{
name: 'KDE 5',
key: 'plasma-shell'
},
{
name: 'Kodi',
key: 'kodi'
},
{
name: 'Gnome 3',
key: 'gnome-shell'
},
{
name: 'XFCE 4',
key: 'xfce'
},
{
name: 'Openbox',
key: 'openbox'
},
{
name: 'Cinnamon',
key: 'cinnamon'
},
{
name: 'xmonad',
key: 'xmonad'
}
{ name: 'i3wm', key: 'i3' },
{ name: 'KDE 5', key: 'plasma-shell' },
{ name: 'Kodi', key: 'kodi' },
{ name: 'Gnome 3', key: 'gnome-shell' },
{ name: 'XFCE 4', key: 'xfce' },
{ name: 'Openbox', key: 'openbox' },
{ name: 'Cinnamon', key: 'cinnamon' },
{ name: 'xmonad', key: 'xmonad' }
],
users: [
{
@ -103,144 +48,71 @@ if (lightdmDebug) {
username: 'Warinyourself',
image: 'https://avatars.githubusercontent.com/u/83131232?s=200&u=26fbedfe561a2b37225c78c10b1c5d67d6fe1832&v=4'
},
{
display_name: 'Bob',
username: 'Bob'
}
{ display_name: 'Bob', username: 'Bob' }
],
languages: [
{
name: 'Русский',
code: 'ru_RU.utf8'
},
{
name: 'American English',
code: 'en_US.utf8'
}
{ name: 'Русский', code: 'ru_RU.utf8' },
{ name: 'American English', code: 'en_US.utf8' }
],
language: { code: 'en_US', name: 'American English' },
start_authentication: (username: string) => {
console.log(`Starting authenticating here: '${username}'`)
const inputNode = document.getElementById('password') as HTMLInputElement
window.lightdm?.respond(inputNode?.value || '')
},
authenticate: (username: string) => {
const inputNode = document.getElementById('password') as HTMLInputElement
console.log(`Starting authenticating user: '${username}'`)
if (window.lightdm) {
(window.lightdm as any).authentication_user = username
}
if (window.lightdm) (window.lightdm as any).authentication_user = username
window.lightdm?.respond(inputNode?.value || '')
},
cancel_authentication: () => {
console.log('Auth cancelled')
},
start_session(session: string) {
alert(`Start session: ${session}`)
},
cancel_authentication: () => { console.log('Auth cancelled') },
start_session(session: string) { alert(`Start session: ${session}`) },
respond: (password: string) => {
console.log(`Password provided : '${password}'`)
if (password === DEBUG_PASSWORD) {
setIsAuthenticated(true)
} else {
setIsAuthenticated(false)
}
window.lightdm?.authentication_complete._emit()
},
shutdown() {
alert('(DEBUG: System is shutting down)')
},
hibernate() {
alert('(DEBUG: System is shutting down)')
},
suspend: () => {
alert('(DEBUG: System is suspending)')
},
restart: () => {
alert('(DEBUG: System is rebooting)')
}
shutdown() { alert('(DEBUG: System is shutting down)') },
hibernate() { alert('(DEBUG: System is hibernating)') },
suspend: () => { alert('(DEBUG: System is suspending)') },
restart: () => { alert('(DEBUG: System is rebooting)') }
} as any
}
const isSupportFullApi = 'battery_data' in (window.lightdm || {}) || 'brightness' in (window.lightdm || {})
const isSupportFullApi =
'battery_data' in (window.lightdm || {}) || 'brightness' in (window.lightdm || {})
class LightdmWebkit {
protected _inputErrorTimer!: null | NodeJS.Timeout
protected _inputErrorTimer!: null | ReturnType<typeof setTimeout>
get defaultSession() {
return this.sessions[0]?.key || window.lightdm?.default_session || 'i3'
}
get isSupportFullApi() {
return isSupportFullApi
}
get sessions() {
return window.lightdm?.sessions || []
}
get hasGuestAccount() {
return window.lightdm?.has_guest_account
}
get isSupportFullApi() { return isSupportFullApi }
get sessions() { return window.lightdm?.sessions || [] }
get hasGuestAccount() { return window.lightdm?.has_guest_account }
get users() { return window.lightdm?.users || [] }
get username() { return this.users[0]?.username || 'Username' }
get languages() { return window.lightdm?.languages }
get canRestart() { return window.lightdm?.can_restart }
get canShutdown() { return window.lightdm?.can_shutdown }
get canSuspend() { return window.lightdm?.can_suspend }
get canHibernate() { return window.lightdm?.can_hibernate }
get users() {
return window.lightdm?.users || []
}
get username() {
return this.users[0]?.username || 'Username'
}
get languages() {
return window.lightdm?.languages
}
get canRestart() {
return window.lightdm?.can_restart
}
get canShutdown() {
return window.lightdm?.can_shutdown
}
get canSuspend() {
return window.lightdm?.can_suspend
}
get canHibernate() {
return window.lightdm?.can_hibernate
}
shutdown() {
return window.lightdm?.shutdown()
}
hibernate() {
return window.lightdm?.hibernate()
}
suspend() {
return window.lightdm?.suspend()
}
restart() {
return window.lightdm?.restart()
}
shutdown() { return window.lightdm?.shutdown() }
hibernate() { return window.lightdm?.hibernate() }
suspend() { return window.lightdm?.suspend() }
restart() { return window.lightdm?.restart() }
protected _setInputError() {
const inputNode = document.getElementById('password')
if (!inputNode) return
inputNode.classList.add('password-input--error')
if (this._inputErrorTimer) {
clearTimeout(this._inputErrorTimer)
}
if (this._inputErrorTimer) clearTimeout(this._inputErrorTimer)
this._inputErrorTimer = setTimeout(() => {
inputNode.classList.remove('password-input--error')
@ -263,7 +135,6 @@ class LightdmNode extends LightdmWebkit {
this._username = username
this._password = password
this._session = session || this.defaultSession
window.lightdm?.authenticate(username)
}
@ -287,12 +158,11 @@ class LightdmNode extends LightdmWebkit {
}
public setSignalHandler(): void {
window.lightdm?.show_message?.connect(function(text, type) {
window.lightdm?.show_message?.connect(function (text, type) {
console.log({ text, type })
})
window.lightdm?.show_prompt?.connect((_message, type) => {
console.log({ _message, type })
if (!window.lightdm) return
if (type === 0) {
window.lightdm.respond(this._username)
@ -313,13 +183,13 @@ class LightdmNode extends LightdmWebkit {
}
public async updateBatteryData(): Promise<void> {
const module = await getAppModule()
module.SET_STATE_APP({ key: 'battery', value: window.lightdm?.battery_data || null })
const store = await getAppStore()
store.battery = window.lightdm?.battery_data || null
}
public async updateBrightData(): Promise<void> {
const module = await getAppModule()
module.SET_STATE_APP({ key: 'brightness', value: window.lightdm?.brightness })
const store = await getAppStore()
store.brightness = window.lightdm?.brightness || 0
}
public init(): void {

@ -1,15 +1,14 @@
import { PageModule } from '@/store/page'
import Vue from 'vue'
import { DateTimeFormatOptions } from 'vue-i18n'
import { reactive } from 'vue'
import { usePageStore } from '@/store/page'
const timeRef = Vue.observable({
const timeRef = reactive({
time: new Date(),
shortTime: '',
longTime: ''
})
const formatTime = (type: 'long' | 'short' = 'short') => {
const options: DateTimeFormatOptions = {
const options: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
hour: 'numeric',
@ -22,20 +21,18 @@ const formatTime = (type: 'long' | 'short' = 'short') => {
options.weekday = 'long'
}
return new Intl.DateTimeFormat(PageModule.locale, options).format(new Date())
}
export const initTimer = () => {
setInterval(updateTime, 1000)
return new Intl.DateTimeFormat(usePageStore().locale, options).format(new Date())
}
const updateTime = () => {
timeRef.time = new Date()
timeRef.shortTime = formatTime('short')
timeRef.longTime = formatTime('long')
}
updateTime()
export const initTimer = () => {
updateTime()
setInterval(updateTime, 1000)
}
export default timeRef

@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

@ -1,8 +1,8 @@
import { Component, Vue } from 'vue-property-decorator'
import { LoginPosition } from '@/models/page'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { defineComponent, onMounted, onBeforeUnmount } from 'vue'
import { useAppStore } from '@/store/app'
import { usePageStore } from '@/store/page'
import { useI18n } from 'vue-i18n'
import { hasSomeParentClass } from '@/utils/helper'
import AppBar from '@/components/app/AppBar'
import AppMenu from '@/components/app/AppMenu'
@ -13,135 +13,74 @@ import FrameRateBlock from '@/components/base/FrameRateBlock'
import BackgroundImage from '@/components/base/BackgroundImage'
import ShutdownButton from '@/components/base/ShutdownButton'
import GithubButton from '@/components/base/GithubButton'
import { hasSomeParentClass } from '@/utils/helper'
@Component({
components: {
AppMenu,
GithubButton,
ShutdownButton,
FrameRateBlock,
LoginComponent,
BackgroundImage,
SettingsComponent
}
})
export default class HomePage extends Vue {
get activeBlock() {
return PageModule.activeBlock
}
get menu() {
return PageModule.menu
}
get showFrameRate() {
return AppModule.showFrameRate
}
get isOpenLogin() {
return PageModule.isOpenBlock('login')
}
get isOpenBlock() {
return !!PageModule.activeBlock
}
get isOpenSettings() {
return PageModule.isOpenBlock('settings')
}
get isViewThemeOnly() {
return AppModule.viewThemeOnly
}
get isGithubMode() {
return AppModule.isGithubMode
}
get showGithubButton() {
return this.isGithubMode && this.isOpenBlock
}
get showLogin() {
return !this.isViewThemeOnly && this.isOpenLogin
}
get showMenu() {
return PageModule.menu.view
}
get isSupportFullApi() {
return AppModule.isSupportFullApi
}
get hasActiveBlock() {
return !!PageModule.activeBlock
}
get showAppBar() {
return !AppModule.viewThemeOnly && this.isSupportFullApi && this.hasActiveBlock
}
get showShutdownButton() {
return !this.isViewThemeOnly && this.hasActiveBlock
}
created() {
// Set language
const language = localStorage.getItem('language') || 'en'
this.$i18n.locale = language
PageModule.SET_STATE_PAGE({ key: 'language', value: language })
// Set login position
const loginPosition = localStorage.getItem('loginPosition') as LoginPosition || 'center'
PageModule.SET_STATE_PAGE({ key: 'loginPosition', value: loginPosition })
// Set active block
PageModule.openBlock({ id: 'login' })
PageModule.SET_STATE_PAGE({ key: 'languages', value: this.$i18n.availableLocales })
document.addEventListener('mousedown', this.handleClick)
}
closeMenu() {
PageModule.ASSIGN_MENU({ view: false })
}
handleClick(event: MouseEvent) {
if (!this.activeBlock) {
return PageModule.openFirstBlock()
} else if (this.showMenu) {
return false
export default defineComponent({
name: 'HomePage',
setup() {
const appStore = useAppStore()
const pageStore = usePageStore()
const { locale } = useI18n()
const handleClick = (event: MouseEvent) => {
if (!pageStore.activeBlock) {
return pageStore.openFirstBlock()
} else if (pageStore.menu.view) {
return
}
const target = event.target as HTMLElement
const activeBlocks = document.querySelectorAll(`.block-${pageStore.activeBlock.id}`)
const isClickOnActiveBlock = Array.from(activeBlocks).some((node) => node.contains(target))
const ignoreClick = hasSomeParentClass(target, '.active-block')
if (!ignoreClick && !isClickOnActiveBlock) {
pageStore.closeBlock()
}
}
const target = event.target as HTMLElement
const activeBlocks = document.querySelectorAll(`.block-${this.activeBlock.id}`)
const isClickOnAciveBlock = Array.from(activeBlocks).some(node => node.contains(target))
const ignoreClick = hasSomeParentClass(target, '.active-block')
if (ignoreClick) {
return false
} else if (!isClickOnAciveBlock) {
PageModule.closeBlock()
// Initialize language from localStorage
const language = localStorage.getItem('language') || 'en'
locale.value = language
pageStore.language = language
pageStore.languages = ['ru', 'en', 'fr', 'de', 'es']
pageStore.loginPosition = (localStorage.getItem('loginPosition') as any) || 'center'
pageStore.openBlock({ id: 'login' })
onMounted(() => {
document.addEventListener('mousedown', handleClick)
})
onBeforeUnmount(() => {
document.removeEventListener('mousedown', handleClick)
})
return () => {
const showFrameRate = appStore.showFrameRate
const isOpenLogin = pageStore.isOpenBlock('login') && !appStore.viewThemeOnly
const isOpenSettings = pageStore.isOpenBlock('settings')
const hasActiveBlock = !!pageStore.activeBlock
const showShutdownButton = !appStore.viewThemeOnly && hasActiveBlock
const showAppBar = !appStore.viewThemeOnly && appStore.isSupportFullApi && hasActiveBlock
const showGithubButton = appStore.isGithubMode && hasActiveBlock
return (
<div class="index">
{showFrameRate && <FrameRateBlock />}
<BackgroundImage />
<transition-group class="login-transition" name="fade" tag="div">
{isOpenLogin && <LoginComponent key="LoginComponent" />}
{isOpenSettings && <SettingsComponent key="SettingsComponent" />}
{showShutdownButton && <ShutdownButton key="ShutdownButton" />}
{showGithubButton && <GithubButton key="GithubButton" />}
{showAppBar && <AppBar key="AppBar" />}
</transition-group>
<AppDialog />
<AppMenu />
</div>
)
}
}
render() {
return <div class="index">
{ this.showFrameRate && <FrameRateBlock /> }
<BackgroundImage />
<transition-group class="login-transition" name='fade' tag="div">
{ this.showLogin && <LoginComponent key='LoginComponent' /> }
{ this.isOpenSettings && <SettingsComponent key='SettingsComponent' /> }
{ this.showShutdownButton && <ShutdownButton key='ShutdownButton' /> }
{ this.showGithubButton && <GithubButton key='GithubButton' /> }
{ this.showAppBar && <AppBar key='AppBar' /> }
</transition-group>
<AppDialog />
<AppMenu />
</div>
}
}
})

@ -0,0 +1,9 @@
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>

@ -1,6 +0,0 @@
#!/bin/bash
lightdm-webkit2-greeter
# dm-tool add-nested-seat --screen 1366x768
# yarn build && sudo sh ./install.sh && nody-greeter --debug

@ -0,0 +1,18 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
// Extra safety for array and object lookups, but may have false positives.
"noUncheckedIndexedAccess": true,
// Path mapping for cleaner imports.
"paths": {
"@/*": ["./src/*"]
},
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
}
}

@ -1,43 +1,11 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"node",
],
"typeRoots": ["./node_modules/@types", "./types/application", "nody-greeter-types"],
"paths": {
"@/*": [
"./src/*"
]
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx",
"src/plugins/swiper.js"
],
"exclude": [
"node_modules"
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,27 @@
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
{
"extends": "@tsconfig/node24/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
// Most tools use transpilation instead of Node.js's native type-stripping.
// Bundler mode provides a smoother developer experience.
"module": "preserve",
"moduleResolution": "bundler",
// Include Node.js types and avoid accidentally including other `@types/*` packages.
"types": ["node"],
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
"noEmit": true,
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save