@ -0,0 +1,7 @@ |
|||||
|
.DS_Store |
||||
|
node_modules |
||||
|
node_modules_win |
||||
|
package-lock.json |
||||
|
yarn.lock |
||||
|
release/ |
||||
|
dist/ |
||||
@ -0,0 +1,4 @@ |
|||||
|
Contributors |
||||
|
=== |
||||
|
|
||||
|
* Michael Shanks - [@mikebudi](https://github.com/mikebudi) |
||||
@ -0,0 +1,22 @@ |
|||||
|
### Contributing to budibase-core |
||||
|
|
||||
|
* The contributors are listed in [AUTHORS.md](https://github.com/budibase/builder/blob/master/AUTHORS.md) (add yourself). |
||||
|
|
||||
|
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/builder/blob/master/LICENSE.md). |
||||
|
|
||||
|
* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. |
||||
|
Please read this if you are unfamiliar with it. |
||||
|
|
||||
|
* Please maintain the existing code style. |
||||
|
|
||||
|
* Please try to keep your commits small and focussed. |
||||
|
|
||||
|
* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. |
||||
|
|
||||
|
#### p.S... |
||||
|
|
||||
|
I am using contribution guidelines from the fantastic [ZeroMQ](https://github.com/zeromq) community. If you are interested why, it's because I believe in the ethos laid out by this community, and written about in depth in the book ["Social Architecture"](https://www.amazon.com/Social-Architecture-Building-line-Communities/dp/1533112452) by Pieter Hintjens. |
||||
|
|
||||
|
I am very much open to evolving this to suit our needs. |
||||
|
|
||||
|
Love from [Mike](https://github.com/mikebudi). |
||||
@ -0,0 +1,661 @@ |
|||||
|
GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
Version 3, 19 November 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 Affero General Public License is a free, copyleft license for |
||||
|
software and other kinds of works, specifically designed to ensure |
||||
|
cooperation with the community in the case of network server software. |
||||
|
|
||||
|
The licenses for most software and other practical works are designed |
||||
|
to take away your freedom to share and change the works. By contrast, |
||||
|
our General Public Licenses are intended to guarantee your freedom to |
||||
|
share and change all versions of a program--to make sure it remains free |
||||
|
software for all its users. |
||||
|
|
||||
|
When we speak of free software, we are referring to freedom, not |
||||
|
price. Our General Public Licenses are designed to make sure that you |
||||
|
have the freedom to distribute copies of free software (and charge for |
||||
|
them if you wish), that you receive source code or can get it if you |
||||
|
want it, that you can change the software or use pieces of it in new |
||||
|
free programs, and that you know you can do these things. |
||||
|
|
||||
|
Developers that use our General Public Licenses protect your rights |
||||
|
with two steps: (1) assert copyright on the software, and (2) offer |
||||
|
you this License which gives you legal permission to copy, distribute |
||||
|
and/or modify the software. |
||||
|
|
||||
|
A secondary benefit of defending all users' freedom is that |
||||
|
improvements made in alternate versions of the program, if they |
||||
|
receive widespread use, become available for other developers to |
||||
|
incorporate. Many developers of free software are heartened and |
||||
|
encouraged by the resulting cooperation. However, in the case of |
||||
|
software used on network servers, this result may fail to come about. |
||||
|
The GNU General Public License permits making a modified version and |
||||
|
letting the public access it on a server without ever releasing its |
||||
|
source code to the public. |
||||
|
|
||||
|
The GNU Affero General Public License is designed specifically to |
||||
|
ensure that, in such cases, the modified source code becomes available |
||||
|
to the community. It requires the operator of a network server to |
||||
|
provide the source code of the modified version running there to the |
||||
|
users of that server. Therefore, public use of a modified version, on |
||||
|
a publicly accessible server, gives the public access to the source |
||||
|
code of the modified version. |
||||
|
|
||||
|
An older license, called the Affero General Public License and |
||||
|
published by Affero, was designed to accomplish similar goals. This is |
||||
|
a different license, not a version of the Affero GPL, but Affero has |
||||
|
released a new version of the Affero GPL which permits relicensing under |
||||
|
this license. |
||||
|
|
||||
|
The precise terms and conditions for copying, distribution and |
||||
|
modification follow. |
||||
|
|
||||
|
TERMS AND CONDITIONS |
||||
|
|
||||
|
0. Definitions. |
||||
|
|
||||
|
"This License" refers to version 3 of the GNU Affero General Public License. |
||||
|
|
||||
|
"Copyright" also means copyright-like laws that apply to other kinds of |
||||
|
works, such as semiconductor masks. |
||||
|
|
||||
|
"The Program" refers to any copyrightable work licensed under this |
||||
|
License. Each licensee is addressed as "you". "Licensees" and |
||||
|
"recipients" may be individuals or organizations. |
||||
|
|
||||
|
To "modify" a work means to copy from or adapt all or part of the work |
||||
|
in a fashion requiring copyright permission, other than the making of an |
||||
|
exact copy. The resulting work is called a "modified version" of the |
||||
|
earlier work or a work "based on" the earlier work. |
||||
|
|
||||
|
A "covered work" means either the unmodified Program or a work based |
||||
|
on the Program. |
||||
|
|
||||
|
To "propagate" a work means to do anything with it that, without |
||||
|
permission, would make you directly or secondarily liable for |
||||
|
infringement under applicable copyright law, except executing it on a |
||||
|
computer or modifying a private copy. Propagation includes copying, |
||||
|
distribution (with or without modification), making available to the |
||||
|
public, and in some countries other activities as well. |
||||
|
|
||||
|
To "convey" a work means any kind of propagation that enables other |
||||
|
parties to make or receive copies. Mere interaction with a user through |
||||
|
a computer network, with no transfer of a copy, is not conveying. |
||||
|
|
||||
|
An interactive user interface displays "Appropriate Legal Notices" |
||||
|
to the extent that it includes a convenient and prominently visible |
||||
|
feature that (1) displays an appropriate copyright notice, and (2) |
||||
|
tells the user that there is no warranty for the work (except to the |
||||
|
extent that warranties are provided), that licensees may convey the |
||||
|
work under this License, and how to view a copy of this License. If |
||||
|
the interface presents a list of user commands or options, such as a |
||||
|
menu, a prominent item in the list meets this criterion. |
||||
|
|
||||
|
1. Source Code. |
||||
|
|
||||
|
The "source code" for a work means the preferred form of the work |
||||
|
for making modifications to it. "Object code" means any non-source |
||||
|
form of a work. |
||||
|
|
||||
|
A "Standard Interface" means an interface that either is an official |
||||
|
standard defined by a recognized standards body, or, in the case of |
||||
|
interfaces specified for a particular programming language, one that |
||||
|
is widely used among developers working in that language. |
||||
|
|
||||
|
The "System Libraries" of an executable work include anything, other |
||||
|
than the work as a whole, that (a) is included in the normal form of |
||||
|
packaging a Major Component, but which is not part of that Major |
||||
|
Component, and (b) serves only to enable use of the work with that |
||||
|
Major Component, or to implement a Standard Interface for which an |
||||
|
implementation is available to the public in source code form. A |
||||
|
"Major Component", in this context, means a major essential component |
||||
|
(kernel, window system, and so on) of the specific operating system |
||||
|
(if any) on which the executable work runs, or a compiler used to |
||||
|
produce the work, or an object code interpreter used to run it. |
||||
|
|
||||
|
The "Corresponding Source" for a work in object code form means all |
||||
|
the source code needed to generate, install, and (for an executable |
||||
|
work) run the object code and to modify the work, including scripts to |
||||
|
control those activities. However, it does not include the work's |
||||
|
System Libraries, or general-purpose tools or generally available free |
||||
|
programs which are used unmodified in performing those activities but |
||||
|
which are not part of the work. For example, Corresponding Source |
||||
|
includes interface definition files associated with source files for |
||||
|
the work, and the source code for shared libraries and dynamically |
||||
|
linked subprograms that the work is specifically designed to require, |
||||
|
such as by intimate data communication or control flow between those |
||||
|
subprograms and other parts of the work. |
||||
|
|
||||
|
The Corresponding Source need not include anything that users |
||||
|
can regenerate automatically from other parts of the Corresponding |
||||
|
Source. |
||||
|
|
||||
|
The Corresponding Source for a work in source code form is that |
||||
|
same work. |
||||
|
|
||||
|
2. Basic Permissions. |
||||
|
|
||||
|
All rights granted under this License are granted for the term of |
||||
|
copyright on the Program, and are irrevocable provided the stated |
||||
|
conditions are met. This License explicitly affirms your unlimited |
||||
|
permission to run the unmodified Program. The output from running a |
||||
|
covered work is covered by this License only if the output, given its |
||||
|
content, constitutes a covered work. This License acknowledges your |
||||
|
rights of fair use or other equivalent, as provided by copyright law. |
||||
|
|
||||
|
You may make, run and propagate covered works that you do not |
||||
|
convey, without conditions so long as your license otherwise remains |
||||
|
in force. You may convey covered works to others for the sole purpose |
||||
|
of having them make modifications exclusively for you, or provide you |
||||
|
with facilities for running those works, provided that you comply with |
||||
|
the terms of this License in conveying all material for which you do |
||||
|
not control copyright. Those thus making or running the covered works |
||||
|
for you must do so exclusively on your behalf, under your direction |
||||
|
and control, on terms that prohibit them from making any copies of |
||||
|
your copyrighted material outside their relationship with you. |
||||
|
|
||||
|
Conveying under any other circumstances is permitted solely under |
||||
|
the conditions stated below. Sublicensing is not allowed; section 10 |
||||
|
makes it unnecessary. |
||||
|
|
||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
||||
|
|
||||
|
No covered work shall be deemed part of an effective technological |
||||
|
measure under any applicable law fulfilling obligations under article |
||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
||||
|
similar laws prohibiting or restricting circumvention of such |
||||
|
measures. |
||||
|
|
||||
|
When you convey a covered work, you waive any legal power to forbid |
||||
|
circumvention of technological measures to the extent such circumvention |
||||
|
is effected by exercising rights under this License with respect to |
||||
|
the covered work, and you disclaim any intention to limit operation or |
||||
|
modification of the work as a means of enforcing, against the work's |
||||
|
users, your or third parties' legal rights to forbid circumvention of |
||||
|
technological measures. |
||||
|
|
||||
|
4. Conveying Verbatim Copies. |
||||
|
|
||||
|
You may convey verbatim copies of the Program's source code as you |
||||
|
receive it, in any medium, provided that you conspicuously and |
||||
|
appropriately publish on each copy an appropriate copyright notice; |
||||
|
keep intact all notices stating that this License and any |
||||
|
non-permissive terms added in accord with section 7 apply to the code; |
||||
|
keep intact all notices of the absence of any warranty; and give all |
||||
|
recipients a copy of this License along with the Program. |
||||
|
|
||||
|
You may charge any price or no price for each copy that you convey, |
||||
|
and you may offer support or warranty protection for a fee. |
||||
|
|
||||
|
5. Conveying Modified Source Versions. |
||||
|
|
||||
|
You may convey a work based on the Program, or the modifications to |
||||
|
produce it from the Program, in the form of source code under the |
||||
|
terms of section 4, provided that you also meet all of these conditions: |
||||
|
|
||||
|
a) The work must carry prominent notices stating that you modified |
||||
|
it, and giving a relevant date. |
||||
|
|
||||
|
b) The work must carry prominent notices stating that it is |
||||
|
released under this License and any conditions added under section |
||||
|
7. This requirement modifies the requirement in section 4 to |
||||
|
"keep intact all notices". |
||||
|
|
||||
|
c) You must license the entire work, as a whole, under this |
||||
|
License to anyone who comes into possession of a copy. This |
||||
|
License will therefore apply, along with any applicable section 7 |
||||
|
additional terms, to the whole of the work, and all its parts, |
||||
|
regardless of how they are packaged. This License gives no |
||||
|
permission to license the work in any other way, but it does not |
||||
|
invalidate such permission if you have separately received it. |
||||
|
|
||||
|
d) If the work has interactive user interfaces, each must display |
||||
|
Appropriate Legal Notices; however, if the Program has interactive |
||||
|
interfaces that do not display Appropriate Legal Notices, your |
||||
|
work need not make them do so. |
||||
|
|
||||
|
A compilation of a covered work with other separate and independent |
||||
|
works, which are not by their nature extensions of the covered work, |
||||
|
and which are not combined with it such as to form a larger program, |
||||
|
in or on a volume of a storage or distribution medium, is called an |
||||
|
"aggregate" if the compilation and its resulting copyright are not |
||||
|
used to limit the access or legal rights of the compilation's users |
||||
|
beyond what the individual works permit. Inclusion of a covered work |
||||
|
in an aggregate does not cause this License to apply to the other |
||||
|
parts of the aggregate. |
||||
|
|
||||
|
6. Conveying Non-Source Forms. |
||||
|
|
||||
|
You may convey a covered work in object code form under the terms |
||||
|
of sections 4 and 5, provided that you also convey the |
||||
|
machine-readable Corresponding Source under the terms of this License, |
||||
|
in one of these ways: |
||||
|
|
||||
|
a) Convey the object code in, or embodied in, a physical product |
||||
|
(including a physical distribution medium), accompanied by the |
||||
|
Corresponding Source fixed on a durable physical medium |
||||
|
customarily used for software interchange. |
||||
|
|
||||
|
b) Convey the object code in, or embodied in, a physical product |
||||
|
(including a physical distribution medium), accompanied by a |
||||
|
written offer, valid for at least three years and valid for as |
||||
|
long as you offer spare parts or customer support for that product |
||||
|
model, to give anyone who possesses the object code either (1) a |
||||
|
copy of the Corresponding Source for all the software in the |
||||
|
product that is covered by this License, on a durable physical |
||||
|
medium customarily used for software interchange, for a price no |
||||
|
more than your reasonable cost of physically performing this |
||||
|
conveying of source, or (2) access to copy the |
||||
|
Corresponding Source from a network server at no charge. |
||||
|
|
||||
|
c) Convey individual copies of the object code with a copy of the |
||||
|
written offer to provide the Corresponding Source. This |
||||
|
alternative is allowed only occasionally and noncommercially, and |
||||
|
only if you received the object code with such an offer, in accord |
||||
|
with subsection 6b. |
||||
|
|
||||
|
d) Convey the object code by offering access from a designated |
||||
|
place (gratis or for a charge), and offer equivalent access to the |
||||
|
Corresponding Source in the same way through the same place at no |
||||
|
further charge. You need not require recipients to copy the |
||||
|
Corresponding Source along with the object code. If the place to |
||||
|
copy the object code is a network server, the Corresponding Source |
||||
|
may be on a different server (operated by you or a third party) |
||||
|
that supports equivalent copying facilities, provided you maintain |
||||
|
clear directions next to the object code saying where to find the |
||||
|
Corresponding Source. Regardless of what server hosts the |
||||
|
Corresponding Source, you remain obligated to ensure that it is |
||||
|
available for as long as needed to satisfy these requirements. |
||||
|
|
||||
|
e) Convey the object code using peer-to-peer transmission, provided |
||||
|
you inform other peers where the object code and Corresponding |
||||
|
Source of the work are being offered to the general public at no |
||||
|
charge under subsection 6d. |
||||
|
|
||||
|
A separable portion of the object code, whose source code is excluded |
||||
|
from the Corresponding Source as a System Library, need not be |
||||
|
included in conveying the object code work. |
||||
|
|
||||
|
A "User Product" is either (1) a "consumer product", which means any |
||||
|
tangible personal property which is normally used for personal, family, |
||||
|
or household purposes, or (2) anything designed or sold for incorporation |
||||
|
into a dwelling. In determining whether a product is a consumer product, |
||||
|
doubtful cases shall be resolved in favor of coverage. For a particular |
||||
|
product received by a particular user, "normally used" refers to a |
||||
|
typical or common use of that class of product, regardless of the status |
||||
|
of the particular user or of the way in which the particular user |
||||
|
actually uses, or expects or is expected to use, the product. A product |
||||
|
is a consumer product regardless of whether the product has substantial |
||||
|
commercial, industrial or non-consumer uses, unless such uses represent |
||||
|
the only significant mode of use of the product. |
||||
|
|
||||
|
"Installation Information" for a User Product means any methods, |
||||
|
procedures, authorization keys, or other information required to install |
||||
|
and execute modified versions of a covered work in that User Product from |
||||
|
a modified version of its Corresponding Source. The information must |
||||
|
suffice to ensure that the continued functioning of the modified object |
||||
|
code is in no case prevented or interfered with solely because |
||||
|
modification has been made. |
||||
|
|
||||
|
If you convey an object code work under this section in, or with, or |
||||
|
specifically for use in, a User Product, and the conveying occurs as |
||||
|
part of a transaction in which the right of possession and use of the |
||||
|
User Product is transferred to the recipient in perpetuity or for a |
||||
|
fixed term (regardless of how the transaction is characterized), the |
||||
|
Corresponding Source conveyed under this section must be accompanied |
||||
|
by the Installation Information. But this requirement does not apply |
||||
|
if neither you nor any third party retains the ability to install |
||||
|
modified object code on the User Product (for example, the work has |
||||
|
been installed in ROM). |
||||
|
|
||||
|
The requirement to provide Installation Information does not include a |
||||
|
requirement to continue to provide support service, warranty, or updates |
||||
|
for a work that has been modified or installed by the recipient, or for |
||||
|
the User Product in which it has been modified or installed. Access to a |
||||
|
network may be denied when the modification itself materially and |
||||
|
adversely affects the operation of the network or violates the rules and |
||||
|
protocols for communication across the network. |
||||
|
|
||||
|
Corresponding Source conveyed, and Installation Information provided, |
||||
|
in accord with this section must be in a format that is publicly |
||||
|
documented (and with an implementation available to the public in |
||||
|
source code form), and must require no special password or key for |
||||
|
unpacking, reading or copying. |
||||
|
|
||||
|
7. Additional Terms. |
||||
|
|
||||
|
"Additional permissions" are terms that supplement the terms of this |
||||
|
License by making exceptions from one or more of its conditions. |
||||
|
Additional permissions that are applicable to the entire Program shall |
||||
|
be treated as though they were included in this License, to the extent |
||||
|
that they are valid under applicable law. If additional permissions |
||||
|
apply only to part of the Program, that part may be used separately |
||||
|
under those permissions, but the entire Program remains governed by |
||||
|
this License without regard to the additional permissions. |
||||
|
|
||||
|
When you convey a copy of a covered work, you may at your option |
||||
|
remove any additional permissions from that copy, or from any part of |
||||
|
it. (Additional permissions may be written to require their own |
||||
|
removal in certain cases when you modify the work.) You may place |
||||
|
additional permissions on material, added by you to a covered work, |
||||
|
for which you have or can give appropriate copyright permission. |
||||
|
|
||||
|
Notwithstanding any other provision of this License, for material you |
||||
|
add to a covered work, you may (if authorized by the copyright holders of |
||||
|
that material) supplement the terms of this License with terms: |
||||
|
|
||||
|
a) Disclaiming warranty or limiting liability differently from the |
||||
|
terms of sections 15 and 16 of this License; or |
||||
|
|
||||
|
b) Requiring preservation of specified reasonable legal notices or |
||||
|
author attributions in that material or in the Appropriate Legal |
||||
|
Notices displayed by works containing it; or |
||||
|
|
||||
|
c) Prohibiting misrepresentation of the origin of that material, or |
||||
|
requiring that modified versions of such material be marked in |
||||
|
reasonable ways as different from the original version; or |
||||
|
|
||||
|
d) Limiting the use for publicity purposes of names of licensors or |
||||
|
authors of the material; or |
||||
|
|
||||
|
e) Declining to grant rights under trademark law for use of some |
||||
|
trade names, trademarks, or service marks; or |
||||
|
|
||||
|
f) Requiring indemnification of licensors and authors of that |
||||
|
material by anyone who conveys the material (or modified versions of |
||||
|
it) with contractual assumptions of liability to the recipient, for |
||||
|
any liability that these contractual assumptions directly impose on |
||||
|
those licensors and authors. |
||||
|
|
||||
|
All other non-permissive additional terms are considered "further |
||||
|
restrictions" within the meaning of section 10. If the Program as you |
||||
|
received it, or any part of it, contains a notice stating that it is |
||||
|
governed by this License along with a term that is a further |
||||
|
restriction, you may remove that term. If a license document contains |
||||
|
a further restriction but permits relicensing or conveying under this |
||||
|
License, you may add to a covered work material governed by the terms |
||||
|
of that license document, provided that the further restriction does |
||||
|
not survive such relicensing or conveying. |
||||
|
|
||||
|
If you add terms to a covered work in accord with this section, you |
||||
|
must place, in the relevant source files, a statement of the |
||||
|
additional terms that apply to those files, or a notice indicating |
||||
|
where to find the applicable terms. |
||||
|
|
||||
|
Additional terms, permissive or non-permissive, may be stated in the |
||||
|
form of a separately written license, or stated as exceptions; |
||||
|
the above requirements apply either way. |
||||
|
|
||||
|
8. Termination. |
||||
|
|
||||
|
You may not propagate or modify a covered work except as expressly |
||||
|
provided under this License. Any attempt otherwise to propagate or |
||||
|
modify it is void, and will automatically terminate your rights under |
||||
|
this License (including any patent licenses granted under the third |
||||
|
paragraph of section 11). |
||||
|
|
||||
|
However, if you cease all violation of this License, then your |
||||
|
license from a particular copyright holder is reinstated (a) |
||||
|
provisionally, unless and until the copyright holder explicitly and |
||||
|
finally terminates your license, and (b) permanently, if the copyright |
||||
|
holder fails to notify you of the violation by some reasonable means |
||||
|
prior to 60 days after the cessation. |
||||
|
|
||||
|
Moreover, your license from a particular copyright holder is |
||||
|
reinstated permanently if the copyright holder notifies you of the |
||||
|
violation by some reasonable means, this is the first time you have |
||||
|
received notice of violation of this License (for any work) from that |
||||
|
copyright holder, and you cure the violation prior to 30 days after |
||||
|
your receipt of the notice. |
||||
|
|
||||
|
Termination of your rights under this section does not terminate the |
||||
|
licenses of parties who have received copies or rights from you under |
||||
|
this License. If your rights have been terminated and not permanently |
||||
|
reinstated, you do not qualify to receive new licenses for the same |
||||
|
material under section 10. |
||||
|
|
||||
|
9. Acceptance Not Required for Having Copies. |
||||
|
|
||||
|
You are not required to accept this License in order to receive or |
||||
|
run a copy of the Program. Ancillary propagation of a covered work |
||||
|
occurring solely as a consequence of using peer-to-peer transmission |
||||
|
to receive a copy likewise does not require acceptance. However, |
||||
|
nothing other than this License grants you permission to propagate or |
||||
|
modify any covered work. These actions infringe copyright if you do |
||||
|
not accept this License. Therefore, by modifying or propagating a |
||||
|
covered work, you indicate your acceptance of this License to do so. |
||||
|
|
||||
|
10. Automatic Licensing of Downstream Recipients. |
||||
|
|
||||
|
Each time you convey a covered work, the recipient automatically |
||||
|
receives a license from the original licensors, to run, modify and |
||||
|
propagate that work, subject to this License. You are not responsible |
||||
|
for enforcing compliance by third parties with this License. |
||||
|
|
||||
|
An "entity transaction" is a transaction transferring control of an |
||||
|
organization, or substantially all assets of one, or subdividing an |
||||
|
organization, or merging organizations. If propagation of a covered |
||||
|
work results from an entity transaction, each party to that |
||||
|
transaction who receives a copy of the work also receives whatever |
||||
|
licenses to the work the party's predecessor in interest had or could |
||||
|
give under the previous paragraph, plus a right to possession of the |
||||
|
Corresponding Source of the work from the predecessor in interest, if |
||||
|
the predecessor has it or can get it with reasonable efforts. |
||||
|
|
||||
|
You may not impose any further restrictions on the exercise of the |
||||
|
rights granted or affirmed under this License. For example, you may |
||||
|
not impose a license fee, royalty, or other charge for exercise of |
||||
|
rights granted under this License, and you may not initiate litigation |
||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that |
||||
|
any patent claim is infringed by making, using, selling, offering for |
||||
|
sale, or importing the Program or any portion of it. |
||||
|
|
||||
|
11. Patents. |
||||
|
|
||||
|
A "contributor" is a copyright holder who authorizes use under this |
||||
|
License of the Program or a work on which the Program is based. The |
||||
|
work thus licensed is called the contributor's "contributor version". |
||||
|
|
||||
|
A contributor's "essential patent claims" are all patent claims |
||||
|
owned or controlled by the contributor, whether already acquired or |
||||
|
hereafter acquired, that would be infringed by some manner, permitted |
||||
|
by this License, of making, using, or selling its contributor version, |
||||
|
but do not include claims that would be infringed only as a |
||||
|
consequence of further modification of the contributor version. For |
||||
|
purposes of this definition, "control" includes the right to grant |
||||
|
patent sublicenses in a manner consistent with the requirements of |
||||
|
this License. |
||||
|
|
||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free |
||||
|
patent license under the contributor's essential patent claims, to |
||||
|
make, use, sell, offer for sale, import and otherwise run, modify and |
||||
|
propagate the contents of its contributor version. |
||||
|
|
||||
|
In the following three paragraphs, a "patent license" is any express |
||||
|
agreement or commitment, however denominated, not to enforce a patent |
||||
|
(such as an express permission to practice a patent or covenant not to |
||||
|
sue for patent infringement). To "grant" such a patent license to a |
||||
|
party means to make such an agreement or commitment not to enforce a |
||||
|
patent against the party. |
||||
|
|
||||
|
If you convey a covered work, knowingly relying on a patent license, |
||||
|
and the Corresponding Source of the work is not available for anyone |
||||
|
to copy, free of charge and under the terms of this License, through a |
||||
|
publicly available network server or other readily accessible means, |
||||
|
then you must either (1) cause the Corresponding Source to be so |
||||
|
available, or (2) arrange to deprive yourself of the benefit of the |
||||
|
patent license for this particular work, or (3) arrange, in a manner |
||||
|
consistent with the requirements of this License, to extend the patent |
||||
|
license to downstream recipients. "Knowingly relying" means you have |
||||
|
actual knowledge that, but for the patent license, your conveying the |
||||
|
covered work in a country, or your recipient's use of the covered work |
||||
|
in a country, would infringe one or more identifiable patents in that |
||||
|
country that you have reason to believe are valid. |
||||
|
|
||||
|
If, pursuant to or in connection with a single transaction or |
||||
|
arrangement, you convey, or propagate by procuring conveyance of, a |
||||
|
covered work, and grant a patent license to some of the parties |
||||
|
receiving the covered work authorizing them to use, propagate, modify |
||||
|
or convey a specific copy of the covered work, then the patent license |
||||
|
you grant is automatically extended to all recipients of the covered |
||||
|
work and works based on it. |
||||
|
|
||||
|
A patent license is "discriminatory" if it does not include within |
||||
|
the scope of its coverage, prohibits the exercise of, or is |
||||
|
conditioned on the non-exercise of one or more of the rights that are |
||||
|
specifically granted under this License. You may not convey a covered |
||||
|
work if you are a party to an arrangement with a third party that is |
||||
|
in the business of distributing software, under which you make payment |
||||
|
to the third party based on the extent of your activity of conveying |
||||
|
the work, and under which the third party grants, to any of the |
||||
|
parties who would receive the covered work from you, a discriminatory |
||||
|
patent license (a) in connection with copies of the covered work |
||||
|
conveyed by you (or copies made from those copies), or (b) primarily |
||||
|
for and in connection with specific products or compilations that |
||||
|
contain the covered work, unless you entered into that arrangement, |
||||
|
or that patent license was granted, prior to 28 March 2007. |
||||
|
|
||||
|
Nothing in this License shall be construed as excluding or limiting |
||||
|
any implied license or other defenses to infringement that may |
||||
|
otherwise be available to you under applicable patent law. |
||||
|
|
||||
|
12. No Surrender of Others' Freedom. |
||||
|
|
||||
|
If conditions are imposed on you (whether by court order, agreement or |
||||
|
otherwise) that contradict the conditions of this License, they do not |
||||
|
excuse you from the conditions of this License. If you cannot convey a |
||||
|
covered work so as to satisfy simultaneously your obligations under this |
||||
|
License and any other pertinent obligations, then as a consequence you may |
||||
|
not convey it at all. For example, if you agree to terms that obligate you |
||||
|
to collect a royalty for further conveying from those to whom you convey |
||||
|
the Program, the only way you could satisfy both those terms and this |
||||
|
License would be to refrain entirely from conveying the Program. |
||||
|
|
||||
|
13. Remote Network Interaction; Use with the GNU General Public License. |
||||
|
|
||||
|
Notwithstanding any other provision of this License, if you modify the |
||||
|
Program, your modified version must prominently offer all users |
||||
|
interacting with it remotely through a computer network (if your version |
||||
|
supports such interaction) an opportunity to receive the Corresponding |
||||
|
Source of your version by providing access to the Corresponding Source |
||||
|
from a network server at no charge, through some standard or customary |
||||
|
means of facilitating copying of software. This Corresponding Source |
||||
|
shall include the Corresponding Source for any work covered by version 3 |
||||
|
of the GNU General Public License that is incorporated pursuant to the |
||||
|
following paragraph. |
||||
|
|
||||
|
Notwithstanding any other provision of this License, you have |
||||
|
permission to link or combine any covered work with a work licensed |
||||
|
under version 3 of the GNU General Public License into a single |
||||
|
combined work, and to convey the resulting work. The terms of this |
||||
|
License will continue to apply to the part which is the covered work, |
||||
|
but the work with which it is combined will remain governed by version |
||||
|
3 of the GNU General Public License. |
||||
|
|
||||
|
14. Revised Versions of this License. |
||||
|
|
||||
|
The Free Software Foundation may publish revised and/or new versions of |
||||
|
the GNU Affero General Public License from time to time. Such new versions |
||||
|
will be similar in spirit to the present version, but may differ in detail to |
||||
|
address new problems or concerns. |
||||
|
|
||||
|
Each version is given a distinguishing version number. If the |
||||
|
Program specifies that a certain numbered version of the GNU Affero General |
||||
|
Public License "or any later version" applies to it, you have the |
||||
|
option of following the terms and conditions either of that numbered |
||||
|
version or of any later version published by the Free Software |
||||
|
Foundation. If the Program does not specify a version number of the |
||||
|
GNU Affero General Public License, you may choose any version ever published |
||||
|
by the Free Software Foundation. |
||||
|
|
||||
|
If the Program specifies that a proxy can decide which future |
||||
|
versions of the GNU Affero General Public License can be used, that proxy's |
||||
|
public statement of acceptance of a version permanently authorizes you |
||||
|
to choose that version for the Program. |
||||
|
|
||||
|
Later license versions may give you additional or different |
||||
|
permissions. However, no additional obligations are imposed on any |
||||
|
author or copyright holder as a result of your choosing to follow a |
||||
|
later version. |
||||
|
|
||||
|
15. Disclaimer of Warranty. |
||||
|
|
||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
||||
|
|
||||
|
16. Limitation of Liability. |
||||
|
|
||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
||||
|
SUCH DAMAGES. |
||||
|
|
||||
|
17. Interpretation of Sections 15 and 16. |
||||
|
|
||||
|
If the disclaimer of warranty and limitation of liability provided |
||||
|
above cannot be given local legal effect according to their terms, |
||||
|
reviewing courts shall apply local law that most closely approximates |
||||
|
an absolute waiver of all civil liability in connection with the |
||||
|
Program, unless a warranty or assumption of liability accompanies a |
||||
|
copy of the Program in return for a fee. |
||||
|
|
||||
|
END OF TERMS AND CONDITIONS |
||||
|
|
||||
|
How to Apply These Terms to Your New Programs |
||||
|
|
||||
|
If you develop a new program, and you want it to be of the greatest |
||||
|
possible use to the public, the best way to achieve this is to make it |
||||
|
free software which everyone can redistribute and change under these terms. |
||||
|
|
||||
|
To do so, attach the following notices to the program. It is safest |
||||
|
to attach them to the start of each source file to most effectively |
||||
|
state the exclusion of warranty; and each file should have at least |
||||
|
the "copyright" line and a pointer to where the full notice is found. |
||||
|
|
||||
|
<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 Affero General Public License as published |
||||
|
by the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
This program is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU Affero General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU Affero General Public License |
||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
|
||||
|
Also add information on how to contact you by electronic and paper mail. |
||||
|
|
||||
|
If your software can interact with users remotely through a computer |
||||
|
network, you should also make sure that it provides a way for users to |
||||
|
get its source. For example, if your program is a web application, its |
||||
|
interface could display a "Source" link that leads users to an archive |
||||
|
of the code. There are many ways you could offer source, and different |
||||
|
solutions will be better for different programs; see section 13 for the |
||||
|
specific requirements. |
||||
|
|
||||
|
You should also get your employer (if you work as a programmer) or school, |
||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. |
||||
|
For more information on this, and how to apply and follow the GNU AGPL, see |
||||
|
<https://www.gnu.org/licenses/>. |
||||
@ -0,0 +1,6 @@ |
|||||
|
## Get Started |
||||
|
|
||||
|
`npm install` |
||||
|
|
||||
|
Webpack dev server (port 8080): `npm run dev` |
||||
|
Build `npm run build` |
||||
@ -0,0 +1,40 @@ |
|||||
|
{ |
||||
|
"name": "budibase-builder", |
||||
|
"version": "0.0.1", |
||||
|
"license": "AGPL-3.0", |
||||
|
"scripts": { |
||||
|
"build": "rollup -c", |
||||
|
"start": "rollup -c -w" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@nx-js/compiler-util": "^2.0.0", |
||||
|
"budibase-core": "file:../core/dist", |
||||
|
"date-fns": "^1.29.0", |
||||
|
"feather-icons": "^4.21.0", |
||||
|
"flatpickr": "^4.5.7", |
||||
|
"lodash": "^4.17.11", |
||||
|
"lunr": "^2.3.5", |
||||
|
"safe-buffer": "^5.1.2", |
||||
|
"shortid": "^2.2.8", |
||||
|
"string_decoder": "^1.2.0", |
||||
|
"uikit": "^3.1.5" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"browser-sync": "^2.26.7", |
||||
|
"http-proxy-middleware": "^0.19.1", |
||||
|
"npm-run-all": "^4.1.5", |
||||
|
"rollup": "^1.12.0", |
||||
|
"rollup-plugin-alias": "^1.5.2", |
||||
|
"rollup-plugin-browsersync": "^1.0.0", |
||||
|
"rollup-plugin-commonjs": "^10.0.0", |
||||
|
"rollup-plugin-copy": "^3.0.0", |
||||
|
"rollup-plugin-livereload": "^1.0.0", |
||||
|
"rollup-plugin-node-builtins": "^2.1.2", |
||||
|
"rollup-plugin-node-globals": "^1.4.0", |
||||
|
"rollup-plugin-node-resolve": "^5.2.0", |
||||
|
"rollup-plugin-svelte": "^5.0.3", |
||||
|
"rollup-plugin-terser": "^4.0.4", |
||||
|
"rollup-plugin-url": "^2.2.2", |
||||
|
"svelte": "^3.0.0" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
import svelte from 'rollup-plugin-svelte'; |
||||
|
import resolve from 'rollup-plugin-node-resolve'; |
||||
|
import commonjs from 'rollup-plugin-commonjs'; |
||||
|
import url from 'rollup-plugin-url'; |
||||
|
import livereload from 'rollup-plugin-livereload'; |
||||
|
import { terser } from 'rollup-plugin-terser'; |
||||
|
import builtins from 'rollup-plugin-node-builtins'; |
||||
|
import nodeglobals from 'rollup-plugin-node-globals'; |
||||
|
import copy from 'rollup-plugin-copy'; |
||||
|
import browsersync from "rollup-plugin-browsersync"; |
||||
|
import proxy from "http-proxy-middleware"; |
||||
|
|
||||
|
const apiProxy = proxy('/api', { |
||||
|
target: 'http://localhost:4001/_builder', |
||||
|
logLevel: "debug", |
||||
|
changeOrigin: true, |
||||
|
cookieDomainRewrite: true, |
||||
|
onProxyReq(proxyReq) { |
||||
|
if (proxyReq.getHeader("origin")) { |
||||
|
proxyReq.setHeader("origin", target) |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const production = !process.env.ROLLUP_WATCH; |
||||
|
|
||||
|
const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant", |
||||
|
"take", "first", "intersection", "mapValues", "isNull", "has", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", |
||||
|
"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy", |
||||
|
"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign"]; |
||||
|
|
||||
|
const lodash_exports = ["toNumber", "flow", "isArray", "join", "replace", "trim", "dropRight", "takeRight", "head", "isUndefined", "isNull", "isNaN", "reduce", "isEmpty", |
||||
|
"constant", "tail", "includes", "startsWith", "findIndex", "isInteger", "isDate", "isString", "split", "clone", "keys", "isFunction", "merge", "has", "isBoolean", "isNumber", |
||||
|
"isObjectLike", "assign", "some", "each", "find", "orderBy", "union", "cloneDeep"]; |
||||
|
|
||||
|
const writeoptions = {dest: "output/output.js"}; |
||||
|
|
||||
|
export default { |
||||
|
input: 'src/main.js', |
||||
|
output: { |
||||
|
sourcemap: true, |
||||
|
format: 'iife', |
||||
|
name: 'app', |
||||
|
file: 'dist/bundle.js' |
||||
|
}, |
||||
|
plugins: [ |
||||
|
copy({ |
||||
|
targets: [ |
||||
|
{ src: 'src/index.html', dest: 'dist' }, |
||||
|
{ src: 'src/favicon.png', dest: 'dist' } |
||||
|
] |
||||
|
}), |
||||
|
|
||||
|
svelte({ |
||||
|
// enable run-time checks when not in production
|
||||
|
dev: !production, |
||||
|
include: 'src/**/*.svelte', |
||||
|
// we'll extract any component CSS out into
|
||||
|
// a separate file — better for performance
|
||||
|
css: css => { |
||||
|
css.write('dist/bundle.css'); |
||||
|
} |
||||
|
}), |
||||
|
|
||||
|
// If you have external dependencies installed from
|
||||
|
// npm, you'll most likely need these plugins. In
|
||||
|
// some cases you'll need additional configuration —
|
||||
|
// consult the documentation for details:
|
||||
|
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
|
resolve({ |
||||
|
browser: true, |
||||
|
dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/') |
||||
|
|
||||
|
}), |
||||
|
commonjs({ |
||||
|
namedExports: { |
||||
|
"lodash/fp": lodash_fp_exports, |
||||
|
"lodash":lodash_exports, |
||||
|
"shortid": ["generate"] |
||||
|
}, |
||||
|
include: /node_modules/ |
||||
|
}), |
||||
|
url({ |
||||
|
limit: 0, |
||||
|
include: ["**/*.woff2", "**/*.png"], |
||||
|
fileName: "[dirname][name][extname]", |
||||
|
emitFiles: true |
||||
|
}), |
||||
|
url({ |
||||
|
limit: 0, |
||||
|
include: ["**/*.css"], |
||||
|
fileName: "[name][extname]", |
||||
|
emitFiles: true |
||||
|
}), |
||||
|
builtins(), |
||||
|
nodeglobals(), |
||||
|
|
||||
|
// Watch the `dist` directory and refresh the
|
||||
|
// browser on changes when not in production
|
||||
|
!production && livereload('dist'), |
||||
|
!production && browsersync({ |
||||
|
server: 'dist', |
||||
|
middleware: [apiProxy] |
||||
|
}), |
||||
|
|
||||
|
// If we're building for production (npm run build
|
||||
|
// instead of npm run dev), minify
|
||||
|
production && terser() |
||||
|
], |
||||
|
watch: { |
||||
|
clearScreen: false |
||||
|
} |
||||
|
}; |
||||
@ -0,0 +1,38 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import NoPackage from "./NoPackage.svelte"; |
||||
|
import PackageRoot from "./PackageRoot.svelte"; |
||||
|
import {database, initialise} from "./builderStore"; |
||||
|
import { onMount } from 'svelte'; |
||||
|
|
||||
|
let init = initialise(); |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<main> |
||||
|
|
||||
|
{#await init} |
||||
|
|
||||
|
<h1>loading</h1> |
||||
|
|
||||
|
{:then result} |
||||
|
{#if $database.hasAppPackage} |
||||
|
<PackageRoot /> |
||||
|
{/if} |
||||
|
|
||||
|
{#if !$database.hasAppPackage} |
||||
|
<NoPackage /> |
||||
|
{/if} |
||||
|
|
||||
|
{:catch err} |
||||
|
<h1 style="color:red">{err}</h1> |
||||
|
{/await} |
||||
|
</main> |
||||
|
|
||||
|
<style> |
||||
|
main { |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
font-family: "Lato", Helvetica, Arial, sans-serif; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,55 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Button from "./common/Button.svelte" |
||||
|
|
||||
|
let errors = []; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<div class="inner"> |
||||
|
<img src="./assets/budibase-logo.png" class="logo" alt="budibase logo"/> |
||||
|
<div> |
||||
|
|
||||
|
<div> |
||||
|
<h4 style="margin-bottom: 20px">What would you like not to do?</h4> |
||||
|
<Button color="primary" |
||||
|
class="option"> |
||||
|
Create a New Package |
||||
|
</Button> |
||||
|
<Button color="primary-outline" |
||||
|
class="option"> |
||||
|
Import a Package |
||||
|
</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
position: fixed; |
||||
|
margin: 0 auto; |
||||
|
text-align: center; |
||||
|
top: 20%; |
||||
|
/*color: #333333; |
||||
|
background-color: #fdfdfd;*/ |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.inner { |
||||
|
display:inline-block; |
||||
|
margin: auto; |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
width: 300px; |
||||
|
margin-bottom: 40px; |
||||
|
} |
||||
|
|
||||
|
.root :global(.option) { |
||||
|
width:250px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,49 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Nav from "./nav/Nav.svelte"; |
||||
|
import Database from "./database/DatabaseRoot.svelte" ; |
||||
|
import UserInterface from "./userInterface/UserInterfaceRoot.svelte" ; |
||||
|
import ActionsAndTriggers from "./actionsAndTriggers/ActionsAndTriggersRoot.svelte" ; |
||||
|
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte" ; |
||||
|
import ComingSoon from "./common/ComingSoon.svelte"; |
||||
|
|
||||
|
import {database} from "./builderStore"; |
||||
|
|
||||
|
export let navWidth = "50px"; |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Nav width={navWidth} /> |
||||
|
<div class="content" |
||||
|
style="width: calc(100% - {navWidth}); left: {navWidth}"> |
||||
|
{#if $database.activeNav === "database"} |
||||
|
<Database /> |
||||
|
{:else if $database.activeNav === "actions"} |
||||
|
<ActionsAndTriggers /> |
||||
|
{:else if $database.activeNav === "access levels"} |
||||
|
<AccessLevels /> |
||||
|
{:else if $database.activeNav === "user interface"} |
||||
|
<UserInterface /> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.content { |
||||
|
position: fixed; |
||||
|
height: 100%; |
||||
|
background-color: var(--white); |
||||
|
margin:0; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,96 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import {cloneDeep, map, some, filter} from "lodash/fp"; |
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Checkbox from "../common/Checkbox.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import {validateAccessLevels} from "../common/core"; |
||||
|
import ErrorsBox from "../common/ErrorsBox.svelte"; |
||||
|
|
||||
|
export let level; |
||||
|
export let allPermissions; |
||||
|
export let onFinished; |
||||
|
export let isNew; |
||||
|
export let allLevels; |
||||
|
export let hierarchy; |
||||
|
export let actions; |
||||
|
|
||||
|
let errors = []; |
||||
|
let clonedLevel = cloneDeep(level); |
||||
|
|
||||
|
const matchPermissions = (p1, p2) => |
||||
|
p1.type === p2.type |
||||
|
&& |
||||
|
((!p2.nodeKey && !p1.nodeKey) |
||||
|
|| p2.nodeKey === p1.nodeKey); |
||||
|
|
||||
|
const hasPermission = hasPerm => |
||||
|
some(p => matchPermissions(p, hasPerm)) |
||||
|
(clonedLevel.permissions); |
||||
|
|
||||
|
$: permissionMatrix = |
||||
|
map(p => ({permission:p, hasPermission: hasPermission(p)})) |
||||
|
(allPermissions) |
||||
|
|
||||
|
const getPermissionName = perm => |
||||
|
perm.nodeKey |
||||
|
? `${perm.type} - ${perm.nodeKey}` |
||||
|
: perm.type; |
||||
|
|
||||
|
const save = () => { |
||||
|
|
||||
|
const newLevels = |
||||
|
isNew |
||||
|
? [...allLevels, clonedLevel] |
||||
|
: [...filter(l => l.name !== level.name)(allLevels), clonedLevel]; |
||||
|
|
||||
|
errors = validateAccessLevels( |
||||
|
hierarchy, |
||||
|
actions, |
||||
|
newLevels |
||||
|
); |
||||
|
|
||||
|
if(errors.length > 0) return; |
||||
|
|
||||
|
onFinished(clonedLevel); |
||||
|
} |
||||
|
|
||||
|
const permissionChanged = perm => ev => { |
||||
|
const hasPermission = ev.target.checked; |
||||
|
|
||||
|
if(hasPermission) { |
||||
|
clonedLevel.permissions.push(perm); |
||||
|
} else { |
||||
|
clonedLevel.permissions = filter(p => !matchPermissions(p, perm)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
|
||||
|
<ErrorsBox {errors} /> |
||||
|
|
||||
|
<Textbox label="Name" bind:text={clonedLevel.name} /> |
||||
|
|
||||
|
{#each permissionMatrix as permission} |
||||
|
<div> |
||||
|
<Checkbox label={getPermissionName(permission.permission)} |
||||
|
checked={permission.hasPermission} |
||||
|
on:change={permissionChanged(permission.permission)} /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
|
||||
|
|
||||
|
<ButtonGroup style="margin-top: 10px"> |
||||
|
<Button color="primary" grouped on:click={save}>Save</Button> |
||||
|
<Button color="secondary" grouped on:click={() => onFinished()}>Cancel</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,116 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import {generateFullPermissions, getNewAccessLevel} from "../common/core"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
import AccessLevelView from "./AccessLevelView.svelte"; |
||||
|
import Modal from "../common/Modal.svelte"; |
||||
|
|
||||
|
let editingLevel = null; |
||||
|
let editingLevelIsNew = false; |
||||
|
$: isEditing = (editingLevel !== null); |
||||
|
|
||||
|
let allPermissions = []; |
||||
|
database.subscribe(db => { |
||||
|
allPermissions = generateFullPermissions(db.hierarchy, db.actions); |
||||
|
}) |
||||
|
|
||||
|
let onLevelEdit = (level) => { |
||||
|
editingLevel = level; |
||||
|
editingLevelIsNew = false; |
||||
|
}; |
||||
|
|
||||
|
let onLevelCancel = () => { |
||||
|
editingAction = null; |
||||
|
}; |
||||
|
|
||||
|
let onLevelDelete = (level) => { |
||||
|
database.deleteLevel(level); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
let createNewLevel = () => { |
||||
|
editingLevelIsNew = true; |
||||
|
editingLevel = getNewAccessLevel(); |
||||
|
} |
||||
|
|
||||
|
let onEditingFinished = (level) => { |
||||
|
if(level) { |
||||
|
database.saveLevel(level, editingLevelIsNew, editingLevel); |
||||
|
} |
||||
|
editingLevel = null; |
||||
|
} |
||||
|
|
||||
|
const getPermissionsString = perms => { |
||||
|
return `${perms.length} / ${allPermissions.length}`; |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<ButtonGroup> |
||||
|
<Button grouped color="secondary" on:click={createNewLevel}>Create New Access Level</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
{#if $database.accessLevels} |
||||
|
<table class="fields-table uk-table uk-table-small"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Name</th> |
||||
|
<th>Permissions</th> |
||||
|
<th></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{#each $database.accessLevels as level} |
||||
|
<tr> |
||||
|
<td >{level.name}</td> |
||||
|
<td >{getPermissionsString(level.permissions)}</td> |
||||
|
<td class="edit-button"> |
||||
|
<span on:click={() => onLevelEdit(level)}>{@html getIcon("edit")}</span> |
||||
|
<span on:click={() => onLevelDelete(level)}>{@html getIcon("trash")}</span> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{/each} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{:else} |
||||
|
(no actions added) |
||||
|
{/if} |
||||
|
|
||||
|
|
||||
|
<Modal bind:isOpen={isEditing}> |
||||
|
{#if isEditing} |
||||
|
<AccessLevelView level={editingLevel} |
||||
|
allPermissions={allPermissions} |
||||
|
onFinished={onEditingFinished} |
||||
|
isNew={editingLevelIsNew} |
||||
|
allLevels={$database.accessLevels} |
||||
|
hierarchy={$database.hierarchy} |
||||
|
actions={$database.actions} /> |
||||
|
{/if} |
||||
|
</Modal> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
padding:10px; |
||||
|
} |
||||
|
|
||||
|
.edit-button { |
||||
|
cursor:pointer; |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
tr:hover .edit-button { |
||||
|
color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,115 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import {cloneDeep, filter, keys, |
||||
|
map, isUndefined} from "lodash/fp"; |
||||
|
import ErrorsBox from "../common/ErrorsBox.svelte"; |
||||
|
import {validateActions, chain} from "../common/core"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
|
||||
|
export let action; |
||||
|
export let onFinished = (action) => {}; |
||||
|
export let allActions; |
||||
|
export let isNew = true; |
||||
|
|
||||
|
let optKey = ""; |
||||
|
let optValue = ""; |
||||
|
|
||||
|
let clonedAction = cloneDeep(action); |
||||
|
let initialOptions = chain(action.initialOptions, [ |
||||
|
keys, |
||||
|
map(k => ({key:k, value:action.initialOptions[k]})) |
||||
|
]); |
||||
|
let errors = []; |
||||
|
|
||||
|
const addNewOption = () => { |
||||
|
|
||||
|
if(optKey && optValue && isUndefined(clonedAction.initialOptions[optKey])) { |
||||
|
clonedAction.initialOptions[optKey] = optValue; |
||||
|
initialOptions = [...initialOptions, { |
||||
|
key:optKey, value: optValue |
||||
|
}]; |
||||
|
optKey = ""; |
||||
|
optValue = ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const removeOption = (opt) => { |
||||
|
if(opt) { |
||||
|
delete clonedAction.initialOptions[opt.key] |
||||
|
initialOptions = chain(initialOptions, [ |
||||
|
filter(o => o.key !== opt.key) |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const save = () => { |
||||
|
|
||||
|
const newActionsList = [ |
||||
|
...chain(allActions ,[filter(a => a !== action)]), |
||||
|
clonedAction] |
||||
|
|
||||
|
errors = chain(newActionsList ,[ |
||||
|
validateActions, |
||||
|
map(e => e.error) |
||||
|
]); |
||||
|
|
||||
|
if(errors.length === 0) |
||||
|
onFinished(clonedAction); |
||||
|
} |
||||
|
|
||||
|
const cancel = () => { |
||||
|
onFinished(); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<ErrorsBox {errors} /> |
||||
|
|
||||
|
<Textbox label="Name" bind:text={clonedAction.name} /> |
||||
|
<Textbox label="Behaviour Source" bind:text={clonedAction.behaviourSource} /> |
||||
|
<Textbox label="Behaviour" bind:text={clonedAction.behaviourName} /> |
||||
|
|
||||
|
|
||||
|
<div class=" uk-form-stacked" style="margin-bottom: 20px"> |
||||
|
<label class="uk-form-label">Default Options</label> |
||||
|
<div class="uk-grid-small" uk-grid> |
||||
|
<input class="uk-input uk-width-1-4" placeholder="key" bind:value={optKey} > |
||||
|
<input class="uk-input uk-width-1-4" placeholder="value" bind:value={optValue} > |
||||
|
<Button color="primary-outline uk-width-1-4" on:click={addNewOption}>Add</Button> |
||||
|
</div> |
||||
|
<div style="margin-top: 10px"> |
||||
|
{#each initialOptions as option} |
||||
|
<span class="option-container">{option.key} : {option.value} <span style="font-size:10pt; cursor: pointer" on:click={() => removeOption(option)}>{@html getIcon("trash-2")}</span></span> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<ButtonGroup> |
||||
|
<Button color="primary" grouped on:click={save}>Save</Button> |
||||
|
<Button color="secondary" grouped on:click={cancel}>Cancel</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
.option-container { |
||||
|
border-style: dotted; |
||||
|
border-width: 1px; |
||||
|
border-color: var(--primary75); |
||||
|
padding: 3px; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,95 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import getIcon from "../common/icon"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import ActionView from "./ActionView.svelte"; |
||||
|
import Modal from "../common/Modal.svelte"; |
||||
|
import {chain} from "../common/core"; |
||||
|
import {keys, map, join} from "lodash/fp"; |
||||
|
|
||||
|
export let editingActionIsNew = false; |
||||
|
export let editingAction = null; |
||||
|
export let onActionEdit = (action) => {}; |
||||
|
export let onActionDelete = (action) => {}; |
||||
|
export let onActionSave = (action) => {}; |
||||
|
export let onActionCancel = () => {}; |
||||
|
|
||||
|
$: isEditing = (editingAction !== null); |
||||
|
|
||||
|
let getDefaultOptionsHtml = defaultOptions => |
||||
|
chain(defaultOptions, [ |
||||
|
keys, |
||||
|
map(k => `<span style="color:var(--slate)">${k}: </span>${JSON.stringify(defaultOptions[k])}`), |
||||
|
join("<br>") |
||||
|
]); |
||||
|
|
||||
|
|
||||
|
let actionEditingFinished = (action) => { |
||||
|
|
||||
|
if(action) { |
||||
|
onActionSave(action) |
||||
|
} else { |
||||
|
onActionCancel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<h3>Actions</h3> |
||||
|
|
||||
|
{#if $database.actions} |
||||
|
<table class="fields-table uk-table uk-table-small"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Description</th> |
||||
|
<th>Behaviour Source</th> |
||||
|
<th>Behaviour Name</th> |
||||
|
<th>Default Options</th> |
||||
|
<th></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{#each $database.actions as action} |
||||
|
<tr> |
||||
|
<td >{action.name}</td> |
||||
|
<td >{action.behaviourSource}</td> |
||||
|
<td >{action.behaviourName}</td> |
||||
|
<td >{@html getDefaultOptionsHtml(action.initialOptions)}</td> |
||||
|
<td class="edit-button"> |
||||
|
<span on:click={() => onActionEdit(action)}>{@html getIcon("edit")}</span> |
||||
|
<span on:click={() => onActionDelete(action)}>{@html getIcon("trash")}</span> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{/each} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{:else} |
||||
|
(no actions added) |
||||
|
{/if} |
||||
|
|
||||
|
|
||||
|
<Modal bind:isOpen={isEditing}> |
||||
|
{#if isEditing} |
||||
|
<ActionView action={editingAction} |
||||
|
allActions={$database.actions} |
||||
|
onFinished={actionEditingFinished} |
||||
|
isNew={editingActionIsNew}/> |
||||
|
{/if} |
||||
|
</Modal> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.edit-button { |
||||
|
cursor:pointer; |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
tr:hover .edit-button { |
||||
|
color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,115 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import getIcon from "../common/icon"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import Actions from "./Actions.svelte"; |
||||
|
import Triggers from "./Triggers.svelte"; |
||||
|
import {getNewAction, getNewTrigger} from "../common/core"; |
||||
|
|
||||
|
let editingAction = null; |
||||
|
let editingActionIsNew = true; |
||||
|
let editingTrigger = null; |
||||
|
let editingTriggerIsNew = true; |
||||
|
|
||||
|
let getDefaultOptionsHtml = defaultOptions => |
||||
|
chain(defaultOptions, [ |
||||
|
keys, |
||||
|
map(k => `<span style="color:var(--slate)">${k}: </span>${JSON.parse(typeOptions[k])}`), |
||||
|
join("<br>") |
||||
|
]); |
||||
|
|
||||
|
let onActionEdit = (action) => { |
||||
|
editingAction = action; |
||||
|
editingActionIsNew = false; |
||||
|
} |
||||
|
|
||||
|
let newAction = () => { |
||||
|
editingAction = getNewAction(); |
||||
|
editingActionIsNew = true; |
||||
|
} |
||||
|
|
||||
|
let onActionDelete = (action) => { |
||||
|
database.deleteAction(action); |
||||
|
} |
||||
|
|
||||
|
let deleteTrigger = () => {} |
||||
|
|
||||
|
let editTrigger = (trigger) => { |
||||
|
editingTrigger = trigger; |
||||
|
editingTriggerIsNew = false; |
||||
|
} |
||||
|
|
||||
|
let newTrigger = () => { |
||||
|
editingTrigger = getNewTrigger(); |
||||
|
editingTriggerIsNew = true; |
||||
|
} |
||||
|
|
||||
|
let onActionSave = action => { |
||||
|
database.saveAction( |
||||
|
action, |
||||
|
editingActionIsNew, |
||||
|
editingAction); |
||||
|
|
||||
|
editingAction = null; |
||||
|
} |
||||
|
|
||||
|
let onActionCancel = () => { |
||||
|
editingAction = null; |
||||
|
} |
||||
|
|
||||
|
let onTriggerSave = trigger => { |
||||
|
database.saveTrigger( |
||||
|
trigger, |
||||
|
editingTriggerIsNew, |
||||
|
editingTrigger); |
||||
|
|
||||
|
editingTrigger = null; |
||||
|
} |
||||
|
|
||||
|
let onTriggerCancel = () => { |
||||
|
editingTrigger = null; |
||||
|
} |
||||
|
|
||||
|
let onTriggerEdit = (trigger) => { |
||||
|
editingTrigger = trigger; |
||||
|
editingTriggerIsNew = false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
let onTriggerDelete = (trigger) => { |
||||
|
database.deleteTrigger(trigger); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<ButtonGroup> |
||||
|
<Button color="secondary" |
||||
|
grouped |
||||
|
on:click={newAction}>Create New Action</Button> |
||||
|
<Button color="secondary" |
||||
|
grouped |
||||
|
on:click={newTrigger}>Create New Trigger</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
<Actions {editingActionIsNew} {editingAction} |
||||
|
{onActionEdit} {onActionDelete} {onActionSave} |
||||
|
{onActionCancel} /> |
||||
|
|
||||
|
<Triggers {editingTriggerIsNew} {editingTrigger} |
||||
|
{onTriggerEdit} {onTriggerDelete} {onTriggerSave} |
||||
|
{onTriggerCancel} /> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,64 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import Dropdown from "../common/Dropdown.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import CodeArea from "../common/CodeArea.svelte"; |
||||
|
import {cloneDeep, filter, keys, some, |
||||
|
map, isUndefined} from "lodash/fp"; |
||||
|
import ErrorsBox from "../common/ErrorsBox.svelte"; |
||||
|
import {validateTriggers, chain, events} from "../common/core"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
|
||||
|
export let trigger; |
||||
|
export let onFinished = (action) => {}; |
||||
|
export let allTriggers; |
||||
|
export let allActions; |
||||
|
export let isNew = true; |
||||
|
|
||||
|
let clonedTrigger = cloneDeep(trigger); |
||||
|
let errors = []; |
||||
|
$: actionNames = map(a => a.name)(allActions); |
||||
|
|
||||
|
let cancel = () => onFinished(); |
||||
|
let save = () => { |
||||
|
const newTriggersList = [ |
||||
|
...chain(allTriggers ,[filter(t => t !== trigger)]), |
||||
|
clonedTrigger] |
||||
|
|
||||
|
errors = validateTriggers(newTriggersList, allActions); |
||||
|
|
||||
|
const test = map(t =>(!t.actionName || some(a => a.name === t.actionName)(allActions)))(newTriggersList) |
||||
|
|
||||
|
if(errors.length === 0) |
||||
|
onFinished(clonedTrigger); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
|
||||
|
<ErrorsBox {errors} style="margin-bottom:20px"/> |
||||
|
|
||||
|
<Dropdown label="Event" |
||||
|
options={["",...events]} |
||||
|
bind:selected={clonedTrigger.eventName} /> |
||||
|
<Dropdown label="Action" |
||||
|
options={["",...actionNames]} |
||||
|
bind:selected={clonedTrigger.actionName} /> |
||||
|
<CodeArea label="Condition (javascript)" |
||||
|
bind:text={clonedTrigger.condition} /> |
||||
|
<CodeArea label="Action Options Creator (javascript)" |
||||
|
bind:text={clonedTrigger.optionsCreator} /> |
||||
|
|
||||
|
<ButtonGroup> |
||||
|
<Button grouped on:click={save}>Save</Button> |
||||
|
<Button grouped on:click={cancel}>Cancel</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,84 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import {database} from "../builderStore"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import Modal from "../common/Modal.svelte"; |
||||
|
import TriggerView from "./TriggerView.svelte"; |
||||
|
|
||||
|
|
||||
|
export let editingTrigger = null; |
||||
|
export let editingTriggerIsNew = true; |
||||
|
export let onTriggerEdit = (trigger) => {}; |
||||
|
export let onTriggerDelete = (trigger) => {}; |
||||
|
export let onTriggerSave = (trigger) => {}; |
||||
|
export let onTriggerCancel = () => {}; |
||||
|
|
||||
|
$: isEditing = (editingTrigger !== null); |
||||
|
|
||||
|
let triggerEditingFinished = (trigger) => { |
||||
|
|
||||
|
if(trigger) { |
||||
|
onTriggerSave(trigger) |
||||
|
} else { |
||||
|
onTriggerCancel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<h3>Triggers</h3> |
||||
|
|
||||
|
{#if $database.triggers} |
||||
|
<table class="fields-table uk-table uk-table-small"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Event</th> |
||||
|
<th>Action</th> |
||||
|
<th>Condition</th> |
||||
|
<th>Create Options</th> |
||||
|
<th></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{#each $database.triggers as trigger} |
||||
|
<tr> |
||||
|
<td >{trigger.eventName}</td> |
||||
|
<td >{trigger.actionName}</td> |
||||
|
<td >{trigger.condition}</td> |
||||
|
<td >{trigger.optionsCreator}</td> |
||||
|
<td class="edit-button"> |
||||
|
<span on:click={() => onTriggerEdit(trigger)}>{@html getIcon("edit")}</span> |
||||
|
<span on:click={() => onTriggerDelete(trigger)}>{@html getIcon("trash")}</span> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{/each} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{:else} |
||||
|
(no triggers added) |
||||
|
{/if} |
||||
|
|
||||
|
|
||||
|
<Modal bind:isOpen={isEditing}> |
||||
|
{#if isEditing} |
||||
|
<TriggerView trigger={editingTrigger} |
||||
|
allActions={$database.actions} |
||||
|
allTriggers={$database.triggers} |
||||
|
onFinished={triggerEditingFinished} |
||||
|
isNew={editingTriggerIsNew}/> |
||||
|
{/if} |
||||
|
</Modal> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.edit-button { |
||||
|
cursor:pointer; |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
tr:hover .edit-button { |
||||
|
color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,663 @@ |
|||||
|
import {createNewHeirarchy} from "../common/core"; |
||||
|
|
||||
|
export const createPackage = (packageInfo, database) => { |
||||
|
packageInfo.createNewPackage(""); |
||||
|
const root = createNewHeirarchy(); |
||||
|
database.importAppDefinition({ |
||||
|
hierarchy:root, |
||||
|
actions:[], |
||||
|
triggers:[], |
||||
|
accessLevels: [], |
||||
|
accessLevelsVersion: 0 |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const testroot = { |
||||
|
"name": "root", |
||||
|
"type": "root", |
||||
|
"children": [ |
||||
|
{ |
||||
|
"name": "settings", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "appName", |
||||
|
"type": "string", |
||||
|
"typeOptions": { |
||||
|
"maxLength": null, |
||||
|
"values": null, |
||||
|
"allowDeclaredValuesOnly": false |
||||
|
}, |
||||
|
"label": "appName", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 1, |
||||
|
"indexes": [], |
||||
|
"allidsShardFactor": 64, |
||||
|
"collectionName": "", |
||||
|
"isSingle": true |
||||
|
}, |
||||
|
{ |
||||
|
"name": "customer", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "surname", |
||||
|
"type": "string", |
||||
|
"typeOptions": { |
||||
|
"maxLength": null, |
||||
|
"values": null, |
||||
|
"allowDeclaredValuesOnly": false |
||||
|
}, |
||||
|
"label": "surname", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "isalive", |
||||
|
"type": "bool", |
||||
|
"typeOptions": { |
||||
|
"allowNulls": true |
||||
|
}, |
||||
|
"label": "isalive", |
||||
|
"getInitialValue": "true", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "createddate", |
||||
|
"type": "datetime", |
||||
|
"typeOptions": { |
||||
|
"maxValue": "+275760-09-13T00:00:00.000Z", |
||||
|
"minValue": "-271821-04-20T00:00:00.000Z" |
||||
|
}, |
||||
|
"label": "createddate", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "age", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 0 |
||||
|
}, |
||||
|
"label": "age", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "profilepic", |
||||
|
"type": "file", |
||||
|
"typeOptions": {}, |
||||
|
"label": "profilepic", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partner", |
||||
|
"type": "reference", |
||||
|
"typeOptions": { |
||||
|
"indexNodeKey": "/partnersReference", |
||||
|
"displayValue": "name", |
||||
|
"reverseIndexNodeKeys": [ |
||||
|
"/partners/4-{id}/partnerCustomers" |
||||
|
] |
||||
|
}, |
||||
|
"label": "partner", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "referredBy", |
||||
|
"type": "reference", |
||||
|
"typeOptions": { |
||||
|
"indexNodeKey": "/customer_index", |
||||
|
"displayValue": "surname", |
||||
|
"reverseIndexNodeKeys": [ |
||||
|
"/customers/2-{id}/referredToCustomers" |
||||
|
] |
||||
|
}, |
||||
|
"label": "referredBy", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [ |
||||
|
{ |
||||
|
"name": "invoice", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "totalIncVat", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 2 |
||||
|
}, |
||||
|
"label": "totalIncVat", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "createdDate", |
||||
|
"type": "datetime", |
||||
|
"typeOptions": { |
||||
|
"maxValue": "+275760-09-13T00:00:00.000Z", |
||||
|
"minValue": "-271821-04-20T00:00:00.000Z" |
||||
|
}, |
||||
|
"label": "createdDate", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "paidAmount", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 0 |
||||
|
}, |
||||
|
"label": "paidAmount", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "invoiceType", |
||||
|
"type": "string", |
||||
|
"typeOptions": { |
||||
|
"maxLength": null, |
||||
|
"values": null, |
||||
|
"allowDeclaredValuesOnly": false |
||||
|
}, |
||||
|
"label": "invoiceType", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "isWrittenOff", |
||||
|
"type": "bool", |
||||
|
"typeOptions": { |
||||
|
"allowNulls": true |
||||
|
}, |
||||
|
"label": "isWrittenOff", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "customer", |
||||
|
"type": "reference", |
||||
|
"typeOptions": { |
||||
|
"indexNodeKey": "/customersReference", |
||||
|
"reverseIndexNodeKeys": [ |
||||
|
"/customers/2-{id}/invoice_index" |
||||
|
], |
||||
|
"displayValue": "name" |
||||
|
}, |
||||
|
"label": "customer", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [ |
||||
|
{ |
||||
|
"name": "charge", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "amount", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 0 |
||||
|
}, |
||||
|
"label": "amount", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partnerInvoice", |
||||
|
"type": "reference", |
||||
|
"typeOptions": { |
||||
|
"reverseIndexNodeKeys": [ |
||||
|
"/partners/4-{id}/invoices/6-{id}/partnerCharges" |
||||
|
], |
||||
|
"displayValue": "createdDate", |
||||
|
"indexNodeKey": "/partners/4-{id}/partnerInvoices_index" |
||||
|
}, |
||||
|
"label": "partnerInvoice", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 10, |
||||
|
"indexes": [], |
||||
|
"allidsShardFactor": 1, |
||||
|
"collectionName": "charges", |
||||
|
"isSingle": false |
||||
|
} |
||||
|
], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 8, |
||||
|
"indexes": [ |
||||
|
{ |
||||
|
"name": "charge_index", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
10 |
||||
|
], |
||||
|
"nodeId": 11 |
||||
|
} |
||||
|
], |
||||
|
"allidsShardFactor": 1, |
||||
|
"collectionName": "invoices", |
||||
|
"isSingle": false |
||||
|
} |
||||
|
], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 2, |
||||
|
"indexes": [ |
||||
|
{ |
||||
|
"name": "invoice_index", |
||||
|
"type": "index", |
||||
|
"map": "return {createdDate: record.createdDate, totalIncVat: record.totalIncVat};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
8 |
||||
|
], |
||||
|
"nodeId": 9 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "referredToCustomers", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "reference", |
||||
|
"getShardName": "return !record.surname ? 'null' : record.surname.substring(0,1);", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 14 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "invoicesByOutstanding", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "return (record.totalIncVat > record.paidAmount ? 'outstanding' : 'paid');", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [ |
||||
|
{ |
||||
|
"name": "all_invoices_by_type", |
||||
|
"type": "aggregateGroup", |
||||
|
"groupBy": "return record.invoiceType", |
||||
|
"aggregates": [ |
||||
|
{ |
||||
|
"name": "totalIncVat", |
||||
|
"aggregatedValue": "return record.totalIncVat" |
||||
|
} |
||||
|
], |
||||
|
"condition": "", |
||||
|
"nodeId": 26 |
||||
|
} |
||||
|
], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
6, |
||||
|
8 |
||||
|
], |
||||
|
"nodeId": 25 |
||||
|
} |
||||
|
], |
||||
|
"allidsShardFactor": 64, |
||||
|
"collectionName": "customers", |
||||
|
"isSingle": false |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partner", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "businessName", |
||||
|
"type": "string", |
||||
|
"typeOptions": { |
||||
|
"maxLength": null, |
||||
|
"values": null, |
||||
|
"allowDeclaredValuesOnly": false |
||||
|
}, |
||||
|
"label": "businessName", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [ |
||||
|
{ |
||||
|
"name": "invoice", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "totalIncVat", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 2 |
||||
|
}, |
||||
|
"label": "totalIncVat", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "createdDate", |
||||
|
"type": "datetime", |
||||
|
"typeOptions": { |
||||
|
"maxValue": "+275760-09-13T00:00:00.000Z", |
||||
|
"minValue": "-271821-04-20T00:00:00.000Z" |
||||
|
}, |
||||
|
"label": "createdDate", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "paidAmount", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 0 |
||||
|
}, |
||||
|
"label": "paidAmount", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 6, |
||||
|
"indexes": [ |
||||
|
{ |
||||
|
"name": "partnerCharges", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "reference", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
{ |
||||
|
"name": "charge", |
||||
|
"type": "record", |
||||
|
"fields": [ |
||||
|
{ |
||||
|
"name": "amount", |
||||
|
"type": "number", |
||||
|
"typeOptions": { |
||||
|
"maxValue": 9007199254740991, |
||||
|
"minValue": -9007199254740991, |
||||
|
"decimalPlaces": 0 |
||||
|
}, |
||||
|
"label": "amount", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partnerInvoice", |
||||
|
"type": "reference", |
||||
|
"typeOptions": { |
||||
|
"reverseIndexNodeKeys": [ |
||||
|
"/partners/4-{id}/invoices/6-{id}/partnerCharges" |
||||
|
], |
||||
|
"displayValue": "createdDate", |
||||
|
"indexNodeKey": "/partners/4-{id}/partnerInvoices_index" |
||||
|
}, |
||||
|
"label": "partnerInvoice", |
||||
|
"getInitialValue": "default", |
||||
|
"getUndefinedValue": "default" |
||||
|
} |
||||
|
], |
||||
|
"children": [], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 10, |
||||
|
"indexes": [], |
||||
|
"allidsShardFactor": 1, |
||||
|
"collectionName": "charges", |
||||
|
"isSingle": false |
||||
|
} |
||||
|
], |
||||
|
"nodeId": 15 |
||||
|
} |
||||
|
], |
||||
|
"allidsShardFactor": 1, |
||||
|
"collectionName": "invoices", |
||||
|
"isSingle": false |
||||
|
} |
||||
|
], |
||||
|
"validationRules": [], |
||||
|
"nodeId": 4, |
||||
|
"indexes": [ |
||||
|
{ |
||||
|
"name": "partnerInvoices_index", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
6 |
||||
|
], |
||||
|
"nodeId": 7 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partnerCustomers", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "record.isalive === true", |
||||
|
"indexType": "reference", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 13 |
||||
|
} |
||||
|
], |
||||
|
"allidsShardFactor": 64, |
||||
|
"collectionName": "partners", |
||||
|
"isSingle": false |
||||
|
} |
||||
|
], |
||||
|
"pathMaps": [], |
||||
|
"indexes": [ |
||||
|
{ |
||||
|
"name": "customer_index", |
||||
|
"type": "index", |
||||
|
"map": "return record;", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [ |
||||
|
{ |
||||
|
"name": "Customers Summary", |
||||
|
"type": "aggregateGroup", |
||||
|
"groupBy": "", |
||||
|
"aggregates": [ |
||||
|
{ |
||||
|
"name": "all customers - age breakdown", |
||||
|
"aggregatedValue": "return record.age" |
||||
|
} |
||||
|
], |
||||
|
"condition": "", |
||||
|
"nodeId": 24 |
||||
|
} |
||||
|
], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 3 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partner_index", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
4 |
||||
|
], |
||||
|
"nodeId": 5 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "partnersReference", |
||||
|
"type": "index", |
||||
|
"map": "return {name:record.businessName};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
4 |
||||
|
], |
||||
|
"nodeId": 12 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "customersReference", |
||||
|
"type": "index", |
||||
|
"map": "return {name:record.surname}", |
||||
|
"filter": "record.isalive === true", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 16 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "deceased", |
||||
|
"type": "index", |
||||
|
"map": "return {surname: record.surname, age:record.age};", |
||||
|
"filter": "record.isalive === false", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 17 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "customer_invoices", |
||||
|
"type": "index", |
||||
|
"map": "return record;", |
||||
|
"filter": "record.type === 'invoice'", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
8 |
||||
|
], |
||||
|
"nodeId": 18 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "Outstanding Invoices", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "record.type === 'invoice' && record.paidAmount < record.totalIncVat", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [ |
||||
|
{ |
||||
|
"name": "all_invoices", |
||||
|
"type": "aggregateGroup", |
||||
|
"groupBy": "", |
||||
|
"aggregates": [], |
||||
|
"condition": "", |
||||
|
"nodeId": 20 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "all_invoices_by_type", |
||||
|
"type": "aggregateGroup", |
||||
|
"groupBy": "return record.invoiceType", |
||||
|
"aggregates": [ |
||||
|
{ |
||||
|
"name": "totalIncVat", |
||||
|
"aggregatedValue": "return record.totalIncVat" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "paidAmount", |
||||
|
"aggregatedValue": "return record.paidAmount" |
||||
|
} |
||||
|
], |
||||
|
"condition": "", |
||||
|
"nodeId": 21 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "written_off", |
||||
|
"type": "aggregateGroup", |
||||
|
"groupBy": "return record.invoiceType", |
||||
|
"aggregates": [ |
||||
|
{ |
||||
|
"name": "totalIncVat", |
||||
|
"aggregatedValue": "return record.totalIncVat" |
||||
|
} |
||||
|
], |
||||
|
"condition": "record.isWrittenOff === true", |
||||
|
"nodeId": 22 |
||||
|
} |
||||
|
], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
8, |
||||
|
6 |
||||
|
], |
||||
|
"nodeId": 19 |
||||
|
}, |
||||
|
{ |
||||
|
"name": "customersBySurname", |
||||
|
"type": "index", |
||||
|
"map": "return {...record};", |
||||
|
"filter": "", |
||||
|
"indexType": "ancestor", |
||||
|
"getShardName": "return !record.surname ? 'null' : record.surname.substring(0,1);", |
||||
|
"getSortKey": "record.id", |
||||
|
"aggregateGroups": [], |
||||
|
"allowedRecordNodeIds": [ |
||||
|
2 |
||||
|
], |
||||
|
"nodeId": 23 |
||||
|
} |
||||
|
], |
||||
|
"nodeId": 0 |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
import {createPackage} from "./createPackage"; |
||||
|
import getStore from "./store"; |
||||
|
import { last } from "lodash/fp"; |
||||
|
|
||||
|
const appName = last(window.location.hash.substr(1).split("/")); |
||||
|
|
||||
|
export const database = getStore(appName); |
||||
|
|
||||
|
export const createNewPackage = () => |
||||
|
createPackage(packageInfo, database); |
||||
|
|
||||
|
export const initialise = async () => { |
||||
|
try { |
||||
|
await database.initialise(); |
||||
|
} catch(err) { |
||||
|
console.log(err); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,330 @@ |
|||||
|
import {hierarchy as hierarchyFunctions, |
||||
|
common, getTemplateApi } from "budibase-core"; |
||||
|
import {filter, cloneDeep, sortBy, map, |
||||
|
find, isEmpty, groupBy, reduce} from "lodash/fp"; |
||||
|
import {chain, getNode, validate, |
||||
|
constructHierarchy, templateApi} from "../common/core"; |
||||
|
import {writable} from "svelte/store"; |
||||
|
|
||||
|
export const getStore = (appname) => { |
||||
|
|
||||
|
const initial = { |
||||
|
hierarchy: {}, |
||||
|
actions: [], |
||||
|
triggers: [], |
||||
|
currentNodeIsNew: false, |
||||
|
errors: [], |
||||
|
activeNav: "database", |
||||
|
hasAppPackage: (!!appname && appname.length > 0), |
||||
|
accessLevels: [], |
||||
|
currentNode: null}; |
||||
|
|
||||
|
const store = writable(initial); |
||||
|
|
||||
|
store.appname = appname; |
||||
|
store.initialise = initialise(store, initial, appname); |
||||
|
store.newChildRecord = newRecord(store, false); |
||||
|
store.newRootRecord = newRecord(store, true); |
||||
|
store.selectExistingNode = selectExistingNode(store); |
||||
|
store.newChildIndex = newIndex(store, false); |
||||
|
store.newRootIndex = newIndex(store, true); |
||||
|
store.saveCurrentNode = saveCurrentNode(store); |
||||
|
store.importAppDefinition = importAppDefinition(store); |
||||
|
store.deleteCurrentNode = deleteCurrentNode(store); |
||||
|
store.saveField = saveField(store); |
||||
|
store.deleteField = deleteField(store); |
||||
|
store.saveAction = saveAction(store); |
||||
|
store.deleteAction = deleteAction(store); |
||||
|
store.saveTrigger = saveTrigger(store); |
||||
|
store.deleteTrigger = deleteTrigger(store); |
||||
|
store.saveLevel = saveLevel(store); |
||||
|
store.deleteLevel = deleteLevel(store); |
||||
|
store.setActiveNav = setActiveNav(store); |
||||
|
return store; |
||||
|
} |
||||
|
|
||||
|
export default getStore; |
||||
|
|
||||
|
const initialise = (store, initial) => async () => { |
||||
|
|
||||
|
const pkg = await fetch(`/api/${store.appname}/appPackage`) |
||||
|
.then(r => r.json()); |
||||
|
|
||||
|
initial.hierarchy = pkg.appDefinition.hierarchy; |
||||
|
initial.accessLevels = pkg.accessLevels; |
||||
|
initial.actions = reduce((arr, action) => { |
||||
|
arr.push(action); |
||||
|
return arr; |
||||
|
})(pkg.appDefinition.actions, []); |
||||
|
initial.triggers = pkg.appDefinition.triggers; |
||||
|
|
||||
|
if(!!initial.hierarchy && !isEmpty(initial.hierarchy)) { |
||||
|
initial.hierarchy = constructHierarchy(initial.hierarchy); |
||||
|
const shadowHierarchy = createShadowHierarchy(initial.hierarchy); |
||||
|
if(initial.currentNode !== null) |
||||
|
initial.currentNode = getNode( |
||||
|
shadowHierarchy, initial.currentNode.nodeId |
||||
|
); |
||||
|
} |
||||
|
store.set(initial); |
||||
|
return initial; |
||||
|
} |
||||
|
|
||||
|
const newRecord = (store, useRoot) => () => { |
||||
|
store.update(s => { |
||||
|
s.currentNodeIsNew = true; |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
parent = useRoot ? shadowHierarchy |
||||
|
: getNode( |
||||
|
shadowHierarchy, |
||||
|
s.currentNode.nodeId); |
||||
|
s.errors = []; |
||||
|
s.currentNode = templateApi(shadowHierarchy) |
||||
|
.getNewRecordTemplate(parent, "", true); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const selectExistingNode = (store) => (nodeId) => { |
||||
|
store.update(s => { |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
s.currentNode = getNode( |
||||
|
shadowHierarchy, nodeId |
||||
|
); |
||||
|
s.currentNodeIsNew = false; |
||||
|
s.errors = []; |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const newIndex = (store, useRoot) => () => { |
||||
|
store.update(s => { |
||||
|
s.currentNodeIsNew = true; |
||||
|
s.errors = []; |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
parent = useRoot ? shadowHierarchy |
||||
|
: getNode( |
||||
|
shadowHierarchy, |
||||
|
s.currentNode.nodeId); |
||||
|
|
||||
|
s.currentNode = templateApi(shadowHierarchy) |
||||
|
.getNewIndexTemplate(parent); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveCurrentNode = (store) => () => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const errors = validate.node(s.currentNode); |
||||
|
s.errors = errors; |
||||
|
if(errors.length > 0) { |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
const parentNode = getNode( |
||||
|
s.hierarchy, s.currentNode.parent().nodeId); |
||||
|
|
||||
|
const existingNode = getNode( |
||||
|
s.hierarchy, s.currentNode.nodeId); |
||||
|
|
||||
|
let index = parentNode.children.length; |
||||
|
if(!!existingNode) { |
||||
|
// remove existing
|
||||
|
index = existingNode.parent().children.indexOf(existingNode); |
||||
|
existingNode.parent().children = chain(existingNode.parent().children, [ |
||||
|
filter(c => c.nodeId !== existingNode.nodeId) |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
// should add node into existing hierarchy
|
||||
|
const cloned = cloneDeep(s.currentNode); |
||||
|
templateApi(s.hierarchy).constructNode( |
||||
|
parentNode, |
||||
|
cloned |
||||
|
); |
||||
|
|
||||
|
const newIndexOfchild = child => { |
||||
|
if(child === cloned) return index; |
||||
|
const currentIndex = parentNode.children.indexOf(child); |
||||
|
return currentIndex >= index ? currentIndex + 1 : currentIndex; |
||||
|
} |
||||
|
|
||||
|
parentNode.children = chain(parentNode.children, [ |
||||
|
sortBy(newIndexOfchild) |
||||
|
]); |
||||
|
|
||||
|
s.currentNodeIsNew = false; |
||||
|
|
||||
|
savePackage(store, s); |
||||
|
|
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const importAppDefinition = store => appDefinition => { |
||||
|
store.update(s => { |
||||
|
s.hierarchy = appDefinition.hierarchy; |
||||
|
s.currentNode = appDefinition.hierarchy.children.length > 0 |
||||
|
? appDefinition.hierarchy.children[0] |
||||
|
: null; |
||||
|
s.actions = appDefinition.actions; |
||||
|
s.triggers = appDefinition.triggers; |
||||
|
s.currentNodeIsNew = false; |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteCurrentNode = store => () => { |
||||
|
store.update(s => { |
||||
|
const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId); |
||||
|
s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent()) |
||||
|
? find(n => n != s.currentNode) |
||||
|
(s.hierarchy.children) |
||||
|
: nodeToDelete.parent(); |
||||
|
if(hierarchyFunctions.isRecord(nodeToDelete)) { |
||||
|
nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId) |
||||
|
(nodeToDelete.parent().children); |
||||
|
} else { |
||||
|
nodeToDelete.parent().indexes = filter(c => c.nodeId !== nodeToDelete.nodeId) |
||||
|
(nodeToDelete.parent().indexes); |
||||
|
} |
||||
|
s.errors = []; |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveField = databaseStore => (field) => { |
||||
|
databaseStore.update(db => { |
||||
|
db.currentNode.fields = filter(f => f.name !== field.name) |
||||
|
(db.currentNode.fields); |
||||
|
|
||||
|
templateApi(db.hierarchy).addField(db.currentNode, field); |
||||
|
return db; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const deleteField = databaseStore => field => { |
||||
|
databaseStore.update(db => { |
||||
|
db.currentNode.fields = filter(f => f.name !== field.name) |
||||
|
(db.currentNode.fields); |
||||
|
|
||||
|
return db; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const saveAction = store => (newAction, isNew, oldAction=null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const existingAction = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldAction.name)(s.actions); |
||||
|
|
||||
|
if(existingAction) { |
||||
|
s.actions = chain(s.actions, [ |
||||
|
map(a => a === existingAction ? newAction : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.actions.push(newAction); |
||||
|
} |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteAction = store => action => { |
||||
|
store.update(s => { |
||||
|
s.actions = filter(a => a.name !== action.name)(s.actions); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveTrigger = store => (newTrigger, isNew, oldTrigger=null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const existingTrigger = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldTrigger.name)(s.triggers); |
||||
|
|
||||
|
if(existingTrigger) { |
||||
|
s.triggers = chain(s.triggers, [ |
||||
|
map(a => a === existingTrigger ? newTrigger : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.triggers.push(newTrigger); |
||||
|
} |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteTrigger = store => trigger => { |
||||
|
store.update(s => { |
||||
|
s.triggers = filter(t => t.name !== trigger.name)(s.triggers); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveLevel = store => (newLevel, isNew, oldLevel=null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const existingLevel = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldLevel.name)(s.accessLevels); |
||||
|
|
||||
|
if(existingLevel) { |
||||
|
s.accessLevels = chain(s.accessLevels, [ |
||||
|
map(a => a === existingLevel ? newLevel : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.accessLevels.push(newLevel); |
||||
|
} |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteLevel = store => level => { |
||||
|
store.update(s => { |
||||
|
s.accessLevels = filter(t => t.name !== level.name)(s.accessLevels); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const setActiveNav = databaseStore => navName => { |
||||
|
databaseStore.update(db => { |
||||
|
db.activeNav = navName; |
||||
|
return db; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const createShadowHierarchy = hierarchy => |
||||
|
constructHierarchy(JSON.parse(JSON.stringify(hierarchy))); |
||||
|
|
||||
|
const savePackage = (store, db) => { |
||||
|
|
||||
|
const appDefinition = { |
||||
|
hierarchy:db.hierarchy, |
||||
|
triggers:db.triggers, |
||||
|
actions: groupBy("name")(db.actions) |
||||
|
}; |
||||
|
|
||||
|
const data = { |
||||
|
appDefinition, |
||||
|
accessLevels:db.accessLevels |
||||
|
} |
||||
|
|
||||
|
fetch(`/api/${store.appname}/appPackage`, { |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
<script> |
||||
|
|
||||
|
export let color = "primary"; |
||||
|
export let className = ""; |
||||
|
export let style = ""; |
||||
|
export let groupPosition = ""; |
||||
|
export let grouped = false; |
||||
|
|
||||
|
$: borderClass = grouped |
||||
|
? "" |
||||
|
: "border-normal"; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<button class="{color} {className} {borderClass} {grouped ? "grouped" : ""}" |
||||
|
style="{style}" |
||||
|
on:click> |
||||
|
<slot/> |
||||
|
</button> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.border-normal { border-radius: var(--borderradiusall); } |
||||
|
.border-left { border-radius: var(--borderradius) 0 0 var(--borderradius); } |
||||
|
.border-right { border-radius: 0 var(--borderradius) var(--borderradius) 0; } |
||||
|
.border-middle { border-radius: 0; } |
||||
|
|
||||
|
button { |
||||
|
border-style: solid; |
||||
|
padding: 7px 15px; |
||||
|
cursor:pointer; |
||||
|
} |
||||
|
|
||||
|
/* ---- PRIMARY ----*/ |
||||
|
.primary { |
||||
|
background-color: var(--primary100); |
||||
|
border-color: var(--primary100); |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.primary:hover { |
||||
|
background-color: var(--primary75); |
||||
|
border-color: var(--primary75); |
||||
|
} |
||||
|
|
||||
|
.primary:active { |
||||
|
background-color: var(--primarydark); |
||||
|
border-color: var(--primarydark); |
||||
|
} |
||||
|
|
||||
|
.primary-outline { |
||||
|
background-color: var(--white); |
||||
|
border-color: var(--primary100); |
||||
|
color: var(--primary100); |
||||
|
} |
||||
|
|
||||
|
.primary-outline:hover { |
||||
|
background-color: var(--primary10); |
||||
|
} |
||||
|
|
||||
|
.primary-outline:pressed { |
||||
|
background-color: var(--primary25); |
||||
|
} |
||||
|
|
||||
|
/* ---- secondary ----*/ |
||||
|
|
||||
|
.secondary { |
||||
|
background-color: var(--secondary100); |
||||
|
border-color: var(--secondary100); |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.secondary:hover { |
||||
|
background-color: var(--secondary75); |
||||
|
border-color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
.secondary:pressed { |
||||
|
background-color: var(--secondarydark); |
||||
|
border-color: var(--secondarydark); |
||||
|
} |
||||
|
|
||||
|
.secondary-outline { |
||||
|
background-color: var(--white); |
||||
|
border-color: var(--secondary100); |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.secondary-outline:hover { |
||||
|
background-color: var(--secondary10); |
||||
|
} |
||||
|
|
||||
|
.secondary-outline:pressed { |
||||
|
background-color: var(--secondary25); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* ---- success ----*/ |
||||
|
.success { |
||||
|
background-color: var(--success100); |
||||
|
border-color: var(--success100); |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.success:hover { |
||||
|
background-color: var(--success75); |
||||
|
border-color: var(--success75); |
||||
|
} |
||||
|
|
||||
|
.success:pressed { |
||||
|
background-color: var(--successdark); |
||||
|
border-color: var(--successdark); |
||||
|
} |
||||
|
|
||||
|
.success-outline { |
||||
|
background-color: var(--white); |
||||
|
border-color: var(--success100); |
||||
|
color: var(--success100); |
||||
|
} |
||||
|
|
||||
|
.success-outline:hover { |
||||
|
background-color: var(--success10); |
||||
|
} |
||||
|
|
||||
|
.success-outline:pressed { |
||||
|
background-color: var(--success25); |
||||
|
} |
||||
|
|
||||
|
/* ---- deletion ----*/ |
||||
|
.deletion { |
||||
|
background-color: var(--deletion100); |
||||
|
border-color: var(--deletion100); |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.deletion:hover { |
||||
|
background-color: var(--deletion75); |
||||
|
border-color: var(--deletion75); |
||||
|
} |
||||
|
|
||||
|
.deletion:pressed { |
||||
|
background-color: var(--deletiondark); |
||||
|
border-color: var(--deletiondark); |
||||
|
} |
||||
|
|
||||
|
.deletion-outline { |
||||
|
background-color: var(--white); |
||||
|
border-color: var(--deletion100); |
||||
|
color: var(--deletion100); |
||||
|
} |
||||
|
|
||||
|
.deletion-outline:hover { |
||||
|
background-color: var(--deletion10); |
||||
|
} |
||||
|
|
||||
|
.deletion-outline:pressed { |
||||
|
background-color: var(--deletion25); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,27 @@ |
|||||
|
<script> |
||||
|
export let style=""; |
||||
|
</script> |
||||
|
|
||||
|
<div class="root" style={style}> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.root:last-child { |
||||
|
border-radius: 0 var(--borderradius) var(--borderradius) 0; |
||||
|
} |
||||
|
|
||||
|
.root:first-child { |
||||
|
border-radius: var(--borderradius) 0 0 var(--borderradius); |
||||
|
} |
||||
|
|
||||
|
.root:not(:first-child):not(:last-child) { |
||||
|
border-radius: 0; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,16 @@ |
|||||
|
<script> |
||||
|
|
||||
|
export let checked=false; |
||||
|
export let label=""; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<input class="uk-checkbox" type="checkbox" bind:checked on:change />{label} |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
input { |
||||
|
margin-right:7px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,21 @@ |
|||||
|
<script> |
||||
|
// todo: use https://ace.c9.io |
||||
|
export let text = ""; |
||||
|
export let label = ""; |
||||
|
</script> |
||||
|
|
||||
|
<div>{label}</div> |
||||
|
<textarea bind:value={text}></textarea> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
textarea { |
||||
|
padding:3px; |
||||
|
background: var(--darkslate); |
||||
|
color: var(--white); |
||||
|
font-family: 'Courier New', Courier, monospace; |
||||
|
width:95%; |
||||
|
height:100px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,17 @@ |
|||||
|
<script> |
||||
|
|
||||
|
export let name = ""; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
<h4>Coming Sometime: {name}</h4> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
h4 { |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import flatpickr from "flatpickr"; |
||||
|
import "flatpickr/dist/flatpickr.css"; |
||||
|
import { onMount } from 'svelte'; |
||||
|
|
||||
|
export let value; |
||||
|
export let label; |
||||
|
let input; |
||||
|
let fpInstance; |
||||
|
|
||||
|
$: if (fpInstance) fpInstance.setDate(value); |
||||
|
|
||||
|
onMount(() => { |
||||
|
fpInstance = flatpickr(input, {}); |
||||
|
|
||||
|
fpInstance.config.onChange.push(selectedDates => { |
||||
|
if(selectedDates.length > 0) |
||||
|
value = new Date(selectedDates[0]); |
||||
|
}); |
||||
|
|
||||
|
return fpInstance; |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<div class="container" > |
||||
|
<div class="label">{label}</div> |
||||
|
<input class="control" bind:this={input} /> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: [label] 100px [control] auto; |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
.label { |
||||
|
grid-column-start: label; |
||||
|
align-self: center; |
||||
|
} |
||||
|
|
||||
|
.control { |
||||
|
grid-column-start: control; |
||||
|
align-self: center; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
input { |
||||
|
width:300px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,59 @@ |
|||||
|
<script> |
||||
|
import {createEventDispatcher} from "svelte"; |
||||
|
|
||||
|
export let selected; |
||||
|
export let label; |
||||
|
export let options; |
||||
|
export let valueMember; |
||||
|
export let textMember; |
||||
|
export let multiple=false; |
||||
|
|
||||
|
const dispatch =createEventDispatcher(); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="label">{label}</div> |
||||
|
|
||||
|
{#if multiple} |
||||
|
|
||||
|
<select class="control uk-select" multiple bind:value={selected} on:change> |
||||
|
{#each options as option} |
||||
|
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> |
||||
|
{/each} |
||||
|
</select> |
||||
|
|
||||
|
{:else} |
||||
|
|
||||
|
<select class="control uk-select" bind:value={selected} on:change> |
||||
|
{#each options as option} |
||||
|
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> |
||||
|
{/each} |
||||
|
</select> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: [label] 100px [control] auto; |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
.label { |
||||
|
grid-column-start: label; |
||||
|
align-self: center; |
||||
|
} |
||||
|
.control { |
||||
|
grid-column-start: control; |
||||
|
align-self: center; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
select { |
||||
|
width:300px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,68 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import getIcon from "./icon"; |
||||
|
export let iconName; |
||||
|
export let actions = []; // [ {label: "Action Name", onclick: () => {...} } ] |
||||
|
let isDroppedDown = false; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<div class="root" on:click={() => isDroppedDown = !isDroppedDown}> |
||||
|
{@html getIcon(iconName)} |
||||
|
|
||||
|
<div class="dropdown-background" on:click|stopPropagation={() => isDroppedDown = false} style="display: {isDroppedDown ? 'block' : 'none'}"></div> |
||||
|
|
||||
|
<div class="dropdown-content" style="display: {isDroppedDown ? 'inline-block' : 'none'}"> |
||||
|
{#each actions as action} |
||||
|
<div class="action-row" on:click={action.onclick}> |
||||
|
{action.label} |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.dropdown-background { |
||||
|
position: fixed; |
||||
|
top:0; |
||||
|
left:0; |
||||
|
width:100vw; |
||||
|
height:100vh; |
||||
|
} |
||||
|
|
||||
|
.root { |
||||
|
cursor: pointer; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.dropdown-content { |
||||
|
position: absolute; |
||||
|
background-color: var(--white); |
||||
|
min-width: 160px; |
||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); |
||||
|
z-index: 1; |
||||
|
font-weight: normal; |
||||
|
border-style:solid; |
||||
|
border-width: 1px; |
||||
|
border-color: var(--secondary10); |
||||
|
} |
||||
|
|
||||
|
.dropdown-content:not(:focus) { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.action-row { |
||||
|
padding: 7px 10px; |
||||
|
cursor:pointer; |
||||
|
} |
||||
|
|
||||
|
.action-row:hover { |
||||
|
background-color: var(--primary100); |
||||
|
color:var(--white); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<script> |
||||
|
export let errors = []; |
||||
|
|
||||
|
$:hasErrors = errors.length > 0; |
||||
|
</script> |
||||
|
|
||||
|
{#if hasErrors} |
||||
|
<div class="error-container"> |
||||
|
{#each errors as error} |
||||
|
<div class="error-row">{error.field ? `${error.field}: ` : ""}{error.error}</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.error-container { |
||||
|
padding:10px; |
||||
|
border-style: solid; |
||||
|
border-color: var(--deletion100); |
||||
|
border-radius: var(--borderradiusall); |
||||
|
background: var(--deletion75); |
||||
|
} |
||||
|
|
||||
|
.error-row { |
||||
|
padding: 5px 0px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,36 @@ |
|||||
|
<script> |
||||
|
import UIkit from "uikit"; |
||||
|
|
||||
|
export let isOpen = false; |
||||
|
export let onClosed = () => {}; |
||||
|
|
||||
|
let ukModal; |
||||
|
let listenerAdded = false; |
||||
|
|
||||
|
$: if(ukModal && !listenerAdded) { |
||||
|
listenerAdded = true; |
||||
|
ukModal.addEventListener("hide", onClosed); |
||||
|
} |
||||
|
|
||||
|
$: { |
||||
|
if(ukModal) { |
||||
|
if(isOpen) { |
||||
|
UIkit.modal(ukModal).show(); |
||||
|
} else { |
||||
|
UIkit.modal(ukModal).hide(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div bind:this={ukModal} uk-modal > |
||||
|
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,46 @@ |
|||||
|
<script> |
||||
|
|
||||
|
export let value; |
||||
|
export let label; |
||||
|
|
||||
|
const inputChanged = ev => { |
||||
|
try { |
||||
|
value = Number(ev.target.value); |
||||
|
} catch(_) { |
||||
|
value = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let numberText = value === null || value === undefined |
||||
|
? "" : value.toString(); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="label">{label}</div> |
||||
|
<input class="control" type="text" value={value} on:change={inputChanged} > |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: [label] 100px [control] auto; |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
.label { |
||||
|
grid-column-start: label; |
||||
|
align-self: center; |
||||
|
} |
||||
|
.control { |
||||
|
grid-column-start: control; |
||||
|
align-self: center; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
input { |
||||
|
width:300px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,32 @@ |
|||||
|
<script> |
||||
|
export let text = ""; |
||||
|
export let label = ""; |
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="label">{label}</div> |
||||
|
<input class="control uk-input" bind:value={text} > |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: [label] 100px [control] auto; |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
.label { |
||||
|
grid-column-start: label; |
||||
|
align-self: center; |
||||
|
} |
||||
|
.control { |
||||
|
grid-column-start: control; |
||||
|
align-self: center; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
input { |
||||
|
width:300px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,47 @@ |
|||||
|
<script> |
||||
|
import {join} from "lodash/fp"; |
||||
|
|
||||
|
export let values; |
||||
|
export let label; |
||||
|
|
||||
|
const inputChanged = ev => { |
||||
|
try { |
||||
|
values = ev.target.value.split("\n"); |
||||
|
} catch(_) { |
||||
|
values = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$: valuesText = join("\n")(values); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="label">{label}</div> |
||||
|
<textarea class="control" value={valuesText} on:change={inputChanged} ></textarea> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: [label] 100px [control] auto; |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
.label { |
||||
|
grid-column-start: label; |
||||
|
align-self: center; |
||||
|
} |
||||
|
.control { |
||||
|
grid-column-start: control; |
||||
|
align-self: center; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
textarea { |
||||
|
width:300px; |
||||
|
height:200px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,75 @@ |
|||||
|
import {hierarchy as hierarchyFunctions, |
||||
|
common, getTemplateApi, getAuthApi } from "budibase-core"; |
||||
|
import {find, filter, includes, keyBy, some, |
||||
|
flatten, map} from "lodash/fp"; |
||||
|
|
||||
|
export const chain = common.$; |
||||
|
|
||||
|
export const events = common.eventsList; |
||||
|
|
||||
|
export const getNode = (hierarchy, nodeId) => |
||||
|
chain(hierarchy, [ |
||||
|
hierarchyFunctions.getFlattenedHierarchy, |
||||
|
find(n => n.nodeId === nodeId || n.nodeKey() === nodeId) |
||||
|
]); |
||||
|
|
||||
|
export const constructHierarchy = node => { |
||||
|
if(!node) return node; |
||||
|
return templateApi(node).constructHeirarchy(node); |
||||
|
} |
||||
|
|
||||
|
export const createNewHeirarchy = () => { |
||||
|
return templateApi().getNewRootLevel(); |
||||
|
} |
||||
|
|
||||
|
export const templateApi = hierarchy => getTemplateApi({heirarchy:hierarchy}) |
||||
|
export const authApi = (hierarchy, actions) => getAuthApi({ |
||||
|
heirarchy:hierarchy, actions: keyBy("name")(actions), publish:()=>{}}) |
||||
|
|
||||
|
export const allTypes = templateApi({}).allTypes; |
||||
|
|
||||
|
export const validate = { |
||||
|
all: templateApi({}).validateAll, |
||||
|
node: templateApi({}).validateNode, |
||||
|
field: templateApi({}).validateField |
||||
|
}; |
||||
|
|
||||
|
export const getPotentialReverseReferenceIndexes = (hierarchy, refIndex) => { |
||||
|
const res = chain(hierarchy, [ |
||||
|
hierarchyFunctions.getFlattenedHierarchy, |
||||
|
filter(n => hierarchyFunctions.isAncestor(refIndex)(n) |
||||
|
|| hierarchyFunctions.isAncestor(refIndex)(n.parent())), |
||||
|
map(n => n.indexes), |
||||
|
flatten, |
||||
|
filter(hierarchyFunctions.isReferenceIndex) |
||||
|
]); |
||||
|
|
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
export const getPotentialReferenceIndexes = (hierarchy, record) => |
||||
|
chain(hierarchy, [ |
||||
|
hierarchyFunctions.getFlattenedHierarchy, |
||||
|
filter(hierarchyFunctions.isAncestorIndex), |
||||
|
filter(i => hierarchyFunctions.isAncestor(record)(i.parent()) |
||||
|
|| i.parent().nodeId === record.parent().nodeId |
||||
|
|| hierarchyFunctions.isRoot(i.parent())) |
||||
|
]); |
||||
|
|
||||
|
export const getDefaultTypeOptions = type => |
||||
|
!type ? {} : allTypes[type].getDefaultOptions(); |
||||
|
|
||||
|
export const getNewAction = () => templateApi({}).createAction(); |
||||
|
export const getNewTrigger = () => templateApi({}).createTrigger(); |
||||
|
|
||||
|
export const validateActions = actions => templateApi({}).validateActions(actions); |
||||
|
export const validateTriggers = (triggers, actions) => templateApi({}).validateTriggers(triggers, actions); |
||||
|
|
||||
|
export const generateFullPermissions = (hierarchy, actions) => |
||||
|
authApi(hierarchy,actions).generateFullPermissions(); |
||||
|
|
||||
|
export const getNewAccessLevel = () => |
||||
|
authApi().getNewAccessLevel(); |
||||
|
|
||||
|
export const validateAccessLevels = (hierarchy, actions, accessLevels) => |
||||
|
authApi(hierarchy, actions).validateAccessLevels(accessLevels); |
||||
@ -0,0 +1,2 @@ |
|||||
|
import feather from "feather-icons"; |
||||
|
export default icon => feather.toSvg(icon); |
||||
@ -0,0 +1,62 @@ |
|||||
|
<script> |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import Modal from "../common/Modal.svelte"; |
||||
|
import ErrorsBox from "../common/ErrorsBox.svelte"; |
||||
|
|
||||
|
export let left; |
||||
|
let confirmDelete = false; |
||||
|
const openConfirmDelete = () => { |
||||
|
confirmDelete = true; |
||||
|
} |
||||
|
|
||||
|
const deleteCurrentNode = () => { |
||||
|
confirmDelete = false; |
||||
|
database.deleteCurrentNode(); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root" style="left: {left}"> |
||||
|
|
||||
|
<ButtonGroup> |
||||
|
<Button color="secondary" grouped on:click={database.saveCurrentNode}> |
||||
|
{#if $database.currentNodeIsNew} |
||||
|
Create |
||||
|
{:else} |
||||
|
Update |
||||
|
{/if} |
||||
|
</Button> |
||||
|
|
||||
|
{#if !$database.currentNodeIsNew} |
||||
|
<Button color="secondary" grouped on:click={openConfirmDelete}> |
||||
|
Delete |
||||
|
</Button> |
||||
|
{/if} |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
{#if !!$database.errors && $database.errors.length > 0} |
||||
|
<div style="width: 500px"> |
||||
|
<ErrorsBox errors={$database.errors}/> |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
<Modal bind:isOpen={confirmDelete}> |
||||
|
<div style="margin: 10px 0px 20px 0px">Are you sure you want to delete {$database.currentNode.name} ?</div> |
||||
|
<div style="float:right"> |
||||
|
<Button color="primary" on:click={deleteCurrentNode}>Yes</Button> |
||||
|
<Button color="secondary" on:click={() => confirmDelete = false}>No</Button> |
||||
|
</div> |
||||
|
</Modal> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
padding:5px; |
||||
|
top:0; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import RecordCard from "./RecordCard.svelte"; |
||||
|
|
||||
|
export let record = {}; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<div class="title">{record.name}</div> |
||||
|
<div class="inner"> |
||||
|
<div class="node-path">{record.nodeKey()}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,129 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import HierarchyRow from "./HierarchyRow.svelte"; |
||||
|
import RecordView from "./RecordView.svelte"; |
||||
|
import IndexView from "./IndexView.svelte"; |
||||
|
import ActionsHeader from "./ActionsHeader.svelte"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
import DropdownButton from "../common/DropdownButton.svelte"; |
||||
|
import {hierarchy as hierarchyFunctions} from "budibase-core"; |
||||
|
|
||||
|
const hierarchyWidth = "200px"; |
||||
|
|
||||
|
const defaultNewIndexActions = [{ |
||||
|
label:"New Root Index", |
||||
|
onclick: database.newRootIndex |
||||
|
}]; |
||||
|
|
||||
|
const defaultNewRecordActions = [{ |
||||
|
label:"New Root Record", |
||||
|
onclick: database.newRootRecord |
||||
|
}]; |
||||
|
|
||||
|
let newIndexActions = defaultNewIndexActions; |
||||
|
let newRecordActions = defaultNewRecordActions; |
||||
|
|
||||
|
database.subscribe(db => { |
||||
|
if(!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) { |
||||
|
newRecordActions = defaultNewRecordActions; |
||||
|
newIndexActions = defaultNewIndexActions; |
||||
|
} else { |
||||
|
newRecordActions = [ |
||||
|
...defaultNewRecordActions, |
||||
|
{label: `New Child Record of ${db.currentNode.name}`, |
||||
|
onclick: database.newChildRecord} |
||||
|
]; |
||||
|
|
||||
|
newIndexActions = [ |
||||
|
...defaultNewIndexActions, |
||||
|
{label: `New Index on ${db.currentNode.name}`, |
||||
|
onclick: database.newChildIndex} |
||||
|
]; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<div class="hierarchy" style="width: {hierarchyWidth}"> |
||||
|
<div class="hierarchy-title-row"> |
||||
|
<div class="hierarchy-title">Records</div> |
||||
|
<DropdownButton iconName="plus" actions={newRecordActions} /> |
||||
|
</div> |
||||
|
{#each $database.hierarchy.children as record} |
||||
|
<HierarchyRow node={record} /> |
||||
|
{/each} |
||||
|
|
||||
|
<div class="hierarchy-title-row" style="margin-top: 20px"> |
||||
|
<div class="hierarchy-title">Indexes</div> |
||||
|
<DropdownButton iconName="plus" actions={newIndexActions} /> |
||||
|
</div> |
||||
|
{#each $database.hierarchy.indexes as index} |
||||
|
<HierarchyRow node={index} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
<div class="node-container"> |
||||
|
<div class="actions-header"> |
||||
|
{#if $database.currentNode} |
||||
|
<ActionsHeader left={hierarchyWidth}/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
<div class="node-view"> |
||||
|
{#if !$database.currentNode} |
||||
|
<h1 style="margin-left: 100px">:)</h1> |
||||
|
{:else if $database.currentNode.type === "record"} |
||||
|
<RecordView /> |
||||
|
{:else} |
||||
|
<IndexView /> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.hierarchy { |
||||
|
flex: 0 1 auto; |
||||
|
background-color: var(--primary10); |
||||
|
overflow-y: auto; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.node-container { |
||||
|
flex: 1 1 auto; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.actions-header { |
||||
|
flex: 0 1 auto; |
||||
|
} |
||||
|
|
||||
|
.node-view { |
||||
|
overflow-y: auto; |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
|
||||
|
.hierarchy-title-row { |
||||
|
padding: 15px 7px; |
||||
|
/*background-color: var(--secondary75); |
||||
|
color: var(--white);*/ |
||||
|
font-size: 11pt; |
||||
|
display: flex; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.hierarchy-title { |
||||
|
flex: auto 1 1; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,117 @@ |
|||||
|
<script> |
||||
|
import Dropdown from "../common/Dropdown.svelte"; |
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import NumberBox from "../common/NumberBox.svelte"; |
||||
|
import ValuesList from "../common/ValuesList.svelte"; |
||||
|
import ErrorsBox from "../common/ErrorsBox.svelte"; |
||||
|
import Checkbox from "../common/Checkbox.svelte"; |
||||
|
import DatePicker from "../common/DatePicker.svelte"; |
||||
|
import {cloneDeep, assign, keys, |
||||
|
isNumber, includes, map, isBoolean} from "lodash/fp"; |
||||
|
import {allTypes, validate, getPotentialReferenceIndexes, |
||||
|
getDefaultTypeOptions, getNode, |
||||
|
getPotentialReverseReferenceIndexes} from "../common/core"; |
||||
|
|
||||
|
export let field; |
||||
|
export let allFields; |
||||
|
export let onFinished = () => {}; |
||||
|
export let database; |
||||
|
|
||||
|
let errors = []; |
||||
|
let clonedField = cloneDeep(field); |
||||
|
|
||||
|
$: isNew = !!field && field.name.length === 0; |
||||
|
|
||||
|
$: possibleReferenceIndexes = getPotentialReferenceIndexes( |
||||
|
database.hierarchy, database.currentNode |
||||
|
); |
||||
|
|
||||
|
$: selectedReverseRefIndex = |
||||
|
!clonedField.typeOptions.indexNodeKey |
||||
|
? "" |
||||
|
: getNode(database.hierarchy, clonedField.typeOptions.indexNodeKey); |
||||
|
|
||||
|
$: possibleReverseReferenceIndexes = |
||||
|
!selectedReverseRefIndex |
||||
|
? [] |
||||
|
: getPotentialReverseReferenceIndexes( |
||||
|
database.hierarchy, selectedReverseRefIndex); |
||||
|
|
||||
|
const typeChanged = (ev) => |
||||
|
clonedField.typeOptions = getDefaultTypeOptions(ev.detail); |
||||
|
|
||||
|
|
||||
|
const save = () => { |
||||
|
|
||||
|
errors = validate.field(allFields)(clonedField); |
||||
|
if(errors.length > 0) return; |
||||
|
field.typeOptions = cloneDeep(clonedField.typeOptions); |
||||
|
onFinished( |
||||
|
assign(field)(clonedField) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<ErrorsBox errors={errors} /> |
||||
|
|
||||
|
<Dropdown label="Type" bind:selected={clonedField.type} options={keys(allTypes)} on:change={typeChanged} /> |
||||
|
|
||||
|
{#if isNew} |
||||
|
<Textbox label="Field Name" bind:text={clonedField.name} /> |
||||
|
{:else} |
||||
|
<div style="font-weight: bold">{clonedField.name}</div> |
||||
|
{/if} |
||||
|
|
||||
|
<Textbox label="Label" bind:text={clonedField.label} /> |
||||
|
|
||||
|
{#if clonedField.type === "string"} |
||||
|
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} /> |
||||
|
<ValuesList label="Values (options)" bind:values={clonedField.typeOptions.values} /> |
||||
|
<Checkbox label="Declared Values Only" bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} /> |
||||
|
{:else if clonedField.type === "bool"} |
||||
|
<Checkbox label="Allow Null" bind:checked={clonedField.typeOptions.allowNulls} /> |
||||
|
{:else if clonedField.type === "datetime"} |
||||
|
<DatePicker label="Min Value" bind:value={clonedField.typeOptions.minValue} /> |
||||
|
<DatePicker label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> |
||||
|
{:else if clonedField.type === "number"} |
||||
|
<NumberBox label="Min Value" bind:value={clonedField.typeOptions.minValue} /> |
||||
|
<NumberBox label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> |
||||
|
<NumberBox label="Decimal Places" bind:value={clonedField.typeOptions.decimalPlaces} /> |
||||
|
{:else if clonedField.type === "reference"} |
||||
|
<Dropdown label="Lookup Index" |
||||
|
options={possibleReferenceIndexes} |
||||
|
valueMember={n => n.nodeKey()} |
||||
|
textMember={n => n.name} |
||||
|
bind:selected={clonedField.typeOptions.indexNodeKey} /> |
||||
|
|
||||
|
<Dropdown label="Reverse Reference Index" |
||||
|
options={possibleReverseReferenceIndexes} |
||||
|
multiple=true |
||||
|
valueMember={n => n.nodeKey()} |
||||
|
textMember={n => n.name} |
||||
|
bind:selected={clonedField.typeOptions.reverseIndexNodeKeys} /> |
||||
|
|
||||
|
<Textbox label="Display Value" bind:text={clonedField.typeOptions.displayValue} /> |
||||
|
|
||||
|
{:else if clonedField.type.startsWith("array")} |
||||
|
<NumberBox label="Min Length" bind:value={clonedField.typeOptions.minLength} /> |
||||
|
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} /> |
||||
|
{/if} |
||||
|
|
||||
|
<ButtonGroup style="float: right;"> |
||||
|
<Button color="primary" grouped on:click={save}>Save</Button> |
||||
|
<Button color="secondary" grouped on:click={() => onFinished(false)}>Cancel</Button> |
||||
|
</ButtonGroup> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,44 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import {database} from "../builderStore"; |
||||
|
import {cloneDeep} from "lodash"; |
||||
|
export let level = 0; |
||||
|
export let node; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<div class="title" on:click={() => database.selectExistingNode(node.nodeId)} style="padding-left: {20 + (level * 20)}px"> |
||||
|
{node.name} |
||||
|
</div> |
||||
|
{#if node.children} |
||||
|
{#each node.children as child} |
||||
|
<svelte:self node={child} |
||||
|
level={level+1}/> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
display: block; |
||||
|
font-size: 13pt; |
||||
|
width: 100%; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font: var(--bodytext); |
||||
|
padding-top: 10px; |
||||
|
padding-right: 5px; |
||||
|
padding-bottom: 10px; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.title:hover { |
||||
|
background-color: var(--secondary10); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,76 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import CodeArea from "../common/CodeArea.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import Dropdown from "../common/Dropdown.svelte"; |
||||
|
import {database} from "../builderStore"; |
||||
|
import {filter, some, map} from "lodash/fp"; |
||||
|
import {hierarchy as hierarchyFunctions, common} from "budibase-core"; |
||||
|
|
||||
|
const chain = common.$; |
||||
|
|
||||
|
let index; |
||||
|
let indexableRecords = []; |
||||
|
|
||||
|
database.subscribe($database => { |
||||
|
index = $database.currentNode; |
||||
|
indexableRecords = chain($database.hierarchy,[ |
||||
|
hierarchyFunctions.getFlattenedHierarchy, |
||||
|
filter(hierarchyFunctions.isDecendant(index.parent())), |
||||
|
filter(hierarchyFunctions.isRecord), |
||||
|
map(n => ({ |
||||
|
node:n, |
||||
|
isallowed: some(id => n.nodeId === id)(index.allowedRecordNodeIds) |
||||
|
})) |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
const toggleAllowedRecord = record => { |
||||
|
if(record.isallowed) { |
||||
|
index.allowedRecordNodeIds = filter(id => id !== record.node.nodeId) |
||||
|
(index.allowedRecordNodeIds); |
||||
|
} else { |
||||
|
index.allowedRecordNodeIds.push(record.node.nodeId); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Textbox bind:text={index.name} label="Name"/> |
||||
|
|
||||
|
<div class="allowed-records"> |
||||
|
<div>Records to Index</div> |
||||
|
{#each indexableRecords as rec} |
||||
|
<input type="checkbox" checked={rec.isallowed} on:change={() => toggleAllowedRecord(rec)}/> |
||||
|
<span>{rec.node.name}</span> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<Dropdown label="Index Type" bind:selected={index.indexType} options={["ancestor", "reference"]} /> |
||||
|
|
||||
|
<CodeArea bind:text={index.map} label="Map (javascript)"/> |
||||
|
<CodeArea bind:text={index.filter} label="Filter (javascript expression)"/> |
||||
|
<CodeArea bind:text={index.getShardName} label="Shard Name (javascript expression)"/> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
height: 100%; |
||||
|
padding: 15px; |
||||
|
} |
||||
|
|
||||
|
.allowed-records { |
||||
|
margin: 20px 0px; |
||||
|
} |
||||
|
|
||||
|
.allowed-records > span { |
||||
|
margin-right:30px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,258 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
import FieldView from "./FieldView.svelte"; |
||||
|
import Modal from "../common/Modal.svelte"; |
||||
|
import {map, join, filter, some, |
||||
|
find, keys, isDate} from "lodash/fp"; |
||||
|
import { database } from "../builderStore"; |
||||
|
import {common, hierarchy as h} from "budibase-core"; |
||||
|
import {templateApi, chain, validate} from "../common/core"; |
||||
|
|
||||
|
let record; |
||||
|
let getIndexAllowedRecords; |
||||
|
let editingField = false; |
||||
|
let fieldToEdit; |
||||
|
let isNewField = false; |
||||
|
let newField; |
||||
|
let editField; |
||||
|
let deleteField; |
||||
|
let onFinishedFieldEdit; |
||||
|
let editIndex; |
||||
|
|
||||
|
database.subscribe($database => { |
||||
|
record = $database.currentNode; |
||||
|
const flattened = h.getFlattenedHierarchy($database.hierarchy); |
||||
|
getIndexAllowedRecords = index => |
||||
|
chain(index.allowedRecordNodeIds, [ |
||||
|
filter(id => some(n => n.nodeId === id)(flattened)), |
||||
|
map(id => find(n => n.nodeId === id) |
||||
|
(flattened).name), |
||||
|
join(", ") |
||||
|
]); |
||||
|
|
||||
|
newField = () => { |
||||
|
isNewField = true; |
||||
|
fieldToEdit = templateApi($database.hierarchy).getNewField("string"); |
||||
|
editingField = true; |
||||
|
} |
||||
|
|
||||
|
onFinishedFieldEdit = (field) => { |
||||
|
if(field) { |
||||
|
database.saveField(field); |
||||
|
} |
||||
|
editingField = false; |
||||
|
} |
||||
|
|
||||
|
editField = (field) => { |
||||
|
isNewField = false; |
||||
|
fieldToEdit = field; |
||||
|
editingField = true; |
||||
|
} |
||||
|
|
||||
|
deleteField = (field) => { |
||||
|
database.deleteField(field); |
||||
|
} |
||||
|
|
||||
|
editIndex = index => { |
||||
|
database.selectExistingNode(index.nodeId); |
||||
|
} |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
let getTypeOptionsValueText = value => { |
||||
|
if(value === Number.MAX_SAFE_INTEGER |
||||
|
|| value === Number.MIN_SAFE_INTEGER |
||||
|
|| new Date(value).getTime() === new Date(8640000000000000).getTime() |
||||
|
|| new Date(value).getTime() === new Date(-8640000000000000).getTime()) return "(any)"; |
||||
|
|
||||
|
if(value === null) return "(not set)"; |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
let getTypeOptions = typeOptions => |
||||
|
chain(typeOptions, [ |
||||
|
keys, |
||||
|
map(k => `<span style="color:var(--slate)">${k}: </span>${getTypeOptionsValueText(typeOptions[k])}`), |
||||
|
join("<br>") |
||||
|
]); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<Textbox label="Name" bind:text={record.name} /> |
||||
|
<div>{record.nodeKey()}</div> |
||||
|
{#if !record.isSingle} |
||||
|
<Textbox label="Collection Name" bind:text={record.collectionName} /> |
||||
|
<Textbox label="Shard Factor" bind:text={record.allidsShardFactor} /> |
||||
|
{/if} |
||||
|
|
||||
|
<h4> |
||||
|
Fields <span class="add-field-button" on:click={newField}>{@html getIcon("plus")}</span> |
||||
|
</h4> |
||||
|
|
||||
|
{#if record.fields.length > 0} |
||||
|
<table class="fields-table uk-table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Name</th> |
||||
|
<th>Type</th> |
||||
|
<th>Options</th> |
||||
|
<th></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{#each record.fields as field} |
||||
|
<tr> |
||||
|
<td > |
||||
|
<div>{field.label}</div> |
||||
|
<div style="font-size: 0.7em; color: var(--slate)">{field.name}</div> |
||||
|
</td> |
||||
|
<td >{field.type}</td> |
||||
|
<td >{@html getTypeOptions(field.typeOptions)}</td> |
||||
|
<td> |
||||
|
<span class="edit-button" on:click={() => editField(field)}>{@html getIcon("edit")}</span> |
||||
|
<span class="edit-button" on:click={() => deleteField(field)}>{@html getIcon("trash")}</span> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{/each} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{:else} |
||||
|
(no fields added) |
||||
|
{/if} |
||||
|
|
||||
|
{#if editingField} |
||||
|
<Modal bind:isOpen={editingField}> |
||||
|
<FieldView field={fieldToEdit} |
||||
|
onFinished={onFinishedFieldEdit} |
||||
|
allFields={record.fields} |
||||
|
database={$database}/> |
||||
|
</Modal> |
||||
|
{/if} |
||||
|
|
||||
|
<h4> |
||||
|
Indexes |
||||
|
</h4> |
||||
|
|
||||
|
{#each record.indexes as index} |
||||
|
<div class="index-container"> |
||||
|
<div class="index-name"> |
||||
|
{index.name} |
||||
|
<span style="margin-left: 7px" on:click={() => editIndex(index)}>{@html getIcon("edit")}</span> |
||||
|
</div> |
||||
|
<div class="index-field-row"> |
||||
|
<span class="index-label">records indexed: </span> |
||||
|
<span>{getIndexAllowedRecords(index)}</span> |
||||
|
<span class="index-label" style="margin-left: 15px">type:</span> |
||||
|
<span>{index.indexType}</span> |
||||
|
</div> |
||||
|
<div class="index-field-row"> |
||||
|
<span class="index-label">map:</span> |
||||
|
<code class="index-mapfilter">{index.map}</code> |
||||
|
</div> |
||||
|
{#if index.filter} |
||||
|
<div class="index-field-row"> |
||||
|
<span class="index-label">filter:</span> |
||||
|
<code class="index-mapfilter">{index.filter}</code> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{:else} |
||||
|
(no indexes added) |
||||
|
{/each} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
height: 100%; |
||||
|
padding: 15px; |
||||
|
} |
||||
|
|
||||
|
.fields-table { |
||||
|
margin:10px; |
||||
|
border-collapse:collapse; |
||||
|
} |
||||
|
|
||||
|
.add-field-button { |
||||
|
margin-left:15px; |
||||
|
cursor:pointer; |
||||
|
} |
||||
|
|
||||
|
.edit-button { |
||||
|
cursor:pointer; |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.edit-button:hover { |
||||
|
color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
th { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
td { |
||||
|
padding: 5px 30px 5px 0px; |
||||
|
margin:0; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
thead > tr { |
||||
|
border-width: 0px 0px 1px 0px; |
||||
|
border-style: solid; |
||||
|
border-color: var(--secondary75); |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
tbody > tr { |
||||
|
border-width: 0px 0px 1px 0px; |
||||
|
border-style: solid; |
||||
|
border-color: var(--primary10); |
||||
|
} |
||||
|
|
||||
|
tbody > tr:hover { |
||||
|
background-color: var(--primary10); |
||||
|
} |
||||
|
|
||||
|
tbody > tr:hover .edit-button { |
||||
|
color: var(--secondary75); |
||||
|
} |
||||
|
|
||||
|
.index-container { |
||||
|
border-style: solid; |
||||
|
border-width: 0 0 1px 0; |
||||
|
border-color: var(--secondary25); |
||||
|
padding: 10px; |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
.index-label { |
||||
|
color: var(--slate); |
||||
|
} |
||||
|
|
||||
|
.index-name { |
||||
|
font-weight: bold; |
||||
|
color: var(--primary100); |
||||
|
} |
||||
|
|
||||
|
.index-container code { |
||||
|
margin: 0; |
||||
|
display: inline; |
||||
|
background-color: var(--primary10); |
||||
|
color: var(--secondary100); |
||||
|
padding:3px; |
||||
|
} |
||||
|
|
||||
|
.index-field-row { |
||||
|
margin-top: 7px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,61 @@ |
|||||
|
/* latin-ext */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato'; |
||||
|
font-weight: 400; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin-ext.woff2) format('woff2'); |
||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
|
} |
||||
|
/* latin */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato'; |
||||
|
font-weight: 400; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin.woff2) format('woff2'); |
||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Black latin-ext */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Black'; |
||||
|
font-weight: 900; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin-ext.woff2) format('woff2'); |
||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
|
} |
||||
|
/* Black latin */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Black'; |
||||
|
font-weight: 900; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin.woff2) format('woff2'); |
||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
|
} |
||||
|
|
||||
|
/* Bold latin-ext */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Bold'; |
||||
|
font-weight: 700; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin-ext.woff2) format('woff2'); |
||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
|
} |
||||
|
/* Bold latin */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Bold'; |
||||
|
font-weight: 700; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin.woff2) format('woff2'); |
||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
|
} |
||||
|
|
||||
|
/* Semi Bold latin-ext */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Semi Bold'; |
||||
|
font-weight: 600; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin-ext.woff2) format('woff2'); |
||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
|
} |
||||
|
/* Semi Bold latin */ |
||||
|
@font-face { |
||||
|
font-family: 'Lato Semi Bold'; |
||||
|
font-weight: 600; |
||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/lato-latin.woff2) format('woff2'); |
||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,91 @@ |
|||||
|
:root { |
||||
|
--primary100: #454CA0FF; |
||||
|
--primary75: #454CA0BF; |
||||
|
--primary25: #454CA040; |
||||
|
--primary10: #454CA01A; |
||||
|
--primarydark: #3F448A; |
||||
|
|
||||
|
--secondary100: #162B4DFF; |
||||
|
--secondary75: #162B4DBF; |
||||
|
--secondary25: #162B4D40; |
||||
|
--secondary10: #162B4D1A; |
||||
|
--secondarydark: #3F448A; |
||||
|
|
||||
|
--tertiary: #F2F5F7; |
||||
|
|
||||
|
--success100: #49C39EFF; |
||||
|
--success75: #49C39EBF; |
||||
|
--success25: #49C39E40; |
||||
|
--success10: #49C39E1A; |
||||
|
--successdark: #44B492; |
||||
|
|
||||
|
--deletion100: #F2545BFF; |
||||
|
--deletion75: #F2545BBF; |
||||
|
--deletion25: #F2545B40; |
||||
|
--deletion10: #F2545B1A; |
||||
|
--deletiondark: #CF4046; |
||||
|
|
||||
|
--white: #FFFFFF; |
||||
|
--darkslate: #5C6B82; |
||||
|
--slate: #8B95A6; |
||||
|
|
||||
|
--borderradius: 2px; |
||||
|
--borderradiusall: 2px 2px 2px 2px; |
||||
|
|
||||
|
--fontblack: "Lato Black"; |
||||
|
--fontbold: "Lato Bold"; |
||||
|
--fontsemibold: "Lato Semi Bold"; |
||||
|
--fontnormal: "Lato"; |
||||
|
|
||||
|
--bodytext: var(--fontnormal) "regular" var(--secondary100) 16pt; |
||||
|
--bigbodytext: var(--fontnormal) "regular" var(--secondary100) 20pt; |
||||
|
--smallbodytext: var(--fontnormal) "regular" var(--secondary100) 12pt; |
||||
|
--lightbodytext: var(--fontnormal) "regular" var(--darkslate) 16pt; |
||||
|
--heavybodytext: var(--fontbold) "regular" var(--secondary100) 16pt; |
||||
|
--quotation: var(--fontnormal) "italics" var(--darkslate) 16pt; |
||||
|
--smallheavybodytext: var(--fontbold) "regular" var(--secondary100) 14pt; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
html, body { |
||||
|
font-family: var(--fontnormal); |
||||
|
color: var(--secondary100); |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-family: var(--fontblack); |
||||
|
font-size: 36pt; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-family: var(--fontbold); |
||||
|
font-size: 30pt; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-family: var(--fontbold); |
||||
|
font-size: 24pt; |
||||
|
color: var(--darkslate); |
||||
|
} |
||||
|
|
||||
|
h4 { |
||||
|
font-family: var(--fontsemibold); |
||||
|
font-size: 18pt; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
h5 { |
||||
|
font-family: var(--fontblack); |
||||
|
font-size: 14pt; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
h5 { |
||||
|
font-family: var(--fontblack); |
||||
|
font-size: 12pt; |
||||
|
color: var(--darkslate); |
||||
|
} |
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,18 @@ |
|||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<meta charset='utf8'> |
||||
|
<meta name='viewport' content='width=device-width'> |
||||
|
|
||||
|
<title>Budibase Builder</title> |
||||
|
|
||||
|
<link rel='icon' type='image/png' href='/favicon.png'> |
||||
|
<link rel='stylesheet' href='/global.css'> |
||||
|
<link rel='stylesheet' href='/bundle.css'> |
||||
|
<link rel='stylesheet' href="/uikit.min.css"> |
||||
|
</head> |
||||
|
|
||||
|
<body id="app"> |
||||
|
<script src='/bundle.js'></script> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,15 @@ |
|||||
|
import App from "./App.svelte"; |
||||
|
import "./global.css"; |
||||
|
import "./fonts.css"; |
||||
|
import "./assets/lato-latin-ext.woff2"; |
||||
|
import "./assets/lato-latin.woff2"; |
||||
|
import "./assets/budibase-logo.png"; |
||||
|
import "./assets/budibase-logo-only.png"; |
||||
|
import "uikit/dist/css/uikit.min.css"; |
||||
|
import "uikit/dist/js/uikit.min.js"; |
||||
|
|
||||
|
const app = new App({ |
||||
|
target: document.getElementById("app") |
||||
|
}); |
||||
|
|
||||
|
|
||||
@ -0,0 +1,38 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import NavItem from "./NavItem.svelte"; |
||||
|
|
||||
|
export let width=50; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<nav class="nav" |
||||
|
style="width: {width};"> |
||||
|
<img src="./assets/budibase-logo-only.png" class="logo" alt="budibase logo"/> |
||||
|
<NavItem name="database" label="Database" icon="database" /> |
||||
|
<NavItem name="actions" label="Actions" icon="zap" /> |
||||
|
<NavItem name="access levels" label="Access Levels" icon="user" /> |
||||
|
<NavItem name="user interface" label="User Interface" icon="monitor"/> |
||||
|
<NavItem name="package" label="Package" icon="package"/> |
||||
|
</nav> |
||||
|
|
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.nav { |
||||
|
height: 100%; |
||||
|
position: fixed; |
||||
|
left: 0px; |
||||
|
background-color: var(--secondary100); |
||||
|
color: var(--darkslate); |
||||
|
} |
||||
|
|
||||
|
.nav > img { |
||||
|
width:100%; |
||||
|
margin-bottom: 30px; |
||||
|
margin-top: 5px; |
||||
|
margin-left:0px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,75 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import {database} from "../builderStore"; |
||||
|
import getIcon from "../common/icon"; |
||||
|
|
||||
|
export let name = ""; |
||||
|
export let label = ""; |
||||
|
export let icon = ""; |
||||
|
|
||||
|
let navActive = ""; |
||||
|
|
||||
|
database.subscribe(db => { |
||||
|
navActive = (db.activeNav === name ? "active" : "") |
||||
|
}); |
||||
|
|
||||
|
const setActive = () => |
||||
|
database.setActiveNav(name); |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="{navActive}"> |
||||
|
<div class="nav-item" |
||||
|
on:click={setActive}> |
||||
|
<div class="inner"> |
||||
|
<div class="icon">{@html getIcon(icon)}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.nav-item { |
||||
|
padding: 0px 5px; |
||||
|
display: block; |
||||
|
padding: 10px; |
||||
|
color: var(--slate); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.inner { |
||||
|
padding: 0px 20px 10px 0px; |
||||
|
display: inline-block; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.nav-item:hover { |
||||
|
background-color: var(--primary25); |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
font-size:0.9em; |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
top: 5px; |
||||
|
margin-right:5px; |
||||
|
width:100%; |
||||
|
} |
||||
|
|
||||
|
.active > div { |
||||
|
background-color: var(--primary10); |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.active>div:hover { |
||||
|
background-color: var(--slate); |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.active { |
||||
|
background-color: white; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,11 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import ComingSoon from "../common/ComingSoon.svelte"; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<ComingSoon name="User Interface Designer"/> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
||||
@ -1 +1 @@ |
|||||
Subproject commit ab14ab9db1698a82b1f98b692ecfa6b71c17752a |
Subproject commit d418f210e2af6f6a37d590ec255730a0da2241c7 |
||||
@ -1,75 +0,0 @@ |
|||||
{ |
|
||||
"levels": [ |
|
||||
{ |
|
||||
"name": "owner", |
|
||||
"permissions": [ |
|
||||
{ |
|
||||
"type": "create record", |
|
||||
"nodeKey": "/customers/1-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "delete record", |
|
||||
"nodeKey": "/customers/1-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "update record", |
|
||||
"nodeKey": "/customers/1-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "read record", |
|
||||
"nodeKey": "/customers/1-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "create record", |
|
||||
"nodeKey": "/customers/1-{id}/invoices/2-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "update record", |
|
||||
"nodeKey": "/customers/1-{id}/invoices/2-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "delete record", |
|
||||
"nodeKey": "/customers/1-{id}/invoices/2-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "read record", |
|
||||
"nodeKey": "/customers/1-{id}/invoices/2-{id}" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "write templates" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "create user" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "set password" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "create temporary access" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "enable or disable user" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "write access levels" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "list users" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "list access levels" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "manage index" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "set user access levels" |
|
||||
}, |
|
||||
{ |
|
||||
"type": "manage collection" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
], |
|
||||
"version": 0 |
|
||||
} |
|
||||
@ -1,84 +0,0 @@ |
|||||
{ |
|
||||
"hierarchy": { |
|
||||
"name": "root", |
|
||||
"type": "root", |
|
||||
"children": [ |
|
||||
{ |
|
||||
"name": "customer", |
|
||||
"type": "record", |
|
||||
"fields": [ |
|
||||
{ |
|
||||
"name": "name", |
|
||||
"type": "string", |
|
||||
"typeOptions": { |
|
||||
"maxLength": 1000, |
|
||||
"values": null, |
|
||||
"allowDeclaredValuesOnly": false |
|
||||
}, |
|
||||
"label": "name", |
|
||||
"getInitialValue": "default", |
|
||||
"getUndefinedValue": "default" |
|
||||
} |
|
||||
], |
|
||||
"children": [ |
|
||||
{ |
|
||||
"name": "invoice", |
|
||||
"type": "record", |
|
||||
"fields": [ |
|
||||
{ |
|
||||
"name": "amount", |
|
||||
"type": "number", |
|
||||
"typeOptions": { |
|
||||
"minValue": 99999999999, |
|
||||
"maxValue": 99999999999, |
|
||||
"decimalPlaces": 2 |
|
||||
}, |
|
||||
"label": "amount", |
|
||||
"getInitialValue": "default", |
|
||||
"getUndefinedValue": "default" |
|
||||
} |
|
||||
], |
|
||||
"children": [], |
|
||||
"validationRules": [], |
|
||||
"nodeId": 2, |
|
||||
"indexes": [], |
|
||||
"allidsShardFactor": 1, |
|
||||
"collectionName": "invoices", |
|
||||
"isSingle": false |
|
||||
} |
|
||||
], |
|
||||
"validationRules": [], |
|
||||
"nodeId": 1, |
|
||||
"indexes": [], |
|
||||
"allidsShardFactor": 64, |
|
||||
"collectionName": "customers", |
|
||||
"isSingle": false |
|
||||
} |
|
||||
], |
|
||||
"pathMaps": [], |
|
||||
"indexes": [], |
|
||||
"nodeId": 0 |
|
||||
}, |
|
||||
"actions": { |
|
||||
"output_to_file": { |
|
||||
"name": "output_to_file", |
|
||||
"behaviourSource": "main", |
|
||||
"behaviourName": "outputToFile", |
|
||||
"initialOptions": {} |
|
||||
} |
|
||||
}, |
|
||||
"triggers": [ |
|
||||
{ |
|
||||
"actionName": "output_to_file", |
|
||||
"eventName": "authApi:createUser:onComplete", |
|
||||
"optionsCreator": "return { filename:'tempaccess' + context.user.name, content:context.result.tempCode };", |
|
||||
"condition": "!context.password" |
|
||||
}, |
|
||||
{ |
|
||||
"actionName": "output_to_file", |
|
||||
"eventName": "authApi:createTemporaryAccess:onComplete", |
|
||||
"optionsCreator": "return { filename:'tempaccess' + context.userName, content:context.result };", |
|
||||
"condition": "" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
const fs = require("fs"); |
|
||||
|
|
||||
module.exports = (config) => ({ |
|
||||
main: { |
|
||||
outputToFile : ({filename, content}) => { |
|
||||
fs.writeFile(`./tests/.data/${filename}`, content, {encoding:"utf8"}); |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
@ -0,0 +1,31 @@ |
|||||
|
const { appPackageFolder } = require("./createAppPackage"); |
||||
|
const { writeFile, readFile } = require("./fsawait"); |
||||
|
|
||||
|
module.exports.getPackageForBuilder = async (config, appname) => { |
||||
|
const appPath = appPackageFolder(config, appname); |
||||
|
return ({ |
||||
|
appDefinition: JSON.parse(await readFile( |
||||
|
`${appPath}/appDefinition.json`, |
||||
|
"utf8")), |
||||
|
|
||||
|
accessLevels: JSON.parse(await readFile( |
||||
|
`${appPath}/access_levels.json`, |
||||
|
"utf8")) |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
module.exports.savePackage = async (config, appname, pkg) => { |
||||
|
const appPath = appPackageFolder(config, appname); |
||||
|
await writeFile( |
||||
|
`${appPath}/appDefinition.json`, |
||||
|
JSON.stringify(pkg.appDefinition), |
||||
|
"utf8"); |
||||
|
|
||||
|
await writeFile( |
||||
|
`${appPath}/access_levels.json`, |
||||
|
JSON.stringify(pkg.accessLevels), |
||||
|
"utf8"); |
||||
|
|
||||
|
} |
||||