Initial commit
43
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "CI"
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-tauri:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: install tauri bundler
|
||||
run: cargo install tauri-bundler --force
|
||||
- name: install webkit2gtk (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- name: install app dependencies
|
||||
run: yarn
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
npmScript: "tauri:build"
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: my-artifact
|
||||
path: src-tauri/target/release/bundle
|
2
.gitignore
vendored
|
@ -21,3 +21,5 @@ pnpm-debug.log*
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tns
|
||||
|
|
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
13
package.json
|
@ -5,11 +5,19 @@
|
|||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"lint": "vue-cli-service lint",
|
||||
"tauri:build": "vue-cli-service tauri:build",
|
||||
"tauri:serve": "vue-cli-service tauri:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/custom-forms": "^0.2.1",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.13.2",
|
||||
"feather-icons": "^4.28.0",
|
||||
"filesize": "^6.1.0",
|
||||
"tauri": "^0.12.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-async-computed": "^3.9.0",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-property-decorator": "^8.4.2",
|
||||
"vue-router": "^3.2.0"
|
||||
|
@ -25,9 +33,12 @@
|
|||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-cli-plugin-tailwind": "~1.5.0",
|
||||
"vue-cli-plugin-tauri": "~0.12.1",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
||||
|
|
7
postcss.config.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
'vue-cli-plugin-tailwind/purgecss': {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
10
src-tauri/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
config.json
|
||||
bundle.json
|
2105
src-tauri/Cargo.lock
generated
Normal file
33
src-tauri/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "n-link"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = [ "Ben Schattinger <developer@lights0123.com>" ]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/lights0123/n-link"
|
||||
default-run = "n-link"
|
||||
edition = "2018"
|
||||
build = "src/build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.32"
|
||||
serde_json = "1.0"
|
||||
libnspire = "0.2.1"
|
||||
lazy_static = "1.4.0"
|
||||
rusb = "0.6.4"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tauri = { version = "0.9", features = [ "event", "notification" ] }
|
||||
native-dialog = "0.4.3"
|
||||
clap = "3.0.0-beta.2"
|
||||
indicatif = "0.15.0"
|
||||
|
||||
[target."cfg(windows)".build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[features]
|
||||
embedded-server = [ "tauri/embedded-server" ]
|
||||
no-server = [ "tauri/no-server" ]
|
||||
|
||||
[[bin]]
|
||||
name = "n-link"
|
||||
path = "src/main.rs"
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 88 KiB |
13
src-tauri/rustfmt.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
max_width = 100
|
||||
hard_tabs = false
|
||||
tab_spaces = 2
|
||||
newline_style = "Auto"
|
||||
use_small_heuristics = "Default"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
remove_nested_parens = true
|
||||
edition = "2018"
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
force_explicit_abi = true
|
7
src-tauri/src/NOTICE.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
This program contains parts of other programs licensed under the GPL version
|
||||
3.0. They are as follows:
|
||||
|
||||
- libnspire, available for download at
|
||||
https://github.com/lights0123/libnspire-rs
|
||||
- Flat Remix, originally at https://github.com/daniruiz/flat-remix, with
|
||||
parts extracted available at {}
|
16
src-tauri/src/build.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
#[cfg(windows)]
|
||||
extern crate winres;
|
||||
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
if std::path::Path::new("icons/icon.ico").exists() {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("icons/icon.ico");
|
||||
res.compile().expect("Unable to find visual studio tools");
|
||||
} else {
|
||||
panic!("No Icon.ico found. Please add one or check the path");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
101
src-tauri/src/cli.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Clap;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use libnspire::{PID, PID_CX2, VID};
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(author, about, version)]
|
||||
struct Opt {
|
||||
#[clap(subcommand)]
|
||||
cmd: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
enum SubCommand {
|
||||
Upload(Upload),
|
||||
/// View license information
|
||||
License,
|
||||
// Download(Download),
|
||||
}
|
||||
|
||||
/// Upload files to the calculator
|
||||
#[derive(Clap, Debug)]
|
||||
struct Upload {
|
||||
/// Files to upload
|
||||
#[clap(required = true, parse(from_os_str))]
|
||||
files: Vec<PathBuf>,
|
||||
/// Destination path
|
||||
dest: String,
|
||||
}
|
||||
|
||||
// /// Download files from the calculator
|
||||
// #[derive(Clap, Debug)]
|
||||
// struct Download {
|
||||
// /// Files to download
|
||||
// #[clap(required = true)]
|
||||
// files: Vec<String>,
|
||||
// /// Destination path
|
||||
// #[clap(parse(from_os_str))]
|
||||
// dest: PathBuf,
|
||||
// }
|
||||
|
||||
fn get_dev() -> Option<libnspire::Handle<rusb::GlobalContext>> {
|
||||
rusb::devices()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|dev| {
|
||||
let descriptor = match dev.device_descriptor() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return false,
|
||||
};
|
||||
descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)
|
||||
})
|
||||
.next()
|
||||
.map(|dev| libnspire::Handle::new(dev.open().unwrap()).unwrap())
|
||||
}
|
||||
|
||||
pub fn run() -> bool {
|
||||
let opt: Opt = Opt::parse();
|
||||
if let Some(cmd) = opt.cmd {
|
||||
match cmd {
|
||||
SubCommand::Upload(Upload { files, mut dest }) => {
|
||||
if let Some(handle) = get_dev() {
|
||||
for file in files {
|
||||
let mut buf = vec![];
|
||||
File::open(&file).unwrap().read_to_end(&mut buf).unwrap();
|
||||
let name = file
|
||||
.file_name()
|
||||
.expect("Failed to get file name")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let bar = ProgressBar::new(buf.len() as u64);
|
||||
bar.set_style(ProgressStyle::default_bar().template("{spinner:.green} {msg}[{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})"));
|
||||
bar.set_message(&format!("Upload {}", name));
|
||||
bar.enable_steady_tick(100);
|
||||
if dest.ends_with('/') {
|
||||
dest.remove(dest.len() - 1);
|
||||
}
|
||||
handle
|
||||
.write_file(&format!("{}/{}", dest, name), &buf, &mut |remaining| {
|
||||
bar.set_position((buf.len() - remaining) as u64)
|
||||
})
|
||||
.unwrap();
|
||||
bar.finish();
|
||||
}
|
||||
} else {
|
||||
eprintln!("Couldn't find any device");
|
||||
}
|
||||
}
|
||||
SubCommand::License => {
|
||||
println!("{}", include_str!("../../LICENSE"));
|
||||
println!(include_str!("NOTICE.txt"), env!("CARGO_PKG_REPOSITORY"));
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
203
src-tauri/src/cmd.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use libnspire::{PID, PID_CX2, VID};
|
||||
use rusb::GlobalContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Device, DeviceState};
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Promise {
|
||||
pub callback: String,
|
||||
pub error: String,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct DevId {
|
||||
pub bus_number: u8,
|
||||
pub address: u8,
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
pub enum Cmd {
|
||||
// your custom commands
|
||||
// multiple arguments are allowed
|
||||
// note that rename_all = "camelCase": you need to use "myCustomCommand" on JS
|
||||
Enumerate {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
OpenDevice {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CloseDevice {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
UpdateDevice {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ListDir {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
DownloadFile {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: (String, u64),
|
||||
dest: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
UploadFile {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: String,
|
||||
src: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
UploadOs {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
src: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
DeleteFile {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
DeleteDir {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CreateNspireDir {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
path: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Move {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
src: String,
|
||||
dest: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Copy {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
#[serde(flatten)]
|
||||
dev: DevId,
|
||||
src: String,
|
||||
dest: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SelectFile {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
filter: Vec<String>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SelectFiles {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
filter: Vec<String>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SelectFolder {
|
||||
#[serde(flatten)]
|
||||
promise: Promise,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u8), Device)> {
|
||||
let descriptor = dev.device_descriptor()?;
|
||||
if !(descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)) {
|
||||
return Err(rusb::Error::Other);
|
||||
}
|
||||
let handle = dev.open()?;
|
||||
|
||||
Ok((
|
||||
(dev.bus_number(), dev.address()),
|
||||
Device {
|
||||
name: handle.read_product_string(
|
||||
handle.read_languages(Duration::from_millis(100))?[0],
|
||||
&descriptor,
|
||||
Duration::from_millis(100),
|
||||
)?,
|
||||
device: dev,
|
||||
state: DeviceState::Closed,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn enumerate() -> Result<(), libnspire::Error> {
|
||||
crate::DEVICES.write().unwrap().extend(
|
||||
rusb::devices()?
|
||||
.iter()
|
||||
.filter_map(|dev| add_device(Arc::new(dev)).ok()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddDevice {
|
||||
#[serde(flatten)]
|
||||
pub dev: DevId,
|
||||
pub name: String,
|
||||
pub is_cx_ii: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgressUpdate {
|
||||
#[serde(flatten)]
|
||||
pub dev: DevId,
|
||||
pub remaining: usize,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileInfo {
|
||||
pub path: String,
|
||||
pub is_dir: bool,
|
||||
pub date: u64,
|
||||
pub size: u64,
|
||||
}
|
471
src-tauri/src/main.rs
Normal file
|
@ -0,0 +1,471 @@
|
|||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use libnspire::dir::EntryType;
|
||||
use libnspire::{PID_CX2, VID};
|
||||
use native_dialog::Dialog;
|
||||
use rusb::{GlobalContext, Hotplug, UsbContext};
|
||||
use tauri::WebviewMut;
|
||||
|
||||
use crate::cmd::{add_device, AddDevice, DevId, FileInfo, ProgressUpdate};
|
||||
use crate::promise::promise_fn;
|
||||
|
||||
mod cli;
|
||||
mod cmd;
|
||||
mod promise;
|
||||
|
||||
pub enum DeviceState {
|
||||
Open(
|
||||
Arc<Mutex<libnspire::Handle<GlobalContext>>>,
|
||||
libnspire::info::Info,
|
||||
),
|
||||
Closed,
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
name: String,
|
||||
device: Arc<rusb::Device<GlobalContext>>,
|
||||
state: DeviceState,
|
||||
}
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEVICES: RwLock<HashMap<(u8, u8), Device>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
struct DeviceMon {
|
||||
handle: WebviewMut,
|
||||
}
|
||||
|
||||
impl Hotplug<GlobalContext> for DeviceMon {
|
||||
fn device_arrived(&mut self, device: rusb::Device<GlobalContext>) {
|
||||
let mut handle = self.handle.clone();
|
||||
let is_cx_ii = device
|
||||
.device_descriptor()
|
||||
.map(|d| d.product_id() == PID_CX2)
|
||||
.unwrap_or(false);
|
||||
let device = Arc::new(device);
|
||||
std::thread::spawn(move || loop {
|
||||
match add_device(device.clone()) {
|
||||
Ok(dev) => {
|
||||
let name = (dev.1).name.clone();
|
||||
DEVICES.write().unwrap().insert(dev.0, dev.1);
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
&mut handle,
|
||||
"addDevice",
|
||||
Some(AddDevice {
|
||||
dev: DevId {
|
||||
bus_number: (dev.0).0,
|
||||
address: (dev.0).1,
|
||||
},
|
||||
name,
|
||||
is_cx_ii,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
return;
|
||||
}
|
||||
Err(rusb::Error::Busy) => {
|
||||
println!("busy");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(250));
|
||||
});
|
||||
}
|
||||
|
||||
fn device_left(&mut self, device: rusb::Device<GlobalContext>) {
|
||||
if let Some((dev, _)) = DEVICES
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove_entry(&(device.bus_number(), device.address()))
|
||||
{
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
&mut self.handle,
|
||||
"removeDevice",
|
||||
Some(DevId {
|
||||
bus_number: dev.0,
|
||||
address: dev.1,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_sender<'a>(
|
||||
handle: &'a mut WebviewMut,
|
||||
dev: DevId,
|
||||
total: usize,
|
||||
) -> impl FnMut(usize) + 'a {
|
||||
let mut i = 0;
|
||||
move |remaining| {
|
||||
if i > 5 {
|
||||
i = 0;
|
||||
}
|
||||
if i == 0 || remaining == 0 {
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
handle,
|
||||
"progress",
|
||||
Some(ProgressUpdate {
|
||||
dev,
|
||||
remaining,
|
||||
total,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_open_dev(
|
||||
dev: &DevId,
|
||||
) -> Result<Arc<Mutex<libnspire::Handle<GlobalContext>>>, anyhow::Error> {
|
||||
if let Some(dev) = DEVICES.read().unwrap().get(&(dev.bus_number, dev.address)) {
|
||||
match &dev.state {
|
||||
DeviceState::Open(handle, _) => Ok(handle.clone()),
|
||||
DeviceState::Closed => anyhow::bail!("Device closed"),
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("Failed to find device");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if cli::run() {
|
||||
return;
|
||||
}
|
||||
let mut has_registered_callback = false;
|
||||
tauri::AppBuilder::new()
|
||||
.invoke_handler(move |webview, arg| {
|
||||
use cmd::Cmd::*;
|
||||
match serde_json::from_str(arg) {
|
||||
Err(e) => Err(e.to_string()),
|
||||
Ok(command) => {
|
||||
let mut wv_handle = webview.as_mut();
|
||||
match command {
|
||||
Enumerate { promise } => {
|
||||
if !has_registered_callback {
|
||||
has_registered_callback = true;
|
||||
if rusb::has_hotplug() {
|
||||
if let Err(msg) = GlobalContext::default().register_callback(
|
||||
Some(VID),
|
||||
None,
|
||||
None,
|
||||
Box::new(DeviceMon {
|
||||
handle: webview.as_mut(),
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
std::thread::spawn(|| loop {
|
||||
GlobalContext::default().handle_events(None).unwrap();
|
||||
});
|
||||
} else {
|
||||
println!("no hotplug");
|
||||
}
|
||||
}
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let _ = cmd::enumerate();
|
||||
Ok(
|
||||
DEVICES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|dev| AddDevice {
|
||||
dev: DevId {
|
||||
bus_number: (dev.0).0,
|
||||
address: (dev.0).1,
|
||||
},
|
||||
name: (dev.1).name.clone(),
|
||||
is_cx_ii: (dev.1)
|
||||
.device
|
||||
.device_descriptor()
|
||||
.map(|d| d.product_id() == PID_CX2)
|
||||
.unwrap_or(false),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
OpenDevice { promise, dev } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let device = if let Some(dev) =
|
||||
DEVICES.read().unwrap().get(&(dev.bus_number, dev.address))
|
||||
{
|
||||
anyhow::ensure!(matches!(dev.state, DeviceState::Closed), "Already open");
|
||||
dev.device.clone()
|
||||
} else {
|
||||
anyhow::bail!("Failed to find device");
|
||||
};
|
||||
let handle = libnspire::Handle::new(device.open()?)?;
|
||||
let info = handle.info()?;
|
||||
{
|
||||
let mut guard = DEVICES.write().unwrap();
|
||||
let device = guard
|
||||
.get_mut(&(dev.bus_number, dev.address))
|
||||
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
||||
device.state = DeviceState::Open(Arc::new(Mutex::new(handle)), info.clone());
|
||||
}
|
||||
Ok(info)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
CloseDevice { promise, dev } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
{
|
||||
let mut guard = DEVICES.write().unwrap();
|
||||
let device = guard
|
||||
.get_mut(&(dev.bus_number, dev.address))
|
||||
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
||||
device.state = DeviceState::Closed;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
UpdateDevice { promise, dev } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
|
||||
let info = handle.info()?;
|
||||
Ok(info)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
ListDir { promise, dev, path } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let dir = handle.list_dir(&path)?;
|
||||
|
||||
Ok(
|
||||
dir
|
||||
.iter()
|
||||
.map(|file| FileInfo {
|
||||
path: file.name().to_string_lossy().to_string(),
|
||||
is_dir: file.entry_type() == EntryType::Directory,
|
||||
date: file.date(),
|
||||
size: file.size(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
DownloadFile {
|
||||
promise,
|
||||
dev,
|
||||
path: (file, size),
|
||||
dest,
|
||||
} => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let dest = PathBuf::from(dest);
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let mut buf = vec![0; size as usize];
|
||||
handle.read_file(
|
||||
&file,
|
||||
&mut buf,
|
||||
&mut progress_sender(&mut wv_handle, dev, size as usize),
|
||||
)?;
|
||||
if let Some(name) = file.split('/').last() {
|
||||
File::create(dest.join(name))?.write_all(&buf)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
UploadFile {
|
||||
promise,
|
||||
dev,
|
||||
path,
|
||||
src,
|
||||
} => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let file = PathBuf::from(src);
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let mut buf = vec![];
|
||||
File::open(&file)?.read_to_end(&mut buf)?;
|
||||
let name = file
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get file name"))?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
handle.write_file(
|
||||
&format!("{}/{}", path, name),
|
||||
&buf,
|
||||
&mut progress_sender(&mut wv_handle, dev, buf.len()),
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
UploadOs { promise, dev, src } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let mut buf = vec![];
|
||||
File::open(&src)?.read_to_end(&mut buf)?;
|
||||
handle.send_os(&buf, &mut progress_sender(&mut wv_handle, dev, buf.len()))?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
DeleteFile { promise, dev, path } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.delete_file(&path)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
DeleteDir { promise, dev, path } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.delete_dir(&path)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
CreateNspireDir { promise, dev, path } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.create_dir(&path)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
Move {
|
||||
promise,
|
||||
dev,
|
||||
src,
|
||||
dest,
|
||||
} => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.move_file(&src, &dest)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
Copy {
|
||||
promise,
|
||||
dev,
|
||||
src,
|
||||
dest,
|
||||
} => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.copy_file(&src, &dest)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
SelectFile { promise, filter } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let filter = filter.iter().map(|t| t.as_str()).collect::<Vec<_>>();
|
||||
Ok(
|
||||
(native_dialog::OpenSingleFile {
|
||||
filter: Some(&filter),
|
||||
dir: None,
|
||||
})
|
||||
.show()?,
|
||||
)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
SelectFiles { promise, filter } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let filter = filter.iter().map(|t| t.as_str()).collect::<Vec<_>>();
|
||||
Ok(
|
||||
(native_dialog::OpenMultipleFile {
|
||||
filter: Some(&filter),
|
||||
dir: None,
|
||||
})
|
||||
.show()?,
|
||||
)
|
||||
},
|
||||
promise,
|
||||
);
|
||||
}
|
||||
SelectFolder { promise } => {
|
||||
promise_fn(
|
||||
webview,
|
||||
move || Ok((native_dialog::OpenSingleDir { dir: None }).show()?),
|
||||
promise,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.build()
|
||||
.run();
|
||||
}
|
28
src-tauri/src/promise.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use crate::cmd::Promise;
|
||||
use serde::Serialize;
|
||||
use tauri::api::rpc::{format_callback, format_callback_result};
|
||||
use tauri::Webview;
|
||||
|
||||
pub fn promise_fn<R: Serialize, F: FnOnce() -> tauri::Result<R> + Send + 'static>(
|
||||
webview: &mut Webview<'_>,
|
||||
task: F,
|
||||
Promise {
|
||||
callback: success_callback,
|
||||
error: error_callback,
|
||||
}: Promise,
|
||||
) {
|
||||
let mut webview = webview.as_mut();
|
||||
std::thread::spawn(move || {
|
||||
let callback_string = match format_callback_result(
|
||||
task().map_err(|err| err.to_string()),
|
||||
success_callback,
|
||||
error_callback.clone(),
|
||||
) {
|
||||
Ok(callback_string) => callback_string,
|
||||
Err(e) => format_callback(error_callback, e.to_string()),
|
||||
};
|
||||
webview
|
||||
.dispatch(move |webview_ref| webview_ref.eval(callback_string.as_str()))
|
||||
.expect("Failed to dispatch promise callback");
|
||||
});
|
||||
}
|
49
src-tauri/tauri.conf.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"ctx": {},
|
||||
"tauri": {
|
||||
"embeddedServer": {
|
||||
"active": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["osx", "msi", "appimage", "dmg"],
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "Copyright (c) 2020 Ben Schattinger. Licensed under GPL-3.0",
|
||||
"category": "Utility",
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"osx": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
"useBootstrapper": false
|
||||
},
|
||||
"exceptionDomain": ""
|
||||
},
|
||||
"allowlist": {
|
||||
"event": true,
|
||||
"notification": true
|
||||
},
|
||||
"window": {
|
||||
"title": "N-Link",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
},
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
},
|
||||
"inliner": {
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
}
|
21
src/App.vue
|
@ -1,32 +1,15 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<div id="nav">
|
||||
<router-link to="/">Home</router-link> |
|
||||
<router-link to="/about">About</router-link>
|
||||
</div>
|
||||
<div id="app" class="h-screen">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
user-select: none;
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
#nav {
|
||||
padding: 30px;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
|
||||
&.router-link-exact-active {
|
||||
color: #42b983;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
1
src/assets/files/binary.svg
Normal file
After Width: | Height: | Size: 8.7 KiB |
1
src/assets/files/calendar.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(83.137%,9.803%,9.803%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(88.235%,36.862%,36.862%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 12.417969 14.089844 C 12.140625 14.121094 12.128906 14.253906 12.128906 14.742188 L 12.128906 16.699219 L 23.871094 16.699219 L 23.871094 14.742188 C 23.871094 14.089844 23.871094 14.089844 23.21875 14.089844 L 12.78125 14.089844 C 12.621094 14.089844 12.507812 14.082031 12.414062 14.089844 Z M 12.128906 17.351562 L 12.128906 25.175781 C 12.128906 25.824219 12.128906 25.824219 12.78125 25.824219 L 23.21875 25.824219 C 23.871094 25.824219 23.871094 25.824219 23.871094 25.175781 L 23.871094 17.351562 Z M 20.121094 18.65625 C 20.546875 18.65625 20.921875 18.761719 21.242188 18.960938 C 21.5625 19.160156 21.804688 19.4375 21.972656 19.816406 C 22.144531 20.191406 22.238281 20.640625 22.238281 21.160156 C 22.238281 22.289062 22.003906 23.132812 21.527344 23.6875 C 21.046875 24.242188 20.316406 24.523438 19.34375 24.523438 C 19.003906 24.523438 18.746094 24.496094 18.570312 24.460938 L 18.570312 23.503906 C 18.792969 23.558594 19.019531 23.585938 19.261719 23.585938 C 19.671875 23.585938 20 23.519531 20.261719 23.402344 C 20.527344 23.28125 20.734375 23.105469 20.875 22.851562 C 21.011719 22.597656 21.097656 22.238281 21.117188 21.792969 L 21.058594 21.792969 C 20.902344 22.039062 20.726562 22.203125 20.527344 22.300781 C 20.328125 22.402344 20.09375 22.464844 19.792969 22.464844 C 19.289062 22.464844 18.878906 22.292969 18.589844 21.976562 C 18.300781 21.65625 18.164062 21.21875 18.164062 20.652344 C 18.164062 20.039062 18.34375 19.539062 18.691406 19.183594 C 19.046875 18.828125 19.511719 18.65625 20.121094 18.65625 Z M 15.617188 18.714844 L 16.632812 18.714844 L 16.632812 24.441406 L 15.410156 24.441406 L 15.410156 20.589844 L 15.433594 20 C 15.230469 20.199219 15.082031 20.324219 15.003906 20.386719 L 14.351562 20.917969 L 13.761719 20.183594 Z M 19.976562 19.632812 C 19.808594 19.664062 19.65625 19.75 19.550781 19.878906 C 19.40625 20.050781 19.34375 20.308594 19.34375 20.632812 C 19.34375 20.90625 19.398438 21.121094 19.527344 21.28125 C 19.65625 21.445312 19.855469 21.527344 20.121094 21.527344 C 20.367188 21.527344 20.574219 21.441406 20.75 21.28125 C 20.929688 21.121094 21.015625 20.941406 21.015625 20.734375 C 21.015625 20.421875 20.9375 20.152344 20.773438 19.9375 C 20.609375 19.722656 20.394531 19.632812 20.140625 19.632812 C 20.078125 19.632812 20.035156 19.621094 19.976562 19.632812 Z M 19.976562 19.632812"/> </g> </svg>
|
After Width: | Height: | Size: 3.8 KiB |
1
src/assets/files/cfg.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(90.196%,90.196%,90.196%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 22.160156 16.734375 C 22.898438 16.960938 22.253906 18.011719 23.308594 18.53125 C 24.519531 19.121094 25.117188 17.632812 25.84375 18.695312 C 26.570312 19.757812 24.84375 19.59375 24.890625 20.84375 C 24.941406 22.09375 26.648438 21.828125 26.007812 22.933594 C 25.367188 24.035156 24.65625 22.585938 23.496094 23.246094 C 22.335938 23.910156 23.445312 25.132812 22.078125 25.171875 C 20.710938 25.214844 21.722656 23.929688 20.515625 23.339844 C 19.308594 22.75 18.707031 24.238281 17.980469 23.175781 C 17.253906 22.113281 18.984375 22.277344 18.933594 21.027344 C 18.886719 19.777344 17.175781 20.042969 17.816406 18.9375 C 18.460938 17.835938 19.171875 19.28125 20.332031 18.621094 C 21.492188 17.960938 20.382812 16.738281 21.75 16.695312 C 21.921875 16.691406 22.054688 16.703125 22.160156 16.734375 Z M 22.148438 19.917969 C 21.539062 19.800781 20.9375 20.15625 20.808594 20.71875 C 20.679688 21.28125 21.066406 21.832031 21.679688 21.953125 C 22.289062 22.070312 22.886719 21.710938 23.015625 21.152344 C 23.148438 20.589844 22.757812 20.039062 22.148438 19.917969 Z M 22.148438 19.917969"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 16.292969 11.515625 C 17.03125 11.742188 16.382812 12.796875 17.441406 13.3125 C 18.648438 13.902344 19.25 12.414062 19.976562 13.476562 C 20.699219 14.539062 18.972656 14.375 19.023438 15.625 C 19.070312 16.875 20.78125 16.609375 20.136719 17.714844 C 19.496094 18.816406 18.785156 17.371094 17.625 18.03125 C 16.464844 18.691406 17.574219 19.914062 16.207031 19.957031 C 14.839844 19.996094 15.855469 18.710938 14.648438 18.121094 C 13.4375 17.53125 12.835938 19.023438 12.113281 17.957031 C 11.386719 16.894531 13.113281 17.0625 13.066406 15.808594 C 13.015625 14.558594 11.304688 14.824219 11.949219 13.722656 C 12.589844 12.617188 13.300781 14.066406 14.460938 13.40625 C 15.621094 12.742188 14.511719 11.519531 15.878906 11.480469 C 16.050781 11.472656 16.1875 11.484375 16.292969 11.515625 Z M 16.277344 14.699219 C 15.667969 14.582031 15.070312 14.941406 14.9375 15.5 C 14.808594 16.0625 15.199219 16.613281 15.808594 16.734375 C 16.417969 16.855469 17.019531 16.496094 17.148438 15.933594 C 17.277344 15.371094 16.886719 14.820312 16.277344 14.699219 Z M 16.277344 14.699219"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 14.335938 18.691406 C 15.074219 18.917969 14.425781 19.96875 15.484375 20.488281 C 16.691406 21.078125 17.292969 19.585938 18.019531 20.652344 C 18.746094 21.714844 17.015625 21.546875 17.066406 22.800781 C 17.113281 24.050781 18.824219 23.785156 18.183594 24.886719 C 17.539062 25.992188 16.828125 24.542969 15.667969 25.203125 C 14.507812 25.867188 15.617188 27.089844 14.25 27.128906 C 12.882812 27.171875 13.898438 25.886719 12.691406 25.296875 C 11.480469 24.707031 10.882812 26.195312 10.15625 25.132812 C 9.429688 24.070312 11.15625 24.234375 11.109375 22.984375 C 11.058594 21.734375 9.351562 22 9.992188 20.894531 C 10.632812 19.789062 11.34375 21.238281 12.503906 20.578125 C 13.664062 19.917969 12.554688 18.695312 13.921875 18.652344 C 14.09375 18.648438 14.230469 18.65625 14.335938 18.691406 Z M 14.320312 21.875 C 13.710938 21.753906 13.113281 22.113281 12.984375 22.675781 C 12.851562 23.238281 13.242188 23.789062 13.851562 23.910156 C 14.460938 24.027344 15.0625 23.667969 15.191406 23.109375 C 15.320312 22.546875 14.929688 21.996094 14.320312 21.875 Z M 14.320312 21.875"/> </g> </svg>
|
After Width: | Height: | Size: 4.8 KiB |
1
src/assets/files/contact.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <g> <path style="fill:rgb(74.117%,59.215%,46.666%)" d="M 6 0.75 C 5.230469 0.75 4.5 1.480469 4.5 2.25 L 4.5 33.75 C 4.5 34.480469 5.269531 35.25 6 35.25 L 30 35.25 C 30.730469 35.25 31.5 34.480469 31.5 33.75 L 31.5 10.5 L 21.75 0.75 Z M 6 0.75"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.196078" d="M 21.75 9 L 21.796875 9.046875 L 21.914062 9 Z M 23.25 10.5 L 31.5 18.75 L 31.5 10.5 Z M 23.25 10.5"/> <path style="fill:rgb(100.000%,100.000%,100.000%);fill-opacity:0.392157" d="M 21.75 0.75 L 31.5 10.5 L 23.25 10.5 C 22.519531 10.5 21.75 9.730469 21.75 9 Z M 21.75 0.75"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 17.351562 14.125 L 18.648438 14.125 C 20.585938 14.125 20.90625 16.398438 20.90625 16.398438 L 20.90625 18.660156 C 20.90625 19.421875 19.289062 20.972656 19.289062 20.972656 L 19.289062 21.734375 L 21.875 23.230469 L 21.875 24.753906 L 17.679688 24.78125 L 14.125 24.78125 L 14.125 23.261719 L 16.710938 21.738281 L 16.710938 20.976562 C 16.710938 20.976562 15.089844 19.433594 15.09375 18.667969 L 15.09375 16.410156 C 15.09375 16.410156 15.414062 14.125 17.351562 14.125 Z M 17.351562 14.125"/> </g> </svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/assets/files/csv.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(4.705%,63.137%,36.862%);fill-opacity:0.996078" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(32.941%,74.117%,55.686%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 24.519531 14.085938 L 24.519531 27.132812 L 11.476562 27.132812 L 11.476562 14.085938 Z M 23.21875 15.390625 L 17.347656 15.390625 L 17.347656 18 L 23.21875 18 Z M 16.042969 15.390625 L 12.78125 15.390625 L 12.78125 18 L 16.042969 18 Z M 23.21875 19.304688 L 17.347656 19.304688 L 17.347656 21.914062 L 23.21875 21.914062 Z M 16.042969 19.304688 L 12.78125 19.304688 L 12.78125 21.914062 L 16.042969 21.914062 Z M 23.21875 23.21875 L 17.347656 23.21875 L 17.347656 25.828125 L 23.21875 25.828125 Z M 16.042969 23.21875 L 12.78125 23.21875 L 12.78125 25.828125 L 16.042969 25.828125 Z M 16.042969 23.21875"/> </g> </svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/files/db.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(72.156%,9.019%,29.803%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(80.392%,36.078%,50.588%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 18 12.78125 C 15.117188 12.78125 12.78125 13.511719 12.78125 14.414062 L 12.78125 15.71875 C 12.78125 16.617188 15.117188 17.347656 18 17.347656 C 20.882812 17.347656 23.21875 16.617188 23.21875 15.71875 L 23.21875 14.414062 C 23.21875 13.511719 20.882812 12.78125 18 12.78125 Z M 18 12.78125"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 17.984375 18 C 15.648438 18 13.679688 17.519531 13.011719 16.859375 C 12.859375 17.011719 12.78125 17.175781 12.78125 17.347656 L 12.78125 18.652344 C 12.78125 19.550781 15.117188 20.28125 18 20.28125 C 20.882812 20.28125 23.21875 19.550781 23.21875 18.652344 L 23.21875 17.347656 C 23.21875 17.175781 23.125 17.011719 22.96875 16.859375 C 22.304688 17.519531 20.320312 18 17.984375 18 Z M 17.984375 18"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 17.984375 20.933594 C 15.648438 20.933594 13.679688 20.453125 13.011719 19.792969 C 12.859375 19.949219 12.78125 20.113281 12.78125 20.28125 L 12.78125 21.585938 C 12.78125 22.488281 15.117188 23.21875 18 23.21875 C 20.882812 23.21875 23.21875 22.488281 23.21875 21.585938 L 23.21875 20.28125 C 23.21875 20.113281 23.125 19.949219 22.96875 19.792969 C 22.304688 20.453125 20.320312 20.933594 17.984375 20.933594 Z M 17.984375 20.933594"/> </g> </svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
src/assets/files/epub.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(4.705%,63.137%,36.862%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(32.941%,74.117%,55.686%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 18 23.402344 L 13.902344 19.304688 L 18 15.207031 L 19.367188 16.570312 L 16.632812 19.304688 L 18 20.671875 L 22.097656 16.570312 L 18.527344 13 C 18.238281 12.710938 17.761719 12.710938 17.472656 13 L 11.695312 18.777344 C 11.40625 19.066406 11.40625 19.542969 11.695312 19.832031 L 17.472656 25.609375 C 17.765625 25.898438 18.238281 25.898438 18.527344 25.609375 L 24.304688 19.832031 C 24.59375 19.539062 24.59375 19.066406 24.304688 18.777344 L 23.464844 17.9375 Z M 18 23.402344"/> </g> </svg>
|
After Width: | Height: | Size: 2 KiB |
1
src/assets/files/exec.svg
Normal file
After Width: | Height: | Size: 8.6 KiB |
1
src/assets/files/font.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(90.196%,90.196%,90.196%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.607843" d="M 20.640625 13.132812 L 19.761719 23.738281 C 19.714844 24.234375 19.691406 24.5625 19.691406 24.71875 C 19.691406 24.96875 19.734375 25.160156 19.820312 25.292969 C 19.929688 25.472656 20.074219 25.609375 20.257812 25.695312 C 20.445312 25.785156 20.757812 25.828125 21.199219 25.828125 L 21.105469 26.175781 L 16.535156 26.175781 L 16.632812 25.828125 L 16.828125 25.828125 C 17.199219 25.828125 17.5 25.738281 17.734375 25.5625 C 17.898438 25.445312 18.027344 25.246094 18.121094 24.972656 C 18.183594 24.777344 18.242188 24.320312 18.300781 23.597656 L 18.4375 21.980469 L 15.117188 21.980469 L 13.9375 23.738281 C 13.667969 24.132812 13.5 24.417969 13.433594 24.59375 C 13.363281 24.765625 13.328125 24.925781 13.328125 25.074219 C 13.328125 25.277344 13.402344 25.449219 13.550781 25.59375 C 13.699219 25.734375 13.945312 25.816406 14.289062 25.828125 L 14.191406 26.175781 L 10.761719 26.175781 L 10.859375 25.828125 C 11.28125 25.808594 11.648438 25.65625 11.96875 25.367188 C 12.292969 25.070312 12.777344 24.449219 13.414062 23.496094 L 20.34375 13.132812 L 20.640625 13.132812 M 18.949219 16.261719 L 15.585938 21.292969 L 18.503906 21.292969 L 18.949219 16.261719"/> </g> </svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
src/assets/files/image.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="matrix(.087527 0 0 .087527 1.1948 1.1948)"><path d="m39.785 47.799c-15.237 0-27.729 12.1-27.729 27.124v234.15c0 15.024 12.491 27.124 27.729 27.124h304.43c15.237 0 27.729-12.1 27.729-27.124v-234.15c0-15.024-12.491-27.124-27.729-27.124zm0 7.4208h304.43c11.395 0 20.342 8.7882 20.342 19.703v234.15c0 10.915-8.947 19.703-20.342 19.703h-304.43c-11.395 0-20.342-8.7882-20.342-19.703v-234.15c0-10.915 8.947-19.703 20.342-19.703z" color="#000000" color-rendering="auto" dominant-baseline="auto" image-rendering="auto" opacity=".2" shape-rendering="auto" solid-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/><path d="m39.785 47.797c-13.316 0-24.035 10.719-24.035 24.031v240.34c0 13.312 10.719 24.031 24.035 24.031h304.43c13.316 0 24.035-10.719 24.035-24.031v-240.34c0-13.312-10.719-24.031-24.035-24.031z" fill="#fff"/><path d="m39.785 47.797c-13.316 0-24.035 10.719-24.035 24.031v8.0117c0-13.312 10.719-24.031 24.035-24.031h304.43c13.316 0 24.035 10.719 24.035 24.031v-8.0117c0-13.312-10.719-24.031-24.035-24.031z" fill="#fff" fill-opacity=".2"/><path d="m47.797 79.836h288.41v224.32h-288.41z" fill="#353a4a"/><path d="m117.79 79.84 9 224.32h30.434l91.637-91.637 16.02-16.023-16.02-16.023-100.64-100.64z" opacity=".3"/><path d="m47.797 79.84v224.32h94.523l112.16-112.16-112.16-112.16z" fill="#ffc72e"/><path d="m52.297 79.84 9 224.32h30.434l91.637-91.637 16.02-16.023-16.02-16.023-100.64-100.64z" opacity=".3"/><path d="m47.797 79.84v224.32h30.434l96.137-96.137 16.02-16.023-16.02-16.023-96.137-96.137z" fill="#21b8c2"/><path d="m336.2 193.61-110.55 110.55h110.55z" fill="#fd4747" fill-opacity=".99608"/><path transform="scale(.75)" d="m21 406.23v10c0 17.75 14.292 32.043 32.047 32.043h405.91c17.755 0 32.047-14.293 32.047-32.043v-10c0 17.75-14.292 32.043-32.047 32.043h-405.91c-17.755 0-32.047-14.293-32.047-32.043z" opacity=".3" stroke-width="1.3333"/><path transform="scale(.75)" d="m53.049 63.725c-17.755 0-32.047 14.291-32.047 32.041v10c0-17.75 14.292-32.041 32.047-32.041h405.91c17.755 0 32.047 14.291 32.047 32.041v-10c0-17.75-14.292-32.041-32.047-32.041z" opacity=".1" stroke-width="1.3333"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
src/assets/files/installer.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(4.705%,63.137%,36.862%);fill-opacity:0.996078" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 16.042969 14.085938 L 19.957031 14.085938 L 19.957031 19.304688 L 21.261719 19.304688 L 18 21.914062 L 14.738281 19.304688 L 16.042969 19.304688 Z M 16.042969 14.085938"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 12.78125 23.871094 L 12.78125 25.175781 C 12.78125 25.824219 12.78125 25.824219 13.433594 25.824219 L 22.566406 25.824219 C 23.21875 25.824219 23.21875 25.828125 23.21875 25.175781 L 23.21875 23.871094 C 23.21875 23.21875 23.21875 23.21875 22.566406 23.21875 L 13.433594 23.21875 C 12.78125 23.21875 12.78125 23.21875 12.78125 23.871094 Z M 18 23.871094 L 22.566406 23.871094 L 22.566406 25.175781 L 18 25.175781 Z M 18 23.871094"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(32.941%,74.117%,55.686%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> </g> </svg>
|
After Width: | Height: | Size: 2.2 KiB |
1
src/assets/files/js.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(97.254%,87.058%,40.784%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(98.039%,90.980%,58.431%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 14.085938 12.78125 L 16.695312 12.78125 L 16.695312 25.824219 L 11.476562 25.824219 L 11.476562 23.21875 L 14.085938 23.21875 Z M 14.085938 12.78125"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 18 12.78125 L 23.21875 12.78125 L 23.21875 15.390625 L 20.609375 15.390625 L 20.609375 18 L 23.21875 18 L 23.21875 25.824219 L 18 25.824219 L 18 23.21875 L 20.609375 23.21875 L 20.609375 20.609375 L 18 20.609375 Z M 18 12.78125"/> </g> </svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
src/assets/files/json.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(97.254%,87.058%,40.784%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(98.039%,90.980%,58.431%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill-rule:evenodd;fill:rgb(100.000%,100.000%,100.000%)" d="M 18 14.125 C 14.34375 14.125 11.21875 16.804688 11.21875 20.421875 C 11.21875 23.570312 13.476562 25.933594 15.890625 26.71875 C 14.285156 25.632812 12.8125 23.570312 12.8125 21.210938 C 12.8125 18.488281 14.808594 16.484375 17.128906 16.484375 C 19.296875 16.484375 20.785156 18.523438 20.792969 20.421875 C 20.792969 21.753906 19.683594 22.851562 18.796875 23.570312 C 20.394531 23.570312 22.386719 22.265625 22.386719 19.636719 C 22.386719 17.273438 20.394531 14.125 18 14.125 Z M 18 14.125"/> <path style="fill-rule:evenodd;fill:rgb(100.000%,100.000%,100.000%)" d="M 18 26.71875 C 21.65625 26.71875 24.78125 24.039062 24.78125 20.421875 C 24.78125 17.273438 22.523438 14.910156 20.109375 14.125 C 21.714844 15.210938 23.1875 17.273438 23.1875 19.636719 C 23.1875 22.355469 21.191406 24.355469 18.871094 24.355469 C 16.703125 24.355469 15.214844 22.320312 15.207031 20.421875 C 15.207031 19.089844 16.316406 17.992188 17.203125 17.273438 C 15.605469 17.273438 13.613281 18.578125 13.613281 21.207031 C 13.613281 23.570312 15.605469 26.71875 18 26.71875 Z M 18 26.71875"/> </g> </svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
src/assets/files/lua.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(54.901%,25.882%,67.058%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(68.235%,47.843%,76.862%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill-rule:evenodd;fill:rgb(100.000%,100.000%,100.000%)" d="M 22.359375 15.09375 C 21.558594 15.09375 20.90625 15.746094 20.90625 16.546875 C 20.90625 17.347656 21.558594 18 22.359375 18 C 23.160156 18 23.8125 17.347656 23.8125 16.546875 C 23.8125 15.746094 23.160156 15.09375 22.359375 15.09375 Z M 17.03125 17.03125 C 14.355469 17.03125 12.1875 19.199219 12.1875 21.875 C 12.1875 24.550781 14.355469 26.71875 17.03125 26.71875 C 19.707031 26.71875 21.875 24.550781 21.875 21.875 C 21.875 19.199219 19.707031 17.03125 17.03125 17.03125 Z M 18.484375 18.96875 C 19.285156 18.96875 19.9375 19.621094 19.9375 20.421875 C 19.9375 21.222656 19.285156 21.875 18.484375 21.875 C 17.683594 21.875 17.03125 21.222656 17.03125 20.421875 C 17.03125 19.621094 17.683594 18.96875 18.484375 18.96875 Z M 18.484375 18.96875"/> </g> </svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
src/assets/files/pdf.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(83.137%,9.803%,9.803%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(88.235%,36.862%,36.862%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 15.097656 11.074219 C 14.332031 11.058594 13.667969 11.824219 13.832031 12.585938 C 13.941406 13.75 14.691406 14.699219 15.277344 15.660156 C 15.34375 16.019531 15.15625 16.367188 15.144531 16.722656 C 14.761719 18.816406 14.175781 20.894531 13.28125 22.8125 C 12.167969 23.320312 10.910156 23.828125 10.261719 24.929688 C 9.847656 25.695312 10.429688 26.726562 11.28125 26.808594 C 12.070312 26.914062 12.660156 26.238281 13.089844 25.671875 C 13.542969 25.058594 13.839844 24.332031 14.214844 23.675781 C 16.488281 22.761719 18.878906 22.121094 21.320312 21.878906 C 22.304688 22.585938 23.351562 23.417969 24.617188 23.46875 C 25.351562 23.457031 25.949219 22.742188 25.867188 22.015625 C 25.820312 21.355469 25.234375 20.894531 24.625 20.769531 C 23.644531 20.476562 22.597656 20.636719 21.597656 20.644531 C 19.679688 19.109375 17.910156 17.347656 16.515625 15.328125 C 16.617188 14.101562 16.824219 12.746094 16.1875 11.628906 C 15.945312 11.277344 15.53125 11.035156 15.097656 11.074219 M 15.136719 12.1875 C 15.351562 12.230469 15.367188 12.550781 15.425781 12.722656 C 15.484375 13.074219 15.476562 13.429688 15.472656 13.785156 C 15.214844 13.304688 14.882812 12.800781 14.941406 12.230469 C 15.007812 12.230469 15.070312 12.195312 15.136719 12.1875 M 16.328125 16.972656 C 17.46875 18.394531 18.742188 19.703125 20.125 20.890625 C 18.296875 21.167969 16.484375 21.636719 14.742188 22.265625 C 15.410156 20.523438 15.917969 18.722656 16.253906 16.882812 C 16.277344 16.914062 16.300781 16.941406 16.328125 16.972656 M 23.460938 21.75 C 23.902344 21.792969 24.414062 21.769531 24.769531 22.074219 C 24.800781 22.351562 24.464844 22.390625 24.285156 22.289062 C 23.910156 22.164062 23.539062 22.007812 23.222656 21.769531 C 23.300781 21.761719 23.378906 21.753906 23.460938 21.75 M 12.378906 24.652344 C 12.167969 25.042969 11.863281 25.414062 11.542969 25.691406 C 11.4375 25.863281 11.144531 25.546875 11.277344 25.410156 C 11.554688 24.976562 12.039062 24.746094 12.46875 24.484375 C 12.441406 24.542969 12.410156 24.597656 12.378906 24.652344"/> </g> </svg>
|
After Width: | Height: | Size: 3.5 KiB |
1
src/assets/files/python.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(14.901%,35.294%,69.411%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 18.140625 12.128906 C 17.613281 12.132812 17.097656 12.175781 16.652344 12.253906 C 15.34375 12.480469 15.0625 12.550781 15.0625 13.433594 L 15.0625 15.390625 L 18.324219 15.390625 L 18.324219 16.042969 L 13.757812 16.042969 C 12.855469 16.042969 12.269531 16.59375 12.023438 17.613281 C 11.738281 18.78125 11.726562 19.511719 12.023438 20.730469 C 12.242188 21.640625 12.855469 22.566406 13.753906 22.566406 L 14.40625 22.566406 L 14.40625 20.609375 C 14.40625 19.597656 15.316406 18.652344 16.367188 18.652344 L 19.628906 18.652344 C 20.488281 18.652344 20.933594 18.203125 20.933594 17.347656 L 20.933594 14.085938 C 20.933594 13.253906 20.589844 12.390625 19.75 12.253906 C 19.21875 12.164062 18.667969 12.128906 18.140625 12.128906 Z M 17.019531 13.433594 C 17.378906 13.433594 17.671875 13.726562 17.671875 14.085938 C 17.671875 14.445312 17.378906 14.738281 17.019531 14.738281 C 16.660156 14.738281 16.367188 14.445312 16.367188 14.085938 C 16.367188 13.726562 16.660156 13.433594 17.019531 13.433594 Z M 17.019531 13.433594"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 17.859375 25.824219 C 18.386719 25.824219 18.902344 25.78125 19.347656 25.703125 C 20.65625 25.472656 20.9375 25.40625 20.9375 24.523438 L 20.9375 22.566406 L 17.675781 22.566406 L 17.675781 21.914062 L 22.242188 21.914062 C 23.144531 21.914062 23.730469 21.363281 23.976562 20.34375 C 24.261719 19.175781 24.273438 18.445312 23.976562 17.226562 C 23.757812 16.316406 23.144531 15.390625 22.242188 15.390625 L 21.589844 15.390625 L 21.589844 17.347656 C 21.589844 18.359375 20.683594 19.304688 19.632812 19.304688 L 16.371094 19.304688 C 15.507812 19.304688 15.066406 19.75 15.066406 20.609375 L 15.066406 23.871094 C 15.066406 24.703125 15.40625 25.566406 16.246094 25.703125 C 16.78125 25.792969 17.328125 25.828125 17.859375 25.824219 Z M 18.980469 24.523438 C 18.621094 24.523438 18.328125 24.230469 18.328125 23.871094 C 18.328125 23.507812 18.621094 23.21875 18.980469 23.21875 C 19.339844 23.21875 19.632812 23.507812 19.632812 23.871094 C 19.632812 24.230469 19.339844 24.523438 18.980469 24.523438 Z M 18.980469 24.523438"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(23.137%,54.509%,72.156%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> </g> </svg>
|
After Width: | Height: | Size: 3.6 KiB |
21
src/assets/files/resources.svg
Normal file
After Width: | Height: | Size: 7.7 KiB |
1
src/assets/files/rom.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <g> <path style="fill:rgb(90.196%,90.196%,90.196%);stroke-width:0.25;stroke:rgb(80.000%,80.000%,80.000%)" d="M 24 4 C 12.953125 4 4 12.953125 4 24 C 4 35.046875 12.953125 44 24 44 C 35.046875 44 44 35.046875 44 24 C 44 12.953125 35.046875 4 24 4 Z M 24 18.546875 C 27.010417 18.546875 29.453125 20.989583 29.453125 24 C 29.453125 27.010417 27.010417 29.453125 24 29.453125 C 20.989583 29.453125 18.546875 27.010417 18.546875 24 C 18.546875 20.989583 20.989583 18.546875 24 18.546875 Z M 24 18.546875" transform="matrix(0.75,0,0,0.75,0,0)"/> <path style="fill:rgb(100.000%,100.000%,100.000%);stroke-width:0.442773;stroke:rgb(90.196%,90.196%,90.196%)" d="M 24 15.463542 C 19.286458 15.463542 15.463542 19.286458 15.463542 24 C 15.463542 28.713542 19.286458 32.536458 24 32.536458 C 28.713542 32.536458 32.536458 28.713542 32.536458 24 C 32.536458 19.286458 28.713542 15.463542 24 15.463542 Z M 24 19.46875 C 26.5 19.46875 28.53125 21.5 28.53125 24 C 28.53125 26.5 26.5 28.53125 24 28.53125 C 21.5 28.53125 19.46875 26.5 19.46875 24 C 19.46875 21.5 21.5 19.46875 24 19.46875 Z M 24 19.46875" transform="matrix(0.75,0,0,0.75,0,0)"/> </g> </svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/assets/files/svg.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(10.980%,58.431%,60.784%);fill-opacity:0.996078" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(36.862%,74.117%,67.058%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.550781 14.085938 C 21.175781 14.085938 20.855469 14.285156 20.679688 14.574219 L 16.984375 14.574219 C 16.984375 14.574219 16.25 14.5625 15.496094 14.925781 C 14.738281 15.292969 13.941406 16.125 13.941406 17.511719 C 13.941406 18.898438 14.738281 19.730469 15.496094 20.09375 C 15.851562 20.265625 16.207031 20.339844 16.476562 20.382812 L 16.476562 20.597656 L 15.082031 21.945312 C 15.039062 21.933594 15.003906 21.914062 14.957031 21.914062 C 14.675781 21.914062 14.449219 22.132812 14.449219 22.402344 C 14.449219 22.671875 14.675781 22.890625 14.957031 22.890625 C 15.238281 22.890625 15.464844 22.671875 15.464844 22.402344 C 15.464844 22.355469 15.445312 22.320312 15.433594 22.28125 L 16.828125 20.933594 L 18.507812 20.933594 L 18.507812 20.445312 L 19.015625 20.445312 C 19.015625 20.445312 19.546875 20.457031 20.058594 20.707031 C 20.574219 20.953125 21.042969 21.34375 21.042969 22.402344 C 21.042969 23.460938 20.574219 23.851562 20.058594 24.097656 C 19.546875 24.347656 19.015625 24.359375 19.015625 24.359375 L 15.320312 24.359375 C 15.144531 24.066406 14.824219 23.871094 14.449219 23.871094 C 13.890625 23.871094 13.433594 24.308594 13.433594 24.847656 C 13.433594 25.386719 13.890625 25.824219 14.449219 25.824219 C 14.824219 25.824219 15.144531 25.628906 15.320312 25.335938 L 19.015625 25.335938 C 19.015625 25.335938 19.75 25.347656 20.503906 24.984375 C 21.261719 24.621094 22.058594 23.789062 22.058594 22.402344 C 22.058594 21.015625 21.261719 20.183594 20.503906 19.820312 C 19.75 19.453125 19.015625 19.46875 19.015625 19.46875 L 18.507812 19.46875 L 18.507812 19.316406 L 19.902344 17.96875 C 19.945312 17.980469 19.984375 18 20.027344 18 C 20.308594 18 20.535156 17.78125 20.535156 17.511719 C 20.535156 17.242188 20.308594 17.023438 20.027344 17.023438 C 19.75 17.023438 19.523438 17.242188 19.523438 17.511719 C 19.523438 17.554688 19.542969 17.59375 19.554688 17.632812 L 18.160156 18.976562 L 16.476562 18.976562 L 16.476562 19.390625 C 16.3125 19.351562 16.128906 19.300781 15.941406 19.207031 C 15.425781 18.960938 14.957031 18.570312 14.957031 17.511719 C 14.957031 16.449219 15.425781 16.0625 15.941406 15.8125 C 16.453125 15.566406 16.984375 15.554688 16.984375 15.554688 L 20.679688 15.554688 C 20.855469 15.847656 21.175781 16.042969 21.550781 16.042969 C 22.109375 16.042969 22.566406 15.605469 22.566406 15.066406 C 22.566406 14.523438 22.109375 14.085938 21.550781 14.085938 Z M 21.550781 14.085938"/> </g> </svg>
|
After Width: | Height: | Size: 3.9 KiB |
1
src/assets/files/txt.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(90.196%,90.196%,90.196%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 11.476562 14.738281 L 11.476562 15.390625 L 24.523438 15.390625 L 24.523438 14.738281 Z M 11.476562 14.738281"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 11.476562 17.347656 L 11.476562 18 L 24.523438 18 L 24.523438 17.347656 Z M 11.476562 17.347656"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 11.476562 19.957031 L 11.476562 20.609375 L 24.523438 20.609375 L 24.523438 19.957031 Z M 11.476562 19.957031"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 11.476562 22.566406 L 11.476562 23.21875 L 24.523438 23.21875 L 24.523438 22.566406 Z M 11.476562 22.566406"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 11.476562 25.175781 L 11.476562 25.824219 L 19.183594 25.824219 L 19.183594 25.175781 Z M 11.476562 25.175781"/> </g> </svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
src/assets/files/unknown.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(90.196%,90.196%,90.196%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> </g> </svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
src/assets/files/video.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><defs><clipPath><rect width="384" height="384"/></clipPath><clipPath><rect width="384" height="384"/></clipPath><clipPath><rect width="384" height="384"/></clipPath><clipPath><rect width="384" height="384"/></clipPath><linearGradient id="b" x1="165.12" x2="267.84" y1="53.815" y2="394.13" gradientTransform="matrix(.75386 0 0 .69848 -.94825 5.2909)" gradientUnits="userSpaceOnUse"><stop stop-color="#262933" offset="0"/><stop stop-color="#4b4b4b" offset="1"/></linearGradient><clipPath id="a"><rect width="384" height="384"/></clipPath></defs><g transform="matrix(.0973 0 0 .0973 -.68143 -.68155)"><path d="m30.965 56.234h321.93c4.6875 0 8.4844 3.8008 8.4844 8.4844v130.03c0 4.6875-3.7969 8.4844-8.4844 8.4844h-321.93c-4.6875 0-8.4883-3.7969-8.4883-8.4844v-130.03c0-4.6836 3.8008-8.4844 8.4883-8.4844z" fill="#5f626d"/><path d="m82.832 56.012c-11.992 0-21.926 0.11719-22.07 0.26172-0.14844 0.14453 4.7852 5.2812 10.961 11.414l11.23 11.102-0.10156 12.082-0.10156 12.031-60.238 0.19531v28.348c-0.02734 0.0781-0.06641 0.15234-0.08984 0.23438v191.89c0.77734 2.3945 2.9023 4.1641 5.4844 4.4141h327.86c3.2695-0.31641 5.8086-3.0664 5.8086-6.4336v-187.85c0-0.29297-0.0234-0.58203-0.0625-0.86328l-0.20703-66.977c0-4.8359-1.9805-9.4883-9.0664-9.5664l-17.25-0.19141c-9.7852-0.10937-15.492-0.02734-15.793 0.26953-0.30469 0.30469 2.5117 3.3242 10.488 11.234 6 5.9492 11.031 11.023 11.18 11.184 0.15234 0.16016-5.1836 5.6328-11.855 12.258l-12.133 12.047h-15.094c-10.527 0-15.168-0.125-15.348-0.41406-0.14844-0.23828 4.8711-5.5039 11.844-12.426l12.098-11.465-10.629-11.125c-5.8477-5.8164-11.094-10.82-11.656-11.113-0.83594-0.44141-3.6641-0.53906-15.273-0.53906-10.137 0-14.328 0.12109-14.516 0.42188-0.15625 0.25391 4.1445 4.7969 10.609 11.207 5.9805 5.9297 10.996 10.988 11.145 11.148 0.15234 0.16016-5.1836 5.6328-11.855 12.258l-12.133 12.047h-15.094c-10.527 0-15.168-0.125-15.348-0.41406-0.14844-0.23828 4.8711-5.5039 11.844-12.426l12.098-11.465-10.629-11.125c-5.8477-5.8164-11.094-10.82-11.656-11.113-0.83594-0.44141-3.6641-0.53906-15.273-0.53906-10.137 0-14.328 0.12109-14.516 0.42188-0.15625 0.25391 4.1445 4.7969 10.609 11.207 5.9805 5.9297 10.996 10.988 11.145 11.148 0.15234 0.16016-5.1836 5.6328-11.855 12.258l-12.133 12.047h-15.094c-10.527 0-15.168-0.125-15.348-0.41406-0.14844-0.23828 4.8711-5.5039 11.844-12.426l12.191-11.465-10.723-11.125c-5.8477-5.8164-11.094-10.82-11.656-11.113-0.83594-0.44141-3.6641-0.53906-15.273-0.53906-10.137 0-14.328 0.12109-14.516 0.42188-0.15625 0.25391 4.1445 4.7969 10.609 11.207 5.9805 5.9297 10.996 10.988 11.145 11.148 0.15234 0.16016-5.1836 5.6328-11.855 12.258l-12.133 12.047h-15.094c-10.527 0-15.168-0.125-15.348-0.41406-0.14844-0.23828 4.8711-5.5039 11.844-12.426l12.098-11.465-10.629-11.125c-5.8477-5.8164-11.094-10.82-11.656-11.113-0.85547-0.44922-4.6758-0.53906-22.836-0.53906z" fill="url(#b)"/><path d="m22.422 211.36v112.23c0.78516 2.4023 2.9336 4.1719 5.543 4.3984h327.75c3.2969-0.28906 5.8633-3.0352 5.8633-6.4102v-110.21z" fill="#4b4b4b"/><path transform="scale(.75)" d="m482.1 418.77c0 4.5-3.4206 8.1615-7.8164 8.5469h-437c-3.4792-0.30208-6.3438-2.6621-7.3906-5.8652v10c1.0469 3.2031 3.9115 5.5632 7.3906 5.8652h437c4.3958-0.38541 7.8164-4.0469 7.8164-8.5469z" fill="#080000" opacity=".3" stroke-width="1.3333"/><path d="m30.978 56.279c-4.6875 0-8.4888 3.8008-8.4888 8.4844v14.338h338.91v-14.338c0-1.9908-0.69104-3.8184-1.8398-5.2661-0.57219-0.77931-1.3069-1.4504-2.2339-1.9717-1.2862-0.7857-2.7926-1.2466-4.4106-1.2466z" opacity=".3"/><g fill="#000000ff" opacity=".3"><path d="m100.87 178.56c1.6953-7.8047 8.6211-13.633 16.906-13.617l99.992 0.15625c9.5469 0.0195 17.285 7.7656 17.266 17.312l-0.0312 31.738 48.633-35.551-0.12891 70.055-48.547-33.801-0.0156 29.859c0.0312 9.5781-7.7578 17.32-17.305 17.301l-99.996-0.15234c-4.7734-0.0156-8.8477-1.7031-12.219-5.0664-3.3789-3.3867-5.0625-7.4844-5.0508-12.285l0.0898-56.527z"/></g><g clip-path="url(#a)" fill="#000000ff" opacity=".3"><path d="m100.87 173.48c1.6953-7.8047 8.6211-13.633 16.906-13.617l99.992 0.15625c9.5469 0.0195 17.285 7.7656 17.266 17.312l-0.0312 31.738 48.633-35.551-0.12891 70.055-48.547-33.801-0.0156 29.859c0.0312 9.5742-7.7578 17.32-17.305 17.301l-99.996-0.15234c-4.7734-0.0156-8.8477-1.7031-12.219-5.0664-3.3789-3.3906-5.0625-7.4844-5.0508-12.285l0.0898-56.527z"/></g></g></svg>
|
After Width: | Height: | Size: 4.3 KiB |
1
src/assets/files/xml.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <clipPath id="clip1"> <path d="M 20 2 L 31 2 L 31 13 L 20 13 Z M 20 2"/> </clipPath> <clipPath id="clip2"> <path d="M 20.515625 2.625 L 30.328125 12.4375 L 22.023438 12.4375 C 21.292969 12.4375 20.515625 11.660156 20.515625 10.929688 Z M 20.515625 2.625"/> </clipPath> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAAmklEQVRYhe3T0QqCQBBG4aOtYEoUvf9Lildltl00C4tkbBeTIP+BAUEYP1YW1Pcqh53B9kbgaVPcwQF0tAmGSrMZ6AKcDQXw+AUVHEAtcAUaewYYgYmC3+cBCkAPnDJQMcoDVNnebgEqQnmAclTz4d0I3P4JStc9AjXvk4qLWQV53LLeEOl05gw4A3dgcPiuUkoppZRSSqk99gLWvSJpuDnlsAAAAABJRU5ErkJggg=="/> </defs> <g> <path style="fill:rgb(90.196%,90.196%,90.196%)" d="M 7.347656 3.152344 C 6.675781 3.152344 6.042969 3.785156 6.042969 4.457031 L 6.042969 31.847656 C 6.042969 32.480469 6.714844 33.152344 7.347656 33.152344 L 28.21875 33.152344 C 28.851562 33.152344 29.523438 32.480469 29.523438 31.847656 L 29.523438 11.632812 L 21.042969 3.152344 Z M 7.347656 3.152344"/> <g clip-path="url(#clip1)"> <g clip-path="url(#clip2)"> <use xlink:href="#image6"/> </g> </g> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 21.042969 3.152344 L 29.523438 11.632812 L 22.347656 11.632812 C 21.714844 11.632812 21.042969 10.960938 21.042969 10.328125 Z M 21.042969 3.152344"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 14.085938 16.042969 L 10.175781 19.957031 L 10.175781 21.261719 L 14.085938 25.175781 L 14.085938 22.566406 L 12.128906 20.609375 L 14.085938 18.652344 Z M 14.085938 16.042969"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 21.914062 16.042969 L 25.824219 19.957031 L 25.824219 21.261719 L 21.914062 25.175781 L 21.914062 22.566406 L 23.871094 20.609375 L 21.914062 18.652344 Z M 21.914062 16.042969"/> <path style="fill:rgb(0%,0%,0%);fill-opacity:0.352941" d="M 19.304688 16.042969 L 20.609375 16.042969 L 18 25.175781 L 16.695312 25.175781 Z M 19.304688 16.042969"/> </g> </svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/files/zip.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36pt" height="36pt" version="1.1" viewBox="0 0 36 36"> <defs> <image id="image6" width="36" height="36" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAABDklEQVRYhe3SQWpCMRRG4ZObpGrFV7SVOhFX4DYcuAqX4tJczhs8EUoofbW5t5N2KgrBDppvATeHn0B1mS95bL/fC+A3m42fTCay2+04HA72Z0GAXy6Xg77vR03TBMC2263dEiUla9brtU8pjXPOMzN7adu2OR6P8We5qxRdaLVaPYjIk3Nu7pxrVNWnlLL3/uvapYouNJ1OXYwxiMijiDzHGF+B+S1LhZJBADlnF0IIOecxMIwxcj6fadsW4A3o7xoEYGZORIKZDVWV36iu6+yuQafTyQD13mczU0BEZGRmsxCCAgp0l24U/UOLxUKBT1V9B5JzLgEfZqYiMnDOjUu+V1VVVVVVVVVVVf0D3+N0chItnTe7AAAAAElFTkSuQmCC"/> </defs> <g> <path style="fill:rgb(10.980%,58.431%,60.784%)" d="M 7.367188 3.1875 C 6.695312 3.1875 6.0625 3.820312 6.0625 4.492188 L 6.0625 31.882812 C 6.0625 32.515625 6.734375 33.1875 7.367188 33.1875 L 28.238281 33.1875 C 28.871094 33.1875 29.542969 32.515625 29.542969 31.882812 L 29.542969 11.664062 L 21.0625 3.1875 Z M 7.367188 3.1875"/> <use xlink:href="#image6"/> <path style="fill:rgb(36.862%,74.117%,67.058%)" d="M 21.0625 3.1875 L 29.542969 11.664062 L 22.367188 11.664062 C 21.734375 11.664062 21.0625 10.996094 21.0625 10.359375 Z M 21.0625 3.1875"/> <path style="fill:rgb(100.000%,100.000%,100.000%)" d="M 11.28125 3.1875 L 11.28125 5.796875 L 13.890625 5.796875 L 13.890625 8.40625 L 11.28125 8.40625 L 11.28125 11.011719 L 13.890625 11.011719 L 13.890625 13.621094 L 11.28125 13.621094 L 11.28125 16.230469 L 13.890625 16.230469 L 13.890625 20.144531 L 11.28125 20.144531 L 11.28125 25.359375 L 16.496094 25.359375 L 16.496094 20.144531 L 13.890625 20.144531 L 13.890625 18.839844 L 16.496094 18.839844 L 16.496094 16.230469 L 13.890625 16.230469 L 13.890625 13.621094 L 16.496094 13.621094 L 16.496094 11.011719 L 13.890625 11.011719 L 13.890625 8.40625 L 16.496094 8.40625 L 16.496094 5.796875 L 13.890625 5.796875 L 13.890625 3.1875 Z M 11.28125 3.1875"/> </g> </svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
src/assets/folders/folder-accept.svg
Normal file
After Width: | Height: | Size: 29 KiB |
1
src/assets/folders/folder.svg
Normal file
After Width: | Height: | Size: 8.6 KiB |
1
src/assets/folders/games.svg
Normal file
After Width: | Height: | Size: 14 KiB |
1
src/assets/folders/images.svg
Normal file
After Width: | Height: | Size: 9.1 KiB |
1
src/assets/folders/music.svg
Normal file
After Width: | Height: | Size: 9.6 KiB |
1
src/assets/folders/scripts.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
src/assets/folders/templates.svg
Normal file
After Width: | Height: | Size: 9.6 KiB |
1
src/assets/folders/video.svg
Normal file
After Width: | Height: | Size: 9.1 KiB |
18
src/assets/tailwind.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
@tailwind base;
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.el-popover {
|
||||
@apply select-none;
|
||||
}
|
105
src/components/CalcInfo.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2 class="text-center text-xl select-text">{{ info.name }}</h2>
|
||||
<h3 class="text-center select-text">OS {{ formatVersion(info.version) }}</h3>
|
||||
<h3 class="text-center text-xs select-text">{{ info.id }}</h3>
|
||||
<br>
|
||||
<div :title="`${formatSize(info.free_storage)} free`">
|
||||
<small class="block select-text">
|
||||
Storage: {{ formatSize(info.total_storage - info.free_storage) }} / {{ formatSize(info.total_storage) }} used
|
||||
</small>
|
||||
<div class="mb-2 bg-gray-300 rounded-full">
|
||||
<div :style="{width: `${100 - (info.free_storage / info.total_storage) * 100}%`}"
|
||||
class="bg-blue-500 py-1 rounded-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<div :title="`${formatSize(info.free_ram)} free`">
|
||||
<small class="block select-text">
|
||||
RAM: {{ formatSize(info.total_ram - info.free_ram) }} / {{ formatSize(info.total_ram) }} used
|
||||
</small>
|
||||
<div class="mb-2 bg-gray-300 rounded-full">
|
||||
<div :style="{width: `${100 - (info.free_ram / info.total_ram) * 100}%`}"
|
||||
class="bg-teal-400 py-1 rounded-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<small class="block select-text">Boot1: {{ formatVersion(info.boot1_version) }}</small>
|
||||
<small class="block select-text">Boot2: {{ formatVersion(info.boot2_version) }}</small>
|
||||
<button class="mt-4 button" @click="refresh" :disabled="refreshing">
|
||||
<div class="flex">
|
||||
<div v-if="refreshing" class="lds-dual-ring" />
|
||||
Refresh
|
||||
</div>
|
||||
</button>
|
||||
<button class="mt-4 button gray-button" @click="$devices.uploadOs(dev, info.os_extension.split('.').pop())">
|
||||
Upload OS
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
import type {Info, Version, DevId} from '@/components/devices';
|
||||
import fileSize from "filesize";
|
||||
|
||||
@Component
|
||||
export default class FileView extends Vue {
|
||||
@Prop({type: Object, required: true}) private info!: Info;
|
||||
@Prop({type: [Object, String], required: true}) private dev!: DevId | string;
|
||||
refreshing = false;
|
||||
|
||||
formatSize(size: number) {
|
||||
return fileSize(size, {round: 1});
|
||||
}
|
||||
|
||||
formatVersion(version: Version) {
|
||||
return `${version.major}.${version.minor}.${version.patch}.${version.build}`;
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
this.refreshing = true;
|
||||
try {
|
||||
await this.$devices.update(this.dev);
|
||||
} catch(e) {
|
||||
/* */
|
||||
}
|
||||
this.refreshing = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button {
|
||||
@apply bg-blue-500 text-white rounded px-6 py-2.5 font-bold;
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.75;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.gray-button {
|
||||
@apply bg-gray-400 text-gray-800;
|
||||
}
|
||||
|
||||
.lds-dual-ring {
|
||||
$scale-factor: 0.2;
|
||||
margin-right: 64px * $scale-factor + 8px;
|
||||
margin-bottom: 64px * $scale-factor;
|
||||
width: 0;
|
||||
height: 64px * $scale-factor * 0.8;
|
||||
transform: scale($scale-factor);
|
||||
}
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border-radius: 50%;
|
||||
border: 6px solid white;
|
||||
border-color: white transparent white transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
</style>
|
109
src/components/DeviceQueue.vue
Normal file
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-popover width="300" popper-class="focus:outline-none">
|
||||
<div>
|
||||
<div v-if="!queue.length">
|
||||
Nothing to do
|
||||
</div>
|
||||
<div v-for="(item, i) in queue" :key="item.id" class="flex items-center">
|
||||
<div class="min-w-0 flex-grow">
|
||||
<p class="truncate">{{ item.desc }}</p>
|
||||
<div v-if="i === 0 && device.progress">
|
||||
<small class="block tabular-nums">
|
||||
{{ (100 - device.progress.remaining / device.progress.total * 100).toFixed(1) }}%
|
||||
</small>
|
||||
<div class="mb-2 bg-gray-300 rounded-full">
|
||||
<div :style="{width: `${100-device.progress.remaining/device.progress.total * 100}%`}"
|
||||
class="bg-teal-400 py-1 rounded-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 flex-shrink-0">
|
||||
<button class="focus:outline-none" :disabled="i===0" :class="i===0 && 'cursor-not-allowed'"
|
||||
@click="device.queue.splice(i, 1)">
|
||||
<img src="~feather-icons/dist/icons/x-circle.svg" :class="i===0 && 'opacity-25'"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svg slot="reference" viewBox="-1 -1 2 2" class="circle focus:outline-none">
|
||||
<circle r="1" class="bg"/>
|
||||
<path class="fg" :d="pathData"/>
|
||||
</svg>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
import ElPopover from 'element-ui/packages/popover/src/main.vue';
|
||||
import 'element-ui/lib/theme-chalk/popover.css';
|
||||
import {Device} from "@/components/devices";
|
||||
|
||||
function getCoordinatesForPercent(percent: number) {
|
||||
const x = Math.cos(2 * Math.PI * percent);
|
||||
const y = Math.sin(2 * Math.PI * percent);
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
function trimPath(path: string) {
|
||||
return path.split(/[\\/]/).pop() as string;
|
||||
}
|
||||
|
||||
@Component({components: {ElPopover}})
|
||||
export default class FileView extends Vue {
|
||||
@Prop({type: Object, required: true}) private device!: Device;
|
||||
|
||||
get pathData() {
|
||||
const {progress, queue} = this.device;
|
||||
const length = (queue?.length || 0);
|
||||
let percent = progress ? (1 - progress.remaining / progress.total) : 1 / (length);
|
||||
if (!Number.isFinite(percent)) percent = 0;
|
||||
|
||||
const [startX, startY] = getCoordinatesForPercent(0);
|
||||
const [endX, endY] = getCoordinatesForPercent(percent);
|
||||
|
||||
const largeArcFlag = percent > .5 ? 1 : 0;
|
||||
return [
|
||||
`M ${startX} ${startY}`,
|
||||
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`,
|
||||
`L 0 0`,
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
get queue() {
|
||||
if (!this.device.queue) return [];
|
||||
return this.device.queue.map(item => {
|
||||
let desc = '';
|
||||
if (item.action === 'download') desc = `Download ${trimPath(item.path[0])}`;
|
||||
else if (item.action === 'upload') desc = `Upload ${trimPath(item.src)}`;
|
||||
else if (item.action === 'uploadOs') desc = 'Upload OS';
|
||||
else if (item.action === 'deleteFile') desc = `Delete file ${item.path}`;
|
||||
else if (item.action === 'deleteDir') desc = `Delete directory ${item.path}`;
|
||||
else if (item.action === 'createDir') desc = `Create directory ${item.path}`;
|
||||
else if (item.action === 'copy') desc = `Copy file ${item.src} to ${item.dest}`;
|
||||
else if (item.action === 'move') desc = `Move file ${item.src} to ${item.dest}`;
|
||||
return {
|
||||
desc,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.circle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
transform: rotate(-90deg);
|
||||
|
||||
.bg {
|
||||
fill: theme('colors.gray.200');
|
||||
}
|
||||
|
||||
.fg {
|
||||
fill: theme('colors.gray.800');
|
||||
}
|
||||
}
|
||||
</style>
|
73
src/components/DeviceSelect.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="header border-b px-2 py-2">
|
||||
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active">
|
||||
<div slot="reference" class="inline-block relative w-full focus:outline-none">
|
||||
<div
|
||||
class="block w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-1.5 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline h-8 truncate">
|
||||
<span v-if="selectedCalculator">
|
||||
<small class="tabular-nums">{{ selectedCalculator }}</small>
|
||||
<span v-if="calc && calc.info"> {{ calc.info.name }}</span>
|
||||
<span v-else> {{ calc.name }}</span>
|
||||
</span>
|
||||
<span v-else class="text-gray-700 text-sm">
|
||||
Select...
|
||||
</span>
|
||||
</div>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="(device, id) in $devices.devices" :key="id"
|
||||
class="p-2 hover:bg-blue-500 hover:text-white w-full cursor-pointer" @click="select(id)">
|
||||
<small class="tabular-nums">{{ id }}</small>
|
||||
<span v-if="device.info"> {{ device.info.name }}</span>
|
||||
<span v-else> {{ device.name }}</span>
|
||||
</li>
|
||||
<div v-if="!anyCalcs" class="p-2 w-full">
|
||||
No calculators found
|
||||
</div>
|
||||
</ul>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, PropSync, Vue} from 'vue-property-decorator';
|
||||
import ElPopover from 'element-ui/packages/popover/src/main.vue';
|
||||
import 'element-ui/lib/theme-chalk/popover.css';
|
||||
|
||||
@Component({components: {ElPopover}})
|
||||
export default class DeviceSelect extends Vue {
|
||||
@PropSync('selected', {type: [String]}) selectedCalculator!: string | null;
|
||||
active = false;
|
||||
|
||||
select(dev: string) {
|
||||
this.selectedCalculator = dev;
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
get calc() {
|
||||
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator];
|
||||
}
|
||||
|
||||
get anyCalcs() {
|
||||
return !!Object.keys(this.$devices.devices).length;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.header {
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.dev-select-pop {
|
||||
margin-top: 0 !important;
|
||||
@apply p-0 overflow-hidden;
|
||||
}
|
||||
</style>
|
114
src/components/FileBrowser.vue
Normal file
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<div class="border-b py-2 px-2 header flex">
|
||||
<ol class="flex">
|
||||
<li v-for="(part, i) in parts" :key="i" class="" :class="{active: i === parts.length - 1}">
|
||||
<span class="inline-block cursor-pointer bg-gray-200 px-4 py-1 rounded-full"
|
||||
@click="goToIndex(i)">{{ part || 'Home' }}</span>
|
||||
<img v-if="i < parts.length - 1" class="inline img-fix" src="~feather-icons/dist/icons/chevron-right.svg"/>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="flex-grow"/>
|
||||
<device-queue :device="$devices.devices[dev]"/>
|
||||
</div>
|
||||
<div class="h-full flex w-full body">
|
||||
<div class="overflow-auto h-full py-4 flex-grow">
|
||||
<file-view v-if="!loading && files" :files="files" :show-hidden="showHidden" @nav="path = $event"
|
||||
@select="selected = $event"/>
|
||||
<div v-else-if="loading" class="flex items-center justify-center h-full">
|
||||
<div class="lds-dual-ring" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto h-full px-4 pt-4 w-48 flex-shrink-0 border-l">
|
||||
<file-data :files="selected" :show-hidden="showHidden" :dev="dev" :path="path"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue, Watch} from 'vue-property-decorator';
|
||||
import FileView from '@/components/FileView.vue';
|
||||
import type {FileInfo} from "@/components/devices";
|
||||
import FileData from "@/components/FileData.vue";
|
||||
import DeviceQueue from "@/components/DeviceQueue.vue";
|
||||
|
||||
@Component({
|
||||
asyncComputed: {
|
||||
files: {
|
||||
async get(this: FileBrowser) {
|
||||
this.updateIndex;
|
||||
return (await this.$devices.listDir(this.dev, this.path)).map(file => ({
|
||||
...file,
|
||||
path: `${this.path}/${file.path}`
|
||||
}));
|
||||
},
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
components: {DeviceQueue, FileData, FileView}
|
||||
})
|
||||
export default class FileBrowser extends Vue {
|
||||
@Prop({type: String, required: true}) private dev!: string;
|
||||
@Prop({type: Boolean, default: false}) private showHidden!: boolean;
|
||||
path = '';
|
||||
updateIndex = 0;
|
||||
selected: FileInfo[] = [];
|
||||
files!: FileInfo[] | null;
|
||||
|
||||
@Watch('path')
|
||||
onPathChange() {
|
||||
this.selected = [];
|
||||
}
|
||||
|
||||
get parts() {
|
||||
return this.path.split('/');
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this.$asyncComputed.files.updating;
|
||||
}
|
||||
|
||||
goToIndex(i: number) {
|
||||
this.path = this.parts.slice(0, i + 1).join('/');
|
||||
this.updateIndex++;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.header {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.active span {
|
||||
@apply bg-blue-500 text-white;
|
||||
}
|
||||
|
||||
.img-fix {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-dual-ring:after {
|
||||
$color: theme('colors.gray.400');
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border-radius: 50%;
|
||||
border: 6px solid $color;
|
||||
border-color: $color transparent $color transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
</style>
|
158
src/components/FileData.vue
Normal file
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<div v-if="files.length === 1">
|
||||
<div class="w-full flex flex-col items-center">
|
||||
<file-icon :path="files[0].path" :dir="files[0].isDir" :width="96"/>
|
||||
<p class="text-center w-full break-words">{{ formatPath(files[0]) }}</p>
|
||||
<p v-if="!files[0].isDir" class="mt-2 text-sm">{{ formatSize(files[0].size) }}</p>
|
||||
</div>
|
||||
<button v-if="!files[0].isDir" class="mt-4 button w-full" @click="download">
|
||||
Download
|
||||
</button>
|
||||
<el-popover
|
||||
width="190"
|
||||
popper-class="focus:outline-none" @show="newName = formatPath(files[0])" v-model="renamePopup">
|
||||
<form @submit.prevent="rename">
|
||||
<input v-model="newName"
|
||||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type="text" placeholder="New name">
|
||||
<button class="mt-4 button success w-full" :class="{disabled: !isValidName}" type="submit"
|
||||
:disabled="!isValidName">
|
||||
Rename
|
||||
</button>
|
||||
</form>
|
||||
<button slot="reference" class="mt-4 button w-full">
|
||||
Rename
|
||||
</button>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-else-if="files.length && files.every(file => !file.isDir)">
|
||||
<button class="mt-4 button" @click="download">
|
||||
Download {{ files.length > 1 ? `${files.length} files` : '' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="files.length">
|
||||
<el-popover width="170" popper-class="focus:outline-none" v-model="deletePopup">
|
||||
<div>
|
||||
Delete {{ files.length }} file{{ files.length === 1 ? '' : 's' }}?
|
||||
<div class="flex w-full justify-between">
|
||||
<button class="mt-4 button small" @click="deletePopup = false">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="mt-4 button small danger" @click="deleteFiles">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button slot="reference" class="mt-4 button danger w-full">
|
||||
Delete {{ files.length > 1 ? `${files.length} files` : '' }}
|
||||
</button>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="flex-grow"/>
|
||||
<div class="pb-4">
|
||||
<el-popover v-if="path" width="170" popper-class="focus:outline-none" v-model="createDirPopup" @show="newName = ''">
|
||||
<button slot="reference" class="mt-4 button w-full text-sm">
|
||||
Create directory
|
||||
</button>
|
||||
<form @submit.prevent="$devices.createDir(dev, `${path}/${newName}`)">
|
||||
<input v-model="newName"
|
||||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type="text" placeholder="New directory">
|
||||
<button class="mt-4 button success w-full" :class="{disabled: !newName.length}" type="submit"
|
||||
:disabled="!newName.length">
|
||||
Create
|
||||
</button>
|
||||
</form>
|
||||
</el-popover>
|
||||
<button class="mt-4 button w-full" @click="$devices.promptUploadFiles(dev, path)">
|
||||
Upload files
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
import ElPopover from 'element-ui/packages/popover/src/main.vue';
|
||||
import 'element-ui/lib/theme-chalk/popover.css';
|
||||
import fileSize from "filesize";
|
||||
import type {FileInfo} from '@/components/devices';
|
||||
import {DevId} from "@/components/devices";
|
||||
import FileIcon from "@/components/FileIcon.vue";
|
||||
|
||||
@Component({
|
||||
components: {FileIcon, ElPopover}
|
||||
})
|
||||
export default class FileData extends Vue {
|
||||
@Prop({type: Array, default: () => ([])}) private files!: FileInfo[];
|
||||
@Prop({type: String, required: true}) private path!: string;
|
||||
@Prop({type: Boolean, default: false}) private showHidden!: boolean;
|
||||
@Prop({type: [Object, String], required: true}) private dev!: DevId | string;
|
||||
newName = '';
|
||||
renamePopup = false;
|
||||
deletePopup = false;
|
||||
createDirPopup = false;
|
||||
|
||||
formatSize(size: number) {
|
||||
return fileSize(size, {round: 1});
|
||||
}
|
||||
|
||||
formatPath({path, isDir}: FileInfo) {
|
||||
const name = path.split('/').pop() as string;
|
||||
if (this.showHidden || isDir) return name;
|
||||
return name.substring(0, name.length - 4);
|
||||
}
|
||||
|
||||
download() {
|
||||
this.$devices.downloadFiles(this.dev, this.files.map(file => [file.path, file.size]));
|
||||
}
|
||||
|
||||
deleteFiles() {
|
||||
this.deletePopup = false;
|
||||
this.$devices.delete(this.dev, this.files);
|
||||
}
|
||||
|
||||
get isValidName() {
|
||||
if (this.showHidden && !this.files[0]?.isDir && !this.newName.endsWith('.tns')) return false;
|
||||
return this.newName.length && !this.newName.includes('/');
|
||||
}
|
||||
|
||||
rename() {
|
||||
if (this.isValidName) {
|
||||
this.renamePopup = false;
|
||||
const path = this.files[0].path;
|
||||
const newPath = path.split('/');
|
||||
newPath.pop();
|
||||
newPath.push(this.newName + (!this.showHidden && !this.files[0].isDir ? '.tns' : ''));
|
||||
this.$devices.move(this.dev, path, newPath.join('/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button {
|
||||
@apply bg-gray-400 text-gray-800 rounded px-6 py-2.5 font-bold;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
@apply bg-red-600 text-white;
|
||||
}
|
||||
|
||||
&.success {
|
||||
@apply bg-green-500 text-white;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-75 cursor-not-allowed;
|
||||
}
|
||||
|
||||
&.small {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
161
src/components/FileIcon.vue
Normal file
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<img :width="width" :src="icon" alt="name"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class FileView extends Vue {
|
||||
@Prop({type: String, required: true}) private path!: string;
|
||||
@Prop({type: Boolean, default: false}) private dir!: string;
|
||||
@Prop({type: Number, default: 64}) private width!: number;
|
||||
|
||||
get name() {
|
||||
return this.path.split('/').pop() as string;
|
||||
}
|
||||
|
||||
get ext() {
|
||||
const parts = this.name.split('.');
|
||||
if (parts[parts.length - 1] === 'tns') parts.pop();
|
||||
if (parts.length < 2) return '';
|
||||
return parts.pop() as string;
|
||||
}
|
||||
|
||||
get icon() {
|
||||
if (this.dir) {
|
||||
switch (this.name.trim().toLowerCase()) {
|
||||
case 'games':
|
||||
return require('@/assets/folders/games.svg');
|
||||
case 'images':
|
||||
case 'photos':
|
||||
return require('@/assets/folders/images.svg');
|
||||
case 'music':
|
||||
case 'sound':
|
||||
case 'sounds':
|
||||
return require('@/assets/folders/music.svg');
|
||||
case 'script':
|
||||
case 'scripts':
|
||||
case 'program':
|
||||
case 'programs':
|
||||
return require('@/assets/folders/scripts.svg');
|
||||
case 'template':
|
||||
case 'templates':
|
||||
return require('@/assets/folders/templates.svg');
|
||||
case 'video':
|
||||
case 'videos':
|
||||
return require('@/assets/folders/video.svg');
|
||||
default:
|
||||
return require('@/assets/folders/folder.svg');
|
||||
}
|
||||
}
|
||||
if (this.name === 'ndless_resources.tns') return require('@/assets/files/resources.svg');
|
||||
if (this.name.startsWith('ndless_installer')) return require('@/assets/files/installer.svg');
|
||||
switch (this.ext.toLowerCase()) {
|
||||
case 'bin':
|
||||
return require('@/assets/files/binary.svg');
|
||||
case 'ical':
|
||||
case 'ics':
|
||||
case 'ifb':
|
||||
case 'icalendar':
|
||||
return require('@/assets/files/calendar.svg');
|
||||
case 'cfg':
|
||||
return require('@/assets/files/cfg.svg');
|
||||
case 'vcf':
|
||||
return require('@/assets/files/contact.svg');
|
||||
case 'csv':
|
||||
case 'log':
|
||||
case 'logs':
|
||||
return require('@/assets/files/csv.svg');
|
||||
case 'sql':
|
||||
case 'db':
|
||||
case 'sqlite':
|
||||
return require('@/assets/files/db.svg');
|
||||
case 'epub':
|
||||
return require('@/assets/files/epub.svg');
|
||||
case 'eot':
|
||||
case 'otf':
|
||||
case 'ttf':
|
||||
case 'woff':
|
||||
case 'woff2':
|
||||
return require('@/assets/files/font.svg');
|
||||
case 'png':
|
||||
case 'bmp':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'jpe':
|
||||
case 'jif':
|
||||
case 'jfif':
|
||||
case 'jfi':
|
||||
case 'jp2':
|
||||
case 'j2k':
|
||||
case 'jpf':
|
||||
case 'jpx':
|
||||
case 'jpm':
|
||||
case 'mj2':
|
||||
case 'gif':
|
||||
case 'tiff':
|
||||
case 'tif':
|
||||
case 'ppm':
|
||||
case 'pgm':
|
||||
case 'pbm':
|
||||
case 'pnm':
|
||||
case 'webp':
|
||||
case 'heif':
|
||||
case 'heifs':
|
||||
case 'heic':
|
||||
case 'heics':
|
||||
case 'avci':
|
||||
case 'avcs':
|
||||
case 'avif':
|
||||
case 'avifs':
|
||||
case 'ico':
|
||||
case 'icon':
|
||||
case 'icns':
|
||||
case 'pcx':
|
||||
case 'pgf':
|
||||
case 'tga':
|
||||
case 'psd':
|
||||
case 'xcf':
|
||||
return require('@/assets/files/image.svg');
|
||||
case 'js':
|
||||
case 'mjs':
|
||||
return require('@/assets/files/js.svg');
|
||||
case 'json':
|
||||
case 'json5':
|
||||
return require('@/assets/files/json.svg');
|
||||
case 'lua':
|
||||
return require('@/assets/files/lua.svg');
|
||||
case 'pdf':
|
||||
return require('@/assets/files/pdf.svg');
|
||||
case 'py':
|
||||
return require('@/assets/files/python.svg');
|
||||
case 'rom':
|
||||
case '89u':
|
||||
case 'smc':
|
||||
case 'sfc':
|
||||
case 'srm':
|
||||
case 'img':
|
||||
return require('@/assets/files/rom.svg');
|
||||
case 'svg':
|
||||
case 'svgz':
|
||||
return require('@/assets/files/svg.svg');
|
||||
case 'txt':
|
||||
return require('@/assets/files/txt.svg');
|
||||
case 'mp4':
|
||||
return require('@/assets/files/video.svg');
|
||||
case 'xml':
|
||||
return require('@/assets/files/xml.svg');
|
||||
case 'zip':
|
||||
case 'tar':
|
||||
case 'gz':
|
||||
return require('@/assets/files/zip.svg');
|
||||
default:
|
||||
return require('@/assets/files/unknown.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
74
src/components/FileView.vue
Normal file
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap min-h-full items-start content-start" @mousedown.exact="selected = []">
|
||||
<div class="mb-2 mx-1 w-24 flex flex-col items-center cursor-default" :class="{ selected: selected.includes(i) }"
|
||||
v-for="(file, i) in filteredFiles" :key="file.path" @mousedown.ctrl.stop="xorSelection(i)" @mousedown.shift.stop="shiftSelection(i)"
|
||||
@mousedown.exact.stop="selected = [i]"
|
||||
@dblclick="file.isDir && $emit('nav', file.path)">
|
||||
<file-icon :path="file.path" :dir="file.isDir"/>
|
||||
<p class="mt-1 text-sm w-full text-center select-none break-words">{{ formatPath(file) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Prop, Vue, Watch} from 'vue-property-decorator';
|
||||
import type {FileInfo} from '@/components/devices';
|
||||
import FileIcon from "@/components/FileIcon.vue";
|
||||
|
||||
@Component({
|
||||
components: {FileIcon}
|
||||
})
|
||||
export default class FileView extends Vue {
|
||||
@Prop({type: Array, default: () => ([])}) private files!: FileInfo[];
|
||||
@Prop({type: Boolean, default: false}) private showHidden!: boolean;
|
||||
selected: number[] = [];
|
||||
|
||||
@Watch('selected', {deep: true, immediate: true})
|
||||
onSelect(selected: number[]) {
|
||||
this.$emit('select', selected.map(file => this.filteredFiles[file]));
|
||||
}
|
||||
|
||||
@Watch('files')
|
||||
onFileChange() {
|
||||
this.selected = [];
|
||||
}
|
||||
|
||||
get filteredFiles() {
|
||||
if (this.showHidden) return this.files;
|
||||
return this.files.filter(file => file.isDir || file.path.endsWith('.tns'));
|
||||
}
|
||||
|
||||
formatPath({path, isDir}: FileInfo) {
|
||||
const name = path.split('/').pop() as string;
|
||||
if (this.showHidden || isDir) return name;
|
||||
return name.substring(0, name.length - 4);
|
||||
}
|
||||
|
||||
xorSelection(i: number) {
|
||||
if (this.selected.includes(i)) this.selected = this.selected.filter(num => num !== i);
|
||||
else this.selected.push(i);
|
||||
}
|
||||
|
||||
shiftSelection(item: number) {
|
||||
const lastSelected = this.selected[this.selected.length-1];
|
||||
if(lastSelected === undefined) {
|
||||
this.selected.push(item);
|
||||
return;
|
||||
}
|
||||
const [lower, upper] = item > lastSelected ? [lastSelected, item] : [item, lastSelected];
|
||||
for(let i = lower; i <= upper; i++) {
|
||||
if (!this.selected.includes(i))this.selected.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.selected {
|
||||
background-color: #4d88e8;
|
||||
@apply rounded;
|
||||
p {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class HelloWorld extends Vue {
|
||||
@Prop() private msg!: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
278
src/components/devices.ts
Normal file
|
@ -0,0 +1,278 @@
|
|||
import {promisified} from 'tauri/api/tauri';
|
||||
import {listen} from 'tauri/api/event';
|
||||
import {Component, Vue} from 'vue-property-decorator';
|
||||
|
||||
|
||||
export type DevId = { address: number; busNumber: number };
|
||||
|
||||
function devToString(dev: DevId) {
|
||||
return `${dev.busNumber}:${dev.address}`;
|
||||
}
|
||||
|
||||
function stringToDev(dev: string): DevId {
|
||||
const parts = dev.split(':');
|
||||
// eslint-disable-next-line
|
||||
return {busNumber: Number.parseInt(parts[0]), address: Number.parseInt(parts[1])};
|
||||
}
|
||||
|
||||
export type Version = { major: number; minor: number; patch: number; build: number };
|
||||
export type Lcd = { width: number; height: number; bpp: number; sample_mode: number };
|
||||
export type HardwareType =
|
||||
| "Cas"
|
||||
| "NonCas"
|
||||
| "CasCx"
|
||||
| "NonCasCx"
|
||||
| { Unknown: number };
|
||||
// The current state of the calculator.
|
||||
export type RunLevel =
|
||||
| "Recovery"
|
||||
| "Os"
|
||||
| { Unknown: number };
|
||||
export type Battery =
|
||||
| "Powered"
|
||||
| "Low"
|
||||
| "Ok"
|
||||
| { Unknown: number };
|
||||
export type Info = { free_storage: number; total_storage: number; free_ram: number; total_ram: number; version: Version; boot1_version: Version; boot2_version: Version; hw_type: HardwareType; clock_speed: number; lcd: Lcd; os_extension: string; file_extension: string; name: string; id: string; run_level: RunLevel; battery: Battery; is_charging: boolean };
|
||||
|
||||
export type FileInfo = { path: string; isDir: boolean; date: number; size: number };
|
||||
|
||||
export type Progress = { remaining: number; total: number };
|
||||
|
||||
export type PartialCmd = { action: 'download'; path: [string, number]; dest: string }
|
||||
| { action: 'upload'; path: string; src: string }
|
||||
| { action: 'uploadOs'; src: string }
|
||||
| { action: 'deleteFile'; path: string }
|
||||
| { action: 'deleteDir'; path: string }
|
||||
| { action: 'createDir'; path: string }
|
||||
| { action: 'move'; src: string; dest: string }
|
||||
| { action: 'copy'; src: string; dest: string };
|
||||
|
||||
export type Cmd = { id: number } & PartialCmd;
|
||||
|
||||
export type Device = { name: string; info?: Info; progress?: Progress; queue?: Cmd[]; running?: boolean };
|
||||
|
||||
async function downloadFile(dev: DevId | string, path: [string, number], dest: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'downloadFile', path, dest});
|
||||
}
|
||||
|
||||
async function uploadFile(dev: DevId | string, path: string, src: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'uploadFile', path, src});
|
||||
}
|
||||
|
||||
async function uploadOs(dev: DevId | string, src: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'uploadOs', src});
|
||||
}
|
||||
|
||||
async function deleteFile(dev: DevId | string, path: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'deleteFile', path});
|
||||
}
|
||||
|
||||
async function deleteDir(dev: DevId | string, path: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'deleteDir', path});
|
||||
}
|
||||
|
||||
async function createDir(dev: DevId | string, path: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'createNspireDir', path});
|
||||
}
|
||||
|
||||
async function move(dev: DevId | string, src: string, dest: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'move', src, dest});
|
||||
}
|
||||
|
||||
async function copy(dev: DevId | string, src: string, dest: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'copy', src, dest});
|
||||
}
|
||||
|
||||
async function listDir(dev: DevId | string, path: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
return await promisified({...dev, cmd: 'listDir', path}) as FileInfo[];
|
||||
}
|
||||
|
||||
async function listAll(dev: DevId | string, path: FileInfo): Promise<FileInfo[]> {
|
||||
if (!path.isDir) return [path];
|
||||
try {
|
||||
const contents = await listDir(dev, path.path);
|
||||
const parts: FileInfo[] = [];
|
||||
for (const file of contents) {
|
||||
parts.push(...(await listAll(dev, {...file, path: `${path.path}/${file.path}`})));
|
||||
}
|
||||
parts.push(path);
|
||||
return parts;
|
||||
} catch (e) {
|
||||
console.error(path, e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let queueId = 0;
|
||||
|
||||
@Component
|
||||
class Devices extends Vue {
|
||||
devices: Record<string, Device> = {};
|
||||
|
||||
created() {
|
||||
promisified({cmd: 'enumerate'}).then(devs => {
|
||||
for (const dev of devs as (Device & DevId)[]) {
|
||||
this.$set(this.devices, devToString(dev as DevId), dev);
|
||||
}
|
||||
}, console.error);
|
||||
listen('addDevice', dev => {
|
||||
const payload = dev.payload as Device & DevId;
|
||||
const str = devToString(payload);
|
||||
const existing = this.devices[str] || {};
|
||||
this.$set(this.devices, str, {...existing, ...payload});
|
||||
});
|
||||
listen('removeDevice', dev => {
|
||||
this.$delete(this.devices, devToString(dev.payload as DevId));
|
||||
});
|
||||
listen('progress', dev => {
|
||||
const payload = dev.payload as Progress & DevId;
|
||||
const str = devToString(payload);
|
||||
this.$set(this.devices[str], 'progress', payload);
|
||||
});
|
||||
}
|
||||
|
||||
async runQueue(dev: DevId | string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
const device = this.devices[dev];
|
||||
if (!device?.queue || device.running) return;
|
||||
this.$set(device, 'running', true);
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
// The device has been removed
|
||||
if (!this.devices[dev]) return;
|
||||
|
||||
const cmd = device.queue[0];
|
||||
if (!cmd) {
|
||||
device.running = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (cmd.action === 'download') {
|
||||
await downloadFile(dev, cmd.path, cmd.dest);
|
||||
} else if (cmd.action === 'upload') {
|
||||
await uploadFile(dev, cmd.path, cmd.src);
|
||||
} else if (cmd.action === 'uploadOs') {
|
||||
await uploadOs(dev, cmd.src);
|
||||
} else if (cmd.action === 'deleteFile') {
|
||||
await deleteFile(dev, cmd.path);
|
||||
} else if (cmd.action === 'deleteDir') {
|
||||
await deleteDir(dev, cmd.path);
|
||||
} else if (cmd.action === 'createDir') {
|
||||
await createDir(dev, cmd.path);
|
||||
} else if (cmd.action === 'move') {
|
||||
await move(dev, cmd.src, cmd.dest);
|
||||
} else if (cmd.action === 'copy') {
|
||||
await copy(dev, cmd.src, cmd.dest);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if ('progress' in device) this.$delete(device, 'progress');
|
||||
device.queue.shift();
|
||||
await this.update(dev);
|
||||
}
|
||||
}
|
||||
|
||||
private addToQueue(dev: string, ...cmds: PartialCmd[]) {
|
||||
const device = this.devices[dev];
|
||||
if (!device) return;
|
||||
if (!device.queue) {
|
||||
this.$set(device, 'queue', []);
|
||||
}
|
||||
device.queue?.push(...cmds.map(cmd => ({...cmd, id: queueId++} as Cmd)));
|
||||
this.runQueue(dev);
|
||||
}
|
||||
|
||||
async open(dev: DevId | string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
const info = await promisified({...dev, cmd: 'openDevice'});
|
||||
this.$set(this.devices[devToString(dev)], 'info', info);
|
||||
}
|
||||
|
||||
async close(dev: DevId | string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
await promisified({...dev, cmd: 'closeDevice'});
|
||||
this.$delete(this.devices[devToString(dev)], 'info');
|
||||
}
|
||||
|
||||
async update(dev: DevId | string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
const info = await promisified({...dev, cmd: 'updateDevice'});
|
||||
this.$set(this.devices[devToString(dev)], 'info', info);
|
||||
}
|
||||
|
||||
async listDir(dev: DevId | string, path: string) {
|
||||
return await listDir(dev, path);
|
||||
}
|
||||
|
||||
async promptUploadFiles(dev: DevId | string, path: string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
const files = await promisified({cmd: 'selectFiles', filter: ['tns']}) as string[];
|
||||
for (const src of files) {
|
||||
this.addToQueue(dev, {action: 'upload', path, src});
|
||||
}
|
||||
}
|
||||
|
||||
async uploadOs(dev: DevId | string, filter: string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
const src = await promisified({cmd: 'selectFile', filter: [filter]}) as string | null;
|
||||
if (!src) return;
|
||||
this.addToQueue(dev, {action: 'uploadOs', src});
|
||||
}
|
||||
|
||||
async downloadFiles(dev: DevId | string, files: [string, number][]) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
const dest = await promisified({cmd: 'selectFolder'}) as string | null;
|
||||
if (!dest) return;
|
||||
for (const path of files) {
|
||||
this.addToQueue(dev, {action: 'download', path, dest});
|
||||
}
|
||||
}
|
||||
|
||||
async delete(dev: DevId | string, files: FileInfo[]) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
const toDelete: FileInfo[] = [];
|
||||
for (const file of files) {
|
||||
toDelete.push(...await listAll(dev, file));
|
||||
}
|
||||
for (const file of toDelete) {
|
||||
this.addToQueue(dev, {action: file.isDir ? 'deleteDir' : 'deleteFile', path: file.path});
|
||||
}
|
||||
}
|
||||
|
||||
async createDir(dev: DevId | string, path: string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
this.addToQueue(dev, {action: 'createDir', path});
|
||||
}
|
||||
|
||||
async copy(dev: DevId | string, src: string, dest: string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
this.addToQueue(dev, {action: 'copy', src, dest});
|
||||
}
|
||||
|
||||
async move(dev: DevId | string, src: string, dest: string) {
|
||||
if (typeof dev !== 'string') dev = devToString(dev);
|
||||
this.addToQueue(dev, {action: 'move', src, dest});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const devices = new Devices();
|
||||
export default devices;
|
||||
Vue.prototype.$devices = devices;
|
||||
declare module 'vue/types/vue' {
|
||||
// 3. Declare augmentation for Vue
|
||||
interface Vue {
|
||||
$devices: Devices;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import AsyncComputed from 'vue-async-computed';
|
||||
import router from './router'
|
||||
|
||||
import './assets/tailwind.css'
|
||||
import './components/devices';
|
||||
Vue.use(AsyncComputed);
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
|
|
50
src/shims-vue.d.ts
vendored
|
@ -2,3 +2,53 @@ declare module '*.vue' {
|
|||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
|
||||
import Vue, { PluginFunction } from 'vue';
|
||||
|
||||
interface IAsyncComputedOptions {
|
||||
errorHandler?: (error: string[]) => void;
|
||||
useRawError?: boolean;
|
||||
default?: any;
|
||||
}
|
||||
|
||||
export default class AsyncComputed {
|
||||
constructor(options?: IAsyncComputedOptions);
|
||||
static install: PluginFunction<never>;
|
||||
static version: string;
|
||||
}
|
||||
|
||||
type AsyncComputedGetter<T> = () => Promise<T> | T;
|
||||
export interface IAsyncComputedProperty<T> {
|
||||
default?: T | (() => T);
|
||||
get: AsyncComputedGetter<T>;
|
||||
watch?: () => void;
|
||||
shouldUpdate?: () => boolean;
|
||||
lazy?: boolean;
|
||||
}
|
||||
|
||||
interface IAsyncComputedProperties {
|
||||
[K: string]: AsyncComputedGetter<any> | IAsyncComputedProperty<any>;
|
||||
}
|
||||
|
||||
declare module 'vue/types/options' {
|
||||
interface ComponentOptions<V extends Vue> {
|
||||
// @ts-ignore
|
||||
asyncComputed?: IAsyncComputedProperties;
|
||||
}
|
||||
}
|
||||
|
||||
interface IASyncComputedState {
|
||||
state: 'updating' | 'success' | 'error';
|
||||
updating: boolean;
|
||||
success: boolean;
|
||||
error: boolean;
|
||||
exception: Error | null;
|
||||
update: () => void;
|
||||
}
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
// tslint:disable-next-line:interface-name
|
||||
interface Vue {
|
||||
$asyncComputed: { [K: string]: IASyncComputedState };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,66 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<img alt="Vue logo" src="../assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
|
||||
<div class="home h-full overflow-hidden">
|
||||
<div class="flex flex-row h-full">
|
||||
<div class="flex-shrink-0 border-r w-64">
|
||||
<device-select :selected.sync="selectedCalculator"/>
|
||||
<div class="overflow-auto h-full px-4 py-4">
|
||||
<div v-if="calculator && calculator.info">
|
||||
<calc-info :info="calculator.info" :dev="selectedCalculator"/>
|
||||
<label class="inline-flex items-center cursor-pointer mr-2 mt-4">
|
||||
<input type="checkbox" class="form-checkbox h-5 w-5 text-blue-600 cursor-pointer" v-model="showHidden">
|
||||
<span class="mx-2 text-gray-700 select-none">Include hidden files</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="h-full">
|
||||
<file-browser v-if="calculator && calculator.info" :dev="selectedCalculator" :show-hidden="showHidden"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
|
||||
import {Component, Vue, Watch} from 'vue-property-decorator';
|
||||
import CalcInfo from '@/components/CalcInfo.vue';
|
||||
import FileBrowser from '@/components/FileBrowser.vue';
|
||||
import DeviceSelect from "@/components/DeviceSelect.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
HelloWorld,
|
||||
DeviceSelect,
|
||||
FileBrowser,
|
||||
CalcInfo,
|
||||
},
|
||||
})
|
||||
export default class Home extends Vue {}
|
||||
export default class Home extends Vue {
|
||||
selectedCalculator: string | null = null;
|
||||
showHidden = false;
|
||||
|
||||
@Watch('$devices.devices')
|
||||
onDeviceChange() {
|
||||
if (!this.selectedCalculator) {
|
||||
const first = Object.keys(this.$devices.devices)[0];
|
||||
if (first) this.selectedCalculator = first;
|
||||
} else if (!Object.keys(this.$devices.devices).includes(this.selectedCalculator)) {
|
||||
this.selectedCalculator = null;
|
||||
// go back and choose the first if available
|
||||
this.onDeviceChange();
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('selectedCalculator')
|
||||
onSelectCalculator(dev: string | null) {
|
||||
if (dev && !this.$devices.devices[dev].info) {
|
||||
console.log('open', dev);
|
||||
this.$devices.open(dev);
|
||||
}
|
||||
}
|
||||
|
||||
get calculator() {
|
||||
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
54
tailwind.config.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
module.exports = {
|
||||
future: {
|
||||
purgeLayersByDefault: true,
|
||||
removeDeprecatedGapUtilities: true,
|
||||
},
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Cantarell',
|
||||
'Roboto',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'"Noto Sans"',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
'"Noto Color Emoji"',
|
||||
],
|
||||
},
|
||||
extend: {
|
||||
inset: {
|
||||
'1/8': '0.125em',
|
||||
},
|
||||
boxShadow: {
|
||||
even: '0 2px 12px 0 #0000001a',
|
||||
error: '0 0 0 3px #F56C6CA0',
|
||||
},
|
||||
padding: {
|
||||
'2.5': '0.625em',
|
||||
'1.5': '0.375em',
|
||||
},
|
||||
colors: {
|
||||
ui: {
|
||||
background: 'var(--color-ui-background)',
|
||||
emph: 'var(--color-ui-emph)',
|
||||
sidebar: 'var(--color-ui-sidebar)',
|
||||
text: 'var(--color-ui-text)',
|
||||
'text-inv': 'var(--color-ui-text-inv)',
|
||||
primary: 'var(--color-ui-primary)',
|
||||
border: 'var(--color-ui-border)',
|
||||
blockquote: 'var(--color-ui-blockquote)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [require('@tailwindcss/custom-forms')],
|
||||
purge: false,
|
||||
};
|
|
@ -36,5 +36,6 @@
|
|||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
],
|
||||
"typeRoots": ["@/types", "./node_modules/@types"]
|
||||
}
|
||||
|
|