forked from M-Labs/web2019
Compare commits
27 Commits
cbe61efc1d
...
6fe4f124bc
Author | SHA1 | Date |
---|---|---|
Sébastien Bourdeauducq | 6fe4f124bc | |
Egor Savkin | 3fd43e0de8 | |
Egor Savkin | 4448029757 | |
Egor Savkin | 51c9031f24 | |
Egor Savkin | be50b2a3c3 | |
Egor Savkin | ee6da1b282 | |
Egor Savkin | 5e3a9af749 | |
Egor Savkin | d3fb46956e | |
Egor Savkin | 6bfed3e779 | |
Egor Savkin | dfe1f0ea2d | |
Egor Savkin | 839d7c6612 | |
Egor Savkin | 1cb9c90c65 | |
Egor Savkin | eb196b086e | |
Egor Savkin | 56a44ce4a3 | |
Egor Savkin | aa35348288 | |
Egor Savkin | 4bc6f6a3ee | |
Sébastien Bourdeauducq | 2ba10dd2e8 | |
Egor Savkin | df7607ffd8 | |
Sébastien Bourdeauducq | 4906ad1713 | |
Sébastien Bourdeauducq | 41e20d205c | |
Egor Savkin | 67cef120b6 | |
Egor Savkin | 759f7cffcc | |
Egor Savkin | 0b5797b1ba | |
Egor Savkin | bcc8db6819 | |
Egor Savkin | 15d9124025 | |
Egor Savkin | 4527189994 | |
Egor Savkin | bc81035555 |
|
@ -184,6 +184,12 @@ We welcome inquiries from research groups of all sizes.<br>[See what has been fu
|
|||
{% end %}
|
||||
|
||||
|
||||
{% layout_card(title="Argent", sameheight=120) %}
|
||||
<small>High-level sequence control interface for ARTIQ.</small>
|
||||
|
||||
<a href="https://github.com/robertfasano/argent" target="_blank" rel="noopener noreferrer">Repository</a>
|
||||
{% end %}
|
||||
|
||||
{% layout_card(title="flake8-artiq", sameheight=120) %}
|
||||
<small>A Flake8 plugin for checking ARTIQ code</small>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ The first ARTIQ core devices used hardware built in-house by physicists (based o
|
|||
|
||||
The Sinara hardware is in active development, and the latest information is available <a href="https://github.com/sinara-hw" target="_blank" rel="noopener noreferrer">on the wiki of each project's page</a>. Most of the hardware engineering is done at the <a href="http://www.ise.pw.edu.pl/" target="_blank" rel="noopener noreferrer">Institute for Electronics Systems</a> at the Warsaw University of Technology.
|
||||
|
||||
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements and we will establish a quote.
|
||||
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
|
||||
|
||||
{% end %}
|
||||
|
||||
|
@ -293,11 +293,11 @@ The 4624 AWG "Phaser" is a quad channel 1.25 GS/s RF generator card with dual IQ
|
|||
|
||||
{% layout_centered_content(min_width=true, css="row d-flex align-items-center mt-5") %}
|
||||
|
||||
##### Kasli and EEMs can be ordered now
|
||||
##### Ordering from M-Labs is easy and quick
|
||||
|
||||
We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network.
|
||||
We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. The lead time can be as short as a few working days and we will provide assistance to help you set up your new equipment with ARTIQ via the online helpdesk. Using our AFWS tool, you can keep the firmware of your M-Labs devices up-to-date easily, and benefit from the new features we continuously develop into ARTIQ.
|
||||
|
||||
Contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements and we will establish a quote.
|
||||
Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
|
||||
|
||||
{% end %}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
|
@ -13,31 +13,38 @@
|
|||
"url": "https://git.m-labs.hk/M-Labs/web2019.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.23.0",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"@babel/cli": "^7.23.9",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-preset-minify": "^0.5.2",
|
||||
"bootstrap": "^5.3.0",
|
||||
"jquery": "^3.7.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"jquery": "^3.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.9.1",
|
||||
"react-bootstrap": "^2.10.2",
|
||||
"@hello-pangea/dnd": "^16.5.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"uuid": "^9.0.1",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack": "^5.90.3",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"json-logic-js": "^2.0.2",
|
||||
"zustand": "^4.4.7",
|
||||
"zustand": "^4.5.2",
|
||||
"@uidotdev/usehooks":"^2.4.1",
|
||||
"webpack-preprocessor-loader": "^1.3.0"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
"@babel/preset-react",
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"browsers": [
|
||||
">0.25%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,26 +72,32 @@ button {
|
|||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
max-width: max(1/4 * 100%, 310px);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable;
|
||||
scrollbar-color: #6e7e87 transparent;
|
||||
scrollbar-arrow-color: transparent;
|
||||
/*padding-bottom: 4rem!important;*/
|
||||
}
|
||||
|
||||
> aside.aside:after {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
height: 100px;
|
||||
width: calc(2 / 6 * 100%);
|
||||
content: "";
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(13, 53, 71, 1),
|
||||
rgba(13, 53, 71, 0)
|
||||
);
|
||||
pointer-events: none;
|
||||
.gradient-bottom {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
height: 100px;
|
||||
//width: max(1/4 * 100%, 310px);
|
||||
width: inherit;
|
||||
content: "";
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(13, 53, 71, 1),
|
||||
rgba(13, 53, 71, 0)
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
> section.main {
|
||||
flex: 4;
|
||||
max-width: calc(4 / 6 * 100%);
|
||||
width: calc(3/4 * 100%);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +106,7 @@ button {
|
|||
|
||||
display: flex;
|
||||
color: white;
|
||||
padding: 3rem 2rem 1rem;
|
||||
padding: 1rem 0rem 1rem 1.5rem;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
|
@ -119,6 +125,7 @@ button {
|
|||
|
||||
h3 {
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
|
@ -176,8 +183,50 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
.backlog-container {
|
||||
.catalog-container {
|
||||
padding-bottom: 4rem;
|
||||
|
||||
.catalog-bar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 13px 0 0.4rem 0;
|
||||
padding: 0 0.5rem;
|
||||
justify-content: space-around;
|
||||
|
||||
.mobileCloseMenu {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 10%;
|
||||
align-content: center;
|
||||
}
|
||||
.search-catalog {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
width: 90%;
|
||||
|
||||
.search-catalog-input {
|
||||
display: inline;
|
||||
border: 0;
|
||||
color: white;
|
||||
border-radius: 2rem;
|
||||
background: rgba(255, 255, 255, 0.15) url("/images/shop/icon-search.svg") no-repeat;
|
||||
background-position: left 2% center;
|
||||
background-size: 20px;
|
||||
padding-right: 1rem;
|
||||
text-indent: 20px;
|
||||
|
||||
&::placeholder {
|
||||
color: white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
|
@ -241,6 +290,9 @@ button {
|
|||
|
||||
.item-card-name {
|
||||
font-weight: 700;
|
||||
&.tabbed {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
|
@ -384,6 +436,26 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
.order-bar {
|
||||
width: 90%;
|
||||
font-size: 0.9rem;
|
||||
padding: 0;
|
||||
input[type="text"] {
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.options-group {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.shop-radio-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.crate {
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.75rem;
|
||||
padding: .75rem 2rem;
|
||||
padding: .75rem 1.5rem;
|
||||
}
|
||||
|
||||
#accordion_categories .accordion-body {
|
||||
|
@ -73,6 +73,10 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.options-invalid {
|
||||
box-shadow: 0 0 0 .25rem rgba(229, 62, 62, 0.25)!important;
|
||||
--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important;
|
||||
}
|
||||
/*
|
||||
##Device = Tablets, Ipads (portrait)
|
||||
##Screen = B/w 768px to 1024px
|
||||
|
@ -94,6 +98,14 @@
|
|||
height: calc(100vh - 10px - 2.5rem); /* .navbar vertical padding + line height (.navbar-brand.font-size.rem * body.font-size * body.line-height)*/
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside {
|
||||
min-width: max(300px, 30%);
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside .gradient-bottom {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
#root-shop .productItem {
|
||||
padding: 2rem 1rem 1rem;
|
||||
}
|
||||
|
@ -111,7 +123,7 @@
|
|||
}
|
||||
|
||||
#root-shop .productItem .content ul {
|
||||
font-size: .6rem;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
#root-shop .panel .control {
|
||||
|
@ -133,10 +145,11 @@
|
|||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price table {
|
||||
font-size: 1rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-form form {
|
||||
#root-shop .panel .summary>.summary-form form,
|
||||
#root-shop .panel .summary>.summary-form .order-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -162,7 +175,7 @@
|
|||
}
|
||||
|
||||
#root-shop table tr {
|
||||
padding: .8em 0;
|
||||
padding: .2em 0;
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -191,7 +204,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop, #root-shop>div {
|
||||
|
@ -216,7 +229,7 @@
|
|||
}
|
||||
|
||||
#root-shop table tr {
|
||||
padding: .8em 0;
|
||||
padding: .2em 0;
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -226,15 +239,16 @@
|
|||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price table {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-form form {
|
||||
#root-shop .panel .summary>.summary-form form,
|
||||
#root-shop .panel .summary>.summary-form .order-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price tfoot {
|
||||
font-size: .85rem;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
|
||||
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
|
||||
|
@ -286,7 +300,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop, #root-shop>div {
|
||||
|
@ -306,11 +320,11 @@
|
|||
}
|
||||
|
||||
#root-shop .productItem .content h3 {
|
||||
font-size: 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
#root-shop .productItem .content ul {
|
||||
font-size: .5rem;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
#root-shop .panel {
|
||||
|
@ -343,15 +357,16 @@
|
|||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price table {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-form form {
|
||||
#root-shop .panel .summary>.summary-form form,
|
||||
#root-shop .panel .summary>.summary-form .order-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price tfoot {
|
||||
font-size: .85rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
|
||||
|
@ -364,7 +379,6 @@
|
|||
border-top-right-radius: 30px;
|
||||
width: 80px;
|
||||
padding: 5px 0 5px 10px;
|
||||
margin-bottom: -25px;
|
||||
margin-left: -1.3rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
@ -383,7 +397,7 @@
|
|||
}
|
||||
|
||||
#root-shop table tr {
|
||||
padding: .8em 0;
|
||||
padding: .2em 0;
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -391,21 +405,22 @@
|
|||
#root-shop .layout>aside.aside.menu-opened {
|
||||
/*transform: translate3d(0, 0, 0);*/
|
||||
transition: left .3s;
|
||||
width: 310px;
|
||||
width: min(310px, 60vw);
|
||||
max-width: 60%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened + section.main {
|
||||
/*transform: translate3d(310px, 0, 0);*/
|
||||
transition: left .3s;
|
||||
left: 310px;
|
||||
left: min(310px, 60vw);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened + section.main:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
|
@ -419,13 +434,13 @@
|
|||
transition: left .3s;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: -310px;
|
||||
width: 310px;
|
||||
left: max(-310px, -60vw);
|
||||
width: min(310px, 60vw);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside:after {
|
||||
width: 0;
|
||||
#root-shop .layout>aside.aside .gradient-bottom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside + section.main {
|
||||
|
@ -447,7 +462,7 @@
|
|||
overflow: initial;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
|
||||
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -457,7 +472,7 @@
|
|||
}
|
||||
|
||||
#accordion_categories button {
|
||||
font-size: 1rem;
|
||||
font-size: 1.5rem;
|
||||
padding: .5rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
@ -497,7 +512,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop, #root-shop>div {
|
||||
|
@ -513,20 +528,21 @@
|
|||
}
|
||||
|
||||
#root-shop .productItem .content h3 {
|
||||
font-size: 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened {
|
||||
/*transform: translate3d(0, 0, 0);*/
|
||||
transition: left .3s;
|
||||
width: 310px;
|
||||
width: min(310px, 90vw);
|
||||
max-width: 90%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened + section.main {
|
||||
/*transform: translate3d(310px, 0, 0);*/
|
||||
transition: left .3s;
|
||||
left: 310px;
|
||||
left: min(310px, 90vw);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
@ -546,13 +562,13 @@
|
|||
transition: left .3s;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: -310px;
|
||||
width: 310px;
|
||||
left: max(-310px, -90vw);
|
||||
width: min(310px, 90vw);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside:after {
|
||||
width: 0;
|
||||
#root-shop .layout>aside.aside .gradient-bottom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside + section.main {
|
||||
|
@ -608,15 +624,16 @@
|
|||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price table {
|
||||
font-size: .7rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-form form {
|
||||
#root-shop .panel .summary>.summary-form form,
|
||||
#root-shop .panel .summary>.summary-form .order-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#root-shop .panel .summary>.summary-price tfoot {
|
||||
font-size: .85rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
|
||||
|
@ -629,7 +646,6 @@
|
|||
border-top-right-radius: 30px;
|
||||
width: 80px;
|
||||
padding: 5px 0 5px 10px;
|
||||
margin-bottom: 15px;
|
||||
margin-left: -1.3rem;
|
||||
}
|
||||
|
||||
|
@ -646,7 +662,7 @@
|
|||
}
|
||||
|
||||
#root-shop table tr {
|
||||
padding: .8em 0;
|
||||
padding: .2em 0;
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -655,7 +671,7 @@
|
|||
overflow: initial;
|
||||
}
|
||||
|
||||
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
|
||||
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -665,7 +681,7 @@
|
|||
}
|
||||
|
||||
#accordion_categories button {
|
||||
font-size: 1rem;
|
||||
font-size: 1.5rem;
|
||||
padding: .5rem 0.5rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
version="1.1"
|
||||
viewBox="0 0 512 512"
|
||||
id="svg1"
|
||||
sodipodi:docname="icon-search.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.59375"
|
||||
inkscape:cx="255.68627"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M456.69,421.39,362.6,327.3a173.81,173.81,0,0,0,34.84-104.58C397.44,126.38,319.06,48,222.72,48S48,126.38,48,222.72s78.38,174.72,174.72,174.72A173.81,173.81,0,0,0,327.3,362.6l94.09,94.09a25,25,0,0,0,35.3-35.3ZM97.92,222.72a124.8,124.8,0,1,1,124.8,124.8A124.95,124.95,0,0,1,97.92,222.72Z"
|
||||
fill="#fff"
|
||||
id="path1"
|
||||
style="fill:#ffffff;fill-opacity:0.5" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
File diff suppressed because one or more lines are too long
|
@ -3,7 +3,6 @@ import {Droppable} from "@hello-pangea/dnd";
|
|||
import {cartStyle, compareArraysWithIds} from "./utils";
|
||||
import {ProductCartItem} from "./ProductCartItem";
|
||||
import {FakePlaceholder} from "./FakePlaceholder";
|
||||
import {FillExtData} from "./options/utils";
|
||||
import {hp_to_slots} from "./count_resources";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
|
@ -29,12 +28,10 @@ export function Cart({crate_index}) {
|
|||
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
|
||||
|
||||
const products = crate.items.map((item, index) => {
|
||||
const ext_data = FillExtData(crate.items, index);
|
||||
return (
|
||||
<ProductCartItem
|
||||
card_index={index}
|
||||
crate_index={crate_index}
|
||||
ext_data={ext_data}
|
||||
first={index === 0}
|
||||
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
||||
key={item.id}/>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
import {Droppable} from "@hello-pangea/dnd";
|
||||
import {useShopStore} from "./shop_store";
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {CatalogGroups} from "./CatalogGroups";
|
||||
import {SearchBar} from "./SearchBar";
|
||||
import {CatalogSearchResult} from "./CatalogSearchResult";
|
||||
import {GradientBottom} from "./GradientBottom";
|
||||
|
||||
/**
|
||||
* Component that renders the catalog in the aside
|
||||
*/
|
||||
export function Catalog() {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const data = useShopStore((state) => state.groups);
|
||||
const items = useShopStore((state) => state.cards);
|
||||
|
||||
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
|
||||
const isMobile = useShopStore((state) => state.isMobile);
|
||||
const showSearch = useShopStore((state) => state.listed_cards.length > 0);
|
||||
|
||||
// #!render_count
|
||||
console.log("Catalog renders: ", renderCount)
|
||||
|
||||
return (
|
||||
<Droppable
|
||||
droppableId={data.id}
|
||||
isDropDisabled={false}>
|
||||
|
||||
{(provided) => (
|
||||
<div
|
||||
className="catalog-container"
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}>
|
||||
|
||||
|
||||
|
||||
<div className="catalog-bar">
|
||||
<SearchBar/>
|
||||
|
||||
{isMobile ? (
|
||||
<div className="mobileCloseMenu">
|
||||
<button onClick={onClickToggleMobileSideMenu}>
|
||||
<img src="/images/shop/icon-close-white.svg" alt="add"/>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{showSearch ? <CatalogSearchResult/> : <CatalogGroups/>}
|
||||
|
||||
{provided.placeholder && (
|
||||
<div style={{display: 'none'}}>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<GradientBottom/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
}
|
|
@ -1,24 +1,10 @@
|
|||
import React from 'react';
|
||||
import {Droppable} from "@hello-pangea/dnd";
|
||||
import {ProductItem} from "./ProductItem";
|
||||
import React from "react";
|
||||
import {useShopStore} from "./shop_store";
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
/**
|
||||
* Component that renders the backlog in the aside
|
||||
*/
|
||||
export function Backlog() {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
export function CatalogGroups() {
|
||||
const data = useShopStore((state) => state.groups);
|
||||
const items = useShopStore((state) => state.cards);
|
||||
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
|
||||
const isMobile = useShopStore((state) => state.isMobile);
|
||||
|
||||
// #!render_count
|
||||
console.log("Backlog renders: ", renderCount)
|
||||
|
||||
const ordered_groups = data.categories.map(groupItem => ({
|
||||
name: groupItem.name,
|
||||
|
@ -50,39 +36,9 @@ export function Backlog() {
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Droppable
|
||||
droppableId={data.id}
|
||||
isDropDisabled={false}>
|
||||
|
||||
{(provided) => (
|
||||
<div
|
||||
className="backlog-container"
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}>
|
||||
|
||||
{isMobile ? (
|
||||
<div className="mobileCloseMenu">
|
||||
<button onClick={onClickToggleMobileSideMenu}>
|
||||
<img src="/images/shop/icon-close-white.svg" alt="add"/>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="accordion accordion-flush" id="accordion_categories">
|
||||
{groups}
|
||||
</div>
|
||||
|
||||
{provided.placeholder && (
|
||||
<div style={{display: 'none'}}>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
<div className="accordion accordion-flush" id="accordion_categories">
|
||||
{groups}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ProductItem} from "./ProductItem";
|
||||
|
||||
|
||||
export function CatalogSearchResult() {
|
||||
const cards_to_display = useShopStore((state) => state.listed_cards);
|
||||
return ( <>
|
||||
{cards_to_display.map((item, _) => {
|
||||
return (
|
||||
<ProductItem card_index={item} key={"searched_" + item} />
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -3,10 +3,10 @@ import {Cart} from "./Cart";
|
|||
import {CrateMode} from "./CrateMode";
|
||||
import {CrateWarnings} from "./CrateWarnings";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {CrateOptions} from "./CrateOptions";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {CrateFanTray} from "./CrateFanTray";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ export function Crate({crate_index}) {
|
|||
|
||||
<CrateWarnings crate_index={crate_index} />
|
||||
|
||||
<CrateFanTray crate_index={crate_index}/>
|
||||
<CrateOptions crate_index={crate_index}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {Tip} from "./options/components/Tip";
|
||||
import {formatMoney} from "./utils";
|
||||
|
||||
export function CrateFanTray({crate_index}) {
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const fanTray = useShopStore((state) => state.fanTray);
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
||||
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
||||
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
||||
|
||||
const base_id = crate_id + "fan_tray";
|
||||
return fanTrayAvailable ? (
|
||||
<div className="crate-bar">
|
||||
<div className="shop-switch">
|
||||
<div className="form-check form-switch">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id={base_id}
|
||||
checked={fanTrayEnabled}
|
||||
onClick={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
||||
onChange={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={base_id} style={{"display": "inline", marginRight: "0.125rem"}}>
|
||||
{fanTray.optionTitle} (+{`${currency} ${formatMoney(fanTray.price)}`})
|
||||
</label>
|
||||
{fanTray.tip && <Tip id={base_id + "tooltip"} tip={fanTray.tip}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ProcessOptions} from "./options/Options";
|
||||
|
||||
export function CrateOptions({crate_index}) {
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const optionsLogic = useShopStore((state) => state.crate_options);
|
||||
const updateOptions = useShopStore((state) => state.updateCrateOptions);
|
||||
const options_data = useShopStore((state) => state.crates[crate_index].options_data || {});
|
||||
|
||||
const options = ProcessOptions({
|
||||
options: optionsLogic,
|
||||
data: options_data,
|
||||
id: "crate_options" + crate_id,
|
||||
target: {
|
||||
construct: ((outvar, value) => {
|
||||
// #!options_log
|
||||
console.log("construct", outvar, value, options_data);
|
||||
|
||||
options_data[outvar] = value;
|
||||
}),
|
||||
update: ((outvar, value) => {
|
||||
// #!options_log
|
||||
console.log("update", outvar, value, options_data);
|
||||
|
||||
if (outvar in options_data) options_data[outvar] = value;
|
||||
|
||||
updateOptions(crate_id, {[outvar]: value});
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="crate-bar">
|
||||
{options}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
export function GradientBottom() {
|
||||
return (
|
||||
<div className="gradient-bottom"></div>
|
||||
)
|
||||
}
|
|
@ -27,13 +27,16 @@ const JSONExample = JSON.stringify({
|
|||
"options": null
|
||||
}
|
||||
],
|
||||
"type": "rack"
|
||||
"type": "rack",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"items": [],
|
||||
"type": "no_crate"
|
||||
"type": "no_crate",
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
],
|
||||
"options": {}
|
||||
});
|
||||
|
||||
export function ImportJSON() {
|
||||
|
|
|
@ -11,6 +11,11 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
|
|||
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
|
||||
const options_class = useShopStore((state) => state.crates[crate_index].items[card_index].options_class);
|
||||
const sideMenuIsOpen = useShopStore((state) => state.sideMenuIsOpen);
|
||||
const _notificationTimer = useShopStore((state) => state.notificationTimer);
|
||||
const hideNotification = useShopStore((state) => state.hideNotification);
|
||||
const displayNotification = useShopStore((state) =>
|
||||
state.notificationCrateId === crate_id &&
|
||||
(state.notificationCardIndex === card_index || (state.crates[crate_index].items.length + (state.notificationCardIndex || -1)) === card_index));
|
||||
|
||||
const onOptionsUpdate = useShopStore((state) => state.updateOptions);
|
||||
|
||||
|
@ -25,6 +30,8 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
|
|||
first={first}
|
||||
last={last}
|
||||
sideMenuIsOpen={sideMenuIsOpen}
|
||||
onHideNotification={hideNotification}
|
||||
displayNotification={displayNotification}
|
||||
target={{
|
||||
construct: ((outvar, value) => {
|
||||
// #!options_log
|
||||
|
|
|
@ -5,6 +5,7 @@ import {ShowJSON} from "./ShowJSON";
|
|||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {OrderOptions} from "./OrderOptions";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -29,6 +30,8 @@ export function OrderForm() {
|
|||
return (
|
||||
<div className="summary-form">
|
||||
|
||||
<OrderOptions/>
|
||||
|
||||
<form onSubmit={submitForm} noValidate>
|
||||
|
||||
<input
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import {useShopStore} from "./shop_store";
|
||||
import {ProcessOptions} from "./options/Options";
|
||||
import React from "react";
|
||||
|
||||
export function OrderOptions() {
|
||||
const optionsLogic = useShopStore((state) => state.order_options);
|
||||
const updateOptions = useShopStore((state) => state.updateOrderOptions);
|
||||
const options_data = useShopStore((state) => state.order_options_data || {});
|
||||
|
||||
const options = ProcessOptions({
|
||||
options: optionsLogic,
|
||||
data: options_data,
|
||||
id: "order_options",
|
||||
target: {
|
||||
construct: ((outvar, value) => {
|
||||
// #!options_log
|
||||
console.log("construct", outvar, value, options_data);
|
||||
|
||||
options_data[outvar] = value;
|
||||
updateOptions({[outvar]: value});
|
||||
}),
|
||||
update: ((outvar, value) => {
|
||||
// #!options_log
|
||||
console.log("update", outvar, value, options_data);
|
||||
|
||||
if (outvar in options_data) options_data[outvar] = value;
|
||||
|
||||
updateOptions({[outvar]: value});
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="order-bar">
|
||||
{options}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import {OrderSummary} from "./OrderSummary";
|
||||
import {SummaryOrder} from "./SummaryOrder";
|
||||
import {OrderForm} from "./OrderForm";
|
||||
import {CrateList} from "./CrateList";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
@ -8,6 +8,7 @@ import {RFQFeedback} from "./RFQFeedback";
|
|||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {OrderOptions} from "./OrderOptions";
|
||||
|
||||
/**
|
||||
* Component that renders all things for order.
|
||||
|
@ -47,8 +48,7 @@ export function OrderPanel({title, description}) {
|
|||
<CrateList/>
|
||||
|
||||
<section className="summary">
|
||||
<OrderSummary/>
|
||||
|
||||
<SummaryOrder/>
|
||||
<OrderForm/>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
|
|||
|
||||
/**
|
||||
* Component that renders a product.
|
||||
* Used in the aside (e.g backlog of product)
|
||||
* Used in the aside (e.g catalog of product)
|
||||
*/
|
||||
export function ProductItem({card_index}) {
|
||||
// #!render_count
|
||||
|
@ -16,7 +16,7 @@ export function ProductItem({card_index}) {
|
|||
|
||||
const getCardDescription = useShopStore((state) => state.getCardDescription);
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const onAddCard = useShopStore((state) => state.addCardFromBacklog);
|
||||
const onAddCard = useShopStore((state) => state.addCardFromCatalog);
|
||||
const card = getCardDescription(card_index);
|
||||
|
||||
// #!render_count
|
||||
|
@ -44,7 +44,7 @@ export function ProductItem({card_index}) {
|
|||
<section className="productItem">
|
||||
|
||||
<div className="content">
|
||||
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name}</h3>
|
||||
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3>
|
||||
{card.name_codename ? (
|
||||
<p>{card.name_codename}</p>
|
||||
) : null}
|
||||
|
@ -62,7 +62,7 @@ export function ProductItem({card_index}) {
|
|||
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||
</button>
|
||||
|
||||
<Draggable draggableId={card.id} index={card_index}>
|
||||
<Draggable draggableId={card.id + card_index} index={card_index}>
|
||||
{(provided, snapshot) => (
|
||||
<React.Fragment>
|
||||
<img
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
export function SearchBar() {
|
||||
const search_bar_value = useShopStore((state) => state.search_bar_value);
|
||||
const updateSearchBar = useShopStore((state) => state.updateSearchBar);
|
||||
return (
|
||||
<div className="search-catalog form-outline">
|
||||
<input type="search"
|
||||
id="search_bar"
|
||||
className="search-catalog-input form-control"
|
||||
placeholder="Search"
|
||||
value={search_bar_value}
|
||||
onChange={event => updateSearchBar(event.target.value)}
|
||||
aria-label="Search"/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -6,7 +6,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
|
|||
|
||||
|
||||
import {Layout} from "./Layout";
|
||||
import {Backlog} from "./Backlog";
|
||||
import {Catalog} from "./Catalog";
|
||||
import {OrderPanel} from "./OrderPanel";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
|
@ -18,7 +18,8 @@ export function Shop() {
|
|||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const addCardFromBacklog = useShopStore((state) => state.addCardFromBacklog);
|
||||
const addCardFromCatalog = useShopStore((state) => state.addCardFromCatalog);
|
||||
const initExtData = useShopStore((state) => state.initExtData);
|
||||
const moveCard = useShopStore((state) => state.moveCard);
|
||||
const deleteCard = useShopStore((state) => state.deleteCard);
|
||||
const cardIndexById = useShopStore((state) => state.cardIndexById);
|
||||
|
@ -26,18 +27,20 @@ export function Shop() {
|
|||
const handleOnDragEnd = (drop_result, _provided) => {
|
||||
if (!drop_result.destination) {
|
||||
console.warn("No drop destination");
|
||||
console.log(drop_result)
|
||||
return;
|
||||
}
|
||||
if (drop_result.source.droppableId === "backlog")
|
||||
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
|
||||
else if (drop_result.destination.droppableId === "backlog")
|
||||
if (drop_result.source.droppableId === "catalog")
|
||||
addCardFromCatalog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
|
||||
else if (drop_result.destination.droppableId === "catalog")
|
||||
deleteCard(drop_result.source.droppableId, drop_result.source.index);
|
||||
else
|
||||
moveCard(drop_result.source.droppableId, drop_result.source.index, drop_result.destination.droppableId, drop_result.destination.index)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addCardFromBacklog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
|
||||
addCardFromCatalog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
|
||||
initExtData();
|
||||
}, []);
|
||||
|
||||
// #!render_count
|
||||
|
@ -47,7 +50,7 @@ export function Shop() {
|
|||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||
<Layout
|
||||
aside={
|
||||
<Backlog/>
|
||||
<Catalog/>
|
||||
}
|
||||
main={(
|
||||
<OrderPanel
|
||||
|
@ -59,7 +62,7 @@ export function Shop() {
|
|||
the card; many of the options can be adjusted even after the card has been shipped.
|
||||
If you have any issues with this ordering system, or if you need other configurations,
|
||||
email us directly anytime at <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>.
|
||||
The price is estimated and must be confirmed by a quote.
|
||||
The price excludes shipping, is estimated, and must be confirmed by a quote.
|
||||
</p>
|
||||
)}
|
||||
/>
|
||||
|
@ -68,4 +71,4 @@ export function Shop() {
|
|||
</DragDropContext>
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,12 +79,17 @@ export function ShowJSON() {
|
|||
placeholder="There should be description of the crate"/>
|
||||
|
||||
<div className="d-flex flex-column flex-sm-row justify-content-end">
|
||||
<a type="button"
|
||||
onClick={() => {copyToClipboard(description)}}
|
||||
className={"btn btn-sm m-0 mb-1 mt-2 mb-sm-0 me-sm-2 " + copyButtonStates[copiedState].style}
|
||||
>
|
||||
{ copyButtonStates[copiedState].content }
|
||||
</a>
|
||||
{window.isSecureContext && (
|
||||
<a type="button"
|
||||
onClick={() => {
|
||||
copyToClipboard(description)
|
||||
}}
|
||||
className={"btn btn-sm m-0 mb-1 mt-2 mb-sm-0 me-sm-2 " + copyButtonStates[copiedState].style}
|
||||
>
|
||||
{copyButtonStates[copiedState].content}
|
||||
</a>
|
||||
)}
|
||||
|
||||
<a type="button" onClick={closeDescription}
|
||||
className="btn btn-sm btn-outline-primary m-0 mb-1 mt-2 mb-sm-0 me-sm-2">Close</a>
|
||||
</div>
|
||||
|
|
|
@ -3,10 +3,11 @@ import React from "react";
|
|||
import {useShopStore} from "./shop_store";
|
||||
import {SummaryCrateHeader} from "./SummaryCrateHeader";
|
||||
import {SummaryCrateCard} from "./SummaryCrateCard";
|
||||
import {SummaryCratePricedOptions} from "./SummaryCratePricedOptions";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {SummaryCrateFanTray} from "./SummaryCrateFanTray";
|
||||
|
||||
|
||||
export function SummaryCrate({crate_index}) {
|
||||
// #!render_count
|
||||
|
@ -26,7 +27,8 @@ export function SummaryCrate({crate_index}) {
|
|||
{range(0, crate_len).map((index, _i) =>
|
||||
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
|
||||
)}
|
||||
<SummaryCrateFanTray crate_index={crate_index}/>
|
||||
|
||||
<SummaryCratePricedOptions crate_index={crate_index}/>
|
||||
</tbody>
|
||||
)
|
||||
}
|
|
@ -41,11 +41,7 @@ export function SummaryCrateCard({crate_index, card_index}) {
|
|||
onClick={() => setHighlight(crate_id, card_index)}
|
||||
onMouseEnter={() => setHighlight(crate_id, card_index)}
|
||||
onMouseLeave={() => resetHighlight()}>
|
||||
<td className="item-card-name">
|
||||
<span style={{
|
||||
'display': 'inline-block',
|
||||
'width': '16px',
|
||||
}}> </span>
|
||||
<td className="item-card-name tabbed">
|
||||
<div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import {formatMoney} from "./utils";
|
||||
import React from "react";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
|
||||
export function SummaryCrateFanTray({crate_index}) {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const fanTray = useShopStore((state) => state.fanTray);
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
||||
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
||||
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
||||
|
||||
|
||||
// #!render_count
|
||||
console.log("SummaryCrateCard renders: ", renderCount)
|
||||
|
||||
return (fanTrayAvailable && fanTrayEnabled) ? (<tr
|
||||
key={"summary_crate_" + crate_id + "_fan_tray"}>
|
||||
<td className="item-card-name">
|
||||
<span style={{
|
||||
'display': 'inline-block', 'width': '16px',
|
||||
}}> </span>
|
||||
<div>{fanTray.optionTitle}</div>
|
||||
</td>
|
||||
|
||||
<td className="price">
|
||||
<div className="d-inline-flex align-content-center">
|
||||
{`${currency} ${formatMoney(fanTray.price)}`}
|
||||
|
||||
<button onClick={() => updateFanTray(crate_id, false)}>
|
||||
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
||||
</button>
|
||||
|
||||
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>) : null;
|
||||
}
|
|
@ -4,7 +4,6 @@ import {useShopStore} from "./shop_store";
|
|||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {CrateMode} from "./CrateMode";
|
||||
|
||||
export function SummaryCrateHeader({crate_index}) {
|
||||
// #!render_count
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import {formatMoney} from "./utils";
|
||||
import React from "react";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ProcessOptionsToData} from "./options/Options";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
export function SummaryCratePricedOptions({crate_index}) {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const optionsPrices = useShopStore((state) => state.crate_prices);
|
||||
const updateOptions = useShopStore((state) => state.updateCrateOptions);
|
||||
const options_data = useShopStore((state) => state.crates[crate_index].options_data || {});
|
||||
|
||||
const options = ProcessOptionsToData({options: optionsPrices, data: options_data});
|
||||
|
||||
// #!render_count
|
||||
console.log("SummaryCratePricedOptions renders: ", renderCount)
|
||||
|
||||
return options.map((option, _i) => (
|
||||
<tr key={"summary_crate_" + crate_id +"option_" + option.id}>
|
||||
<td className="item-card-name tabbed">
|
||||
<div>{option.title}</div>
|
||||
</td>
|
||||
|
||||
<td className="price">
|
||||
<div className="d-inline-flex align-content-center">
|
||||
{`${currency} ${formatMoney(option.price)}`}
|
||||
|
||||
<button onClick={() => updateOptions(crate_id, option.disable_patch)}>
|
||||
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
||||
</button>
|
||||
|
||||
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
}
|
|
@ -5,6 +5,8 @@ import {SummaryCrate} from "./SummaryCrate";
|
|||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
import {SummaryOrderPricedOptions} from "./SummaryOrderPricedOptions";
|
||||
import {SummaryOrderShipping} from "./SummaryOrderShipping";
|
||||
|
||||
export function SummaryCrates() {
|
||||
// #!render_count
|
||||
|
@ -20,6 +22,8 @@ export function SummaryCrates() {
|
|||
{range(0, crates_l).map((index, _i) => {
|
||||
return <SummaryCrate crate_index={index} key={"summary_crate_body_" + index} />
|
||||
})}
|
||||
<SummaryOrderPricedOptions/>
|
||||
<SummaryOrderShipping/>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {SummaryCrates} from "./SummaryCrates";
|
||||
import {SummaryTotalPrice} from "./SummaryTotalPrice";
|
||||
|
||||
|
@ -11,12 +10,12 @@ import {useRenderCount} from "@uidotdev/usehooks";
|
|||
* Components that displays the list of card that are used in the crate.
|
||||
* It is a summary of purchase
|
||||
*/
|
||||
export function OrderSummary() {
|
||||
export function SummaryOrder() {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
// #!render_count
|
||||
console.log("OrderSummary renders: ", renderCount)
|
||||
console.log("SummaryOrder renders: ", renderCount)
|
||||
|
||||
return (
|
||||
<div className="summary-price">
|
|
@ -0,0 +1,43 @@
|
|||
import {formatMoney} from "./utils";
|
||||
import React from "react";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ProcessOptionsToData} from "./options/Options";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
export function SummaryOrderPricedOptions() {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const optionsPrices = useShopStore((state) => state.order_prices);
|
||||
const updateOptions = useShopStore((state) => state.updateOrderOptions);
|
||||
const options_data = useShopStore((state) => state.order_options_data);
|
||||
|
||||
const options = ProcessOptionsToData({options: optionsPrices, data: options_data});
|
||||
|
||||
// #!render_count
|
||||
console.log("SummaryOrderPricedOptions renders: ", renderCount)
|
||||
|
||||
return <tbody key={"summary_order_body"}>
|
||||
{options.map((option, _i) => (
|
||||
<tr key={"summary_order" + "option_" + option.id}>
|
||||
<td className="item-card-name">
|
||||
<div>{option.title}</div>
|
||||
</td>
|
||||
<td className="price">
|
||||
<div className="d-inline-flex align-content-center">
|
||||
{`${currency} ${formatMoney(option.price)}`}
|
||||
|
||||
<button onClick={() => updateOptions(option.disable_patch)}>
|
||||
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
||||
</button>
|
||||
|
||||
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import {formatMoney} from "./utils";
|
||||
import React from "react";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ProcessOptions, ProcessOptionsToData} from "./options/Options";
|
||||
|
||||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
export function SummaryOrderShipping() {
|
||||
// #!render_count
|
||||
const renderCount = useRenderCount();
|
||||
|
||||
const shipping_summary = useShopStore((state) => state.shipping_summary);
|
||||
const options_data = useShopStore((state) => state.order_options_data);
|
||||
|
||||
// #!render_count
|
||||
console.log("SummaryOrderShipping renders: ", renderCount)
|
||||
|
||||
const options = ProcessOptions({
|
||||
options: shipping_summary,
|
||||
data: options_data,
|
||||
id: "shipping_options",
|
||||
target: null,
|
||||
});
|
||||
|
||||
return <tbody key="summary_shipping_order_body">
|
||||
{options.map((option, i) => (
|
||||
<tr key={"summary_shipping_order_option_" + i} id={"summary_shipping_order_option_" + i}>
|
||||
<td className="item-card-name" key={"summary_shipping_order_key_option_" + i} id={"summary_shipping_order_key_option_" + i}>
|
||||
{option}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>;
|
||||
}
|
|
@ -4,7 +4,7 @@ import React from "react";
|
|||
|
||||
export function SummaryTotalPrice() {
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const total_price = useShopStore((state) => state.totalOrderPrice());
|
||||
const total_price = useShopStore((state) => state.total_order_price);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -5,20 +5,23 @@ import {v4 as uuidv4} from "uuid";
|
|||
|
||||
export function validateJSON(description) {
|
||||
let crates_raw;
|
||||
let order_options;
|
||||
try {
|
||||
const parsed = JSON.parse(description);
|
||||
// here we can check additional fields
|
||||
crates_raw = parsed.crates;
|
||||
order_options = parsed.options;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (!order_options) return false;
|
||||
const crate_modes = useShopStore.getState().crate_modes;
|
||||
const modes_order = useShopStore.getState().modes_order;
|
||||
const pn_to_card = useShopStore.getState().pn_to_cards;
|
||||
|
||||
try {
|
||||
for (const crate of crates_raw) {
|
||||
if (!crate.type || !crate.items || !(crate.type in crate_modes)) return false;
|
||||
if (!crate.type || !crate.items || !crate.options || !(crate.type in crate_modes)) return false;
|
||||
for (const card of crate.items) {
|
||||
if (!(card.pn in pn_to_card) || card.options === undefined) return false;
|
||||
}
|
||||
|
@ -36,12 +39,11 @@ export function JSONToCrates(description) {
|
|||
const parsed = JSON.parse(description);
|
||||
const crates_raw = parsed.crates;
|
||||
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
||||
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
||||
|
||||
const crates = Array.from(crates_raw.map((crate, c_i) => ({
|
||||
id: crate.type === "no_crate" ? "spare" : "crate" + c_i,
|
||||
name: crate.type === "no_crate" ? "Spare cards" : undefined,
|
||||
fan_tray: fanTrayAvailable(crate.type) && crate.fan_tray === true,
|
||||
options_data: crate.options,
|
||||
crate_mode: crate.type,
|
||||
items: Array.from(crate.items.map((card, _i) => ({
|
||||
...pn_to_card(card.pn),
|
||||
|
@ -54,12 +56,15 @@ export function JSONToCrates(description) {
|
|||
|
||||
return {
|
||||
// some additional fields go here
|
||||
order_options_data: parsed.options,
|
||||
crates: crates
|
||||
};
|
||||
}
|
||||
|
||||
export function CratesToJSON(crates) {
|
||||
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
||||
const crateOptions = useShopStore.getState().crate_options;
|
||||
const orderOptions = useShopStore.getState().order_options;
|
||||
const orderOptionsData = useShopStore.getState().order_options_data;
|
||||
return JSON.stringify({
|
||||
// additional fields can go here
|
||||
crates: Array.from(crates.map((crate, _i) => ({
|
||||
|
@ -68,7 +73,8 @@ export function CratesToJSON(crates) {
|
|||
options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
|
||||
}))),
|
||||
type: crate.crate_mode,
|
||||
fan_tray: (!fanTrayAvailable(crate.crate_mode) ? undefined : true) && crate.fan_tray
|
||||
})))
|
||||
options: FilterOptions(crateOptions, crate.options_data)
|
||||
}))),
|
||||
options: FilterOptions(orderOptions, orderOptionsData)
|
||||
}, null, 2)
|
||||
}
|
|
@ -3,7 +3,8 @@ import {useClickAway} from "./useClickAway";
|
|||
import {ProcessOptions} from "./Options";
|
||||
import {Notification} from "./Notification";
|
||||
|
||||
export function DialogPopup({options, data, target, id, big, first, last, options_class, sideMenuIsOpen}) {
|
||||
export function DialogPopup({options, data, target, id, big, first, last, options_class,
|
||||
sideMenuIsOpen, displayNotification, onHideNotification}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const ref = useClickAway((e) => {
|
||||
if (e.type === "mousedown") // ignore touchstart
|
||||
|
@ -22,8 +23,11 @@ export function DialogPopup({options, data, target, id, big, first, last, option
|
|||
id={"processed_options_notification" + id}
|
||||
tip="Customization options available"
|
||||
sideMenuIsOpen={sideMenuIsOpen}
|
||||
show={displayNotification}
|
||||
onHide={onHideNotification}
|
||||
content={
|
||||
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
||||
<img className="alert-info"
|
||||
src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
||||
onClick={handleClick}/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,30 +1,21 @@
|
|||
import {OverlayTrigger, Tooltip} from "react-bootstrap";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
export function Notification({id, tip, content, sideMenuIsOpen}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShow(true)
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
setShow(false)
|
||||
}, 5000);
|
||||
}, []);
|
||||
import React from "react";
|
||||
|
||||
export function Notification({id, tip, content, sideMenuIsOpen, show, onHide}) {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
trigger={["click", "hover"]}
|
||||
style={{display: 'inline'}}
|
||||
show={show}
|
||||
onToggle={() => setShow(false)}
|
||||
overlay={<Tooltip id={id}>{tip}</Tooltip>}
|
||||
rootClose={!sideMenuIsOpen}
|
||||
>
|
||||
{content}
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
trigger={["click", "hover"]}
|
||||
style={{display: 'inline'}}
|
||||
show={show}
|
||||
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
|
||||
rootClose={!sideMenuIsOpen}
|
||||
onToggle={onHide}
|
||||
popperConfig={{
|
||||
strategy: 'fixed'
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
|
@ -24,7 +24,7 @@ export function ProcessOptions({options, data, target, id}) {
|
|||
return componentsList[options.type](target, id + options.type, data, options.args);
|
||||
} else if (options.type === "Group") {
|
||||
return (
|
||||
<div className="border rounded" key={id + "group"}>
|
||||
<div className="border rounded options-group" key={id + "group"}>
|
||||
{ProcessOptions({
|
||||
options: json_logic_apply(options.items, data),
|
||||
data: data,
|
||||
|
@ -41,3 +41,23 @@ export function ProcessOptions({options, data, target, id}) {
|
|||
}
|
||||
}
|
||||
|
||||
export function ProcessOptionsToData({options, data}) {
|
||||
let options_t = true_type_of(options);
|
||||
if (options_t === "array") {
|
||||
return Array.from(
|
||||
options.map((option_item, _i) => ProcessOptionsToData({
|
||||
options: option_item,
|
||||
data: data,
|
||||
}))
|
||||
).filter((item, _i) => !!item).flat();
|
||||
} else if (options_t === "object") {
|
||||
if (true_type_of(options.title) === "string") {
|
||||
return options;
|
||||
} else {
|
||||
return ProcessOptionsToData({options: json_logic_apply(options, data), data: data});
|
||||
}
|
||||
} else {
|
||||
//throw Error("Incompatible type for the option: " + options_t)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import {apply as json_logic_apply, add_operation as json_add_operation} from "json-logic-js";
|
||||
|
||||
|
||||
json_add_operation("lower", (some_str) => some_str && some_str.toLowerCase(some_str));
|
||||
json_add_operation("upper", (some_str) => some_str && some_str.toUpperCase(some_str));
|
||||
json_add_operation("capitalize", (some_str) => some_str && some_str.capitalizeFirstLetter(some_str));
|
||||
|
||||
export function Label(target, id, data, {content}) {
|
||||
const resulting_string = json_logic_apply(content, data).flat().join("");
|
||||
|
||||
return (
|
||||
<div id={id+"label"} key={id+"label"} className="options-label">
|
||||
{resulting_string}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,26 +1,38 @@
|
|||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip";
|
||||
import {Validation} from "../validation";
|
||||
|
||||
class Line extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : "")
|
||||
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : ""),
|
||||
valid: true
|
||||
};
|
||||
// Bind the event handler to this
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.props.target.construct(this.props.outvar, this.state.text);
|
||||
}
|
||||
|
||||
handleClick(element) {
|
||||
handleChange(element) {
|
||||
let text = element.target.value;
|
||||
this.setState({
|
||||
text: text
|
||||
text: text,
|
||||
valid: this.props.validator ? this.props.validator(text) : true
|
||||
});
|
||||
this.props.target.update(this.props.outvar, text);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, current_state) {
|
||||
if (current_state.text !== props.data[props.outvar]) {
|
||||
return {
|
||||
text: props.data[props.outvar]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
|
@ -30,14 +42,14 @@ class Line extends Component {
|
|||
{this.props.title}:
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
<input type="text" className="form-control form-control-sm" id={key} onChange={this.handleClick}
|
||||
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key} onChange={this.handleChange}
|
||||
value={this.state.text}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
export function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes, validator}) {
|
||||
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
|
||||
}
|
|
@ -23,38 +23,49 @@ class Radio extends Component {
|
|||
this.props.target.update(this.props.outvar, variant);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, current_state) {
|
||||
if (current_state.variant !== props.data[props.outvar]) {
|
||||
return {
|
||||
variant: props.data[props.outvar]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
<div className="shop-radio" key={this.props.id}>
|
||||
<div style={{"display": "inline"}}>
|
||||
<div style={{"display": "inline"}} className="shop-radio-label">
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
</div>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
{this.props.variants.map((variant, _) => (
|
||||
<div className="form-check" key={key + variant}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name={key}
|
||||
id={key + variant}
|
||||
checked={this.state.variant === variant}
|
||||
onClick={() => this.handleClick(variant)}
|
||||
onChange={() => this.handleClick(variant)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={key + variant}>
|
||||
{variant}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="d-block">
|
||||
{this.props.variants.map((variant, _) => (
|
||||
<div className={`form-check shop-radio-variant ${this.props.classes}`} key={key + variant}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name={key}
|
||||
id={key + variant}
|
||||
checked={this.state.variant === variant}
|
||||
onClick={() => this.handleClick(variant)}
|
||||
onChange={() => this.handleClick(variant)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={key + variant}>
|
||||
{variant}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip}) {
|
||||
export function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip, classes}) {
|
||||
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
fallback={fallback}
|
||||
fallback={fallback} classes={classes}
|
||||
id={id} data={data}/>;
|
||||
}
|
|
@ -23,6 +23,15 @@ class Switch extends Component {
|
|||
this.props.target.update(this.props.outvar, new_checked);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, current_state) {
|
||||
if (current_state.checked !== props.data[props.outvar]) {
|
||||
return {
|
||||
checked: props.data[props.outvar]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
|
@ -48,7 +57,7 @@ class Switch extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
export function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
|
||||
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
id={id} data={data} classes={classes}/>;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip";
|
||||
import {Validation} from "../validation";
|
||||
|
||||
class SwitchLine extends Component {
|
||||
constructor(props) {
|
||||
|
@ -7,7 +8,8 @@ class SwitchLine extends Component {
|
|||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
text: props.outvar in props.data ? props.data[props.outvar].text : (props.fallback ? props.fallback.text : ""),
|
||||
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false)
|
||||
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false),
|
||||
valid: true
|
||||
};
|
||||
// Bind the event handler to this
|
||||
this.handleText = this.handleText.bind(this);
|
||||
|
@ -18,7 +20,8 @@ class SwitchLine extends Component {
|
|||
handleText(element) {
|
||||
let new_state = {
|
||||
...this.state,
|
||||
text: element.target.value
|
||||
text: element.target.value,
|
||||
valid: this.props.validator ? this.props.validator(element.target.value) : true
|
||||
}
|
||||
this.setState(new_state);
|
||||
this.props.target.update(this.props.outvar, new_state);
|
||||
|
@ -34,6 +37,17 @@ class SwitchLine extends Component {
|
|||
this.props.target.update(this.props.outvar, new_state);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, current_state) {
|
||||
if (current_state.checked !== props.data[props.outvar].checked || current_state.text !== props.data[props.outvar].text) {
|
||||
return {
|
||||
checked: props.data[props.outvar].checked,
|
||||
text: props.data[props.outvar].text,
|
||||
valid: this.props.validator ? this.props.validator(props.data[props.outvar].text) : true
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
|
@ -54,14 +68,14 @@ class SwitchLine extends Component {
|
|||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</div>
|
||||
<input type="text" className="form-control form-control-sm" id={key + "line"} onChange={this.handleText}
|
||||
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key + "line"} onChange={this.handleText}
|
||||
value={this.state.text} disabled={!this.state.checked}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
export function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes, validator}) {
|
||||
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export function Tip({id, tip}) {
|
|||
placement="auto"
|
||||
trigger={['click', 'hover', 'focus']}
|
||||
style={{display: 'inline'}}
|
||||
overlay={<Tooltip id={id}>{tip}</Tooltip>}
|
||||
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
|
||||
rootClose
|
||||
>
|
||||
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
|
||||
|
|
|
@ -5,6 +5,7 @@ import {RadioWrapper} from "./Radio";
|
|||
import {SwitchWrapper} from "./Switch";
|
||||
import {SwitchLineWrapper} from "./SwitchLine";
|
||||
import {UnimplementedComponent} from "./UnimplementedComponent";
|
||||
import {Label} from "./Label";
|
||||
|
||||
|
||||
// Class components are used because we cannot use hooks for updating the state
|
||||
|
@ -13,5 +14,6 @@ export const componentsList = {
|
|||
"Switch": SwitchWrapper,
|
||||
"Line": LineWrapper,
|
||||
"SwitchLine": SwitchLineWrapper,
|
||||
"Label": Label,
|
||||
"Default": UnimplementedComponent,
|
||||
};
|
|
@ -4,7 +4,7 @@ import {componentsList} from "./components/components";
|
|||
// https://stackoverflow.com/a/70511311
|
||||
export const true_type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
|
||||
|
||||
export function FillExtData(data, index) {
|
||||
export function FillExtCardData(data, index) {
|
||||
return {
|
||||
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name &&value.name.endsWith("-TTL")).length > 0,
|
||||
has_dds: data.filter(((value, _) => value.name === "DDS" && value.name_number === "4410" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
|
||||
|
@ -12,6 +12,18 @@ export function FillExtData(data, index) {
|
|||
}
|
||||
}
|
||||
|
||||
export function FillExtCrateData(crate) {
|
||||
return {
|
||||
crate_mode: crate.crate_mode
|
||||
}
|
||||
}
|
||||
|
||||
export function FillExtOrderData(crates, modes_order) {
|
||||
return {
|
||||
has_crate: crates.filter((crate) => modes_order.includes(crate.crate_mode)).length >= 1,
|
||||
}
|
||||
}
|
||||
|
||||
export function FilterOptions(options, data) {
|
||||
let options_t = true_type_of(options);
|
||||
let target = {};
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
|
||||
const ipv4 = (params) => {
|
||||
const ipv4WithMaskPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(0|1[0-9]|2[0-9]|3[0-2]|[0-9])$/;
|
||||
return (text) => {
|
||||
return ipv4WithMaskPattern.test(text);
|
||||
}
|
||||
}
|
||||
|
||||
const ipv6 = (params) => {
|
||||
const ipv6WithMaskPattern = /(^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(\/(\d{1,2}|1[0-1]\d|12[0-8]))(%.+)?\s*$)/;
|
||||
|
||||
|
||||
return (text) => {
|
||||
return ipv6WithMaskPattern.test(text);
|
||||
}
|
||||
}
|
||||
|
||||
const ipv4or6 = (params) => {
|
||||
const ipv4Local = ipv4(params);
|
||||
const ipv6Local = ipv6(params);
|
||||
return (text) => {
|
||||
return ipv4Local(text) || ipv6Local(text);
|
||||
}
|
||||
}
|
||||
|
||||
const frequency = (params) => {
|
||||
const factors = {
|
||||
"mhz": 1e6,
|
||||
"khz": 1e3,
|
||||
"hz": 1e1,
|
||||
"ghz": 1e9,
|
||||
};
|
||||
|
||||
return (text) => {
|
||||
const splited = text.split(/(\s+)/);
|
||||
const numerator = parseFloat(splited[0]);
|
||||
if (splited.length !== 3 || isNaN(numerator)) return false;
|
||||
const factor = factors[splited[2].toLowerCase()];
|
||||
if (!factor) return false;
|
||||
const realFreq = factor * numerator;
|
||||
return realFreq >= (params.min || 10*factors.mhz) && realFreq <= (params.max || 1*factors.ghz);
|
||||
}
|
||||
}
|
||||
|
||||
export const Validation = {
|
||||
ipv4: ipv4,
|
||||
ipv6: ipv6,
|
||||
ipv4or6: ipv4or6,
|
||||
frequency: frequency
|
||||
};
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import {createWithEqualityFn} from "zustand/traditional";
|
||||
import {data as shared_data, itemsUnfoldedList} from "./utils";
|
||||
import {true_type_of} from "./options/utils";
|
||||
import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {FillResources} from "./count_resources";
|
||||
import {FillExtData} from "./options/utils";
|
||||
import {FillExtCardData} from "./options/utils";
|
||||
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
||||
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
|
||||
import {CratesToJSON, JSONToCrates} from "./json_porter";
|
||||
import {ProcessOptionsToData} from "./options/Options";
|
||||
|
||||
|
||||
const cards_to_pn_map = (cards) => {
|
||||
|
@ -17,9 +18,9 @@ const cards_to_pn_map = (cards) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const useBacklog = ((set, get) => ({
|
||||
const useCatalog = ((set, get) => ({
|
||||
cards: shared_data.items,
|
||||
groups: shared_data.columns.backlog,
|
||||
groups: shared_data.columns.catalog,
|
||||
cards_list: itemsUnfoldedList,
|
||||
currency: shared_data.currency,
|
||||
pn_to_cards: cards_to_pn_map(shared_data.items),
|
||||
|
@ -28,17 +29,118 @@ const useBacklog = ((set, get) => ({
|
|||
cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
|
||||
}));
|
||||
|
||||
const useOptionsNotification = ((set, get) => ({
|
||||
notificationCrateId: null,
|
||||
notificationCardIndex: null,
|
||||
notificationTimer: null,
|
||||
_showNotification: (crate_id, card_index) => set(state => ({
|
||||
notificationCrateId: crate_id,
|
||||
notificationCardIndex: card_index,
|
||||
notificationTimer: setTimeout(() => {
|
||||
state.hideNotification()
|
||||
}, 5000)
|
||||
})),
|
||||
showNotification: (crate_id, card_index) => {
|
||||
get().hideNotification()
|
||||
setTimeout(() => get()._showNotification(crate_id, card_index), 100);
|
||||
},
|
||||
hideNotification: () => set(state => ({
|
||||
notificationCrateId: null,
|
||||
notificationCardIndex: null,
|
||||
notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null,
|
||||
}))
|
||||
}));
|
||||
|
||||
const useSearch = ((set, get) => ({
|
||||
search_index: Array.from(Object.values(shared_data.items)
|
||||
.map((card, _) => (
|
||||
[(card.name + " " + card.name_number + " " + card.name_codename).toLowerCase(), card.id]
|
||||
))),
|
||||
search_bar_value: "",
|
||||
listed_cards: [],
|
||||
updateSearchBar: text => set(state => ({
|
||||
search_bar_value: text,
|
||||
listed_cards: text.length > 0 ? Array.from(get().search_index
|
||||
.filter((card, _) => card[0].includes(text.toLowerCase()))
|
||||
.map(([index, card_id], _) => get().cards_list.findIndex(elem => elem === card_id))) : []
|
||||
}))
|
||||
}));
|
||||
|
||||
const useCrateModes = ((set, get) => ({
|
||||
crate_modes: shared_data.crateModes,
|
||||
modes_order: shared_data.crateModeOrder,
|
||||
crateParams: mode => get().crate_modes[mode],
|
||||
}));
|
||||
|
||||
const useFanTray = ((set, get) => ({
|
||||
fanTray: shared_data.fanTray,
|
||||
fanTrayAvailableForMode: (crate_mode) => {
|
||||
return get().fanTray.crateModesAvailable[crate_mode] === true;
|
||||
},
|
||||
const useCrateOptions = ((set, get) => ({
|
||||
crate_options: shared_data.crateOptions.options,
|
||||
crate_prices: shared_data.crateOptions.prices,
|
||||
|
||||
fillExtCrateData: (crate_id) => set(state => ({
|
||||
crates: state.crates.map((crate, _i) => {
|
||||
if (crate_id === crate.id) {
|
||||
const previous_options = crate.options_data || {};
|
||||
return {
|
||||
...crate,
|
||||
options_data: {
|
||||
...previous_options,
|
||||
ext_data: FillExtCrateData(crate)
|
||||
}
|
||||
}
|
||||
}
|
||||
else return crate;
|
||||
})
|
||||
})),
|
||||
|
||||
_updateCrateOption: (crate_id, new_options) => set(state => ({
|
||||
crates: state.crates.map((crate, _i) => {
|
||||
if (crate_id === crate.id) {
|
||||
const previous_options = crate.options_data || {};
|
||||
return {
|
||||
...crate,
|
||||
options_data: {
|
||||
...previous_options,
|
||||
...new_options
|
||||
}
|
||||
}
|
||||
}
|
||||
else return crate;
|
||||
})
|
||||
})),
|
||||
|
||||
updateCrateOptions: (crate_id, new_options) => {
|
||||
get().fillExtCrateData(crate_id);
|
||||
get().fillOrderExtData();
|
||||
get()._updateCrateOption(crate_id, new_options);
|
||||
get()._updateTotalOrderPrice();
|
||||
}
|
||||
}));
|
||||
|
||||
const useOrderOptions = ((set, get) => ({
|
||||
order_options: shared_data.orderOptions.options,
|
||||
order_prices: shared_data.orderOptions.prices,
|
||||
shipping_summary: shared_data.orderOptions.shippingSummary,
|
||||
order_options_data: {},
|
||||
|
||||
fillOrderExtData: () => set(state => ({
|
||||
order_options_data: {
|
||||
...state.order_options_data,
|
||||
ext_data: FillExtOrderData(state.crates, state.modes_order)
|
||||
}
|
||||
})),
|
||||
|
||||
_updateOrderOptions: (new_options) => set(state => ({
|
||||
order_options_data: {
|
||||
...state.order_options_data,
|
||||
...new_options
|
||||
}
|
||||
})),
|
||||
|
||||
updateOrderOptions: (new_options) => {
|
||||
get()._updateOrderOptions(new_options);
|
||||
get()._updateTotalOrderPrice();
|
||||
get().fillOrderExtData();
|
||||
}
|
||||
}));
|
||||
|
||||
const useLayout = ((set, get) => ({
|
||||
|
@ -49,9 +151,15 @@ const useLayout = ((set, get) => ({
|
|||
showNoDestination: false,
|
||||
timerAdded: null,
|
||||
|
||||
switchSideMenu: () => set(state => ({
|
||||
_switchSideMenu: () => set(state => ({
|
||||
sideMenuIsOpen: !state.sideMenuIsOpen
|
||||
})),
|
||||
switchSideMenu: () => {
|
||||
if (!get().sideMenuIsOpen) {
|
||||
get().hideNotification()
|
||||
}
|
||||
get()._switchSideMenu();
|
||||
},
|
||||
cardAdded: () => set(state => ({
|
||||
showCardAddedFeedback: true,
|
||||
showNoDestination: false,
|
||||
|
@ -88,16 +196,21 @@ const useImportJSON = ((set, get) => ({
|
|||
const parsed = JSONToCrates(state.importValue.value);
|
||||
// if (parsed.crates[-1].crate_mode !== "")
|
||||
return {
|
||||
importShouldOpen: false,
|
||||
// additional fields go here
|
||||
crates: parsed.crates
|
||||
importShouldOpen: false,
|
||||
// additional fields go here
|
||||
crates: parsed.crates,
|
||||
order_options_data: parsed.order_options_data
|
||||
}}),
|
||||
loadDescription: () => {
|
||||
get()._loadDescription()
|
||||
get().fillOrderExtData();
|
||||
get().crates.forEach((crate, _i) => {
|
||||
get().fillExtData(crate.id)
|
||||
get().fillWarnings(crate.id)
|
||||
})
|
||||
get().fillExtData(crate.id);
|
||||
get().fillWarnings(crate.id);
|
||||
get().fillExtCrateData(crate.id);
|
||||
});
|
||||
get()._updateTotalOrderPrice();
|
||||
get().showNotification(get().active_crate, null);
|
||||
},
|
||||
updateImportDescription: (new_description) => set(state => ({
|
||||
importValue: {
|
||||
|
@ -246,6 +359,7 @@ const useCart = ((set, get) => ({
|
|||
crates: shared_data.columns.crates,
|
||||
active_crate: "crate0",
|
||||
_defaultCrates: Array.from(shared_data.columns.crates),
|
||||
total_order_price: 0,
|
||||
|
||||
_newCrate: (crate_id) => set((state) => ({
|
||||
crates: state.crates.toSpliced(-1, 0, {
|
||||
|
@ -254,7 +368,7 @@ const useCart = ((set, get) => ({
|
|||
}),
|
||||
active_crate: crate_id || "crate" + state.crates.length
|
||||
})),
|
||||
delCrate: (id) => set(state => ({
|
||||
_delCrate: (id) => set(state => ({
|
||||
crates: state.crates.filter((crate => crate.id !== id || !state.modes_order.includes(crate.crate_mode))),
|
||||
active_crate: state.active_crate === id ? null : state.active_crate,
|
||||
})),
|
||||
|
@ -269,7 +383,7 @@ const useCart = ((set, get) => ({
|
|||
})
|
||||
})),
|
||||
setActiveCrate: (id) => set(state => ({active_crate: id})),
|
||||
_addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
|
||||
_addCardFromCatalog: (crate_to, index_from, index_to) => set(state => {
|
||||
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
|
||||
const dest = crate_to || state.active_crate;
|
||||
if (!dest) return {};
|
||||
|
@ -386,7 +500,7 @@ const useCart = ((set, get) => ({
|
|||
itemsCopy = itemsCopy.map((item, index) => {
|
||||
if (!item.options) return item;
|
||||
if (!item.options_data) item.options_data = {};
|
||||
item.options_data.ext_data = FillExtData(itemsCopy, index);
|
||||
item.options_data.ext_data = FillExtCardData(itemsCopy, index);
|
||||
return item;
|
||||
});
|
||||
return {
|
||||
|
@ -398,17 +512,20 @@ const useCart = ((set, get) => ({
|
|||
})
|
||||
})),
|
||||
|
||||
totalOrderPrice: () => {
|
||||
_updateTotalOrderPrice: () => set(state => {
|
||||
let sum = 0;
|
||||
get().crates.forEach( (crate, i) => {
|
||||
sum += get().crate_modes[crate.crate_mode].price;
|
||||
sum += (crate.fan_tray && get().fanTrayAvailableByIndex(i)) ? get().fanTray.price : 0;
|
||||
const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}});
|
||||
sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
|
||||
crate.items.forEach((item, _) => {
|
||||
sum += item.price;
|
||||
});
|
||||
});
|
||||
return sum;
|
||||
},
|
||||
const order_options = ProcessOptionsToData({options: get().order_prices, data: get().order_options_data || {}});
|
||||
sum += order_options ? order_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
|
||||
return {total_order_price: sum};
|
||||
}),
|
||||
|
||||
// Composite actions that require warnings recalculation:
|
||||
|
||||
|
@ -416,27 +533,40 @@ const useCart = ((set, get) => ({
|
|||
const crate_id = "crate" + get().crates.length;
|
||||
get()._newCrate(crate_id)
|
||||
get().fillExtData(crate_id);
|
||||
get().fillExtCrateData(crate_id);
|
||||
get().fillOrderExtData();
|
||||
get().fillWarnings(crate_id);
|
||||
get()._updateTotalOrderPrice();
|
||||
},
|
||||
|
||||
setCrateMode: (id, mode) => {
|
||||
get()._setCrateMode(id, mode)
|
||||
get().fillExtData(id);
|
||||
get().fillExtCrateData(id);
|
||||
get().fillOrderExtData();
|
||||
get().fillWarnings(id);
|
||||
get().setActiveCrate(id);
|
||||
get()._updateTotalOrderPrice();
|
||||
},
|
||||
|
||||
addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => {
|
||||
delCrate: (id) => {
|
||||
get()._delCrate(id);
|
||||
get().fillOrderExtData();
|
||||
},
|
||||
|
||||
addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => {
|
||||
const dest = crate_to || get().active_crate;
|
||||
if (!dest) {
|
||||
console.warn("No destination");
|
||||
get().noDestinationWarning();
|
||||
return {};
|
||||
}
|
||||
get()._addCardFromBacklog(dest, index_from, index_to)
|
||||
get().showNotification(dest, index_to);
|
||||
get()._addCardFromCatalog(dest, index_from, index_to)
|
||||
get().fillExtData(dest);
|
||||
get().fillWarnings(dest);
|
||||
get().setActiveCrate(dest);
|
||||
get()._updateTotalOrderPrice();
|
||||
if (!just_mounted) {
|
||||
get().cardAdded()
|
||||
}
|
||||
|
@ -447,6 +577,7 @@ const useCart = ((set, get) => ({
|
|||
get().fillExtData(crate_to);
|
||||
get().fillWarnings(crate_to);
|
||||
get().setActiveCrate(crate_to);
|
||||
get()._updateTotalOrderPrice();
|
||||
if (crate_from !== crate_to) {
|
||||
get().fillExtData(crate_from);
|
||||
get().fillWarnings(crate_from);
|
||||
|
@ -456,6 +587,7 @@ const useCart = ((set, get) => ({
|
|||
get()._deleteCard(crate_id, index);
|
||||
get().fillExtData(crate_id);
|
||||
get().fillWarnings(crate_id);
|
||||
get()._updateTotalOrderPrice();
|
||||
if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
|
||||
},
|
||||
clearCrate: (id) => {
|
||||
|
@ -469,31 +601,27 @@ const useCart = ((set, get) => ({
|
|||
get().fillWarnings(crate_id);
|
||||
},
|
||||
|
||||
updateFanTrayOption: (crate_id, add_fan_tray) => set(state => ({
|
||||
crates: state.crates.map((crate, _i) => {
|
||||
if (crate_id === crate.id) {
|
||||
return {
|
||||
...crate,
|
||||
fan_tray: add_fan_tray
|
||||
}
|
||||
}
|
||||
else return crate;
|
||||
initExtData: () => {
|
||||
get().fillOrderExtData();
|
||||
get().crates.forEach((crate, _i) => {
|
||||
get().fillExtData(crate.id);
|
||||
get().fillExtCrateData(crate.id);
|
||||
})
|
||||
})),
|
||||
|
||||
fanTrayAvailableByIndex: (crate_index) => {
|
||||
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
|
||||
get()._updateTotalOrderPrice();
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
export const useShopStore = createWithEqualityFn((...params) => ({
|
||||
...useBacklog(...params),
|
||||
...useOptionsNotification(...params),
|
||||
...useCatalog(...params),
|
||||
...useSearch(...params),
|
||||
...useCrateModes(...params),
|
||||
...useCart(...params),
|
||||
...useSubmitForm(...params),
|
||||
...useLayout(...params),
|
||||
...useHighlighted(...params),
|
||||
...useImportJSON(...params),
|
||||
...useFanTray(...params),
|
||||
...useCrateOptions(...params),
|
||||
...useOrderOptions(...params),
|
||||
}))
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
export const data = window.shop_data;
|
||||
export const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat());
|
||||
export const itemsUnfoldedList = Array.from(data.columns.catalog.categories.map(groupId => groupId.itemIds).flat());
|
||||
|
||||
export const productStyle = (style, snapshot, removeAnim, hovered, selected, cart=false) => {
|
||||
const custom = {
|
||||
|
@ -9,7 +9,7 @@ export const productStyle = (style, snapshot, removeAnim, hovered, selected, car
|
|||
backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial',
|
||||
};
|
||||
|
||||
if (!cart && snapshot.draggingOver == null && // hack for backlog
|
||||
if (!cart && snapshot.draggingOver == null && // hack for catalog
|
||||
((!snapshot.isDragging) // prevent next elements from animation
|
||||
|| (snapshot.isDragging && snapshot.isDropAnimating))) { // prevent dragged element from weird animation
|
||||
style.transform = "none";
|
||||
|
@ -53,7 +53,7 @@ export function formatMoney(amount, decimalCount = 2, decimal = ".", thousands =
|
|||
let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString();
|
||||
let j = (i.length > 3) ? i.length % 3 : 0;
|
||||
|
||||
return negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
|
||||
return negativeSign + (j ? i.substring(0, j) + thousands : '') + i.substring(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
|
||||
} catch (e) {
|
||||
return amount;
|
||||
}
|
||||
|
|
|
@ -28,13 +28,140 @@ const shop_data = {
|
|||
"rack", "desktop"
|
||||
],
|
||||
|
||||
fanTray: {
|
||||
price: 470,
|
||||
crateModesAvailable: {
|
||||
'rack': true
|
||||
},
|
||||
optionTitle: "Add fan tray",
|
||||
tip: "Add 1U 84hp fan tray (to be mounted under the crate) to improve cooling. Fans need 220VAC 50/60Hz power. 3 fans, 167m³/h air flow."
|
||||
crateOptions: {
|
||||
options: [
|
||||
{"if": [
|
||||
{"==": [{"var": "ext_data.crate_mode"}, "rack",]},
|
||||
{type: "Switch", args: {
|
||||
title: "Add fan tray",
|
||||
outvar: "fan_tray",
|
||||
tip: "Add 1U 84hp fan tray (to be mounted under the crate) to improve cooling. " +
|
||||
"Fans need 220VAC 50/60Hz power. 3 fans, 167m³/h air flow.",
|
||||
fallback: false
|
||||
}}
|
||||
]},
|
||||
],
|
||||
prices: [{"if": [
|
||||
{"and": [{"var": "fan_tray"}, {"==": [{"var": "ext_data.crate_mode"}, "rack",]}]},
|
||||
{title: "Add fan tray", price: 470, disable_patch: {"fan_tray": false}, id: "fan_tray"}]}]
|
||||
},
|
||||
|
||||
orderOptions: {
|
||||
options: [
|
||||
{"type": "Group", items:[
|
||||
{type: "Switch", args: {
|
||||
title: "Optional pre-installed NUC mini-computer",
|
||||
outvar: "nuc",
|
||||
tip: "Pre-installed NixOS desktop with ARTIQ and other scientific software. " +
|
||||
"Hardware: Intel® NUC 13 Pro Kit NUC13ANKi7, i7-1360P CPU, " +
|
||||
"32GB RAM, 1TB NVMe. Other options contact us.",
|
||||
fallback: true,
|
||||
}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "nuc"},
|
||||
[
|
||||
{
|
||||
type: "Radio",
|
||||
args: {
|
||||
title: "Desktop Environment:",
|
||||
outvar: "nuc_desktop",
|
||||
variants: ["Gnome", "KDE"],
|
||||
fallback: 0,
|
||||
classes: "form-check-inline ms-4"
|
||||
}
|
||||
},
|
||||
{type: "Line", args: {title: "Additional software to be pre-installed", outvar: "software", fallback: "",
|
||||
tip: "Most software from nixpkgs can be pre-installed."}},
|
||||
{"if": [
|
||||
{"var": "ext_data.has_crate"},
|
||||
{type: "Switch", args: {
|
||||
title: "Promotional USB stick",
|
||||
outvar: "include_usb_stick_nuc",
|
||||
tip: "Branded USB stick with device database and other relevant files. Files can also be emailed to you.",
|
||||
fallback: false,
|
||||
}}
|
||||
]},
|
||||
],
|
||||
{"if": [
|
||||
{"var": "ext_data.has_crate"},
|
||||
{type: "Switch", args: {
|
||||
title: "Include promotional USB stick",
|
||||
outvar: "include_usb_stick",
|
||||
tip: "Choose if you need a USB stick with device database and other relevant files. Alternative is to to receive them via other electronic means (e.g. email or cloud).",
|
||||
fallback: true,
|
||||
}}
|
||||
]},
|
||||
]
|
||||
},
|
||||
]},
|
||||
{"type": "Group", items: [
|
||||
{
|
||||
type: "Radio",
|
||||
args: {
|
||||
title: "Shipping options:",
|
||||
outvar: "shipping",
|
||||
variants: [
|
||||
"Incoterms 2020 FCA",
|
||||
"Incoterms 2020 DAP",
|
||||
"Prepay and add shipping (only available to credit customers)"
|
||||
],
|
||||
fallback: 0
|
||||
}
|
||||
},
|
||||
{"if": [
|
||||
{"==": [{"var": "shipping"}, "Incoterms 2020 FCA"]},
|
||||
{type: "Line", args: {title: "Please provide your carrier account information and/or other shipping instructions",
|
||||
outvar: "shipping_instructions", fallback: ""}}
|
||||
]},
|
||||
{"if": [
|
||||
{"==": [{"var": "shipping"}, "Incoterms 2020 DAP"]},
|
||||
{type: "Line", args: {title: "Please provide delivery address",
|
||||
outvar: "shipping_instructions", fallback: "",
|
||||
tip: "Additional customs fees may be charged to you by the carrier at the time of delivery."}}
|
||||
]},
|
||||
{"if": [
|
||||
{"==": [{"var": "shipping"}, "Prepay and add shipping (only available to credit customers)"]},
|
||||
[{type: "Radio", args: {title: "In case of additional customs fees:",
|
||||
outvar: "prepay_fees_handling", fallback: 0,
|
||||
variants: [
|
||||
"Add to your final invoice",
|
||||
"Carrier bills you directly"
|
||||
]}},
|
||||
{type: "Line", args: {title: "Please provide delivery address",
|
||||
outvar: "shipping_instructions", fallback: "",}}],
|
||||
]},
|
||||
]}
|
||||
],
|
||||
|
||||
prices: [{
|
||||
"if": [{"var": "nuc"}, {title: "Pre-installed NUC mini-computer", price: 1300, disable_patch: {"nuc": false}, id: "nuc"}],
|
||||
}],
|
||||
shippingSummary: [
|
||||
{type: "Label", args: {
|
||||
content: ["Shipping method: ", {"var": "shipping"}]
|
||||
}},
|
||||
{"if": [
|
||||
{"var": "shipping_instructions"},
|
||||
{type: "Label", args: {
|
||||
content: [
|
||||
{"if": [
|
||||
{"==": [{"var": "shipping"}, "Incoterms 2020 FCA"]},
|
||||
"carrier account information and/or other shipping instructions: ",
|
||||
"delivery address: "
|
||||
]},
|
||||
{"var": "shipping_instructions"}
|
||||
]
|
||||
}}]},
|
||||
{type: "Label", args: {
|
||||
content: [
|
||||
{"if": [
|
||||
{"==": [{"var": "shipping"}, "Prepay and add shipping (only available to credit customers)"]},
|
||||
["In case of additional customs fees: ", {"lower": {"var": "prepay_fees_handling"}}],
|
||||
]}
|
||||
]
|
||||
}},
|
||||
]
|
||||
},
|
||||
|
||||
items: {
|
||||
|
@ -67,16 +194,21 @@ const shop_data = {
|
|||
]
|
||||
},
|
||||
[
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24",
|
||||
tip: "Set up IPv4 address and mask used by core device", validator: {name: "ipv4"}}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6",
|
||||
tip: "Set up IPv6 address and prefix used by core device",
|
||||
validator: {name: "ipv6"}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false},
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||
{"if": [
|
||||
{"var": "optics"},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "fiber_cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "copper_cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
]}
|
||||
]
|
||||
]
|
||||
|
@ -135,16 +267,24 @@ const shop_data = {
|
|||
]
|
||||
},
|
||||
[
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4",
|
||||
validator: {name: "ipv4"},
|
||||
fallback: "192.168.1.75/24",
|
||||
tip: "Set up IPv4 address used by core device"}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6",
|
||||
tip: "Set up IPv6 address and prefix used by core device",
|
||||
validator: {name: "ipv6"}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
fallback: {text: "125 MHz", checked: false},
|
||||
tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||
{"if": [
|
||||
{"var": "optics"},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "fiber_cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "copper_cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
]}
|
||||
]
|
||||
]
|
||||
|
@ -531,7 +671,9 @@ const shop_data = {
|
|||
"if": [
|
||||
{"var": "mono_eem"},
|
||||
[
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
fallback: {text: "125 MHz", checked: false}}},
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Synchronization", outvar: "sync", tip: "Synchronize phases across Urukuls"}},
|
||||
|
@ -540,7 +682,9 @@ const shop_data = {
|
|||
{"var": "sync"},
|
||||
null,
|
||||
[
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
fallback: {text: "125 MHz", checked: false}}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "ext_data.has_sampler"},
|
||||
|
@ -589,7 +733,9 @@ const shop_data = {
|
|||
datasheet_name: '4410/4412 Urukul datasheet',
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
|
@ -620,7 +766,9 @@ const shop_data = {
|
|||
'The upconverter is optional, if you would like the baseband version please leave us a note.'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
|
||||
fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
|
||||
],
|
||||
size: 'small',
|
||||
|
@ -756,7 +904,7 @@ const shop_data = {
|
|||
size: 'small',
|
||||
type: 'hd68',
|
||||
options: [
|
||||
{type: "Radio", args: {title: "Cable length", outvar: "cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
||||
],
|
||||
options_class: "hd68-idc",
|
||||
warnings: [
|
||||
|
@ -898,7 +1046,10 @@ const shop_data = {
|
|||
'Can be controlled by Kasli or work stand-alone with PoE supply.'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
|
||||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
|
@ -930,7 +1081,9 @@ const shop_data = {
|
|||
'Large frequency changes take several milliseconds.',
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 600e6}},
|
||||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
|
@ -957,7 +1110,9 @@ const shop_data = {
|
|||
'Each Almazny channel outputs twice the frequency of its corresponding Mirny channel.',
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
validator: {name: "frequency", params: {min: 10e6, max: 600e6}},
|
||||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
|
@ -1039,9 +1194,13 @@ const shop_data = {
|
|||
'AD9959 DDS (500MSPS, 10-bit).'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
|
||||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}},
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
|
@ -1086,12 +1245,12 @@ const shop_data = {
|
|||
|
||||
columns: {
|
||||
/***
|
||||
* backlog is the column containing all items on left aside,
|
||||
* catalog is the column containing all items on left aside,
|
||||
* name should not change
|
||||
*/
|
||||
'backlog': {
|
||||
id: 'backlog',
|
||||
title: 'Backlog',
|
||||
'catalog': {
|
||||
id: 'catalog',
|
||||
title: 'Catalog',
|
||||
/* itemIds define items order - change order to suit your need */
|
||||
categories: [
|
||||
{ name: 'Core',
|
||||
|
|
Loading…
Reference in New Issue