Compare commits

..

1 Commits

Author SHA1 Message Date
Egor Savkin 5bf369fc50 Update bundle file
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-08-22 16:29:12 +08:00
102 changed files with 5251 additions and 6675 deletions

View File

@ -18,10 +18,8 @@ Start:
zola serve
```
To build the .bundle.js from .jsx files:
To update the .bundle.js and .jsx file:
```
nix-shell -p nodejs
npm install
npm run build
nix-shell -p nodejs --run "npm run build"
```

View File

@ -18,4 +18,4 @@ Selling drinks is not our main business and only a service we do to other Club M
* In case of any dispute, the seller is always right.
Contact: [sb@m-labs.hk](mailto:sb@m-labs.hk)
Contact: sb@m-****.hk

View File

@ -73,7 +73,7 @@ We post specific vacancies on job boards such as Indeed.com, but if you think yo
{% layout_centered_content(min_width=true) %}
Our main office is located in the center of Hong Kong, a cosmopolitan city with world-class infrastructure, many cultural and social events, and <a href="https://www.discoverhongkong.com/eng/see-do/great-outdoors/hikes/index.jsp" rel="noopener noreferrer" target="_blank">beautiful natural scenery</a>. It has a separate system from mainland China, where, for example, communications are unrestricted, taxes are low, and customs tariffs virtually inexistent. Hong Kong is located next to Shenzhen, a city with <a href="https://www.wired.com/video/watch/shenzhen-the-silicon-valley-of-hardware-full-documentary-future-cities-wired" rel="noopener noreferrer" target="_blank">a bustling tech scene</a>, and where many of the world's electronic gadgets are designed and manufactured.
Our main office is located in the center of Hong Kong, a cosmopolitan city with world-class infrastructure, many cultural and social events, and <a href="https://www.discoverhongkong.com/eng/see-do/great-outdoors/hikes/index.jsp" rel="noopener noreferrer" target="_blank">beautiful natural scenery</a>. It has a separate system from mainland China, where, for example, communications are unrestricted, taxes are low, and customs tariffs virtually inexistent. Hong Kong is located next to Shenzhen, a city with <a href="https://www.wired.co.uk/video/shenzhen-full-documentary" rel="noopener noreferrer" target="_blank">a bustling tech scene</a>, and where many of the world's electronic gadgets are designed and manufactured.
We also have a location in Manila, Philippines.
@ -83,5 +83,5 @@ Now is your chance to work on top-notch science and technology projects that get
{% layout_centered_content() %}
##### Contact us at [jobs@m-labs.hk](mailto:jobs@m-labs.hk) or [jobs@m-labs.ph](mailto:jobs@m-labs.ph)!
##### Contact us at jobs@m-\*\*\*\*.hk or jobs@m-\*\*\*\*.ph!
{% end %}

View File

@ -62,7 +62,7 @@ The <a href="https://github.com/quartiq/stabilizer" target="_blank" rel="noopene
<div class="col-12 col-md-4">
<p>
To purchase this controller, email <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. We also offer firmware customizations and development of new features. Note that features that are not implemented in the open source code above (e.g. control from Kasli) are not supported unless purchased separately.
To purchase this controller, email sales@m-***s.hk. We also offer firmware customizations and development of new features. Note that features that are not implemented in the open source code above (e.g. control from Kasli) are not supported unless purchased separately.
</p>
</div>
@ -96,7 +96,7 @@ Specifications:
{% layout_text_img(src="images/Thermostat_EEM@2x.png", popup="images/origin/thermostat_eem.jpg", alt="", textleft=true, shadow=false) %}
{% layout_text_img(src="images/side-min.png", textleft=true, shadow=false) %}
##### Sinara 8453 Thermostat EEM

View File

@ -6,8 +6,6 @@ template = "page.html"
[extra]
+++
{% layout_funding(position="center") %}
The free and open dissemination of methods and results is central to scientific progress.
The ARTIQ and Sinara authors, contributors, and supporters consider the free and open exchange of scientific tools to be equally important and have chosen the licensing terms of ARTIQ and Sinara accordingly. ARTIQ, including its gateware, the firmware, and the ARTIQ tools and libraries are licensed as LGPLv3+. The Sinara hardware designs are licensed under CERN OHL.
@ -15,105 +13,68 @@ This ensures that a user of ARTIQ or Sinara hardware designs obtains broad right
ARTIQ and the related components that we are developing ([Migen/MiSoC](/gateware/migen), [nMigen](/gateware/nmigen), [smoltcp](/software/smoltcp), [minimq](https://github.com/quartiq/minimq), [zynq-rs](https://git.m-labs.hk/m-labs/zynq-rs), [SiPyCo](https://github.com/m-labs/sipyco), [RayOpt](https://github.com/quartiq/rayopt), [WFVM](https://git.m-labs.hk/m-labs/wfvm), ...) need to be kept on a sound financial basis. We have been relying on a combination of [hardware sales](../place-order), sponsorships, and grants.
{% end %}
##### Sponsors
{% layout_funding(position="center", title="Sponsors") %}
We acknowledge support from our partners below. Please get in touch (sales@m-\*\*\*.hk) if you also want to move ARTIQ forward!
We acknowledge support from our partners below. Please get in touch ([sales@m-labs.hk](mailto:sales@m-labs.hk)) if you also want to move ARTIQ forward!
###### Platinum level
{% end %}
#### Platinum level
{% layout_funding(logo="/images/logo_nist.png", logo_link="https://www.nist.gov/pml/time-and-frequency-division/ion-storage", logo_alt="logo nist", title="NIST", position="left", icon="/images/icons/diamond.svg") %}
[![log nist](/images/logo_nist.png)](https://www.nist.gov/pml/time-and-frequency-division/ion-storage)
ARTIQ was initiated by the [Ion Storage Group](https://www.nist.gov/pml/time-and-frequency-division/ion-storage) at NIST, who provided valuable technical insight as well as financial support to develop the first version of the ARTIQ software and gateware targeting the KC705 development kit. This included some FPGA SoC libraries such as a SDRAM controller and PHYs, the first two iterations of the ARTIQ runtime including the development of [smoltcp](/software/smoltcp), the RTIO infrastructure and PHYs, the ARTIQ-Python LLVM-based compiler, RTIO DMA, the ARTIQ dashboard and browser, the ARTIQ master, and several controllers. They also supported the more recent ARTIQ port to Zynq, targeting the ZC706 development kit, and enabling ARTIQ-Python kernels to take advantage of the 1GHz CPU in the core device; as well as matrix and trigonometric function support in ARTIQ-Python kernels.
<small>*Disclaimer: NIST and the United States government are not providing an endorsement of ARTIQ.*</small>
{% end %}
{% layout_funding(logo="/images/logo_oxford.png", logo_link="https://www2.physics.ox.ac.uk/research/ion-trap-quantum-computing-group", logo_alt="logo oxford", title="Oxford", icon="/images/icons/diamond.svg", position="right") %}
[![logo oxford](/images/logo_oxford.png)](https://www2.physics.ox.ac.uk/research/ion-trap-quantum-computing-group)
The [University of Oxford](https://www2.physics.ox.ac.uk/research/ion-trap-quantum-computing-group) funded major improvements to the RTIO infrastructure: the Distributed RTIO (DRTIO) system that allows clock synchronization and RTIO command transfer between FPGAs using cost-effective high-speed serial links (e.g. over fiber optics), and changes to the RTIO architecture to improve scalability. They also funded the Sinara Sampler hardware and software, the [SU-Servo](https://m-labs.hk/artiq/manual-beta/core_drivers_reference.html?highlight=suservo#artiq.coredevice.suservo.SUServo) integrated laser intensity servo, parts of Urukul and Zotino, firmware development on the Sinara Thermostat, the Kasli v2.0 ARTIQ support, the Fastino ARTIQ support, parts of the Sinara Phaser gateware and software development, and significant parts of the Booster RF amplifier.
{% end %}
{% layout_funding(logo="/images/logo_wut.png", logo_link="http://www.ise.pw.edu.pl", logo_alt="logo wut", title="Warsaw University of Technology", icon="/images/icons/diamond.svg", position="left") %}
[![logo wut](/images/logo_wut.png)](http://www.ise.pw.edu.pl)
[Warsaw University of Technology](http://www.ise.pw.edu.pl) contributed most of the hardware designs and prototypes for the Sinara ecosystem.
{% end %}
###### Gold level
#### Gold level
{% layout_funding(logo="/images/logo_oregon.png", logo_link="https://ions.uoregon.edu/", logo_alt="logo oregon", title="University of Oregon", icon="/images/icons/gold-badge.svg", position="right") %}
[![logo oregon](/images/logo_oregon.png)](https://ions.uoregon.edu/)
The [University of Oregon](https://ions.uoregon.edu/) funded a large part of the Sinara Phaser gateware and software development, as well as firmware for the Pounder PDH/phase lock signal generator for Stabilizer.
{% end %}
{% layout_funding(logo="/images/duke_brown.svg", logo_link="https://brownlab.pratt.duke.edu/", logo_alt="logo duke brown", title="Brown Lab at Duke University", icon="/images/icons/gold-badge.svg", position="left") %}
[![logo duke](/images/duke_brown.svg)](https://brownlab.pratt.duke.edu/)
The [Brown Lab at Duke University](https://brownlab.pratt.duke.edu/) funded the migration of ARTIQ softcore platforms to the RISC-V architecture, memory protection support, and floating point unit support.
{% end %}
{% layout_funding(logo="/images/logo_arl_devcom.png", logo_link="http://brittonlab.umd.edu/", logo_alt="logo arl", title="Army Research Lab", icon="/images/icons/gold-badge.svg", position="right") %}
[![logo arl](/images/logo_arl_devcom.png)](http://brittonlab.umd.edu/)
The [Army Research Lab](http://brittonlab.umd.edu/) funded the SAWG high-throughput digital waveform generator, as well as ARTIQ support for the Sayma and Metlino boards.
{% end %}
#### Silver level
###### Silver level
{% layout_funding(logo="/images/logo_luh.png", logo_link="https://www.quest-lfs.uni-hannover.de/", logo_alt="logo hannover", title="QUEST/Uni Hannover", icon="/images/icons/silver-badge.svg", position="left") %}
[![logo quest](/images/logo_luh.png)](https://www.quest-lfs.uni-hannover.de/)
[QUEST/Uni Hannover](https://www.quest-lfs.uni-hannover.de/) funded gateware and software support for the Grabber EEM, partial Mirny support, part of the Fastino development, and part of Urukul synchronization support.
{% end %}
{% layout_funding(logo="/images/logo_ptb.png", logo_link="http://ptb.de", logo_alt="logo ptb", title="PTB", icon="/images/icons/silver-badge.svg", position="right") %}
[![logo ptb](/images/logo_ptb.png)](http://ptb.de)
PTB funded the development of lock-in amplifier support for the Sinara Stabilizer, as well as part of Urukul synchronization support.
{% end %}
{% layout_funding(logo="/images/logo_uwisc.png", logo_link="http://hexagon.physics.wisc.edu/", logo_alt="logo uwisc", title="University of Wisconsin-Madison", icon="/images/icons/silver-badge.svg", position="left") %}
[![logo uwisc](/images/logo_uwisc.png)](http://hexagon.physics.wisc.edu/)
The [University of Wisconsin-Madison](http://hexagon.physics.wisc.edu/) funded the development of Urukul RAM mode support.
{% end %}
{% layout_funding(title="MIT", icon="/images/icons/silver-badge.svg", position="right") %}
[MIT](http://web.mit.edu/~cua/www/quanta/) funded parts of the Sinara Phaser gateware and software development.
{% end %}
{% layout_funding(logo="/images/logo_hu.png", logo_link="https://www.physik.hu-berlin.de/en/qom/people/qom-group/markus/view", logo_alt="logo hu", title="Humboldt University of Berlin", icon="/images/icons/silver-badge.svg", position="left") %}
[![logo hu](/images/logo_hu.png)](https://www.physik.hu-berlin.de/en/qom/people/qom-group/markus/view)
[Humboldt University of Berlin](https://www.physik.hu-berlin.de/en/qom/people/qom-group/markus/view) funded [prototype digital spectroscopy modulation/demodulation firmware](https://github.com/quartiq/queenmod), which is part of the "Optical quantum technologies on nanosatellites" (QUEEN) project.
{% end %}
{% layout_funding(position="center", title="Grants") %}
##### Grants
Parts of ARTIQ and Sinara have been developed through the participation of our sister company QUARTIQ GmbH in European research grant programmes.
{% end %}
{% layout_funding(logo="/images/logo_opticlock.png", logo_link="https://www.opticlock.de/", logo_alt="logo opticlock", title="Opticlock", icon="/images/icons/grant.svg", position="left") %}
[![logo opticlock](/images/logo_opticlock.png)](https://www.opticlock.de/)
The BMBF project [Opticlock](https://www.opticlock.de/) developed an optical clock as a replacement for a hydrogen maser, based on a single ytterbium ion captured and laser-cooled in an ion trap. It helped fund the development of the Kasli, Urukul, Zotino and Sampler modules.
{% end %}
{% layout_funding(logo="/images/logo_aiartiq.png", logo_link="https://www.opticlock.de/", logo_alt="logo aiartiq", title="AI-ARTIQ", icon="/images/icons/grant.svg", position="right") %}
![logo aiartiq](/images/logo_aiartiq.png)
AI-ARTIQ develops a control platform for laser sources and atom interferometer-based quantum sensors.
{% end %}

View File

@ -1,15 +1,103 @@
+++
title = "Publications"
weight = 4
template = "publications.html"
template = "page.html"
[extra]
data = "content/experiment-control/publications.yaml"
+++
##### This page lists publications about experiments performed using ARTIQ, or which otherwise cite ARTIQ.
- **Fast quantum logic gates with trapped-ion qubits**<br />
V. M. Schäfer, C. J. Ballance, K. Thirumalai, L. J. Stephenson, T. G. Ballance, A. M. Steane & D. M. Lucas<br />
[Nature volume 555, pages75-78(2018)](https://www.nature.com/articles/nature25737) | [arXiv](https://arxiv.org/pdf/1709.06952.pdf)
- **Programming languages and compiler design for realistic quantum hardware**<br />
Frederic T. Chong, Diana Franklin & Margaret Martonosi<br />
[Nature volume 549, pages180-187(2017)](https://www.nature.com/articles/nature23459)
- **High-rate, high-fidelity entanglement of qubits across an elementary quantum network**<br />
L.J. Stephenson, D.P. Nadlinger, B.C. Nichol, S. An, P. Drmota, T.G. Ballance, K. Thirumalai, J.F. Goodwin, D.M. Lucas & C.J. Ballance<br />
[Phys. Rev. Lett. 124, 110501](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.124.110501) | [arXiv](https://arxiv.org/abs/1911.10841)
- **Device-Independent Quantum Key Distribution**<br />
D. P. Nadlinger, P. Drmota, B. C. Nichol, G. Araneda, D. Main, R. Srinivas, D. M. Lucas, C. J. Ballance, K. Ivanov, E. Y-Z. Tan, P. Sekatski, R. L. Urbanke, R. Renner, N. Sangouard & J-D. Bancal<br />
[arXiv](https://arxiv.org/abs/2109.14600)
- **Universal Graph-Based Scheduling for Quantum Systems**<br />
L. Riesebos, B. Bondurant, & Kenneth R. Brown<br />
[IEEE Micro, Volume: 41, Issue: 5, Sept.-Oct. 1 2021](https://doi.org/10.1109/MM.2021.3094968)<br />
- **Functional simulation of real-time quantum control software**<br />
Leon Riesebos & Kenneth R. Brown<br />
[QCE2022](https://doi.org/10.1109/QCE53715.2022.00076) (Best Paper Award) | [arXiv](https://arxiv.org/abs/2210.14364)<br />
- **Modular software for real-time quantum control systems**<br />
Leon Riesebos, Brad Bondurant, Jacob Whitlow, Junki Kim, Mark Kuzyk, Tianyi Chen, Samuel Phiri, Ye Wang, Chao Fang, Andrew Van Horn, Jungsang Kim & Kenneth R. Brown<br />
[QCE2022](https://doi.org/10.1109/QCE53715.2022.00077) | [arXiv](https://arxiv.org/abs/2210.14341)<br />
- **Fault-Tolerant Operation of a Quantum Error-Correction Code**<br />
Laird Egan, Dripto M. Debroy, Crystal Noel, Andrew Risinger, Daiwei Zhu, Debopriyo Biswas, Michael Newman, Muyuan Li, Kenneth R. Brown, Marko Cetina & Christopher Monroe<br />
[arXiv](https://arxiv.org/abs/2009.11482)
- **Probing Qubit Memory Errors at the Part-per-Million Level**<br />
M.A. Sepiol, A.C. Hughes, J.E. Tarlton, D.P. Nadlinger, T.G. Ballance, C.J. Ballance, T.P. Harty, A.M. Steane, J.F. Goodwin & D.M. Lucas<br />
[Phys. Rev. Lett. 123, 110503](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.110503) | [arXiv](https://arxiv.org/abs/1905.06878)
- **Quantum amplification of mechanical oscillator motion**<br />
S. C. Burd, R. Srinivas, J. J. Bollinger, A. C. Wilson, D. J. Wineland, D. Leibfried, D. H. Slichter & D. T. C. Allcock<br />
[Science, Vol 364, Issue 6446, 21 June 2019](https://science.sciencemag.org/content/364/6446/1163)
- **State Readout of a Trapped Ion Qubit Using a Trap-Integrated Superconducting Photon Detector**<br />
S. L. Todaro, V. B. Verma, K. C. McCormick, D. T. C. Allcock, R. P. Mirin, D. J. Wineland, S. W. Nam, A. C. Wilson, D. Leibfried & D. H. Slichter<br />
[arXiv](https://arxiv.org/abs/2008.00065)
- **Trapped-Ion Spin-Motion Coupling with Microwaves and a Near-Motional Oscillating Magnetic Field Gradient**<br />
R. Srinivas, S.C. Burd, R.T. Sutherland, A.C. Wilson, D.J. Wineland, D. Leibfried, D.T.C. Allcock & D.H. Slichter<br />
[Phys. Rev. Lett. 122, 163201](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.122.163201) | [arXiv](https://arxiv.org/abs/1812.02098.pdf)
- **Speed, retention loss, and motional heating of atoms in an optical conveyor belt**<br />
G. T. Hickman & M. Saffman<br />
[Phys. Rev. A 101, 063411](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.101.063411) | [arXiv](https://arxiv.org/abs/2004.04848)
- **A hybrid ion-atom trap with integrated high resolution mass spectrometer**<br />
S. Jyothi, Kisra N. Egodapitiya, Brad Bondurant, Zhubing Jia, Eric Pretzsch, Piero Chiappina, Gang Shu & Kenneth R. Brown<br />
[Review of Scientific Instruments 90, 103201 (2019)](https://aip.scitation.org/doi/abs/10.1063/1.5121431) | [arXiv](https://arxiv.org/abs/1907.10455)
- **Photon-mediated charge-exchange reactions between <sup>39</sup>K atoms and <sup>40</sup>Ca<sup>+</sup> ions in a hybrid trap**<br />
Hui Li, S. Jyothi, Ming Li, Jacek Klos, Alexander Petrov, Kenneth R Brown & Svetlana Kotochigova<br />
[Phys. Chem. Chem. Phys., 2020,22, 10870-10881](https://pubs.rsc.org/en/content/articlelanding/2020/cp/d0cp01131b) | [arXiv](https://arxiv.org/abs/2003.03430)
- **Spin Readout Techniques of the Nitrogen-Vacancy Center in Diamond**<br />
David A. Hopper, Henry J. Shulevitz, & Lee C. Bassett<br />
[Micromachines 2018, 9(9), 437](https://www.mdpi.com/2072-666X/9/9/437) | [arXiv](https://arxiv.org/abs/1809.05237)
- **High-accuracy inertial measurements with cold-atom sensors**<br />
Remi Geiger, Arnaud Landragin, Sébastien Merlet & Franck Pereira Dos Santos<br />
[AVS Quantum Sci. 2, 024702 (2020)](https://avs.scitation.org/doi/10.1116/5.0009093) | [arXiv](https://arxiv.org/abs/2003.12516)
- **Dynamical low-noise microwave source for cold atom experiments**<br />
Bernd Meyer, Alexander Idel, Fabian Anders, Jan Peise & Carsten Klempt<br />
[arXiv](https://arxiv.org/abs/2003.10989)
- **Ion-trapping lab setup for quantum information experiments**<br />
Alexander Quinn, Jeremy Metzner, Daniel Moore, Vikram Sandhu, Dave Wineland, David Allcock<br />
[DAMOP20 poster](https://meetings.aps.org/Meeting/DAMOP20/Session/K01.113)
- **Short Time Fourier Transform Pulse Generator for Trapped Ion Quantum Gates**<br />
Norman Krackow<br />
[local copy](/thesis_nkrackow.pdf)
- **Precise PID controller for quantum applications**<br />
Jakub Matyas<br />
[Warsaw University of Technology, Engineer's thesis](https://repo.pw.edu.pl/info/bachelor/WUTe90de3461b53456c9d63c55d80d34220/) | [local copy](/JMatyas_inz_v6.pdf)
- **Controller module for real-time quantum applications**<br />
Paweł Kulik<br />
[Warsaw University of Technology, Masters's diploma thesis](https://repo.pw.edu.pl/info/master/WUT6212c3c3cdcd40ea9095f4ead6482596/) | [local copy](/pawel_kulik_kasli_thesis_2019_compressed.pdf)
- **Driver module for quantum computer experiments: Kasli**<br />
Paweł Kulik, Grzegorz Kasprowicz and Michał Gąska<br />
[Proceedings Volume 10808, Photonics Applications in Astronomy, Communications, Industry, and High-Energy Physics Experiments 2018](https://doi.org/10.1117/12.2501709) | [slides](https://raw.githubusercontent.com/wiki/sinara-hw/meta/talks/pawel_kasli_mgr2.pdf)
- **Urukul open-source frequency synthesizer module for quantum physics**<br />
Grzegorz Kasprowicz, Thomas Harty, Sébastien Bourdeauducq, Robert Jördens, David Allcock, David Nadlinger, Joseph Britton, Ana Sotirova, Dorota Nowicka<br />
[International Journal of Electronics and Telecommunications, Vol 68, No 1 (2022)](http://ijet.pl/index.php/ijet/issue/view/58)
- **Control system for ion Penning traps at the AEgIS experiment at CERN**<br />
Dorota Nowicka<br />
[TIPP 2021 Poster](https://indico.cern.ch/event/981823/contributions/4295432/attachments/2250078/3816845/poster_445.pdf)
- **Integrated photonic control of trapped ion qubits**<br />
Jules Stuart, Dave Reens, Cheri Sorace-Agaskar, Dave Kharas, Jeremy Sage, John Chiaverini<br />
[NY CREATES presentation](https://ny-creates.org/wp-content/uploads/Niffenegger_NY_CREATES_04_22_2021.pdf)
- **Sayma: Agile RF for Coherent Quantum Control Using ARTIQ**<br />
Joseph W. Britton, David T. C. Allcock, Chris Ballance, Tom P. Harty, Robert Jordens, Greg Kasprowicz, Pawel Kulik, Daniel H. Slichter, Weida Zhang, Sebastien Bourdeauducq<br />
APS Division of Atomic and Molecular Physics Meeting 2019, abstract id.E01.164
- **Programowalny sterownik siłownika piezoelektrycznego**<br />
Adam Kołodyński<br />
Warsaw University of Technology, Engineer's thesis | [local copy](/AdamKolodynski.pdf)
- **Microwave synthesizer for driving ion traps in quantum computing**<br />
Krzysztof Belewicz<br />
Warsaw University of Technology, Bachelor's dipoma thesis | [local copy](/Krzysztof_Belewicz_V1.1.pdf)
- **Wielokanałowy moduł sterujący pułapką jonową w standardzie MicroTCA.4**<br />
Tomasz Przywózki<br />
Warsaw University of Technology, Master's thesis | [local copy](/TomaszPrzywozki_v3.pdf)
To have your publication listed here, write to sb@m-l\*\*\*.hk.

View File

@ -1,410 +0,0 @@
- title: "Fast quantum logic gates with trapped-ion qubits"
authors: "V.M. Schäfer, C.J. Ballance, K. Thirumalai, L.J. Stephenson, T.G. Ballance, A.M. Steane & D.M. Lucas"
links:
- name: "Nature volume 555, pages75-78(2018)"
path: "https://www.nature.com/articles/nature25737"
- name: "arXiv"
path: "https://arxiv.org/pdf/1709.06952.pdf"
- title: "Experimental quantum key distribution certified by Bell's theorem"
authors: "D.P. Nadlinger, P. Drmota, B.C. Nichol, G. Araneda, D. Main, R. Srinivas, D.M. Lucas, C.J. Ballance, K. Ivanov, E. Y-Z. Tan, P. Sekatski, R.L. Urbanke, R. Renner, N. Sangouard, J-D. Bancal"
links:
- name: "Nature 607, 682686 (2022)"
path: "https://www.nature.com/articles/s41586-022-04941-5"
- name: "arXiv"
path: "https://arxiv.org/abs/2109.14600"
- title: "Programming languages and compiler design for realistic quantum hardware"
authors: "Frederic T. Chong, Diana Franklin & Margaret Martonosi"
links:
- name: "Nature volume 549, pages180-187(2017)"
path: "https://www.nature.com/articles/nature23459"
- title: "High-rate, high-fidelity entanglement of qubits across an elementary quantum network"
authors: "L.J. Stephenson, D.P. Nadlinger, B.C. Nichol, S. An, P. Drmota, T.G. Ballance, K. Thirumalai, J.F. Goodwin, D.M. Lucas & C.J. Ballance"
links:
- name: "Phys. Rev. Lett. 124, 110501"
path: "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.124.110501"
- name: "arXiv"
path: "https://arxiv.org/abs/1911.10841"
- title: "Device-Independent Quantum Key Distribution"
authors: "D.P. Nadlinger, P. Drmota, B.C. Nichol, G. Araneda, D. Main, R. Srinivas, D.M. Lucas, C.J. Ballance, K. Ivanov, E.Y-Z. Tan, P. Sekatski, R.L. Urbanke, R. Renner, N. Sangouard & J-D. Bancal"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2109.14600"
- title: "Universal Graph-Based Scheduling for Quantum Systems"
authors: "L. Riesebos, B. Bondurant, & Kenneth R. Brown"
links:
- name: "IEEE Micro, Volume: 41, Issue: 5, Sept.-Oct. 1 2021"
path: "https://doi.org/10.1109/MM.2021.3094968"
- title: "Functional simulation of real-time quantum control software"
authors: "Leon Riesebos & Kenneth R. Brown"
links:
- name: "QCE2022 (Best Paper Award)"
path: "https://doi.org/10.1109/QCE53715.2022.00076"
- name: "arXiv"
path: "https://arxiv.org/abs/2210.14364"
- title: "Modular software for real-time quantum control systems"
authors: "Leon Riesebos, Brad Bondurant, Jacob Whitlow, Junki Kim, Mark Kuzyk, Tianyi Chen, Samuel Phiri, Ye Wang, Chao Fang, Andrew Van Horn, Jungsang Kim & Kenneth R. Brown"
links:
- name: "QCE2022"
path: "https://doi.org/10.1109/QCE53715.2022.00077"
- name: "arXiv"
path: "https://arxiv.org/abs/2210.14341"
- title: "Fault-Tolerant Operation of a Quantum Error-Correction Code"
authors: "Laird Egan, Dripto M. Debroy, Crystal Noel, Andrew Risinger, Daiwei Zhu, Debopriyo Biswas, Michael Newman, Muyuan Li, Kenneth R. Brown, Marko Cetina & Christopher Monroe"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2009.11482"
- name: "Nature 598, 281286 (2021)"
path: "https://www.nature.com/articles/s41586-021-03928-y"
- title: "Probing Qubit Memory Errors at the Part-per-Million Level"
authors: "M.A. Sepiol, A.C. Hughes, J.E. Tarlton, D.P. Nadlinger, T.G. Ballance, C.J. Ballance, T.P. Harty, A.M. Steane, J.F. Goodwin & D.M. Lucas"
links:
- name: "Phys. Rev. Lett. 123, 110503"
path: "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.110503"
- name: "arXiv"
path: "https://arxiv.org/abs/1905.06878"
- title: "Quantum amplification of mechanical oscillator motion"
authors: "S.C. Burd, R. Srinivas, J.J. Bollinger, A.C. Wilson, D.J. Wineland, D. Leibfried, D.H. Slichter & D.T.C. Allcock"
links:
- name: "Science, Vol 364, Issue 6446, 21 June 2019"
path: "https://science.sciencemag.org/content/364/6446/1163"
- title: "Quantum amplification of boson-mediated interactions"
authors: "S.C. Burd, R. Srinivas, H.M. Knaack, W. Ge, A.C. Wilson, D.J. Wineland, D. Leibfried, J.J. Bollinger, D.T.C. Allcock, D.H. Slichter"
links:
- name: "Nat. Phys. 17, 898902 (2021)"
path: "https://www.nature.com/articles/s41567-021-01237-9"
- name: "arXiv"
path: "https://arxiv.org/abs/2009.14342"
- title: "State Readout of a Trapped Ion Qubit Using a Trap-Integrated Superconducting Photon Detector"
authors: "S.L. Todaro, V.B. Verma, K.C. McCormick, D.T.C. Allcock, R.P. Mirin, D.J. Wineland, S.W. Nam, A.C. Wilson, D. Leibfried & D.H. Slichter"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2008.00065"
- title: "Trapped-Ion Spin-Motion Coupling with Microwaves and a Near-Motional Oscillating Magnetic Field Gradient"
authors: "R. Srinivas, S.C. Burd, R.T. Sutherland, A.C. Wilson, D.J. Wineland, D. Leibfried, D.T.C. Allcock & D.H. Slichter"
links:
- name: "Phys. Rev. Lett. 122, 163201"
path: "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.122.163201"
- name: "arXiv"
path: "https://arxiv.org/abs/1812.02098.pdf"
- title: "Speed, retention loss, and motional heating of atoms in an optical conveyor belt"
authors: "G.T. Hickman & M. Saffman"
links:
- name: "Phys. Rev. A 101, 063411"
path: "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.101.063411"
- name: "arXiv"
path: "https://arxiv.org/abs/2004.04848"
- title: "A hybrid ion-atom trap with integrated high resolution mass spectrometer"
authors: "S. Jyothi, Kisra N. Egodapitiya, Brad Bondurant, Zhubing Jia, Eric Pretzsch, Piero Chiappina, Gang Shu & Kenneth R. Brown"
links:
- name: "Review of Scientific Instruments 90, 103201 (2019)"
path: "https://aip.scitation.org/doi/abs/10.1063/1.5121431"
- name: "arXiv"
path: "https://arxiv.org/abs/1907.10455"
- title: "Photon-mediated charge-exchange reactions between <sup>39</sup>K atoms and <sup>40</sup>Ca<sup>+</sup> ions in a hybrid trap"
authors: "Hui Li, S. Jyothi, Ming Li, Jacek Klos, Alexander Petrov, Kenneth R Brown & Svetlana Kotochigova"
links:
- name: "Phys. Chem. Chem. Phys., 2020,22, 10870-10881"
path: "https://pubs.rsc.org/en/content/articlelanding/2020/cp/d0cp01131b"
- name: "arXiv"
path: "https://arxiv.org/abs/2003.03430"
- title: "Spin Readout Techniques of the Nitrogen-Vacancy Center in Diamond"
authors: "David A. Hopper, Henry J. Shulevitz, & Lee C. Bassett"
links:
- name: "Micromachines 2018, 9(9), 437"
path: "https://www.mdpi.com/2072-666X/9/9/437"
- name: "arXiv"
path: "https://arxiv.org/abs/1809.05237"
- title: "High-accuracy inertial measurements with cold-atom sensors"
authors: "Remi Geiger, Arnaud Landragin, Sébastien Merlet & Franck Pereira Dos Santos"
links:
- name: "AVS Quantum Sci. 2, 024702 (2020)"
path: "https://avs.scitation.org/doi/10.1116/5.0009093"
- name: "arXiv"
path: "https://arxiv.org/abs/2003.12516"
- title: "Dynamical low-noise microwave source for cold atom experiments"
authors: "Bernd Meyer, Alexander Idel, Fabian Anders, Jan Peise & Carsten Klempt"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2003.10989"
- title: "Ion-trapping lab setup for quantum information experiments"
authors: "Alexander Quinn, Jeremy Metzner, Daniel Moore, Vikram Sandhu, Dave Wineland, David Allcock"
links:
- name: "DAMOP20 poster"
path: "https://meetings.aps.org/Meeting/DAMOP20/Session/K01.113"
- title: "Driver module for quantum computer experiments: Kasli"
authors: "Paweł Kulik, Grzegorz Kasprowicz and Michał Gąska"
links:
- name: "Proceedings Volume 10808, Photonics Applications in Astronomy, Communications, Industry, and High-Energy Physics Experiments 2018"
path: "https://doi.org/10.1117/12.2501709"
- name: "slides"
path: "https://raw.githubusercontent.com/wiki/sinara-hw/meta/talks/pawel_kasli_mgr2.pdf"
- title: "Urukul open-source frequency synthesizer module for quantum physics"
authors: "Grzegorz Kasprowicz, Thomas Harty, Sébastien Bourdeauducq, Robert Jördens, David Allcock, David Nadlinger, Joseph Britton, Ana Sotirova, Dorota Nowicka"
links:
- name: "International Journal of Electronics and Telecommunications, Vol 68, No 1 (2022)"
path: "http://ijet.pl/index.php/ijet/issue/view/58"
- title: "Control system for ion Penning traps at the AEgIS experiment at CERN"
authors: "Dorota Nowicka"
links:
- name: "TIPP 2021 Poster"
path: "https://indico.cern.ch/event/981823/contributions/4295432/attachments/2250078/3816845/poster_445.pdf"
- title: "Integrated photonic control of trapped ion qubits"
authors: "Jules Stuart, Dave Reens, Cheri Sorace-Agaskar, Dave Kharas, Jeremy Sage, John Chiaverini"
links:
- name: "NY CREATES presentation"
path: "https://ny-creates.org/wp-content/uploads/Niffenegger_NY_CREATES_04_22_2021.pdf"
- title: "Sayma: Agile RF for Coherent Quantum Control Using ARTIQ"
authors: "Joseph W. Britton, David T.C. Allcock, Chris Ballance, Tom P. Harty, Robert Jordens, Greg Kasprowicz, Pawel Kulik, Daniel H. Slichter, Weida Zhang, Sebastien Bourdeauducq"
links:
- name: "APS Division of Atomic and Molecular Physics Meeting 2019, abstract id.E01.164"
path: null
- title: "Progress towards ultracold Sr for the AION project — sub-microkelvin atoms and an optical-heterodyne diagnostic tool for injection-locked laser diodes"
authors: "E. Pasatembou, C.F.A. Baynham, O. Buchmüller, D. Evans, R. Hobson, L. Iannizzotto-Venezze, A. Josset"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2310.08500"
- title: "Experimental demonstration of enhanced violations of Leggett-Garg inequalities in a PT-symmetric trapped-ion qubit"
authors: "Pengfei Lu, Xinxin Rao, Teng Liu, Yang Liu, Ji Bian, Feng Zhu, Le Luo"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2309.06713"
- title: "Fast adiabatic transport of single laser-cooled <sup>9</sup>Be+ ions in a cryogenic Penning trap stack"
authors: "T. Meiners, J.-A. Coenders, J. Mielke, M. Niemann, J.M. Cornejo, S. Ulmer, C. Ospelkaus"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2309.06776"
- title: "Schlussbericht zum Verbundvorhaben Quanten-Inertialsensorsystem (QGyro)"
authors:
short: "S. Schön, S. Abend, E. v. Hinüber, N.B. Weddig,"
full: "B. Tennstedt, P. Barbey, M. Gersemann, T. Kersten, Yueyang Zou, Ashwin Rajagopalan, Mouine Abidi, Alexander Löwer, Ernst M. Rasel"
links:
- name: "Leibniz Universität Hannover"
path: "https://www.repo.uni-hannover.de/handle/123456789/14823"
- title: "One-Time Compilation of Device-Level Instructions for Quantum Subroutines"
authors: "Aniket S. Dalvi, Jacob Whitlow, Marissa D'Onofrio, Leon Riesebos, Tianyi Chen, Samuel Phiri, Kenneth R. Brown, Jonathan M. Baker"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2308.10787"
- title: "QUANT-NET: A testbed for quantum networking research over deployed fiber"
authors: "Inder Monga, Erhan Saglamyurek, Ezra Kissel, Hartmut Häffner, Wenji Wu"
links:
- name: "Association for Computing Machinery"
path: "https://dl.acm.org/doi/abs/10.1145/3610251.3610561"
- name: "eScholarship University of California"
path: "https://escholarship.org/uc/item/7hk9g5d3"
- title: "An environmental monitoring network for quantum gas experiments and devices"
authors: "T.J. Barrett, W. Evans, A. Gadge, S. Bhumbra, S. Sleegers, R. Shah, J. Fekete, F. Oručević and P. Krüger"
links:
- name: "IOPscience"
path: "https://iopscience.iop.org/article/10.1088/2058-9565/ac3385"
- title: "Adiabatically controlled motional states of a ground-state cooled CaO<sup>+</sup> and Ca<sup>+</sup> trapped ion chain"
authors: "Lu Qi, Evan C. Reed, Kenneth R. Brown"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2212.05105"
- title: "Dynamical low-noise microwave source for cold-atom experiments"
authors: "Bernd Meyer-Hoppe, Maximilian Baron, Christophe Cassens, Fabian Anders, Alexander Idel, Jan Peise, and Carsten Klempt"
links:
- name: "AIP Publishing"
path: "https://pubs.aip.org/aip/rsi/article/94/7/074705/2902630"
- title: "Fast, high-fidelity addressed single-qubit gates using efficient composite pulse sequences"
authors: "A.D. Leu, M.F. Gely, M.A. Weber, M.C. Smith, D.P. Nadlinger, D.M. Lucas"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2305.06725"
- title: "QisDAX: An Open Source Bridge from Qiskit to Trapped Ion Quantum Devices."
authors: "Kaustubh Jagdish Badrike"
links:
- name: "NCSU"
path: "https://repository.lib.ncsu.edu/bitstream/handle/1840.20/40949/etd.pdf"
- title: "Verifiable blind quantum computing with trapped ions and single photons"
authors: "P. Drmota, D.P. Nadlinger, D. Main, B.C. Nichol, E.M. Ainley, D. Leichtle, A. Mantri, E. Kashefi, R. Srinivas, G. Araneda, C.J. Ballance, D.M. Lucas"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2305.02936"
- title: "Low noise quantum frequency conversion of photons from a trapped barium ion to the telecom O-band"
authors: "Uday Saha, James D. Siverns, John Hannegan, Qudsia Quraishi, Edo Waks"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2305.01205"
- title: "Open-source electronics ecosystem for optical atomic clocks"
authors: "Piotr Morzyński, Sławomir Bilicki, Marcin Bober, Adam Ledziński, Marcin Witkowski, Mehrdad Zarei and Michał Zawada"
links:
- name: "IOPscience"
path: "https://iopscience.iop.org/article/10.1088/1361-6501/acc5a1"
- title: "Transient fluorescence with a single trapped ion"
authors: "Nicolás Nuñez Barreto, Lucas Giardino, Carla Crucianelli, Muriel Bonetto, Martin Drechsler, Christian Tomás Schmiegelow"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2303.00650"
- title: "Sampler: Open-Source Data Acquisition Module for Quantum Physics"
authors: "Kasprowicz Grzegorz, Harty Thomas, Bourdeauducq Sébastien, Jördens Robert, Allcock David, Slichter Daniel, Nadlinger David, Britton Joseph W. ,Sotirova Ana"
links:
- name: "BazTech"
path: "https://yadda.icm.edu.pl/baztech/element/bwmeta1.element.baztech-e97e8039-a7dd-4283-b1af-45b6a61993d1"
- title: "Practical Implementation of an Analogue and Digital Electronics System for a Modular Cosmic Ray Detector — MCORD"
authors:
short: "Marcin Bielewicz, Aleksandr Bancer, Andrzej Dziedzic, Jaroslaw Grzyb,"
full: "Elzbieta Jaworska, Grzegorz Kasprowicz, Michal Kiecana, Piotr Kolasinski, Michal Kuc, Michal Kuklewski, Marcin Pietrzak, Krzysztof Pozniak, Maciej Sitek, Mikolaj Sowinski, Łukasz ´Swiderski, Agnieszka Syntfeld-Kazuch, Jaroslaw Szewinski and Wojciech Marek Zabołotny"
links:
- name: "MDPI"
path: "https://www.mdpi.com/2079-9292/12/6/1492"
- title: "Cold highly charged ions in a radio-frequency trap with superconducting magnetic shielding"
authors:
short: "Elwin A. Dijck, Christian Warnecke, Malte Wehrheim, Ruben B. Henninger, Julia Eff,"
full: "Kostas Georgiou, Andrea Graf, Stepan Kokh, Lakshmi P. Kozhiparambil Sajith, Christopher Mayo, Vera M. Schäfer, Claudia Volk, Piet O. Schmidt, Thomas Pfeifer, José R. Crespo López-Urrutia"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2306.01670"
- title: "Positronium laser cooling via the 1<sup>3</sup>S-2<sup>3</sup>P transition with a broadband laser pulse"
authors:
short: "AEgIS Collaboration:"
full: "L.T. Glöggler, N. Gusakova, B. Rienäcker, A. Camper, R. Caravita, S. Huck, M. Volponi, T. Wolz, L. Penasa, V. Krumins, F. Gustafsson, M. Auzins, B. Bergmann, P. Burian, R.S. Brusa, F. Castelli, R. Ciuryło, D. Comparat, G. Consolati, M. Doser, Ł. Graczykowski, M. Grosbart, F. Guatieri, S. Haider, M.A. Janik, G. Kasprowicz, G. Khatri, Ł.Kłosowski, G. Kornakov, L. Lappo, A. Linek, J. Malamant, S. Mariazzi, V. Petracek, M. Piwiński, S. Pospisil, L. Povolo, F. Prelz, S.A. Rangwala, T. Rauschendorfer, B.S. Rawat, V. Rodin, O.M. Røhne, H. Sandaker, P. Smolyanskiy, T. Sowiński, D. Tefelski, T. Vafeiadis, C.P. Welsch, M. Zawada, J. Zielinski, N. Zurlo"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2310.08760"
- name: "CERN"
path: "https://cds.cern.ch/record/2878376"
- title: "Open Hardware in Quantum Technology"
authors:
short: "Nathan Shammah, Anurag Saha Roy, Carmen G. Almudever, Sébastien Bourdeauducq, Anastasiia Butko,"
full: "Gustavo Cancelo, Susan M. Clark, Johannes Heinsoo, Loïc Henriet, Gang Huang, Christophe Jurczak, Janne Kotilahti, Alessandro Landra, Ryan LaRose, Andrea Mari, Kasra Nowrouzi, Caspar Ockeloen-Korppi, Guen Prawiroatmodjo, Irfan Siddiqi, William J. Zeng"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2309.17233"
- title: "Weighing antimatter: AEg over bar IS Phase 2, upgrades and first data"
authors: "M. Volponi, G. Consolati, AEgIS Collaboration"
links:
- name: "Politecnico di Milano"
path: "https://re.public.polimi.it/handle/11311/1249382"
- title: "The Electronic Control System of a Trapped-Ion Quantum Processor: A Systematic Literature Review"
authors: "Stefanie Castillo"
links:
- name: "IEEE"
path: "https://ieeexplore.ieee.org/abstract/document/10164086"
- title: "Engineering a Control System for a Logical Qubit-Scalee Trapped Ion Quantum Computer"
authors: "Andrew Russ Risinger"
links:
- name: "Duke"
path: "https://iontrap.duke.edu/files/2023/07/thesis-arr-2023-04-16.pdf"
- title: "An optical clock based on a highly charged ion"
authors: "Lukas Josef Spieß"
links:
- name: "Leibniz Universität Hannover"
path: "https://www.repo.uni-hannover.de/handle/123456789/13801"
- title: "Advances in performance and automation of a single ytterbium ion optical clock"
authors: "Alexandra Tofful"
links:
- name: "Imperial College London"
path: "https://spiral.imperial.ac.uk/handle/10044/1/103468"
- title: "A many-channel FPGA control system"
authors: "Daniel T. Schussheim, Kurt Gibble"
links:
- name: "arXiv"
path: "https://arxiv.org/abs/2307.16008"
- title: "Radium ions and radioactive molecules for probing new physics"
authors: "Mingyu Fan"
links:
- name: "eScholarship University of California"
path: "https://escholarship.org/uc/item/5j42z6c8"
- title: "Programowalny sterownik siłownika piezoelektrycznego"
authors: "Adam Kołodyński"
links:
- name: "Warsaw University of Technology, Engineer's thesis"
path: null
- name: "local copy"
path: "/AdamKolodynski.pdf"
- title: "Microwave synthesizer for driving ion traps in quantum computing"
authors: "Krzysztof Belewicz"
links:
- name: "Warsaw University of Technology, Bachelor's dipoma thesis"
path: null
- name: "local copy"
path: "/Krzysztof_Belewicz_V1.1.pdf"
- title: "Wielokanałowy moduł sterujący pułapką jonową w standardzie MicroTCA.4"
authors: "Tomasz Przywózki"
links:
- name: "Warsaw University of Technology, Master's thesis"
path: null
- name: "local copy"
path: "/TomaszPrzywozki_v3.pdf"
- title: "Short Time Fourier Transform Pulse Generator for Trapped Ion Quantum Gates"
authors: "Norman Krackow"
links:
- name: "local copy"
path: "/thesis_nkrackow.pdf"
- title: "Precise PID controller for quantum applications"
authors: "Jakub Matyas"
links:
- name: "Warsaw University of Technology, Engineer's thesis"
path: "https://repo.pw.edu.pl/info/bachelor/WUTe90de3461b53456c9d63c55d80d34220/"
- name: "local copy"
path: "/JMatyas_inz_v6.pdf"
- title: "Controller module for real-time quantum applications"
authors: "Paweł Kulik"
links:
- name: "Warsaw University of Technology, Masters's diploma thesis"
path: "https://repo.pw.edu.pl/info/master/WUT6212c3c3cdcd40ea9095f4ead6482596/"
- name: "local copy"
path: "/pawel_kulik_kasli_thesis_2019_compressed.pdf"

View File

@ -114,7 +114,7 @@ template = "page.html"
**Sinara hardware purchases, ports to your hardware, feature development, technical support, bugfixing**
contact [sales@m-labs.hk](mailto:sales@m-labs.hk)
contact sales@m-\*\*\*s.hk
We welcome inquiries from research groups of all sizes.<br>[See what has been funded before](/experiment-control/funding)
@ -146,12 +146,6 @@ We welcome inquiries from research groups of all sizes.<br>[See what has been fu
<a href="https://github.com/OxfordIonTrapGroup/oitg" target="_blank" rel="noopener noreferrer">Repository</a>
{% end %}
{% layout_card(title="UCLA routines", sameheight=120) %}
<small>ARTIQ experiments in use at UCLA AMO</small>
<a href="https://github.com/EGGS-Experiment/LAX_exp" target="_blank" rel="noopener noreferrer">Repository</a>
{% end %}
{% layout_card(title="nvOS", sameheight=120) %}
<small>A quantum operating system built around ARTIQ and NV centers in diamond.</small>
@ -184,12 +178,6 @@ 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>
@ -237,6 +225,6 @@ We welcome inquiries from research groups of all sizes.<br>[See what has been fu
</div>
{% layout_div(css="col-12 text-center") %}
Want your project listed here? Write to [sb@m-labs.hk](mailto:sb@m-labs.hk).
Want your project listed here? Write to sb@m-l\*\*\*.hk.
{% end %}

View File

@ -12,9 +12,9 @@ title = "Sinara hardware"
The first ARTIQ core devices used hardware built in-house by physicists (based on a Xilinx KC705 development board with custom FMC cards). To improve the quality, features and scalability of ARTIQ systems, we have been developing the Sinara device family. It provides turnkey control hardware that is reproducible, open, flexible, modular, well-tested, and well-supported by the ARTIQ control software.
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="https://www.ise.pw.edu.pl/" target="_blank" rel="noopener noreferrer">Institute for Electronics Systems</a> at the Warsaw University of Technology.
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. 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.
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 sales@m-\*\*\*s.hk 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") %}
##### Ordering from M-Labs is easy and quick
##### 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. 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.
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.
Contact sales@m-\*\*\*s.hk with your requirements and we will establish a quote.
{% end %}

View File

@ -15,7 +15,7 @@ The Milkymist One is programmable and customizable at many different levels.
The simplest way one can use a Milkymist One is by affecting MIDI controls to the existing snippets of code (called "patches") that create the effects. The Milkymist One ships with dozens of pre-existing patches.
Creating new patches can be done with a simple programming language based on the <a href="https://web.archive.org/web/20130825023759/http://www.milkdrop.co.uk/guide.htm" target="_blank" rel="noopener noreferrer">MilkDrop preset format</a>. The Milkymist One device comes with a built-in editor.
Creating new patches can be done with a simple programming language based on the <a href="http://www.milkdrop.co.uk/guide.htm" target="_blank" rel="noopener noreferrer">MilkDrop preset format</a>. The Milkymist One device comes with a built-in editor.
Under the hood, the Milkymist One is like a mini-computer running our Flickernoise video synthesis software.
@ -50,10 +50,10 @@ But we did not stop at open source <em>software</em>. As a matter of fact, when
##### Press
- Create Digital Motion (10/02/2012): <a href="http://createdigitalmotion.com/2012/02/milkymist-is-digital-visual-synthesizer-and-processor-built-on-sophisticated-open-source-hardware/">Milkymist is Digital Visual Synthesizer and Processor, Built as Sophisticated Open Source Hardware</a>
- MikroBitti (03/2012): <a href="https://web.archive.org/web/20140226181735/http://www.mbnet.fi/artikkeli/lehti/avointa_vj_rautaa_3_2012">Milkymist One -visualisaattori: Avointa vj-rautaa</a> [FI]
- MikroBitti (03/2012): <a href="http://www.mbnet.fi/artikkeli/lehti/avointa_vj_rautaa_3_2012">Milkymist One -visualisaattori: Avointa vj-rautaa</a> [FI]
- Make Magazine (30/09/2011): <a href="http://blog.makezine.com/2011/09/30/milkymist-one-an-open-source-vj-console-goes-on-sale/">Milkymist One, an Open Source VJ Console, Goes on Sale</a>
- The Register (28/09/2011): <a href="http://www.theregister.co.uk/2011/09/28/milkymist/print.html">Open-source hardware group puts out vid system-on-a-chip</a>
- Theory&amp;Practice (22/09/2011): <a href="https://web.archive.org/web/20160409033154/http://theoryandpractice.ru/seminars/19402-videosintezator-s-otkrytym-kodom-milkymist-one-22-9">Видеосинтезатор с открытым кодом Milkymist One</a> [RU]
- Theory&amp;Practice (22/09/2011): <a href="http://theoryandpractice.ru/seminars/19402-videosintezator-s-otkrytym-kodom-milkymist-one-22-9">Видеосинтезатор с открытым кодом Milkymist One</a> [RU]
- ETN (17/05/2011): <a href="http://www.etn.se/index.php?option=com_content&view=article&id=53785">Videoeffekter i öppen hårdvara söker partners</a> [SE]
- Linux-Magazin (28/01/2011): <a href="http://www.linux-magazin.de/NEWS/VJ-System-Milkymist-als-Entwicklerboard-erhaeltlich">VJ-System Milkymist als Entwicklerboard erhältlich</a> [DE]
- Create Digital Motion (16/08/2010): <a href="http://createdigitalmotion.com/2010/08/milkymist-one-all-in-one-open-source-vj-workstation/">Milkymist One, All-in-One Open Source VJ Workstation</a>
@ -154,17 +154,17 @@ Milkymist SoC is phased out in favor of the more powerful [MiSoC](../migen).
##### Flickernoise
Flickernoise is the video synthesis application developed for the Milkymist One and the Milkymist SoC. It renders hardware-accelerated visual effects comparable (and, to some extent, compatible) with those of <a href="https://www.geisswerks.com/milkdrop/">MilkDrop</a> 1.x, the popular audio visualization plug-in for Winamp.
Flickernoise is the video synthesis application developed for the Milkymist One and the Milkymist SoC. It renders hardware-accelerated visual effects comparable (and, to some extent, compatible) with those of <a href="http://www.nullsoft.com/free/milkdrop">MilkDrop</a> 1.x, the popular audio visualization plug-in for Winamp.
Flickernoise allows the creation of visual patches and their connection with all the interfaces that the Milkymist One provides thanks to a built-in graphical user interface. Let the visuals react to sound and MIDI events, connect a camera and create live phantasmagoric images of yourself...
Flickernoise uses:
- The <a href="http://www.rtems.org">RTEMS</a> real-time operating system.
- The <a href="https://web.archive.org/web/20151025081338/http://www.yaffs.org/">YAFFS</a> flash filesystem (modified version <a href="http://www.github.com/m-labs/rtems-yaffs2">here</a>).
- The <a href="http://www.yaffs.org">YAFFS</a> flash filesystem (modified version <a href="http://www.github.com/m-labs/rtems-yaffs2">here</a>).
- The <a href="http://www.libpng.org">libpng</a>, <a href="http://www.ijg.org">libjpeg</a>, <a href="http://www.ijg.org">openjpeg</a> and <a href="http://jbig2dec.sourceforge.net/">jbig2dec</a> image decompression libraries.
- The <a href="http://www.freetype.org">freetype</a> font rendering system.
- The <a href="https://www.mupdf.com">MuPDF</a> library for the online help system.
- The <a href="http://www.mupdf.com">MuPDF</a> library for the online help system.
- <a href="http://www.github.com/m-labs/mtk">MTK</a>, a modified version of the <a href="http://www.genode-labs.com/products/fpga-graphics">Genode FX</a> embedded GUI toolkit, which provides all the elements for common user interaction (windows, buttons, etc.).
- <a href="http://www.github.com/m-labs/liboscparse">liboscparse</a>, a variant of <a href="http://liblo.sourceforge.net/">liblo</a>, for <a href="http://www.opensoundcontrol.org">OpenSoundControl</a> communications.

View File

@ -54,7 +54,7 @@ You can find the Migen source <a href="http://github.com/m-labs/migen" target="_
- <a href="https://www.wdj-consulting.com/blog/migen-port.html" target="_blank" rel="noopener noreferrer">Tutorial "Porting a New Board To Migen"</a> by cr1901
- <a href="https://lab.whitequark.org/notes/2016-10-18/implementing-an-uart-in-verilog-and-migen/" target="_blank" rel="noopener noreferrer">"Implementing a UART in Verilog and Migen"</a> by whitequark
- <a href="https://lab.whitequark.org/notes/2016-10-19/implementing-a-simple-soc-in-migen/" target="_blank" rel="noopener noreferrer">"Implementing a simple SoC in Migen"</a> by whitequark
- <a href="https://web.archive.org/web/20210416131215/http://blog.lambdaconcept.com/doku.php?id=migen:tutorial" target="_blank" rel="noopener noreferrer">Migen Step by Step Tutorial</a> by LambdaConcept
- <a href="http://blog.lambdaconcept.com/doku.php?id=migen:tutorial" target="_blank" rel="noopener noreferrer">Migen Step by Step Tutorial</a> by LambdaConcept
{% end %}

View File

@ -28,7 +28,7 @@ nMigen itself provides the core language, and is complemented by a number of ext
##### Documentation
Documentation for nMigen and its external components is rather limited at the moment. However, you can follow the <a href="https://web.archive.org/web/20210518042504/http://blog.lambdaconcept.com/doku.php?id=nmigen:tutorial" target="_blank" rel="noopener noreferrer">tutorial by LambdaConcept</a>, and another <a href="https://github.com/RobertBaruch/nmigen-tutorial" target="_blank" rel="noopener noreferrer">tutorial by Robert Baruch</a>, as well as his video series on building a 6800 CPU on an FPGA with nMigen: <a href="https://www.youtube.com/watch?v=85ZCTuekjGA" target="_blank" rel="noopener noreferrer">part 1</a> <a href="https://www.youtube.com/watch?v=AQOXoKQhG3I" target="_blank" rel="noopener noreferrer">part 2</a> <a href="https://www.youtube.com/watch?v=aLQqOxnVMOQ" target="_blank" rel="noopener noreferrer">part 3</a> <a href="https://www.youtube.com/watch?v=xqMtyCu4lME" target="_blank" rel="noopener noreferrer">part 4</a>.
Documentation for nMigen and its external components is rather limited at the moment. However, you can follow the <a href="http://blog.lambdaconcept.com/doku.php?id=nmigen:tutorial" target="_blank" rel="noopener noreferrer">tutorial by LambdaConcept</a>, and another <a href="https://github.com/RobertBaruch/nmigen-tutorial" target="_blank" rel="noopener noreferrer">tutorial by Robert Baruch</a>, as well as his video series on building a 6800 CPU on an FPGA with nMigen: <a href="https://www.youtube.com/watch?v=85ZCTuekjGA" target="_blank" rel="noopener noreferrer">part 1</a> <a href="https://www.youtube.com/watch?v=AQOXoKQhG3I" target="_blank" rel="noopener noreferrer">part 2</a> <a href="https://www.youtube.com/watch?v=aLQqOxnVMOQ" target="_blank" rel="noopener noreferrer">part 3</a> <a href="https://www.youtube.com/watch?v=xqMtyCu4lME" target="_blank" rel="noopener noreferrer">part 4</a>.
{% end %}

View File

@ -30,7 +30,7 @@ Built on the <a href="/gateware/migen/">MiSoC and Migen</a> technologies that or
The Mixxeo supported mixing from two DVI or HDMI sources up to 720p60, with crossfade, fade to black and potentially other effects with a latency of less than two frames.
<b>Status (Aug 2014)</b> - Main board and gateware have been <a href="/images/mixxeo_result.jpg">mostly functional</a> for a while, mechanical design and manufacturing for the case/mechatronics are progressing slowly. If you have the skills and would like to help out, email [sb@m-labs.hk](mailto:sb@m-labs.hk) or the mailing list.
<b>Status (Aug 2014)</b> - Main board and gateware have been <a href="/images/mixxeo_result.jpg">mostly functional</a> for a while, mechanical design and manufacturing for the case/mechatronics are progressing slowly. If you have the skills and would like to help out, email sb at m-labs.hk or the mailing list.
<center><img src="/images/mixxeo_menu.png"><br /><br /><img src="/images/mixxeo_board.jpg" class="picimg"></center>

View File

@ -26,6 +26,6 @@ smoltcp achieves <a href="https://github.com/smoltcp-rs/smoltcp#examplesbenchmar
The source code is available <a href="https://github.com/smoltcp-rs/smoltcp" rel="noopener noreferrer">on GitHub</a>.
**Commercial support for smoltcp is available.** Email [sales@m-labs.hk](mailto:sales@m-labs.hk).
**Commercial support for smoltcp is available.** Email sales@m-l\*\*\*s.hk.
{% end %}

1676
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
{
"name": "m-labs-zola",
"sideEffects": false,
"version": "1.0.0",
"description": "These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. For deployment, see the nix-scripts repository. Commits to https://git.m-labs.hk/M-Labs/web2019.git are automatically deployed to m-labs.hk through Hydra.",
"scripts": {
@ -13,38 +12,27 @@
"url": "https://git.m-labs.hk/M-Labs/web2019.git"
},
"devDependencies": {
"@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/cli": "^7.22.6",
"@babel/core": "^7.22.8",
"@babel/preset-env": "^7.22.7",
"@babel/preset-react": "^7.22.5",
"babel-preset-minify": "^0.5.2",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
"@hello-pangea/dnd": "^16.5.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.1",
"webpack": "^5.90.3",
"babel-loader": "^9.1.3",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"json-logic-js": "^2.0.2",
"zustand": "^4.5.2",
"@uidotdev/usehooks":"^2.4.1",
"webpack-preprocessor-loader": "^1.3.0"
"bootstrap": "^5.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"axios": "^1.4.0",
"prop-types": "^15.8.1",
"jquery": "^3.7.0",
"uuid": "^9.0.0"
},
"babel": {
"presets": [
"@babel/preset-react",
["@babel/preset-env", {
"targets": {
"browsers": [
">0.25%",
"not dead",
"not op_mini all"
]
}
}]
"@babel/preset-env",
"@babel/preset-react"
]
}
}

View File

@ -149,7 +149,7 @@ a {
border: 1px solid $btn-primary-2 !important;
text-decoration: none;
&:hover, &:disabled {
&:hover {
background-color: $btn-secondary-2;
border: 1px solid $btn-secondary-2 !important;
}
@ -242,7 +242,4 @@ a[aria-expanded='true']:after {
margin-right: 12px;
display: block;
float: left;
}
.hide-expanded[aria-expanded="true"], .show-expanded[aria-expanded="false"], .show-expanded {display: none;}
.hide-expanded[aria-expanded="false"], .show-expanded[aria-expanded="true"] {display: inline;}
}

View File

@ -83,18 +83,10 @@
.card h5,
.card h4,
h5 {
color: $brand-color;
}
h4 {
color: $color-primary;
margin-top: 3rem;
margin-bottom: 3rem;
text-align: center;
}
h3, h2, h1 {
color: $color-secondary;
}
@ -244,22 +236,4 @@ img.kf25 {
#root-shop, #root-shop>div {
height: 100%;
}
}
// follow respective border-[start, end, top, bottom] from bootstrap
@media (max-width: 767px) {
.funding-logo-start {
border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important;
}
.funding-logo-end {
border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important;
}
}
@media (min-width: 768px) {
.funding-logo-start {
border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important;
}
.funding-logo-end {
border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important;
}
}
}

View File

@ -8,62 +8,51 @@ button {
outline: none!important;
}
.rfqFeedback {
/* -webkit-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);*/
border: 1px solid $brand-color;
.modal-body, .modal-content, .modal {
border-radius: 0;
}
.form-group {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
text-align: center;
/*position: absolute;
width: 350px;*/
background: white;
/*left: calc(100%/2 - 350px/2);*/
/*top: calc(50% - 50px);*/
font-size: .9rem;
}
button {
background-color: inherit;
align-self: center;
border: 0;
position: absolute;
right: 10px;
top: 10px;
img {
width: 15px;
}
}
.btn.btn-primary.disabled {
background-color: gray;
}
.btn-outline-primary {
color: $btn-primary-2;
border-color: $btn-primary-2;
background-color: inherit;
&:hover {
background-color: inherit;
color: $btn-secondary-2;
border-color: $btn-secondary-2;
}
}
}
#root-shop {
.layout {
.rfqFeedback {
display: flex;
align-items: center;
padding: 2rem 3rem;
text-align: center;
/*position: absolute;
width: 350px;*/
background: white;
/*left: calc(100%/2 - 350px/2);*/
-webkit-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
/*top: calc(50% - 50px);*/
border: 1px solid $brand-color;
font-size: .9rem;
button {
background-color: inherit;
align-self: center;
border: 0;
position: absolute;
right: 10px;
top: 10px;
img {
width: 15px;
}
}
.btn.btn-primary.disabled {
background-color: gray;
}
.btn-outline-primary,
.btn-outline-primary:hover {
color: $btn-primary-2;
border-color: $btn-primary-2;
background-color: inherit;
}
}
display: flex;
> aside.aside {
@ -72,32 +61,26 @@ 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;*/
}
.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;
}
> 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;
}
> section.main {
flex: 4;
width: calc(3/4 * 100%);
max-width: calc(4 / 6 * 100%);
overflow-y: scroll;
}
}
@ -106,7 +89,7 @@ button {
display: flex;
color: white;
padding: 1rem 0rem 1rem 1.5rem;
padding: 3rem 2rem 1rem;
.content {
flex: 1;
@ -125,7 +108,6 @@ button {
h3 {
color: white;
font-size: 1.5rem;
}
button {
@ -183,50 +165,8 @@ button {
}
}
.catalog-container {
.backlog-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 {
@ -246,25 +186,37 @@ button {
padding-bottom: .5rem;
}
.btn-outline-primary {
.btn-outline-primary,
.btn-outline-primary:hover {
color: $btn-primary-2;
border-color: $btn-primary-2;
background-color: inherit;
&:hover {
background-color: inherit;
color: $btn-secondary-2;
border-color: $btn-secondary-2;
}
}
.control {
display: flex;
font-size: .8rem;
> .description {
width: 80%;
> p {
width: 50%;
padding-right: 30px;
}
.crate-mode {
text-align: right;
width: 50%;
a {
cursor: pointer;
margin-right: 1rem;
color: inherit;
text-decoration: none;
padding-bottom: 5px;
}
a.active {
font-weight: 700;
border-bottom: 3px solid $btn-primary-2;
}
}
}
.summary {
@ -290,9 +242,6 @@ button {
.item-card-name {
font-weight: 700;
&.tabbed {
padding-left: 16px;
}
}
.price {
@ -306,31 +255,11 @@ button {
.item-card-name,
.price {
.alert-warning, .alert-info {
> .alert-warning {
background-color: inherit;
height: inherit;
width: 20px;
padding-bottom: 0;
}
}
.overlayVariant {
min-width: 100px;
max-height: 150px;
min-height: 50px;
overflow-y: scroll;
position: absolute;
display: flex;
align-items: start;
text-align: left;
background-color: white;
color: black;
flex-direction: column;
cursor: pointer;
p {
font-size: .875rem;
margin: 0;
padding-bottom: 3px;
}
}
@ -367,19 +296,13 @@ button {
button {
background-color: inherit;
border: 0;
margin-left: 16px;
margin-left: 20px;
img {
width: 20px;
height: auto;
}
}
span {
width: 28px;
}
.span-with-margin {
margin-left: 16px;
}
}
> .summary-form {
@ -404,7 +327,7 @@ button {
outline: none;
}
.order-form-submit,
input[type="submit"],
.btn-cla {
/*background-color: $btn-primary-2;*/
font-weight: 700;
@ -423,39 +346,16 @@ button {
border: 1px solid #e53e3e !important;
}
.btn-outline-primary {
.btn-outline-primary,
.btn-outline-primary:hover {
color: $btn-primary-2;
border-color: $btn-primary-2;
background-color: inherit;
&:hover {
background-color: inherit;
color: $btn-secondary-2;
}
}
}
}
}
.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;
@ -482,7 +382,7 @@ button {
display: flex;
flex-direction: column;
/*max-width: 96px;*/
max-width: 132px;
max-width: 130px;
justify-content: flex-start;
align-content: center;
align-items: center;
@ -532,12 +432,56 @@ button {
height: 24px;
}
> .alert-warning, .alert-info {
> .alert-warning {
background-color: inherit;
height: inherit;
width: 20px;
padding-bottom: 3px;
}
.k-popup-connectors,
.k-popup-warning {
position: absolute;
background-color: $color-primary;
color: white;
font-weight: 700;
font-size: .6rem;
padding: .8rem 1rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
text-align: left;
p {
margin-bottom: 0;
}
p + p {
padding-bottom: 8px;
}
}
.k-popup-connectors {
min-width: 200px;
bottom: 50px;
}
.k-popup-warning {
background-color: #c75e5e;
min-width: 350px;
.rule.warning {
display: flex;
font-size: .75rem;
img {
margin-right: 10px;
height: 100%;
}
}
}
.k-popup-warning.inverted {
right: 0;
}
}
.overlayRemove {
@ -564,67 +508,6 @@ button {
}
}
.overlayVariant {
top: 24px;
width: 140px;
min-height: 40px;
max-height: 320px;
overflow-y: scroll;
position: absolute;
display: flex;
align-items: start;
text-align: left;
background-color: white;
color: black;
flex-direction: column;
cursor: pointer;
padding: 0.2rem 0;
p {
font-size: .65rem;
margin: 0;
}
div {
margin: 0.1rem 0.2rem;
font-size: 0.75rem;
input {
padding: 0;
font-size: 0.75rem;
line-height: 1.1;
}
label {
margin-bottom: 0.1rem;
}
.options-icon {
display: inline;
height: .875rem;
margin-right: 0.2rem;
margin-left: 0.2rem;
}
}
.form-check {
min-height: 1rem;
}
}
}
.overlay-smallcard {
left: -38.5px; // (card width (63) - overlay width (140)) / 2
&.overlay-first {
left: 0;
}
&.overlay-last {
left: -67px;
}
}
.overlay-bigcard {
left: -7px; // (card width (126) - overlay width (140)) / 2
}
.hovered {
@ -635,43 +518,6 @@ button {
.crate-info {
padding: 1rem 0 0;
}
.crate-bar {
width: 100%;
font-size: 0.9rem;
.crate-mode {
text-align: left;
width: 75%;
display: inline;
a {
cursor: pointer;
margin-right: 1rem;
color: inherit;
text-decoration: none;
padding-bottom: 5px;
display: inline-block;
}
a.active {
font-weight: 700;
border-bottom: 3px solid $btn-primary-2;
}
}
.delete-crate {
text-decoration: none;
cursor: pointer;
text-align: right;
display: inline-flex;
width: 25%;
color: inherit;
img {
margin-left: 1rem;
align-self: center;
}
}
}
}
}
@ -704,70 +550,4 @@ button {
margin-left: -10px;
}
#accordion_crates {
background-color: inherit;
.accordion_crates_item {
.accordion-header {
padding-bottom: 0;
}
.accordion-button {
background-color: inherit;
font-weight: bold;
&:hover {
background-color: $color-highlight;
}
}
}
#accordion_crates_add {
.accordion-header {
padding-bottom: 0;
}
.accordion-button {
font-weight: bold;
&:hover {
background-color: $color-highlight;
}
}
.accordion-button:after {
background-image: url("/images/shop/icon-add.svg");
}
}
}
}
.k-popup-connectors,
.k-popup-warning {
background-color: $color-primary;
color: white;
font-weight: 700;
font-size: .6rem;
padding: .5rem .8rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
text-align: left;
p {
margin-bottom: 0;
}
}
.k-popup-connectors {
min-width: 200px;
}
.k-popup-warning {
background-color: #c75e5e;
min-width: 300px;
.rule.warning {
display: flex;
font-size: .75rem;
img {
margin-right: 10px;
height: 100%;
}
}
}

View File

@ -24,8 +24,7 @@ $btn-secondary-2: #a88cfd !default;
$link-primary-dark: #c2affd !default;
$link-secondary-dark: #cec2ea !default;
$color-hover: #eae7f7 !default;
$color-highlight: #dfe9ff !default;
$color-hover: #eae7f7 !default;
// Import partials.
@import

View File

@ -10,18 +10,10 @@ from flask import request
from flask_mail import Mail
from flask_mail import Message
from werkzeug.middleware.proxy_fix import ProxyFix
from jinja2.utils import htmlsafe_json_dumps
load_dotenv()
mail_password_file = getenv("FLASK_MAIL_PASSWORD_FILE")
if mail_password_file is not None:
with open(mail_password_file, "r") as f:
mail_password = f.read().strip()
else:
mail_password = None
app = Flask(__name__)
app.config.update(
DEBUG=getenv("FLASK_DEBUG") == "True",
@ -30,7 +22,7 @@ app.config.update(
MAIL_USE_SSL=getenv("FLASK_MAIL_USE_SSL"),
MAIL_DEBUG=False,
MAIL_USERNAME=getenv("FLASK_MAIL_USERNAME"),
MAIL_PASSWORD=mail_password,
MAIL_PASSWORD=getenv("FLASK_MAIL_PASSWORD"),
MAIL_RECIPIENT=getenv("FLASK_MAIL_RECIPIENT"),
MAIL_SENDER=getenv("FLASK_MAIL_SENDER")
)
@ -49,7 +41,7 @@ def after(response):
@app.route("/rfq", methods=["POST"])
def send_rfq():
payload = request.json
payload = json.loads(htmlsafe_json_dumps(payload))
payload = json.loads(json.htmlsafe_dumps(payload))
if payload is None:
resp = jsonify(error="invalid data")

View File

@ -11,18 +11,6 @@
.feedback-add-success {
display: none;
}
.feedback-add-failure {
background-color: #c75e5e;
display: block;
position: fixed;
top: 20px;
right: 20px;
padding: 1em;
z-index: 100000;
color: white;
border-radius: 10px;
box-shadow: 0 0 5px 3px;
}
#accordion_categories,
#accordion_categories .accordion-header,
@ -55,7 +43,7 @@
color: white;
font-weight: bold;
font-size: 1.75rem;
padding: .75rem 1.5rem;
padding: .75rem 2rem;
}
#accordion_categories .accordion-body {
@ -73,10 +61,6 @@
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
@ -98,14 +82,6 @@
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;
}
@ -123,7 +99,7 @@
}
#root-shop .productItem .content ul {
font-size: .75rem;
font-size: .6rem;
}
#root-shop .panel .control {
@ -132,11 +108,11 @@
}
#root-shop .panel .control > .description,
#root-shop .crate-mode {
#root-shop .panel .control > .crate-mode {
width: 100%;
}
#root-shop .crate-mode {
#root-shop .panel .control > .crate-mode {
text-align: left;
}
@ -145,11 +121,10 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: 0.8rem;
font-size: 1rem;
}
#root-shop .panel .summary>.summary-form form,
#root-shop .panel .summary>.summary-form .order-bar {
#root-shop .panel .summary>.summary-form form {
width: 100%;
}
@ -175,7 +150,7 @@
}
#root-shop table tr {
padding: .2em 0;
padding: .8em 0;
display: flex !important;
justify-content: space-between;
}
@ -204,7 +179,7 @@
}
body {
font-size: .8rem;
font-size: .7rem;
}
#root-shop, #root-shop>div {
@ -229,7 +204,7 @@
}
#root-shop table tr {
padding: .2em 0;
padding: .8em 0;
display: flex !important;
justify-content: space-between;
}
@ -239,16 +214,15 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .8rem;
font-size: .7rem;
}
#root-shop .panel .summary>.summary-form form,
#root-shop .panel .summary>.summary-form .order-bar {
#root-shop .panel .summary>.summary-form form {
width: 100%;
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: 1.0rem;
font-size: .85rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -269,7 +243,7 @@
##Screen = B/w 481px to 767px
*/
@media (min-width: 481px) and (max-width: 767px) {
.feedback-add-success, .feedback-add-failure {
.feedback-add-success {
background-color: green;
display: block;
position: fixed;
@ -281,9 +255,6 @@
border-radius: 10px;
box-shadow: 0 0 5px 3px;
}
.feedback-add-failure {
background-color: #c75e5e;
}
.dropdown-item {
font-size: .75em;
@ -300,7 +271,7 @@
}
body {
font-size: .8rem;
font-size: .7rem;
}
#root-shop, #root-shop>div {
@ -320,11 +291,11 @@
}
#root-shop .productItem .content h3 {
font-size: 1.25rem;
font-size: 1rem;
}
#root-shop .productItem .content ul {
font-size: .75rem;
font-size: .5rem;
}
#root-shop .panel {
@ -336,16 +307,13 @@
margin-bottom: 1.5rem;
}
#root-shop .panel .control > .description {
#root-shop .panel .control > .description,
#root-shop .panel .control > .crate-mode {
width: 100%;
}
#root-shop .panel .crate .crate-bar .crate-mode {
#root-shop .panel .control > .crate-mode {
text-align: left;
width: 50%;
}
#root-shop .panel .crate .crate-bar .crate-mode a {
display: block;
}
#root-shop .panel .summary {
@ -357,16 +325,15 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .8rem;
font-size: .7rem;
}
#root-shop .panel .summary>.summary-form form,
#root-shop .panel .summary>.summary-form .order-bar {
#root-shop .panel .summary>.summary-form form {
width: 100%;
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: 1rem;
font-size: .85rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -379,6 +346,7 @@
border-top-right-radius: 30px;
width: 80px;
padding: 5px 0 5px 10px;
margin-bottom: -25px;
margin-left: -1.3rem;
position: relative;
z-index: 1;
@ -397,7 +365,7 @@
}
#root-shop table tr {
padding: .2em 0;
padding: .8em 0;
display: flex !important;
justify-content: space-between;
}
@ -405,22 +373,21 @@
#root-shop .layout>aside.aside.menu-opened {
/*transform: translate3d(0, 0, 0);*/
transition: left .3s;
width: min(310px, 60vw);
max-width: 60%;
width: 310px;
left: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main {
/*transform: translate3d(310px, 0, 0);*/
transition: left .3s;
left: min(310px, 60vw);
left: 310px;
position: relative;
z-index: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main:after {
content: '';
position: fixed;
position: absolute;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, .3);
@ -434,13 +401,13 @@
transition: left .3s;
position: fixed;
z-index: 1;
left: max(-310px, -60vw);
width: min(310px, 60vw);
left: -310px;
width: 310px;
height: 100%;
}
#root-shop .layout>aside.aside .gradient-bottom {
display: none;
#root-shop .layout>aside.aside:after {
width: 0;
}
#root-shop .layout>aside.aside + section.main {
@ -462,7 +429,7 @@
overflow: initial;
}
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
overflow-y: scroll;
height: 100%;
}
@ -472,7 +439,7 @@
}
#accordion_categories button {
font-size: 1.5rem;
font-size: 1rem;
padding: .5rem 0.5rem;
}
}
@ -481,7 +448,7 @@
##Screen = B/w 320px to 479px
*/
@media (min-width: 320px) and (max-width: 480px) {
.feedback-add-success, .feedback-add-failure {
.feedback-add-success {
background-color: green;
display: block;
position: fixed;
@ -493,9 +460,6 @@
border-radius: 10px;
box-shadow: 0 0 5px 3px;
}
.feedback-add-failure {
background-color: #c75e5e;
}
.dropdown-item {
font-size: .75em;
@ -512,7 +476,7 @@
}
body {
font-size: .8rem;
font-size: .7rem;
}
#root-shop, #root-shop>div {
@ -528,21 +492,20 @@
}
#root-shop .productItem .content h3 {
font-size: 1.25rem;
font-size: 1rem;
}
#root-shop .layout>aside.aside.menu-opened {
/*transform: translate3d(0, 0, 0);*/
transition: left .3s;
width: min(310px, 90vw);
max-width: 90%;
width: 310px;
left: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main {
/*transform: translate3d(310px, 0, 0);*/
transition: left .3s;
left: min(310px, 90vw);
left: 310px;
position: relative;
z-index: 0;
}
@ -562,13 +525,13 @@
transition: left .3s;
position: fixed;
z-index: 1;
left: max(-310px, -90vw);
width: min(310px, 90vw);
left: -310px;
width: 310px;
height: 100%;
}
#root-shop .layout>aside.aside .gradient-bottom {
display: none;
#root-shop .layout>aside.aside:after {
width: 0;
}
#root-shop .layout>aside.aside + section.main {
@ -607,16 +570,13 @@
font-size: 1.5rem;
}
#root-shop .panel .control > .description {
#root-shop .panel .control > .description,
#root-shop .panel .control > .crate-mode {
width: 100%;
}
#root-shop .panel .crate .crate-bar .crate-mode {
#root-shop .panel .control > .crate-mode {
text-align: left;
width: 50%;
}
#root-shop .panel .crate .crate-bar .crate-mode a {
display: block;
}
#root-shop .panel .summary {
@ -624,16 +584,15 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .8rem;
font-size: .7rem;
}
#root-shop .panel .summary>.summary-form form,
#root-shop .panel .summary>.summary-form .order-bar {
#root-shop .panel .summary>.summary-form form {
width: 100%;
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: 1rem;
font-size: .85rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -646,6 +605,7 @@
border-top-right-radius: 30px;
width: 80px;
padding: 5px 0 5px 10px;
margin-bottom: 15px;
margin-left: -1.3rem;
}
@ -662,7 +622,7 @@
}
#root-shop table tr {
padding: .2em 0;
padding: .8em 0;
display: flex !important;
justify-content: space-between;
}
@ -671,7 +631,7 @@
overflow: initial;
}
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
overflow-y: scroll;
height: 100%;
}
@ -681,7 +641,7 @@
}
#accordion_categories button {
font-size: 1.5rem;
font-size: 1rem;
padding: .5rem 0.5rem;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" height="128px" id="Layer_1" version="1.1" viewBox="0 0 128 128" width="128px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><path d="M127.744,46.594c-0.777-2.07-3.086-3.113-5.152-2.34L94.655,54.731L79.847,2.902 C79.354,1.184,77.784,0,75.999,0H52c-1.785,0-3.355,1.184-3.848,2.902L33.344,54.731L5.406,44.254 c-2.063-0.773-4.375,0.27-5.152,2.34c-0.773,2.07,0.273,4.375,2.34,5.152l30.421,11.408l27.32,62.447 c0.637,1.457,2.074,2.398,3.664,2.398s3.027-0.941,3.664-2.398l27.32-62.447l30.421-11.408 C127.471,50.969,128.518,48.664,127.744,46.594z M55.015,8h17.968l13.712,48H41.303L55.015,8z M63.999,114.016L42.117,64h43.765 L63.999,114.016z" fill="#81D4FA"/></g></g><path d="M95.999,8l23.999,40l-55.999,72L8,48L32,8H95.999L95.999,8 M95.999,0H32c-2.809,0-5.414,1.473-6.859,3.883 l-24,40c-1.699,2.836-1.484,6.422,0.543,9.027l55.999,72c1.516,1.949,3.848,3.09,6.316,3.09s4.801-1.141,6.316-3.09l55.999-72 c2.027-2.605,2.242-6.191,0.543-9.027l-23.999-40C101.413,1.473,98.808,0,95.999,0L95.999,0z" fill="#03A9F4"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" height="128px" id="Layer_1" version="1.1" viewBox="0 0 128 128" width="128px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><path d="M117.996,8.242C115.176,3.156,109.816,0,104,0H24c-5.816,0-11.176,3.156-13.996,8.242 c-2.816,5.086-2.656,11.305,0.43,16.234l11.605,18.57c1.809-2.055,3.742-3.992,5.832-5.766L17.215,20.234 c-1.539-2.461-1.621-5.57-0.215-8.109C18.41,9.578,21.094,8,24,8h80c2.906,0,5.59,1.578,6.996,4.117 c1.41,2.547,1.328,5.656-0.215,8.117l-10.656,17.047c2.09,1.773,4.027,3.711,5.836,5.766l11.605-18.57 C120.652,19.547,120.813,13.328,117.996,8.242z" fill="#F44336"/></g></g><path d="M64,56c13.234,0,24,10.766,24,24s-10.766,24-24,24S40,93.234,40,80S50.766,56,64,56 M64,48 c-17.672,0-32,14.328-32,32s14.328,32,32,32s32-14.328,32-32S81.672,48,64,48L64,48z" fill="#FFC107"/><path d="M64,40c22.055,0,40,17.945,40,40s-17.945,40-40,40s-40-17.945-40-40S41.945,40,64,40 M64,32 c-26.508,0-48,21.492-48,48s21.492,48,48,48s48-21.492,48-48S90.508,32,64,32L64,32z" fill="#FFA726"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,65 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
enable-background="new 0 0 128 128"
height="128px"
id="Layer_1"
version="1.1"
viewBox="0 0 128 128"
width="128px"
xml:space="preserve"
sodipodi:docname="728958_economy_finance_money_shopping_currency_icon.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
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="defs1174" /><sodipodi:namedview
id="namedview1172"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="6.2578125"
inkscape:cx="64.0799"
inkscape:cy="64.1598"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g1155" /><g
id="g1157"
style="display:inline"><g
id="g1155"><path
d="M 97.469,32 H 30.531 C 28.119,38.793 22.793,44.121 16,46.531 V 81.469 C 22.793,83.875 28.125,89.207 30.531,96 H 97.469 C 99.879,89.207 105.207,83.879 112,81.469 V 46.531 C 105.207,44.121 99.879,38.793 97.469,32 Z M 104,76.441 C 99.211,79.226 95.227,83.211 92.441,88 H 35.559 C 32.774,83.211 28.789,79.227 24,76.441 V 51.559 c 4.789,-2.785 8.773,-6.77 11.559,-11.559 19.783981,0 36.595699,0 56.882,0 2.785,4.789 6.77,8.773 11.559,11.559 z"
fill="#b0bec5"
id="path1153"
sodipodi:nodetypes="cccccccccccccccccc" /></g></g><path
d="m 64,48 c 8.824,0 16,7.176 16,16 0,8.824 -7.176,16 -16,16 -8.824,0 -16,-7.176 -16,-16 0,-8.824 7.176,-16 16,-16 m 0,-8 C 50.746,40 40,50.746 40,64 40,77.25 50.746,88 64,88 77.254,88 88,77.25 88,64 88,50.746 77.254,40 64,40 Z"
fill="#546e7a"
id="path1159"
style="display:inline" /><path
d="m 0,16 v 96 H 128 V 16 Z m 16,88 C 16,99.578 12.422,96 8,96 V 32 c 4.422,0 8,-3.578 8,-8 h 96 c 0,4.422 3.578,8 8,8 v 64 c -4.422,0 -8,3.578 -8,8 z"
fill="#546e7a"
id="path1161"
style="display:inline" /><path
d="m 20,40 c 2.211,0 4,-1.789 4,-4 0,-2.211 -1.789,-4 -4,-4 -2.211,0 -4,1.789 -4,4 0,2.211 1.789,4 4,4 z"
fill="#546e7a"
id="path1163"
style="display:inline" /><path
d="m 20,88 c -2.211,0 -4,1.789 -4,4 0,2.211 1.789,4 4,4 2.211,0 4,-1.789 4,-4 0,-2.211 -1.789,-4 -4,-4 z"
fill="#546e7a"
id="path1165"
style="display:inline" /><path
d="m 108,88 c -2.211,0 -4,1.789 -4,4 0,2.211 1.789,4 4,4 2.211,0 4,-1.789 4,-4 0,-2.211 -1.789,-4 -4,-4 z"
fill="#546e7a"
id="path1167"
style="display:inline" /><path
d="m 108,32 c -2.211,0 -4,1.789 -4,4 0,2.211 1.789,4 4,4 2.211,0 4,-1.789 4,-4 0,-2.211 -1.789,-4 -4,-4 z"
fill="#546e7a"
id="path1169"
style="display:inline" /></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" height="128px" id="Layer_1" version="1.1" viewBox="0 0 128 128" width="128px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><path d="M117.996,8.242C115.176,3.156,109.816,0,104,0H24c-5.816,0-11.176,3.156-13.996,8.242 c-2.816,5.086-2.656,11.305,0.43,16.234l11.605,18.57c1.809-2.055,3.742-3.992,5.832-5.766L17.215,20.234 c-1.539-2.461-1.621-5.57-0.215-8.109C18.41,9.578,21.094,8,24,8h80c2.906,0,5.59,1.578,6.996,4.117 c1.41,2.547,1.328,5.656-0.215,8.117l-10.656,17.047c2.09,1.773,4.027,3.711,5.836,5.766l11.605-18.57 C120.652,19.547,120.813,13.328,117.996,8.242z" fill="#F44336"/></g></g><path d="M64,56c13.234,0,24,10.766,24,24s-10.766,24-24,24S40,93.234,40,80S50.766,56,64,56 M64,48 c-17.672,0-32,14.328-32,32s14.328,32,32,32s32-14.328,32-32S81.672,48,64,48L64,48z" fill="#BDBDBD"/><path d="M64,40c22.055,0,40,17.945,40,40s-17.945,40-40,40s-40-17.945-40-40S41.945,40,64,40 M64,32 c-26.508,0-48,21.492-48,48s21.492,48,48,48s48-21.492,48-48S90.508,32,64,32L64,32z" fill="#616161"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1 +0,0 @@
<svg width="232" height="232" version="1.1" viewBox="0 0 232 232" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-4.0001 -12)" fill="none" stroke="#715ec7" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"><path d="m61.7 204.1-45.7-76.1 45.7-76.1a7.9 7.9 0 0 1 6.8-3.9h147.5a8 8 0 0 1 8 8v144a8 8 0 0 1-8 8h-147.5a7.9 7.9 0 0 1-6.8-3.9z"/><line x1="160" x2="112" y1="104" y2="152"/><line x1="160" x2="112" y1="152" y2="104"/></g></svg>

Before

Width:  |  Height:  |  Size: 463 B

View File

@ -1 +0,0 @@
<svg enable-background="new 0 0 48 48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g enable-background="new 0 0 48 48" fill="#715ec7"><path d="m36.9 6c-0.4-1.7-2-3-3.9-3s-3.4 1.3-3.9 3h-27.1v2h27.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h9.1v-2zm-3.9 3c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/><path d="m33 37c-1.9 0-3.4 1.3-3.9 3h-27.1v2h27.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h9.1v-2h-9.1c-0.5-1.7-2-3-3.9-3zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/><path d="m15 20c-1.9 0-3.4 1.3-3.9 3h-9.1v2h9.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h27.1v-2h-27.1c-0.5-1.7-2-3-3.9-3zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/></g></svg>

Before

Width:  |  Height:  |  Size: 689 B

View File

@ -1 +0,0 @@
<svg width="32" height="32" version="1.1" viewBox="0 0 8.4667 8.4667" xmlns="http://www.w3.org/2000/svg"><circle cx="4.2333" cy="4.2333" r="3.9077" fill="#d0423f" stroke="#d0423f" stroke-linecap="round" stroke-width=".65136"/></svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@ -1,39 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
import {OverlayTrigger} from "react-bootstrap";
import React from "react";
import {Levels, MaxLevel} from "./warnings";
import {useShopStore} from "./shop_store";
import {compareArraysLevelOne} from "./utils";
export function CardWarnings({crate_index, card_index}) {
const warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareArraysLevelOne);
const max_level = MaxLevel(warnings);
return (
<OverlayTrigger
placement="bottom"
trigger={['click', 'hover', 'focus']}
overlay={
({arrowProps, hasDoneInitialMeasure, show, style, ...props}) => (
<div className="k-popup-warning" style={{...style, backgroundColor: max_level.color}} {...props}>
{warnings.map((warning, _i) => {
return (
<p className="rule warning" key={`warnmsg_${card_index}_${warning.name}`}>
<i>{warning.message}</i>
</p>
)
})}
</div>)
}
rootClose
>
<img className="alert-warning p-0" src={max_level.icon}/>
</OverlayTrigger>
)
}
export function WarningIndicator({crate_index, card_index}) {
const warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareArraysLevelOne);
const max_level = MaxLevel(warnings);
return max_level.priority === Levels.warning.priority ? (
<img
className="alert-warning align-self-start d-block"
src={max_level.icon}
/>
) : (<span className="alert-warning align-self-start d-block"></span>);
}

View File

@ -1,70 +0,0 @@
import React from 'react'
import {Droppable} from "@hello-pangea/dnd";
import {cartStyle, compareArraysWithIds} from "./utils";
import {ProductCartItem} from "./ProductCartItem";
import {FakePlaceholder} from "./FakePlaceholder";
import {hp_to_slots} from "./count_resources";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that displays a list of <ProductCartItem>
*/
export function Cart({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate = useShopStore((state) => state.crates[crate_index], (a, b) => {
return compareArraysWithIds(a.items, b.items) && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode
});
const crateParams = useShopStore((state) => state.crateParams);
// #!render_count
console.log("Cart renders: ", renderCount)
const nbrOccupied = hp_to_slots(crate.occupiedHP);
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
const products = crate.items.map((item, index) => {
return (
<ProductCartItem
card_index={index}
crate_index={crate_index}
first={index === 0}
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
key={item.id}/>
);
});
return (
<Droppable droppableId={crate.id} direction="horizontal">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
style={cartStyle(
provided.droppableProps.style,
snapshot,
)}
className="items-cart-list">
{products}
{provided.placeholder && (
<div style={{display: 'none'}}>
{provided.placeholder}
</div>
)}
<FakePlaceholder
nToDraw={nbrSlots - nbrOccupied}
isDraggingOver={snapshot.isDraggingOver}/>
</div>
)}
</Droppable>
);
}

View File

@ -1,68 +0,0 @@
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>
);
}

View File

@ -1,44 +0,0 @@
import {ProductItem} from "./ProductItem";
import React from "react";
import {useShopStore} from "./shop_store";
export function CatalogGroups() {
const data = useShopStore((state) => state.groups);
const items = useShopStore((state) => state.cards);
const ordered_groups = data.categories.map(groupItem => ({
name: groupItem.name,
items: groupItem.itemIds.map(itemId => items[itemId])
}));
let item_index = -1;
const groups = ordered_groups.map((group, g_index) => {
return (
<div className="accordion-item" key={`${group.name}`}>
<h2 className="accordion-header">
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target={`#collapse${g_index}`} aria-expanded="true"
aria-controls={`collapse${g_index}`}>
{group.name}
</button>
</h2>
<div id={`collapse${g_index}`} className="accordion-collapse collapse" aria-labelledby="headingOne"
data-bs-parent="#accordion_categories">
<div className="accordion-body">
{group.items.map(item => {
item_index++;
return (
<ProductItem card_index={item_index} key={item.id} />
)
})}
</div>
</div>
</div>
);
}
);
return (
<div className="accordion accordion-flush" id="accordion_categories">
{groups}
</div>
)
}

View File

@ -1,16 +0,0 @@
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} />
)
})}
</>
)
}

View File

@ -1,53 +0,0 @@
import React from 'react';
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";
/**
* Component that displays the main crate with reminder rules.
* It includes <Cart> and rules
*/
export function Crate({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate = useShopStore((state) => state.crates[crate_index],
(a, b) => a.length === b.length && a.id === b.id);
const modes_order = useShopStore((state) => state.modes_order);
const onDeleteCrate = useShopStore((state) => state.delCrate);
// #!render_count
console.log("Crate renders: ", renderCount)
return (
<div className="crate">
{
modes_order.includes(crate.crate_mode) ? (
<div className="crate-bar d-inline-flex justify-content-between">
<CrateMode crate_index={crate_index}/>
<div className="delete-crate align-self-start align-content-start justify-content-end" onClick={() => onDeleteCrate(crate.id)}>
Delete crate <img src="/images/shop/icon-remove.svg" alt="remove"/>
</div>
</div>
) : <></>
}
<div className="crate-products">
<Cart crate_index={crate_index}/>
<CrateWarnings crate_index={crate_index} />
<CrateOptions crate_index={crate_index}/>
</div>
</div>
);
}

View File

@ -1,45 +0,0 @@
import React from 'react'
import {Accordion} from "react-bootstrap";
import {Crate} from "./Crate";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function CrateList() {
// #!render_count
const renderCount = useRenderCount();
const crates = useShopStore((state) => state.crates,
(a, b) => a.length === b.length);
const active_crate = useShopStore((state) => state.active_crate);
const onAddCrate = useShopStore((state) => state.newCrate);
const setActiveCrate = useShopStore((state) => state.setActiveCrate);
const onSelectHandler = (e) => {
// if e === null, that means that an accordion item was collapsed rather than expanded. e will be non-null when an item is expanded
if (e !== null)
setActiveCrate(e);
else
setActiveCrate("")
};
// #!render_count
console.log("CrateList renders: ", renderCount)
return (
<Accordion id="accordion_crates" flush activeKey={active_crate} onSelect={onSelectHandler}>
{crates.map((crate, index) =>
<Accordion.Item eventKey={crate.id} key={"accordion"+crate.id} className="accordion_crates_item">
<Accordion.Header>{crate.name ? crate.name : <>Crate #{`${index}`}</>} </Accordion.Header>
<Accordion.Body>
<Crate crate_index={index}/>
</Accordion.Body>
</Accordion.Item>
)}
<Accordion.Item eventKey="last" id="accordion_crates_add">
<Accordion.Header onClick={onAddCrate}>
Add new crate
</Accordion.Header>
</Accordion.Item>
</Accordion>)
}

View File

@ -1,35 +0,0 @@
import React from 'react';
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that displays crate modes
*/
export function CrateMode({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const modes_order = useShopStore((state) => state.modes_order);
const crate_modes = useShopStore((state) => state.crate_modes);
const crate = useShopStore((state) => state.crates[crate_index],
(a, b) => a.id === b.id && a.crate_mode === b.crate_mode);
const setMode = useShopStore((state) => state.setCrateMode);
// #!render_count
console.log("CrateMode renders: ", renderCount)
return (
<div className="crate-mode">
{modes_order.map((mode_name, _) => (
<a
key={mode_name}
className={(crate.crate_mode === mode_name ? 'active' : '')}
onClick={() => setMode(crate.id, mode_name)}
href="#"
role="button">{crate_modes[mode_name].name}</a>
))}
</div>
);
}

View File

@ -1,39 +0,0 @@
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>
)
}

View File

@ -1,27 +0,0 @@
import React from "react";
import {LevelUI} from "./warnings";
import {useShopStore} from "./shop_store";
import {compareArraysWithIds} from "./utils";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function CrateWarnings({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate_warnings = useShopStore(state => (state.crates[crate_index].warnings), compareArraysWithIds)
// #!render_count
console.log("CrateWarnings renders: ", renderCount)
return (
<div className="crate-info">
{crate_warnings.map((rule, index) => (
<p key={index} className="rule" style={{'color': LevelUI(rule.level).color}}>
<img src={LevelUI(rule.level).icon} /> <i>{rule.message}</i>
</p>
))}
</div>
)
}

View File

@ -1,27 +0,0 @@
import React from 'react';
/**
* Component that displays a placeholder inside crate.
* Allows to display how it remains space for the current crate.
*/
export function FakePlaceholder({isDraggingOver, nToDraw}) {
const fakePlaceholder = [];
for (let i = nToDraw; i > 0; i--) {
fakePlaceholder.push(
<div key={i} style={{
display: isDraggingOver ? 'none' : 'block',
border: '1px dashed #ccc',
width: '45px',
marginBottom: '5px',
}}></div>
);
}
return (
<React.Fragment>
{fakePlaceholder}
</React.Fragment>
);
}

View File

@ -1,7 +0,0 @@
import React from 'react';
export function GradientBottom() {
return (
<div className="gradient-bottom"></div>
)
}

View File

@ -1,104 +0,0 @@
import {useShopStore} from "./shop_store";
import {useClickAway} from "./options/useClickAway";
import {Modal} from "react-bootstrap";
import React from "react";
import {Validation} from "./validate";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
const JSONExample = JSON.stringify({
"crates": [
{
"items": [
{
"pn": "1124",
"options": null
},
{
"pn": "2128",
"options": null
},
{
"pn": "2128",
"options": null
},
{
"pn": "2128",
"options": null
}
],
"type": "rack",
"options": {}
},
{
"items": [],
"type": "no_crate",
"options": {}
}
],
"options": {}
});
export function ImportJSON() {
// #!render_count
const renderCount = useRenderCount();
const shouldShow = useShopStore((state) => state.importShouldOpen);
const data = useShopStore((state) => state.importValue);
const loadDescription = useShopStore((state) => state.loadDescription);
const updateImportDescription = useShopStore((state) => state.updateImportDescription);
const closeImport = useShopStore((state) => state.closeImport);
const showImport = useShopStore((state) => state.openImport);
// #!render_count
console.log("ImportJSON renders: ", renderCount)
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
closeImport()
}
);
return (<>
<button
className="btn btn-sm btn-outline-primary m-0 mb-2"
style={{'cursor': 'pointer'}}
onClick={showImport}>Import JSON
</button>
<Modal show={shouldShow} animation={true} centered className="rfqFeedback">
<Modal.Body ref={ref}>
<div className="form-group">
<p className="small">
Input the JSON description below. Should be something like:
<br/>
{JSONExample}
</p>
</div>
<div className="form-group w-100">
<textarea
onChange={(event) => {
updateImportDescription(event.target.value)
}}
value={data.value}
className="form-control w-100"
rows="5"
placeholder="Input JSON description here."/>
</div>
{data.error !== Validation.OK ? (
<div className="form-group">
<p className="text-danger">{data.error === Validation.Empty ? "Empty input" : "Invalid JSON"}</p>
</div>
) : null}
<div className="d-flex flex-column flex-sm-row justify-content-end">
<a type="button" onClick={closeImport}
className="btn btn-sm btn-outline-primary m-0 mb-2 mt-2 mb-sm-0 me-sm-2">Close</a>
<a type="button" onClick={loadDescription}
className={`btn btn-sm btn-primary m-0 ms-sm-2 mt-2 ${data.error ? 'disabled' : ''}`}>Load
configuration</a>
</div>
</Modal.Body>
</Modal>
</>)
}

View File

@ -1,46 +0,0 @@
import React from 'react';
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that provides a base layout (aside/main) for the page.
*/
export function Layout({aside, main}) {
// #!render_count
const renderCount = useRenderCount();
const mobileSideMenuShouldOpen = useShopStore(state => state.sideMenuIsOpen);
const onClickToggleMobileSideMenu = useShopStore(state => state.switchSideMenu);
const showCardAddedFeedback = useShopStore(state => state.showCardAddedFeedback);
const showNoDestination = useShopStore(state => state.showNoDestination);
// #!render_count
console.log("Layout renders: ", renderCount)
return (
<div className="layout">
<aside className={'aside ' + (mobileSideMenuShouldOpen ? 'menu-opened' : '')}>{aside}</aside>
{mobileSideMenuShouldOpen ? (
<section className="main" onClick={onClickToggleMobileSideMenu}>{main}</section>
) : (
<section className="main">{main}</section>
)}
{showCardAddedFeedback ? (
!showNoDestination ?
(<div className="feedback-add-success">
added
</div>)
: (<div className="feedback-add-failure">
No cards added: all crates are closed
</div>)
) : null}
</div>
);
}

View File

@ -1,63 +0,0 @@
import {DialogPopup} from "./options/DialogPopup";
import React from "react";
import {useShopStore} from "./shop_store";
import {SummaryPopup} from "./options/SummaryPopup";
export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
const card_size = useShopStore((state) => state.crates[crate_index].items[card_index].size);
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);
return (
<DialogPopup
options={options}
data={options_data}
options_class={options_class}
key={"popover" + crate_id +card_id}
id={"popover"+ crate_id + card_id}
big={card_size === "big"}
first={first}
last={last}
sideMenuIsOpen={sideMenuIsOpen}
onHideNotification={hideNotification}
displayNotification={displayNotification}
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;
onOptionsUpdate(crate_id, card_index, {[outvar]: value});
})
}}
/>
)
}
export function OptionsSummaryWrapper({crate_index, card_index}) {
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
return (
<SummaryPopup id={card_id + "options"} options={options}
data={options_data}/>
)
}

View File

@ -1,78 +0,0 @@
import React from 'react'
import {Validation} from "./validate.js";
import {useShopStore} from "./shop_store";
import {ShowJSON} from "./ShowJSON";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {OrderOptions} from "./OrderOptions";
/**
* Components that renders the form to request quote.
*/
export function OrderForm() {
// #!render_count
const renderCount = useRenderCount();
const email = useShopStore((state) => state.email);
const note = useShopStore((state) => state.note);
const isProcessing = useShopStore((state) => state.isProcessing);
const updateEmail = useShopStore((state) => state.updateEmail);
const updateNote = useShopStore((state) => state.updateNote);
const submitForm = useShopStore((state) => state.submitForm);
const submitDisabled = useShopStore((state) => state.submitDisabled);
const resetEmailValidation = useShopStore((state) => state.resetEmailValidation);
// #!render_count
console.log("OrderForm renders: ", renderCount)
return (
<div className="summary-form">
<OrderOptions/>
<form onSubmit={submitForm} noValidate>
<input
className={`${email.error > 0 ? 'errorField' : ''}`}
type="email"
placeholder="Email"
onFocus={resetEmailValidation}
onChange={(event) => updateEmail(event.target.value)}
onBlur={(event) => updateEmail(event.target.value)}
value={email.value}/>
{email.error === Validation.Empty ? (
<div className="error">
<small>Required</small>
</div>
) : null}
{email.error === Validation.Invalid ? (
<div className="error">
<small>Your email is incomplete</small>
</div>
) : null}
<textarea
onChange={(event) => updateNote(event.target.value)}
defaultValue={note.value}
rows="5"
placeholder="Additional notes"/>
<div className="d-flex flex-column flex-sm-row justify-content-between">
<ShowJSON/>
<input className="btn btn-primary w-100 m-0 ms-sm-2 order-form-submit" type="button"
disabled={submitDisabled()}
onClick={submitForm}
value={`${isProcessing ? 'Processing ...' : 'Request quote'}`}/>
</div>
{/*This will open an email window. Send the email to make your request.*/}
</form>
</div>
);
}

View File

@ -1,38 +0,0 @@
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>
)
}

View File

@ -1,56 +0,0 @@
import React from 'react'
import {SummaryOrder} from "./SummaryOrder";
import {OrderForm} from "./OrderForm";
import {CrateList} from "./CrateList";
import {useShopStore} from "./shop_store";
import {ImportJSON} from "./ImportJSON";
import {RFQFeedback} from "./RFQFeedback";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {OrderOptions} from "./OrderOptions";
/**
* Component that renders all things for order.
* It acts like-a layout, this component do nothing more.
*/
export function OrderPanel({title, description}) {
// #!render_count
const renderCount = useRenderCount();
const isMobile = useShopStore((state) => state.isMobile);
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
// #!render_count
console.log("OrderPanel renders: ", renderCount)
return (<section className="panel">
<h2>{title}</h2>
<div className="control justify-content-between">
{description}
</div>
<div>
<ImportJSON/>
</div>
<RFQFeedback/>
{isMobile ? (
<div className="mobileBtnDisplaySideMenu">
<button onClick={onClickToggleMobileSideMenu}>
<img src="/images/shop/icon-add.svg" alt="add"/>
</button>
</div>
) : null}
<CrateList/>
<section className="summary">
<SummaryOrder/>
<OrderForm/>
</section>
</section>);
}

View File

@ -1,115 +0,0 @@
import React from 'react'
import {Draggable} from "@hello-pangea/dnd";
import {compareObjectsEmptiness, productStyle} from "./utils";
import {Resources} from "./Resources";
import {CardWarnings} from "./CardWarnings";
import {useShopStore} from "./shop_store";
import {OptionsDialogWrapper} from "./OptionsWrapper";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that renders a product.
* Used in the crate
*/
export function ProductCartItem({card_index, crate_index, first, last}) {
// #!render_count
const renderCount = useRenderCount();
const card = useShopStore((state) => state.crates[crate_index].items[card_index],
(a, b) => a.id === b.id);
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
const card_counted_resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareObjectsEmptiness);
const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const setHighlight = useShopStore((state) => state.highlightCard);
const removeHighlight = useShopStore((state) => state.highlightReset);
const onCardRemove = useShopStore((state) => state.deleteCard);
// #!render_count
console.log("ProductCartItem renders: ", renderCount)
const options = !options_disabled && card && card.options && card.options.length > 0;
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
const resources = !options_disabled && card_counted_resources && card_counted_resources.length > 0;
return (
<Draggable draggableId={card.id} index={card_index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...productStyle(
provided.draggableProps.style,
snapshot,
true,
!!highlighted,
false,
true
)
}}
onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={removeHighlight}
>
{/* warning container */}
<div className="progress-container warning d-flex justify-content-evenly">
{warnings &&
(<CardWarnings crate_index={crate_index} card_index={card_index} />)
}
{options && (
<OptionsDialogWrapper
crate_index={crate_index}
card_index={card_index}
first={first}
last={last}
/>
)}
</div>
<h6>{card.name_number}</h6>
<div
onMouseEnter={() => setHighlight(crate_id, card_index)}
onClick={() => setHighlight(crate_id, card_index)}
>
<img
className='item-cart'
src={card.image}/>
</div>
{/* remove container */}
<div
style={{'display': highlighted ? 'flex' : 'none'}}
className="overlayRemove"
onClick={() => onCardRemove(crate_id, card_index)}>
<img src="/images/shop/icon-remove.svg" alt="rm"/>
<p>Remove</p>
</div>
{/* progression container */}
{resources && (
<Resources crate_index={crate_index} card_index={card_index} />
)}
</div>
)}
</Draggable>
);
}

View File

@ -1,92 +0,0 @@
import React from 'react';
import {Draggable} from "@hello-pangea/dnd";
import {formatMoney, productStyle} from "./utils";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that renders a product.
* Used in the aside (e.g catalog of product)
*/
export function ProductItem({card_index}) {
// #!render_count
const renderCount = useRenderCount();
const getCardDescription = useShopStore((state) => state.getCardDescription);
const currency = useShopStore((state) => state.currency);
const onAddCard = useShopStore((state) => state.addCardFromCatalog);
const card = getCardDescription(card_index);
// #!render_count
console.log("ProductItem renders: ", renderCount)
const render_specs = (card.specs && card.specs.length > 0 && (
<ul>
{card.specs.map((spec, index) =>
<li key={index}>{spec}</li>
)}
</ul>
));
const render_datasheet_link = (card.datasheet_file && card.datasheet_name && (
<div className="ds">
<span className='doc-icon'></span>
<a href={card.datasheet_file} target="_blank" rel="noopener noreferrer">
{card.datasheet_name}
</a>
</div>
));
return (
<section className="productItem">
<div className="content">
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3>
{card.name_codename ? (
<p>{card.name_codename}</p>
) : null}
<div className="price">{`${currency} ${formatMoney(card.price)}`}</div>
{render_specs}
{render_datasheet_link}
</div>
<div className="content">
<button onClick={() => onAddCard(null, card_index, null)}>
<img src="/images/shop/icon-add.svg" alt="add"/>
</button>
<Draggable draggableId={card.id + card_index} index={card_index}>
{(provided, snapshot) => (
<React.Fragment>
<img
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={productStyle(
provided.draggableProps.style,
snapshot,
true, // hack: remove weird animation after a drop
)}
src={card.image}/>
{/* Allows to simulate a clone */}
{snapshot.isDragging && (
<img className="simclone" src={card.image}/>
)}
</React.Fragment>
)}
</Draggable>
</div>
</section>
);
}

View File

@ -1,42 +0,0 @@
import {useShopStore} from "./shop_store";
import {useClickAway} from "./options/useClickAway";
import {Modal} from "react-bootstrap";
import {Validation} from "./validate";
import React from "react";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function RFQFeedback() {
// #!render_count
const renderCount = useRenderCount();
const closeRFQ = useShopStore((state) => state.closeRFQFeedback);
const shouldShow = useShopStore((state) => state.shouldShowRFQFeedback);
const status = useShopStore((state) => state.processingResult);
// #!render_count
console.log("RFQFeedback renders: ", renderCount)
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
closeRFQ()
});
return (
<Modal show={shouldShow} animation={true} centered>
<Modal.Body ref={ref} className="rfqFeedback">
<div className="d-flex">
<div>
{status.status === Validation.OK ?
<img width="30px" src="/images/shop/icon-done.svg" alt="close"/>
: <img width="30px" src="/images/shop/icon-warning.svg" alt="close"/>
}
</div>
<div style={{'padding': '0 .5em'}}>
{status.message}
</div>
</div>
</Modal.Body>
</Modal>
)
}

View File

@ -1,99 +0,0 @@
import {OverlayTrigger} from "react-bootstrap";
import React from "react";
import {v4 as uuidv4} from "uuid";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {useShopStore} from "./shop_store";
import {compareArraysLevelOne} from "./utils";
const resourcesWidthStyle = (occupied, max) => {
return {
width: `${Math.min(occupied * 100 / max, 100)}%`,
}
};
function EEMRenderer({occupied, max}) {
return (
<div className="nbr-connectors" key={uuidv4()}>
<div style={{...resourcesWidthStyle(occupied, max)}}></div>
</div>
)
}
function ClockRenderer({occupied, max}) {
return (
<div className="nbr-clocks" key={uuidv4()}>
<div style={{...resourcesWidthStyle(occupied, max)}}></div>
</div>
)
}
const resource_progress_renderers = {
"eem": EEMRenderer,
"clk": ClockRenderer,
"idc": EEMRenderer
}
function EEMTipRender({occupied, max}) {
return (<p key={uuidv4()}>{`${occupied}/${max} EEM connectors used`}</p>);
}
function IDCTipRender({occupied, max}) {
return (<p key={uuidv4()}>{`${occupied}/${max} IDC connectors used`}</p>);
}
function ClockTipRender({occupied, max}) {
return (<p key={uuidv4()}>{`${occupied}/${max} clock connectors used`}</p>);
}
const resource_tip = {
"eem": EEMTipRender,
"clk": ClockTipRender,
"idc": IDCTipRender
}
function RenderResources({resources, library}) {
if (!resources) return null;
let result = [];
resources.forEach((value, _) => {
if (library[value.name]) result.push(library[value.name](value));
});
return result;
}
export function Resources({crate_index, card_index}) {
// #!render_count
const renderCount = useRenderCount();
const resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareArraysLevelOne);
// #!render_count
console.log("Resources renders: ", renderCount)
return (
<OverlayTrigger
placement="top"
trigger={['click', 'hover', 'focus']}
overlay={({arrowProps, hasDoneInitialMeasure, show, ...props}) => (
<div className="k-popup-connectors" {...props}>
<RenderResources
resources={resources}
library={resource_tip}
/>
</div>
)}
rootClose
>
<div className="progress-container">
<RenderResources
resources={resources}
library={resource_progress_renderers}
/>
</div>
</OverlayTrigger>
)
}

View File

@ -1,18 +0,0 @@
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>
)
}

View File

@ -1,74 +0,0 @@
import React, {useEffect} from 'react';
import {DragDropContext} from "@hello-pangea/dnd";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {Layout} from "./Layout";
import {Catalog} from "./Catalog";
import {OrderPanel} from "./OrderPanel";
import {useShopStore} from "./shop_store";
/**
* Component that renders the entire shop
*/
export function Shop() {
// #!render_count
const renderCount = useRenderCount();
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);
const handleOnDragEnd = (drop_result, _provided) => {
if (!drop_result.destination) {
console.warn("No drop destination");
console.log(drop_result)
return;
}
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(() => {
addCardFromCatalog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
initExtData();
}, []);
// #!render_count
console.log("Shop renders: ", renderCount)
return (
<DragDropContext onDragEnd={handleOnDragEnd}>
<Layout
aside={
<Catalog/>
}
main={(
<OrderPanel
title="Order hardware"
description={(
<p className="description">
Drag and drop the cards you want into the crate below to see how
the combination would look like. Configure the card settings by tapping on the top of
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 excludes shipping, is estimated, and must be confirmed by a quote.
</p>
)}
/>
)}>
</Layout>
</DragDropContext>
);
}

View File

@ -1,99 +0,0 @@
import React, {useState} from "react";
import {Modal} from "react-bootstrap";
import {useShopStore} from "./shop_store";
import {useClickAway} from "./options/useClickAway";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {Validation} from "./validate";
const copyButtonStates = {
[Validation.OK]: {
style: "btn-outline-success",
content: "✓ copied"
},
[Validation.Empty]: {
style: "btn-outline-primary",
content: "Copy"
},
[Validation.Invalid]: {
style: "btn-outline-danger",
content: "Error"
},
};
export function ShowJSON() {
// #!render_count
const renderCount = useRenderCount();
const shouldShow = useShopStore((state) => state.shouldShowDescription);
const description = useShopStore((state) => state.description);
const closeDescription = useShopStore((state) => state.closeDescription);
const showDescription = useShopStore((state) => state.showDescription);
const [copiedState, setCopiedState] = useState(Validation.Empty);
// #!render_count
console.log("ShowJSON renders: ", renderCount)
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
closeDescription()
}
);
const copyToClipboard = (text) => {
try {
navigator.clipboard.writeText(text)
.then((_value) => { // success
setCopiedState(Validation.OK);
setTimeout(() => {setCopiedState(Validation.Empty)}, 1500);
}, (reason) => { // error
setCopiedState(Validation.Invalid);
setTimeout(() => {setCopiedState(Validation.Empty)}, 3000);
console.warn("Copy to clipboard rejected: ", reason)
});
} catch (e) {
setCopiedState(Validation.Invalid);
setTimeout(() => {setCopiedState(Validation.Empty)}, 3000);
console.warn("Copy to clipboard error: ", e)
}
};
return (<>
<input
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
style={{'cursor': 'pointer', 'fontWeight': '700'}}
defaultValue="Show JSON"
onClick={showDescription}
readOnly={true}/>
<Modal show={shouldShow} animation={true} className="rfqFeedback" centered>
<Modal.Body ref={ref}>
<textarea
value={description}
className="form-control w-100"
rows={10}
readOnly={true}
placeholder="There should be description of the crate"/>
<div className="d-flex flex-column flex-sm-row justify-content-end">
{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>
</Modal.Body>
</Modal>
</>)
}

View File

@ -1,34 +0,0 @@
import {range} from "./utils";
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";
export function SummaryCrate({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const crate_len = useShopStore((state) => state.crates[crate_index].items.length);
// #!render_count
console.log("SummaryCrate renders: ", renderCount)
return (
<tbody key={"summary_crate_body" + crate_id}>
<SummaryCrateHeader crate_index={crate_index}/>
{range(0, crate_len).map((index, _i) =>
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
)}
<SummaryCratePricedOptions crate_index={crate_index}/>
</tbody>
)
}

View File

@ -1,78 +0,0 @@
import {compareObjectsEmptiness, formatMoney} from "./utils";
import {WarningIndicator} from "./CardWarnings";
import React from "react";
import {useShopStore} from "./shop_store";
import {OptionsSummaryWrapper} from "./OptionsWrapper";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateCard({crate_index, card_index}) {
// #!render_count
const renderCount = useRenderCount();
const currency = useShopStore((state) => state.currency);
const deleteCard = useShopStore((state) => state.deleteCard);
const setHighlight = useShopStore((state) => state.highlightCard);
const resetHighlight = useShopStore((state) => state.highlightReset);
const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const card = useShopStore((state) => state.crates[crate_index].items[card_index],
(a, b) => a.id === b.id);
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
const card_options_data = useShopStore(state => state.crates[crate_index].items[card_index].options_data, compareObjectsEmptiness);
const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
// #!render_count
console.log("SummaryCrateCard renders: ", renderCount)
const options = !options_disabled && card && card.options && card.options.length > 0;
const options_data = !options_disabled && card_options_data && Object.keys(card_options_data).length > 0;
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
return (
<tr
key={"summary_crate_" + crate_id + "_" + card_index}
className={`hoverable ${highlighted ? 'selected' : ''}`}
onClick={() => setHighlight(crate_id, card_index)}
onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={() => resetHighlight()}>
<td className="item-card-name tabbed">
<div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div>
</td>
<td className="price">
<div className="d-inline-flex align-content-center">
{`${currency} ${formatMoney(card.price)}`}
<button onClick={() => deleteCard(crate_id, card_index)}>
<img src="/images/shop/icon-remove.svg" className="d-block"/>
</button>
<div style={{'width': '45px', 'height': '20px'}}
className="d-inline-flex align-content-center align-self-center justify-content-evenly">
{(warnings ? (
<WarningIndicator crate_index={crate_index} card_index={card_index}/>
) : (
<span style={{
'display': 'inline-block',
'minWidth': '20px',
}}>&nbsp;</span>
))}
{((options && options_data) ? (
<OptionsSummaryWrapper crate_index={crate_index} card_index={card_index}/>
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
</div>
</div>
</td>
</tr>);
}

View File

@ -1,53 +0,0 @@
import {formatMoney} from "./utils";
import React from "react";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateHeader({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const currency = useShopStore((state) => state.currency);
const crateParams = useShopStore((state) => state.crateParams);
const clearCrate = useShopStore((state) => state.clearCrate);
const delCrate = useShopStore((state) => state.delCrate);
const crate_mode = useShopStore((state) => state.crates[crate_index].crate_mode);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const crate_name = useShopStore((state) => state.crates[crate_index].name);
const modes_order = useShopStore((state) => state.modes_order);
const crate_mode_displayed = modes_order.includes(crate_mode);
// #!render_count
console.log("SummaryCrateHeader renders: ", renderCount)
let crate_type = crateParams(crate_mode);
return (
<tr key={"summary_crate_" + crate_id}>
<td className="item-card-name">{!!crate_name ? crate_name : crate_type.name + " #" + crate_index}</td>
<td className="price">
<div className="d-inline-flex">
{crate_mode_displayed && `${currency} ${formatMoney(crate_type.price)}`}
<button onClick={() => clearCrate(crate_id)}>
<img src="/images/shop/icon-clear.svg" alt="empty crate"/>
</button>
{
crate_mode_displayed ? (
<button onClick={() => delCrate(crate_id)}>
<img src="/images/shop/icon-remove.svg" alt="remove crate"/>
</button>
) : <span className="span-with-margin"></span>
}
</div>
</td>
</tr>
)
}

View File

@ -1,43 +0,0 @@
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>
));
}

View File

@ -1,29 +0,0 @@
import {range} from "./utils";
import React from "react";
import {useShopStore} from "./shop_store";
import {SummaryCrate} from "./SummaryCrate";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {SummaryOrderPricedOptions} from "./SummaryOrderPricedOptions";
import {SummaryOrderShipping} from "./SummaryOrderShipping";
export function SummaryCrates() {
// #!render_count
const renderCount = useRenderCount();
const crates_l = useShopStore((state) => state.crates.length);
// #!render_count
console.log("SummaryCrates renders: ", renderCount)
return (
<>
{range(0, crates_l).map((index, _i) => {
return <SummaryCrate crate_index={index} key={"summary_crate_body_" + index} />
})}
<SummaryOrderPricedOptions/>
<SummaryOrderShipping/>
</>
)
}

View File

@ -1,44 +0,0 @@
import React from 'react';
import {SummaryCrates} from "./SummaryCrates";
import {SummaryTotalPrice} from "./SummaryTotalPrice";
// #!render_count
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 SummaryOrder() {
// #!render_count
const renderCount = useRenderCount();
// #!render_count
console.log("SummaryOrder renders: ", renderCount)
return (
<div className="summary-price">
<table>
<SummaryCrates/>
<tfoot>
<tr>
<td className="item-card-name">Price estimate</td>
<td className="price">
<SummaryTotalPrice/>
<span style={{
'display': 'inline-block',
'width': '30px',
}}>&nbsp;</span>
</td>
</tr>
</tfoot>
</table>
</div>
);
}

View File

@ -1,43 +0,0 @@
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>;
}

View File

@ -1,35 +0,0 @@
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>;
}

View File

@ -1,17 +0,0 @@
import {useShopStore} from "./shop_store";
import {formatMoney} from "./utils";
import React from "react";
export function SummaryTotalPrice() {
const currency = useShopStore((state) => state.currency);
const total_price = useShopStore((state) => state.total_order_price);
return (
<div>
{currency} {formatMoney(total_price)}
<button style={{'opacity': '0', 'cursor': 'initial'}}>
<img src="/images/shop/icon-remove.svg" alt="icon remove"/>
</button>
</div>
)
}

View File

@ -1,76 +0,0 @@
const count_item_occupied_eem = (item) => {
if (!item.options_data
|| item.options_data.ext_pwr === false
|| item.options_data.mono_eem === false
)
return (item.consumes && item.consumes.eem) || 0;
else if (item.options_data.ext_pwr === true)
return 0;
else if (item.options_data.mono_eem === true || item.options_data.n_eem === "1 EEM")
return 1;
else if (item.options_data.n_eem === "3 EEM")
return 3;
return (item.consumes && item.consumes.eem) || 0;
}
const count_item_occupied_clock = (item) => {
return (item.options_data && (item.options_data.ext_clk === true || (item.options_data.ext_clk && item.options_data.ext_clk.checked === true)) ) ? 0 : ((item.consumes && item.consumes.clk) || 0);
}
const count_item_occupied_idc = (item) => {
return (item.consumes && item.consumes.idc) || 0;
}
const count_item_occupied_hp = (item) => {
return (item.consumes && item.consumes.hp) || 0;
}
export const item_occupied_counters = {
"eem": count_item_occupied_eem,
"clk": count_item_occupied_clock,
"idc": count_item_occupied_idc,
"hp": count_item_occupied_hp,
}
function CounterFactory(name) {
return (data, index) => {
let count = 0;
for (let i = index + 1; i < data.length; i++) {
count += item_occupied_counters[name](data[i]);
if (data[i].resources && !!data[i].resources.find((value, _i) => value.name === name)) break;
}
return count;
}
}
export const resource_counters = {
"eem": CounterFactory("eem"),
"clk": CounterFactory("clk"),
"idc": CounterFactory("idc"),
"hp": CounterFactory("hp"),
}
export function CountResources(data, index) {
if (!data[index].resources) return null;
let result = [];
data[index].resources.forEach((item, _) => {
if (resource_counters[item.name]) result.push({
name: item.name,
occupied: resource_counters[item.name](data, index),
max: item.max
});
});
return result;
}
export function FillResources(data, disabled) {
return data.map((element, index) => {
element.counted_resources = disabled ? [] : CountResources(data, index);
return element;
})
}
export function hp_to_slots(hp) {
return Math.trunc(hp / 4);
}

View File

@ -1,80 +0,0 @@
import {useShopStore} from "./shop_store";
import {FilterOptions} from "./options/utils";
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.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;
}
}
} catch (e) {
return false;
}
// only last one should be spare cards
return crates_raw.filter((crate) => !modes_order.includes(crate.type)).length === 1 && crates_raw[crates_raw.length - 1].type === "no_crate";
}
// no validation in this function
export function JSONToCrates(description) {
const parsed = JSON.parse(description);
const crates_raw = parsed.crates;
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
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,
options_data: crate.options,
crate_mode: crate.type,
items: Array.from(crate.items.map((card, _i) => ({
...pn_to_card(card.pn),
id: uuidv4(),
options_data: card.options || {}
}))),
warnings: [],
occupiedHP: 0,
})));
return {
// some additional fields go here
order_options_data: parsed.options,
crates: crates
};
}
export function CratesToJSON(crates) {
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) => ({
items: Array.from(crate.items.map((card, _) => ({
pn: card.name_number,
options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
}))),
type: crate.crate_mode,
options: FilterOptions(crateOptions, crate.options_data)
}))),
options: FilterOptions(orderOptions, orderOptionsData)
}, null, 2)
}

View File

@ -1,46 +0,0 @@
import React, {useState} from "react";
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, displayNotification, onHideNotification}) {
const [show, setShow] = useState(false);
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
setShow(false)
}
);
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last) ? "overlay-last" : ""} ${options_class || ""}`;
const handleClick = (_event) => {
setShow(!show);
};
return (
<div ref={ref}>
<Notification
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"}
onClick={handleClick}/>
}
/>
<div style={{'display': show ? 'flex' : 'none'}} className={div_classes}>
<ProcessOptions
options={options}
data={data}
key={"processed_options_" + id}
id={"processed_options_" + id}
target={target}
/>
</div>
</div>
);
}

View File

@ -1,21 +0,0 @@
import {OverlayTrigger, Tooltip} from "react-bootstrap";
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}
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
rootClose={!sideMenuIsOpen}
onToggle={onHide}
popperConfig={{
strategy: 'fixed'
}}
>
{content}
</OverlayTrigger>
)
}

View File

@ -1,63 +0,0 @@
import React from "react";
import {apply as json_logic_apply} from "json-logic-js";
import {componentsList} from "./components/components";
import {true_type_of} from "./utils";
export function ProcessOptions({options, data, target, id}) {
let options_t = true_type_of(options);
if (options_t === "array") {
return Array.from(
options.map((option_item, i) => ProcessOptions({
options: option_item,
data: data,
target: target,
id: id + i
}))
);
} else if (options_t === "object") {
if (
true_type_of(options.type) === "string" &&
(true_type_of(options.args) === "object" || true_type_of(options.items) === "array")
) {
if (options.type in componentsList) {
return componentsList[options.type](target, id + options.type, data, options.args);
} else if (options.type === "Group") {
return (
<div className="border rounded options-group" key={id + "group"}>
{ProcessOptions({
options: json_logic_apply(options.items, data),
data: data,
target: target,
id: id
})}
</div>);
} else {
return componentsList["Default"](options.type, id + "missing");
}
} else {
return ProcessOptions({options: json_logic_apply(options, data), data: data, target: target, id: 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;
}
}

View File

@ -1,95 +0,0 @@
import React, {useEffect, useState} from "react";
import {useClickAway} from "./useClickAway";
import {FilterOptions, true_type_of} from "./utils";
export function SummaryPopup({id, options, data}) {
const [show, setShow] = useState(false);
const [position, setPosition] = useState({x: 0, y: 0});
const [size, setSize] = useState({w: 0, h: 0});
let display_options = FilterOptions(options, data);
const close = () => {
setShow(false);
document.removeEventListener("scroll", handleScroll, true);
}
const ref = useClickAway(close);
const reposition = () => {
let popup_button = document.getElementById(id + "img");
if (!popup_button) {
document.removeEventListener("scroll", handleScroll, true);
return;
}
let rect = popup_button.getBoundingClientRect()
let pos_x = (rect.left + rect.right) / 2;
let pos_y = (rect.top + rect.bottom) / 2;
if (pos_x + size.w > window.innerWidth) {
setPosition({x: pos_x - size.w - 20, y: pos_y - size.h / 2});
} else {
setPosition({x: pos_x - size.w / 2, y: pos_y - size.h - 20});
}
}
const handleScroll = (e) => {
if (e.target !== document.getElementById(id)) {
close();
}
}
useEffect(() => {
if (show) {
let popup = document.getElementById(id);
let width = popup.offsetWidth;
let height = popup.offsetHeight;
setSize({w: width, h: height});
reposition()
}
}, [show])
useEffect(() => {
if (show) {
reposition();
}
}, [show, size])
const handleClick = (_event) => {
setShow(!show);
if (!show) {
document.addEventListener("scroll", handleScroll, true);
}
};
const stringify = (value) => {
let value_type = true_type_of(value);
if (value_type === "string") {
return value;
} else if (value_type === "object") {
if (value.checked === false) {
return "off";
} else if (value.checked === true && value.text) {
return value.text;
}
}
return JSON.stringify(value);
}
return (
<div ref={ref}>
<img className="alert-info d-block" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
id={id + "img"}
onClick={handleClick}/>
<div style={{'display': show ? 'flex' : 'none', 'top': position.y, 'left': position.x}}
className="overlayVariant card border rounded"
id={id}>
<div className="card-body">
{Array.from(Object.entries(display_options)
.filter(([key, value], _) => key !== "ext_data")
.map(([key, value], _) => {
return (<p className="card-text" key={id + key}><i>{key}</i>: {stringify(value)}</p>);
}))}
</div>
</div>
</div>
);
}

View File

@ -1,17 +0,0 @@
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>
)
}

View File

@ -1,55 +0,0 @@
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 : ""),
valid: true
};
// Bind the event handler to this
this.handleChange = this.handleChange.bind(this);
this.props.target.construct(this.props.outvar, this.state.text);
}
handleChange(element) {
let text = element.target.value;
this.setState({
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 (
<div className="shop-line" key={this.props.id}>
<label htmlFor={key} className="form-label">
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{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 ${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, classes, validator}) {
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
}

View File

@ -1,71 +0,0 @@
import React, {Component} from "react";
import {Tip} from "./Tip";
class Radio extends Component {
constructor(props) {
super(props);
// Initialize the state object with the initial values from the props
this.state = {
variant: props.outvar in props.data ? props.data[props.outvar] : props.variants[props.fallback ? props.fallback : 0],
};
// Bind the event handler to this
this.handleClick = this.handleClick.bind(this);
this.props.target.construct(this.props.outvar, this.state.variant);
}
handleClick(variant) {
// Update the state object with the new value for outvar
this.setState({
...this.state,
variant: variant
});
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"}} 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}/>}
<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, classes}) {
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
fallback={fallback} classes={classes}
id={id} data={data}/>;
}

View File

@ -1,63 +0,0 @@
import React, {Component} from "react";
import {Tip} from "./Tip";
class Switch extends Component {
constructor(props) {
super(props);
// Initialize the state object with the initial values from the props
this.state = {
checked: props.outvar in props.data ? !!(props.data[props.outvar]) : !!(props.fallback)
};
// Bind the event handler to this
this.handleClick = this.handleClick.bind(this);
this.props.target.construct(this.props.outvar, this.state.checked);
}
handleClick() {
// Update the state object with the new value for outvar
let new_checked = !this.state.checked;
this.setState({
checked: new_checked
});
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 (
<div className="shop-switch" key={this.props.id}>
<div className="form-check form-switch" key={key}>
<input
className="form-check-input"
type="checkbox"
role="switch"
id={key}
checked={this.state.checked}
onClick={this.handleClick}
onChange={this.handleClick}
/>
<label className="form-check-label" htmlFor={key} style={{"display": "inline"}}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}
</label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div>
</div>
);
}
}
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} classes={classes}/>;
}

View File

@ -1,81 +0,0 @@
import React, {Component} from "react";
import {Tip} from "./Tip";
import {Validation} from "../validation";
class SwitchLine 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].text : (props.fallback ? props.fallback.text : ""),
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);
this.handleCheck = this.handleCheck.bind(this);
this.props.target.construct(this.props.outvar, this.state);
}
handleText(element) {
let new_state = {
...this.state,
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);
}
handleCheck() {
// Update the state object with the new value for outvar
let new_state = {
...this.state,
checked: !this.state.checked
}
this.setState(new_state);
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 (
<div className="shop-switch-line" key={this.props.id}>
<div className="form-check form-switch" key={key}>
<input
className="form-check-input"
type="checkbox"
role="switch"
id={key + "switch"}
checked={this.state.checked}
onClick={this.handleCheck}
onChange={this.handleCheck}
/>
<label className="form-check-label" htmlFor={key + "switch"}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}
</label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div>
<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, classes, validator}) {
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
}

View File

@ -1,16 +0,0 @@
import React from "react";
import {OverlayTrigger, Tooltip} from "react-bootstrap";
export function Tip({id, tip}) {
return (
<OverlayTrigger
placement="auto"
trigger={['click', 'hover', 'focus']}
style={{display: 'inline'}}
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
rootClose
>
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
</OverlayTrigger>
);
}

View File

@ -1,6 +0,0 @@
import React from "react";
export function UnimplementedComponent(type, id) {
//console.error("Missing component with type:", type)
return <div key={type + id} style={{background: "red"}}>UNIMPLEMENTED</div>
}

View File

@ -1,19 +0,0 @@
'use strict'
import {LineWrapper} from "./Line";
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
export const componentsList = {
"Radio": RadioWrapper,
"Switch": SwitchWrapper,
"Line": LineWrapper,
"SwitchLine": SwitchLineWrapper,
"Label": Label,
"Default": UnimplementedComponent,
};

View File

@ -1,31 +0,0 @@
import React from "react";
// copy from "@uidotdev/usehooks"
export function useClickAway(cb) {
const ref = React.useRef(null);
const refCb = React.useRef(cb);
React.useLayoutEffect(() => {
refCb.current = cb;
});
React.useEffect(() => {
const handler = (e) => {
const element = ref.current;
if (element && !element.contains(e.target)) {
refCb.current(e);
}
};
document.addEventListener("mousedown", handler);
document.addEventListener("touchstart", handler);
return () => {
document.removeEventListener("mousedown", handler);
document.removeEventListener("touchstart", handler);
};
}, []);
return ref;
}

View File

@ -1,50 +0,0 @@
import {apply as json_logic_apply} from "json-logic-js";
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 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,
has_sampler: data.filter(((value, _) => value.name === "Sampler" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
}
}
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 = {};
if (options_t === "array") {
options.map((option_item, _) => {
Object.assign(target, FilterOptions(option_item, data))
});
} else if (options_t === "object") {
if (
true_type_of(options.type) === "string" &&
(true_type_of(options.args) === "object" || true_type_of(options.items) === "array")
) {
if (options.type in componentsList) {
target[options.args.outvar] = data[options.args.outvar];
} else if (options.type === "Group") {
Object.assign(target, FilterOptions(json_logic_apply(options.items, data), data))
}
} else {
Object.assign(target, FilterOptions(json_logic_apply(options, data), data))
}
}
return target
}

View File

@ -1,51 +0,0 @@
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
};

View File

@ -1,627 +0,0 @@
'use strict';
import {createWithEqualityFn} from "zustand/traditional";
import {data as shared_data, itemsUnfoldedList} from "./utils";
import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils";
import {v4 as uuidv4} from "uuid";
import {FillResources} from "./count_resources";
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) => {
let result = {};
Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key})
return result;
};
const useCatalog = ((set, get) => ({
cards: shared_data.items,
groups: shared_data.columns.catalog,
cards_list: itemsUnfoldedList,
currency: shared_data.currency,
pn_to_cards: cards_to_pn_map(shared_data.items),
getCardDescription: index => get().cards[get().cards_list[index]],
getCardDescriptionByPn: pn => get().cards[get().pn_to_cards[pn]],
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 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) => ({
isTouch: window.isTouchEnabled(),
isMobile: window.deviceIsMobile(),
sideMenuIsOpen: false,
showCardAddedFeedback: false,
showNoDestination: false,
timerAdded: null,
_switchSideMenu: () => set(state => ({
sideMenuIsOpen: !state.sideMenuIsOpen
})),
switchSideMenu: () => {
if (!get().sideMenuIsOpen) {
get().hideNotification()
}
get()._switchSideMenu();
},
cardAdded: () => set(state => ({
showCardAddedFeedback: true,
showNoDestination: false,
timerAdded: (!!state.timerAdded ? clearTimeout(state.timerAdded) : null) || (state.isMobile && setTimeout(() => {
get()._endCardAdded()
}, 2000))
})),
noDestinationWarning: () => set(state => ({
showCardAddedFeedback: true,
showNoDestination: true,
timerAdded: (!!state.timerAdded ? clearTimeout(state.timerAdded) : null) || (setTimeout(() => {
get()._endCardAdded()
}, 2000))
})),
_endCardAdded: () => set(state => ({
showCardAddedFeedback: false,
timerAdded: !!state.timerAdded ? clearTimeout(state.timerAdded) : null
}))
}));
const useImportJSON = ((set, get) => ({
importShouldOpen: false,
importValue: {
value: "",
error: Validation.OK
},
openImport: () => set(state => ({
importShouldOpen: true
})),
closeImport: () => set(state => ({
importShouldOpen: false
})),
_loadDescription: () => set(state => {
const parsed = JSONToCrates(state.importValue.value);
// if (parsed.crates[-1].crate_mode !== "")
return {
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().fillExtCrateData(crate.id);
});
get()._updateTotalOrderPrice();
get().showNotification(get().active_crate, null);
},
updateImportDescription: (new_description) => set(state => ({
importValue: {
value: new_description,
error: validateJSONInput(new_description)
}
}))
}));
const useSubmitForm = ((set, get) => ({
isProcessing: false,
shouldShowRFQFeedback: false,
processingResult: {
status: Validation.OK,
message: ""
},
API_RFQ: shared_data.API_RFQ,
email: {
value: "",
error: null
},
note: {
value: "",
error: Validation.OK
},
description: "",
shouldShowDescription: false,
updateEmail: (new_email) => set(state => ({
email: {
value: new_email,
error: validateEmail(new_email)
}
})),
updateNote: (new_notes) => set(state => ({
note: {
value: new_notes,
error: validateNote(new_notes)
}
})),
resetEmailValidation: () => set(state => ({
email: {
value: state.email.value,
error: Validation.OK
}
})),
_revalidateForm: () => set(state => ({
email: {
value: state.email.value,
error: validateEmail(state.email.value)
},
note: {
value: state.note.value,
error: validateEmail(state.note.value)
},
})),
updateDescription: () => set(state => ({
description: CratesToJSON(state.crates)
})),
showDescription: () => set(state => ({
description: CratesToJSON(state.crates),
shouldShowDescription: true
})),
closeDescription: () => set(state => ({
shouldShowDescription: false
})),
_submitForm: () => set(state => ({isProcessing: true})),
finishSubmitForm: (result) => set(state => ({
isProcessing: false,
shouldShowRFQFeedback: true,
processingResult: result})),
submitDisabled: () => (get().email.error !== Validation.OK),
submitForm: () => {
get().updateDescription();
get()._revalidateForm();
get()._submitForm();
if (get().submitDisabled()) return;
fetch(get().API_RFQ, {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: get().email.value,
note: get().note.value,
configuration: get().description
})
}).then(response => {
if (response.status !== 200) {
throw Error("Response status is not OK: " + response.status + ".\n" + response);
}
get().finishSubmitForm({status: Validation.OK, message: "We've received your request and will be in contact soon."})
}, reason => {
console.error("Request rejected, reason:", reason)
get().finishSubmitForm({
status: Validation.Invalid,
message: "We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"
})
}).catch(err => {
console.error("Request failed, reason:", err)
get().finishSubmitForm({
status: Validation.Invalid,
message: "We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"
})
})
},
closeRFQFeedback: () => set(state => ({shouldShowRFQFeedback: false}))
}));
const useHighlighted = ((set, get) => ({
highlighted: {
crate: "",
card: 0
},
highlightedTimer: null,
// #!if disable_card_highlight === false
highlightCard: (crate_id, index) => set(state => ({
highlighted: {
crate: crate_id,
card: index
},
highlightedTimer: (!!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null) || (state.isTouch && setTimeout(() => {
get().highlightReset()
}, 2000))
})),
highlightReset: () => set(state => ({
highlighted: {
crate: "",
card: 0
},
highlightedTimer: !!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null
})),
// #!else
highlightCard: () => {return null;},
highlightReset: () => {return null;},
// #!endif
}));
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, {
...state._defaultCrates[0],
id: crate_id || "crate" + state.crates.length,
}),
active_crate: crate_id || "crate" + state.crates.length
})),
_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,
})),
_setCrateMode: (id, mode) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate.id === id) {
return {
...crate,
crate_mode: mode
}
} else return crate;
})
})),
setActiveCrate: (id) => set(state => ({active_crate: id})),
_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 {};
return {
crates: state.crates.map((crate, _i) => {
if (dest === crate.id) {
index_to = index_to != null ? index_to : crate.items.length;
return {
...crate,
items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
return {...state.cards[card_name], id: uuidv4()}
}))
}
} else return crate;
})
}
}),
_moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
return {
crates: state.crates.map((crate, _i) => {
if (crate_to === crate_from && crate_to === crate.id) {
let items_copy = Array.from(crate.items);
let item = items_copy.splice(index_from, 1)[0]
items_copy.splice(index_to, 0, item).filter((item, _) => !!item)
return {
...crate,
items: items_copy
}
} else if (crate_to === crate.id) {
return {
...crate,
items: crate.items.toSpliced(index_to, 0, the_card)
}
} else if (crate_from === crate.id) {
return {
...crate,
items: crate.items.toSpliced(index_to, 1)
}
}
else return crate;
})
}
}),
_deleteCard: (crate_id, index) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
return {
...crate,
items: crate.items.toSpliced(index, 1)
}
}
else return crate;
})
})),
_clearCrate: (id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (id === crate.id) {
return {
...crate,
items: []
}
}
else return crate;
})
})),
clearAll: () => set(state => ({
crates: state._defaultCrates
})),
_updateOptions: (crate_id, index, new_options) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items);
itemsCopy[index] = {
...itemsCopy[index],
options_data: {
...itemsCopy[index].options_data,
...new_options
}};
return {
...crate,
items: itemsCopy
}
}
else return crate;
})
})),
fillWarnings: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
//console.log("--- CHECK ALERTS ---")
let itemsCopy = Array.from(crate.items);
const disabled = !!get().crateParams(crate.crate_mode).warnings_disabled;
itemsCopy = FillResources(itemsCopy, disabled);
itemsCopy = TriggerWarnings(itemsCopy, disabled);
const [crate_warnings, occupied] = TriggerCrateWarnings(crate);
return {
...crate,
items: itemsCopy,
warnings: crate_warnings,
occupiedHP: occupied
}
}
else return crate;
})
})),
fillExtData: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items);
itemsCopy = itemsCopy.map((item, index) => {
if (!item.options) return item;
if (!item.options_data) item.options_data = {};
item.options_data.ext_data = FillExtCardData(itemsCopy, index);
return item;
});
return {
...crate,
items: Array.from(itemsCopy)
}
}
else return crate;
})
})),
_updateTotalOrderPrice: () => set(state => {
let sum = 0;
get().crates.forEach( (crate, i) => {
sum += get().crate_modes[crate.crate_mode].price;
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;
});
});
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:
newCrate: () => {
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();
},
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().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()
}
},
moveCard: (crate_from, index_from, crate_to, index_to) => {
get()._moveCard(crate_from, index_from, crate_to, index_to);
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);
}
},
deleteCard: (crate_id, index) => {
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) => {
get()._clearCrate(id);
get().fillWarnings(id);
},
updateOptions: (crate_id, index, new_options) => {
get()._updateOptions(crate_id, index, new_options);
get().fillExtData(crate_id);
get().fillWarnings(crate_id);
},
initExtData: () => {
get().fillOrderExtData();
get().crates.forEach((crate, _i) => {
get().fillExtData(crate.id);
get().fillExtCrateData(crate.id);
})
get()._updateTotalOrderPrice();
}
}))
export const useShopStore = createWithEqualityFn((...params) => ({
...useOptionsNotification(...params),
...useCatalog(...params),
...useSearch(...params),
...useCrateModes(...params),
...useCart(...params),
...useSubmitForm(...params),
...useLayout(...params),
...useHighlighted(...params),
...useImportJSON(...params),
...useCrateOptions(...params),
...useOrderOptions(...params),
}))

View File

@ -1,94 +0,0 @@
'use strict';
export const data = window.shop_data;
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 = {
opacity: snapshot.isDragging ? .7 : 1,
backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial',
};
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";
}
if (!snapshot.isDropAnimating) {
return { ...style, ...custom};
}
if (removeAnim) {
// cannot be 0, but make it super tiny
custom.transitionDuration = '0.001s';
}
return {
...style,
...custom,
};
}
export const cartStyle = (style, snapshot) => {
const isDraggingOver = snapshot.isDraggingOver;
return {
...style,
...{
backgroundColor: isDraggingOver ? '#f2f2f2' : '#f9f9f9',
border: isDraggingOver ? '1px dashed #ccc' : '0',
},
};
}
export function formatMoney(amount, decimalCount = 2, decimal = ".", thousands = ",") {
// https://stackoverflow.com/questions/149055/how-can-i-format-numbers-as-currency-string-in-javascript
// changes: return amount if error in order to avoid empty value
try {
decimalCount = Math.abs(decimalCount);
decimalCount = isNaN(decimalCount) ? 2 : decimalCount;
const negativeSign = amount < 0 ? "-" : "";
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.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;
}
}
export const range = (start, end) => {
const length = end - start;
return Array.from({ length }, (_, i) => start + i);
}
export const move = (source, destination, droppableSource, droppableDestination) => {
console.log('==> move', source, destination);
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {columns: {}};
result.columns[droppableSource.droppableId] = sourceClone;
result.columns[droppableDestination.droppableId] = destClone;
return result;
};
export const compareArraysWithIds = (a, b) =>
a.length === b.length &&
a.every((element, index) => element.id === b[index].id);
export const compareArraysLevelOne = (a, b) =>
a.length === b.length &&
a.every((element, index) => element === b[index]);
export function compareObjectsEmptiness(a, b) {
return (!a && !b) || (!(!a !== !b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b) &&
(Object.getPrototypeOf(a) !== Object.getPrototypeOf([]) || !!Object.keys(a).length === !!Object.keys(b).length))
}

View File

@ -1,34 +0,0 @@
import {validateJSON} from "./json_porter";
export const Validation = {
OK: 0,
Empty: 1,
Invalid: 2,
};
export function validateEmail(value) {
if (!value || value.trim() === '') {
return Validation.Empty
} else if (value && !value.match(/^\w+([\+\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) {
return Validation.Invalid;
}
return Validation.OK;
}
export function validateNote(value) {
if (!value || value.trim() === '') {
return Validation.Empty
}
return Validation.OK;
}
export function validateJSONInput(value) {
if (!value || value.trim() === '') {
return Validation.Empty
} else if (value && !(validateJSON(value))) {
return Validation.Invalid;
}
return Validation.OK;
}

View File

@ -1,156 +0,0 @@
/**
* This module should contain warnings subsystem.
* First idea - there should be either definitive list of available warnings,
* or defined as some json logic leading to the warning message(s), similar way how FillExtData works.
* Second - resources indicator should be separate component
*/
import {item_occupied_counters, resource_counters} from "./count_resources";
import {useShopStore} from "./shop_store";
export const Levels = {
"reminder": {priority: 1, icon: '/images/shop/icon-reminder.svg', color: "#0d3547"},
"warning": {priority: 2, icon: '/images/shop/icon-warning.svg', color: "#c75e5e"},
}
const find_in_counters = (counters, name) => {
return counters.find((element) => element.name === name)
}
const find_previous_source = (data, index, source) => {
return data.slice(0, index).find((element) => {
return element.resources && find_in_counters(element.resources, source)
})
}
const find_next_source_index = (data, index, source) => {
return data.slice(index + 1).findIndex((element) => {
return element.resources && find_in_counters(element.resources, source)
}) + index + 1
}
const not_enough_resource_trigger = (name) => {
return (_data, _index, counters) => {
const resource = find_in_counters(counters, name);
return resource.occupied > resource.max;
}
}
const no_source_trigger = (name) => {
return (data, index, _counters) => {
const occupied = item_occupied_counters[name](data[index]);
if (occupied > 0)
return !find_previous_source(data, index, name);
return false;
}
}
const wiring_constraint = (name) => {
return (data, index, _counters) => {
const next = find_next_source_index(data, index, name);
return next - index === 1;
}
}
const Types = {
"eem_resource": {
level: "warning",
trigger: not_enough_resource_trigger("eem"),
message: "Insufficient EEM connectors"
},
"no_eem_source": {
level: "warning",
trigger: no_source_trigger("eem"),
message: 'This card needs a card that provides a EEM connector (e.g. Kasli) at its left.'
},
"idc_resource": {
level: "warning",
trigger: not_enough_resource_trigger("idc"),
message: "Insufficient IDC connectors."
},
"no_idc_source": {
level: "warning",
trigger: no_source_trigger("idc"),
message: 'Should be after a Zotino or a HD68-IDC or with another IDC adapter.'
},
"clk_resource": {
level: "warning",
trigger: not_enough_resource_trigger("clk"),
message: "Insufficient clock connectors."
},
"no_clk_source": {
level: "warning",
trigger: no_source_trigger("clk"),
message: 'This card needs either a card that provides a clock source (e.g. Kasli or Clocker) at its left or use an external clock source.'
},
"eem_wiring_constraint": {
level: "reminder",
trigger: wiring_constraint("eem"),
message: "Due to wiring constraints, the carrier can only connect to EEM cards immediately at its right, without crossing another carrier."
},
"default": {
level: "warning",
trigger: (_a, _b, _c) => {
return true;
},
message: 'This item has unimplemented warning'
}
}
export function TriggerWarnings(data, disabled) {
return data.map((element, index) => {
if (!element.warnings) return element;
element.show_warnings = disabled ? [] :element.warnings
.map((warning, _) => {
if (!!Types[warning])
return Types[warning].trigger(data, index, element.counted_resources) ? {trigger: undefined, name: warning, ...Types[warning]} : null;
else
return Types.default;
})
.filter((warning, _) => {
return !!warning
});
return element;
})
}
export function MaxLevel(warnings) {
let mx = {priority: 0, icon: null};
for (const warning of warnings) {
if (Levels[warning.level].priority > mx.priority) mx = Levels[warning.level];
}
return mx;
}
export function LevelUI(warning_level) {
const warning_t = Levels[warning_level];
return {icon: warning_t.icon, color: warning_t.color};
}
const crate_warnings = {
"overfit": {
message: "You have reached the maximum number of slots allowed for this crate. Consider removing cards.",
level: "warning",
trigger: (crate, occupied) => {
const nbrHP = useShopStore.getState().crateParams(crate.crate_mode).hp;
return occupied > nbrHP && nbrHP > 0;
}
},
"underfit_rack": {
message: "The selected cards fit in a 42hp desktop crate, consider switching to it for a more compact system",
level: "reminder",
trigger: (crate, occupied) => {
const nbrHPDesktop = useShopStore.getState().crate_modes.desktop.hp;
return crate.crate_mode === useShopStore.getState().crate_modes.rack.id && occupied < nbrHPDesktop;
}
}
}
export function TriggerCrateWarnings(crate) {
const nbrOccupied = resource_counters.hp(crate.items, -1);
let warnings = [];
Object.entries(crate_warnings).forEach(([id, warning], _) => {
if (warning.trigger(crate, nbrOccupied)) warnings.push({...warning, id: id, trigger: undefined});
})
return [warnings, nbrOccupied];
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
<h5 class="pb-3">Sales inquiries</h5>
<p>
To purchase ARTIQ Sinara hardware, software and gateware development services, or to enter a technical support agreement, write to <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>.
To purchase ARTIQ Sinara hardware, software and gateware development services, or to enter a technical support agreement, write to sales@m-***s.hk.
</p>
</div>
@ -15,7 +15,7 @@
<h5 class="pb-3">Commercial technical support</h5>
<p>
If you need help setting up ARTIQ hardware purchased from us, or if you have a commercial technical support agreement with us, email us anytime at <a href="mailto:helpdesk@m-labs.hk">helpdesk@m-labs.hk</a>.
If you need help setting up ARTIQ hardware purchased from us, or if you have a commercial technical support agreement with us, email us anytime at helpdesk@m-***s.hk.
</p>
<h5 class="pb-3">Community support</h5>

View File

@ -31,15 +31,7 @@
return window.innerWidth < 768;
}
function isTouchEnabled() {
return ('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
}
window.deviceIsMobile = deviceIsMobile;
window.isTouchEnabled = isTouchEnabled;
})();
</script>

View File

@ -1,55 +0,0 @@
{% extends 'page.html' %}
{% block main %}
<main class="container {% block maincss %}{% endblock %}">
<div class="row">
{% block main_content %}
{% if page and page.content %}
{{ page.content | safe }}
{% endif %}
<div class="container">
<div class="row row-cols-1 row-cols-md-3 g-4">
{% set publications = load_data(path=page.extra.data, format="yaml") -%}
{% for publication in publications %}
<div class="col">
<div class="card shadow h-100" >
<div class="card-body">
<strong class="card-title">{{ publication.title | safe }}</strong>
{% if publication.authors.short or publication.authors.full %}
<p class="card-text">
{{ publication.authors.short | safe }}
<span class="collapse" id="viewdetails{{ publication.title | slugify | truncate(length=20, end="")}}">
{{ publication.authors.full }}
</span>
<a data-bs-toggle="collapse" class="hide-expanded" data-bs-target="#viewdetails{{ publication.title | slugify | truncate(length=20, end="") }}"> ...more</a>
<a data-bs-toggle="collapse" class="show-expanded" data-bs-target="#viewdetails{{ publication.title | slugify | truncate(length=20, end="") }}"><br>less</a>
</p>
{% else %}
<p class="card-text">{{ publication.authors }}</p>
{% endif %}
</div>
<div class="card-footer border-light-subtle">
{% for link in publication.links %}
{% if link.path %}
<a href="{{link.path}}" class="card-link">{{link.name}}</a>
{% else %}
<div class="card-link d-inline">{{link.name}}</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
</div>
</main>
{% block outside %}{% endblock %}
{% endblock %}

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