1
0
Fork 0

Compare commits

...

15 Commits

Author SHA1 Message Date
Egor Savkin 4448029757 Update bundle and make example configuration correct
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 51c9031f24 Add text options validation
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin be50b2a3c3 Optimize bundle size, and drop its support for J2ME's Opera (layout doesn't support small screens anyway)
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin ee6da1b282 Update bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 5e3a9af749 Fix notification remained visible when side menu is opened
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin d3fb46956e Fix notification overlay making "added" notification disappear on touchables
When notification doesn't fit viewport on touch-enabled devices, it makes the canvas extend to its boundaries, braking the fixed positioned elements placement

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 6bfed3e779 Show at max only one options notification
Closes #124

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin dfe1f0ea2d Rename backlog to catalog
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 839d7c6612 Fix search bar icon conflicting in webkit and make backlog scroll bar thin
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 1cb9c90c65 Fix bottom side gradient
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin eb196b086e Make fonts sizes more consistent and fiz minor paddings issues
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 56a44ce4a3 Do not show groups when there are search results
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin aa35348288 Apply styles to the search bar
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Egor Savkin 4bc6f6a3ee Prototype search bar for the backlog
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-04-09 14:56:05 +08:00
Sébastien Bourdeauducq 2ba10dd2e8 improve sales info 2024-03-08 09:31:30 +08:00
26 changed files with 594 additions and 283 deletions

View File

@ -14,7 +14,7 @@ The first ARTIQ core devices used hardware built in-house by physicists (based o
The Sinara hardware is in active development, and the latest information is available <a href="https://github.com/sinara-hw" target="_blank" rel="noopener noreferrer">on the wiki of each project's page</a>. Most of the hardware engineering is done at the <a href="http://www.ise.pw.edu.pl/" target="_blank" rel="noopener noreferrer">Institute for Electronics Systems</a> at the Warsaw University of Technology.
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements and we will establish a quote.
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
{% end %}
@ -293,11 +293,11 @@ The 4624 AWG "Phaser" is a quad channel 1.25 GS/s RF generator card with dual IQ
{% layout_centered_content(min_width=true, css="row d-flex align-items-center mt-5") %}
##### Kasli and EEMs can be ordered now
##### Ordering from M-Labs is easy and quick
We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network.
We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. The lead time can be as short as a few working days and we will provide assistance to help you set up your new equipment with ARTIQ via the online helpdesk. Using our AFWS tool, you can keep the firmware of your M-Labs devices up-to-date easily, and benefit from the new features we continuously develop into ARTIQ.
Contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements and we will establish a quote.
Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
{% end %}

156
package-lock.json generated
View File

@ -9,24 +9,24 @@
"version": "1.0.0",
"devDependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"@hello-pangea/dnd": "^16.5.0",
"@uidotdev/usehooks": "^2.4.1",
"babel-loader": "^9.1.3",
"babel-preset-minify": "^0.5.2",
"bootstrap": "^5.3.2",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1",
"json-logic-js": "^2.0.2",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
"react-bootstrap": "^2.10.2",
"react-dom": "^18.2.0",
"uuid": "^9.0.1",
"webpack": "^5.90.1",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-preprocessor-loader": "^1.3.0",
"zustand": "^4.5.0"
"zustand": "^4.5.2"
}
},
"node_modules/@ampproject/remapping": {
@ -94,9 +94,9 @@
}
},
"node_modules/@babel/core": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
"integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
"integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
@ -104,11 +104,11 @@
"@babel/generator": "^7.23.6",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.23.9",
"@babel/parser": "^7.23.9",
"@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.9",
"@babel/helpers": "^7.24.0",
"@babel/parser": "^7.24.0",
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.0",
"@babel/types": "^7.24.0",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -360,9 +360,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
"integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -480,14 +480,14 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
"integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz",
"integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==",
"dev": true,
"dependencies": {
"@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.9"
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.0",
"@babel/types": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
@ -508,9 +508,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
"integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@ -1300,14 +1300,14 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
"integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz",
"integrity": "sha512-y/yKMm7buHpFFXfxVFS4Vk1ToRJDilIa6fKRioB9Vjichv58TDGXTvqV0dN7plobAmTW5eSEGXDngE+Mm+uO+w==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.23.3",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/compat-data": "^7.23.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-transform-parameters": "^7.23.3"
},
@ -1667,14 +1667,14 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz",
"integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz",
"integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.23.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
@ -1727,7 +1727,7 @@
"@babel/plugin-transform-new-target": "^7.23.3",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
"@babel/plugin-transform-numeric-separator": "^7.23.4",
"@babel/plugin-transform-object-rest-spread": "^7.23.4",
"@babel/plugin-transform-object-rest-spread": "^7.24.0",
"@babel/plugin-transform-object-super": "^7.23.3",
"@babel/plugin-transform-optional-catch-binding": "^7.23.4",
"@babel/plugin-transform-optional-chaining": "^7.23.4",
@ -1822,23 +1822,23 @@
}
},
"node_modules/@babel/template": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
"integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.23.9",
"@babel/types": "^7.23.9"
"@babel/parser": "^7.24.0",
"@babel/types": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz",
"integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz",
"integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.23.5",
@ -1847,8 +1847,8 @@
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.9",
"@babel/types": "^7.23.9",
"@babel/parser": "^7.24.0",
"@babel/types": "^7.24.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1857,9 +1857,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
"integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
@ -2025,9 +2025,9 @@
}
},
"node_modules/@react-aria/ssr": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.7.1.tgz",
"integrity": "sha512-ovVPSD1WlRpZHt7GI9DqJrWG3OIYS+NXQ9y5HIewMJpSe+jPQmMQfyRmgX4EnvmxSlp0u04Wg/7oItcoSIb/RA==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz",
"integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==",
"dev": true,
"dependencies": {
"@swc/helpers": "^0.5.0"
@ -2040,9 +2040,9 @@
}
},
"node_modules/@restart/hooks": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.11.tgz",
"integrity": "sha512-Ft/ncTULZN6ldGHiF/k5qt72O8JyRMOeg0tApvCni8LkoiEahO+z3TNxfXIVGy890YtWVDvJAl662dVJSJXvMw==",
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
"dev": true,
"dependencies": {
"dequal": "^2.0.3"
@ -2052,9 +2052,9 @@
}
},
"node_modules/@restart/ui": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz",
"integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz",
"integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.21.0",
@ -2082,9 +2082,9 @@
}
},
"node_modules/@swc/helpers": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
"integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==",
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.7.tgz",
"integrity": "sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==",
"dev": true,
"dependencies": {
"tslib": "^2.4.0"
@ -2180,9 +2180,9 @@
"dev": true
},
"node_modules/@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
"dev": true
},
"node_modules/@uidotdev/usehooks": {
@ -2813,9 +2813,9 @@
}
},
"node_modules/bootstrap": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
"integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"dev": true,
"funding": [
{
@ -4077,14 +4077,14 @@
}
},
"node_modules/react-bootstrap": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.0.tgz",
"integrity": "sha512-87gRP69VAfeU2yKgp8RI3HvzhPNrnYIV2QNranYXataz3ef+k7OhvKGGdxQLQfUsQ2RTmlY66tn4pdFrZ94hNg==",
"version": "2.10.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz",
"integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.22.5",
"@restart/hooks": "^0.4.9",
"@restart/ui": "^1.6.6",
"@restart/ui": "^1.6.8",
"@types/react-transition-group": "^4.4.6",
"classnames": "^2.3.2",
"dom-helpers": "^5.2.1",
@ -4748,9 +4748,9 @@
}
},
"node_modules/webpack": {
"version": "5.90.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
"version": "5.90.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
"integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@ -4974,9 +4974,9 @@
}
},
"node_modules/zustand": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.0.tgz",
"integrity": "sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
"integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==",
"dev": true,
"dependencies": {
"use-sync-external-store": "1.2.0"

View File

@ -14,29 +14,37 @@
},
"devDependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"babel-preset-minify": "^0.5.2",
"bootstrap": "^5.3.2",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
"react-bootstrap": "^2.10.2",
"@hello-pangea/dnd": "^16.5.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.1",
"webpack": "^5.90.1",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"json-logic-js": "^2.0.2",
"zustand": "^4.5.0",
"zustand": "^4.5.2",
"@uidotdev/usehooks":"^2.4.1",
"webpack-preprocessor-loader": "^1.3.0"
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
"@babel/preset-react",
["@babel/preset-env", {
"targets": {
"browsers": [
">0.25%",
"not dead",
"not op_mini all"
]
}
}]
]
}
}

View File

@ -72,26 +72,32 @@ button {
overflow-y: scroll;
overflow-x: hidden;
position: relative;
max-width: max(1/4 * 100%, 310px);
scrollbar-width: thin;
scrollbar-gutter: stable;
scrollbar-color: #6e7e87 transparent;
scrollbar-arrow-color: transparent;
/*padding-bottom: 4rem!important;*/
}
> aside.aside:after {
position: fixed;
bottom: 0;
height: 100px;
width: calc(2 / 6 * 100%);
content: "";
background: linear-gradient(
to top,
rgba(13, 53, 71, 1),
rgba(13, 53, 71, 0)
);
pointer-events: none;
.gradient-bottom {
position: sticky;
bottom: 0;
height: 100px;
//width: max(1/4 * 100%, 310px);
width: inherit;
content: "";
background: linear-gradient(
to top,
rgba(13, 53, 71, 1),
rgba(13, 53, 71, 0)
);
pointer-events: none;
}
}
> section.main {
flex: 4;
max-width: calc(4 / 6 * 100%);
width: calc(3/4 * 100%);
overflow-y: scroll;
}
}
@ -100,7 +106,7 @@ button {
display: flex;
color: white;
padding: 3rem 2rem 1rem;
padding: 1rem 0rem 1rem 1.5rem;
.content {
flex: 1;
@ -119,6 +125,7 @@ button {
h3 {
color: white;
font-size: 1.5rem;
}
button {
@ -176,8 +183,50 @@ button {
}
}
.backlog-container {
.catalog-container {
padding-bottom: 4rem;
.catalog-bar {
display: flex;
width: 100%;
margin: 0.4rem 0;
padding: 0 0.5rem;
justify-content: space-around;
.mobileCloseMenu {
display: flex;
padding: 0;
margin: 0;
width: 10%;
align-content: center;
}
.search-catalog {
display: inline-block;
border: 0;
width: 90%;
.search-catalog-input {
display: inline;
border: 0;
color: white;
border-radius: 2rem;
background: rgba(255, 255, 255, 0.15) url("/images/shop/icon-search.svg") no-repeat;
background-position: left 2% center;
background-size: 20px;
padding-right: 1rem;
text-indent: 20px;
&::placeholder {
color: white;
opacity: 0.5;
}
&:focus {
box-shadow: none;
}
}
}
}
}
.rule {
@ -241,6 +290,9 @@ button {
.item-card-name {
font-weight: 700;
&.tabbed {
padding-left: 16px;
}
}
.price {

View File

@ -55,7 +55,7 @@
color: white;
font-weight: bold;
font-size: 1.75rem;
padding: .75rem 2rem;
padding: .75rem 1.5rem;
}
#accordion_categories .accordion-body {
@ -73,6 +73,10 @@
text-decoration: none;
}
.options-invalid {
box-shadow: 0 0 0 .25rem rgba(229, 62, 62, 0.25)!important;
--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important;
}
/*
##Device = Tablets, Ipads (portrait)
##Screen = B/w 768px to 1024px
@ -94,6 +98,14 @@
height: calc(100vh - 10px - 2.5rem); /* .navbar vertical padding + line height (.navbar-brand.font-size.rem * body.font-size * body.line-height)*/
}
#root-shop .layout>aside.aside {
min-width: max(300px, 30%);
}
#root-shop .layout>aside.aside .gradient-bottom {
width: inherit;
}
#root-shop .productItem {
padding: 2rem 1rem 1rem;
}
@ -111,7 +123,7 @@
}
#root-shop .productItem .content ul {
font-size: .6rem;
font-size: .75rem;
}
#root-shop .panel .control {
@ -133,7 +145,7 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: 1rem;
font-size: 0.8rem;
}
#root-shop .panel .summary>.summary-form form,
@ -163,7 +175,7 @@
}
#root-shop table tr {
padding: .8em 0;
padding: .2em 0;
display: flex !important;
justify-content: space-between;
}
@ -192,7 +204,7 @@
}
body {
font-size: .7rem;
font-size: .8rem;
}
#root-shop, #root-shop>div {
@ -217,7 +229,7 @@
}
#root-shop table tr {
padding: .8em 0;
padding: .2em 0;
display: flex !important;
justify-content: space-between;
}
@ -227,7 +239,7 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .7rem;
font-size: .8rem;
}
#root-shop .panel .summary>.summary-form form,
@ -236,7 +248,7 @@
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: .85rem;
font-size: 1.0rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -288,7 +300,7 @@
}
body {
font-size: .7rem;
font-size: .8rem;
}
#root-shop, #root-shop>div {
@ -308,11 +320,11 @@
}
#root-shop .productItem .content h3 {
font-size: 1rem;
font-size: 1.25rem;
}
#root-shop .productItem .content ul {
font-size: .5rem;
font-size: .75rem;
}
#root-shop .panel {
@ -345,7 +357,7 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .7rem;
font-size: .8rem;
}
#root-shop .panel .summary>.summary-form form,
@ -354,7 +366,7 @@
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: .85rem;
font-size: 1rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -367,7 +379,6 @@
border-top-right-radius: 30px;
width: 80px;
padding: 5px 0 5px 10px;
margin-bottom: -25px;
margin-left: -1.3rem;
position: relative;
z-index: 1;
@ -386,7 +397,7 @@
}
#root-shop table tr {
padding: .8em 0;
padding: .2em 0;
display: flex !important;
justify-content: space-between;
}
@ -394,21 +405,22 @@
#root-shop .layout>aside.aside.menu-opened {
/*transform: translate3d(0, 0, 0);*/
transition: left .3s;
width: 310px;
width: min(310px, 60vw);
max-width: 60%;
left: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main {
/*transform: translate3d(310px, 0, 0);*/
transition: left .3s;
left: 310px;
left: min(310px, 60vw);
position: relative;
z-index: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main:after {
content: '';
position: absolute;
position: fixed;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, .3);
@ -422,13 +434,13 @@
transition: left .3s;
position: fixed;
z-index: 1;
left: -310px;
width: 310px;
left: max(-310px, -60vw);
width: min(310px, 60vw);
height: 100%;
}
#root-shop .layout>aside.aside:after {
width: 0;
#root-shop .layout>aside.aside .gradient-bottom {
display: none;
}
#root-shop .layout>aside.aside + section.main {
@ -450,7 +462,7 @@
overflow: initial;
}
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
overflow-y: scroll;
height: 100%;
}
@ -460,7 +472,7 @@
}
#accordion_categories button {
font-size: 1rem;
font-size: 1.5rem;
padding: .5rem 0.5rem;
}
}
@ -500,7 +512,7 @@
}
body {
font-size: .7rem;
font-size: .8rem;
}
#root-shop, #root-shop>div {
@ -516,20 +528,21 @@
}
#root-shop .productItem .content h3 {
font-size: 1rem;
font-size: 1.25rem;
}
#root-shop .layout>aside.aside.menu-opened {
/*transform: translate3d(0, 0, 0);*/
transition: left .3s;
width: 310px;
width: min(310px, 90vw);
max-width: 90%;
left: 0;
}
#root-shop .layout>aside.aside.menu-opened + section.main {
/*transform: translate3d(310px, 0, 0);*/
transition: left .3s;
left: 310px;
left: min(310px, 90vw);
position: relative;
z-index: 0;
}
@ -549,13 +562,13 @@
transition: left .3s;
position: fixed;
z-index: 1;
left: -310px;
width: 310px;
left: max(-310px, -90vw);
width: min(310px, 90vw);
height: 100%;
}
#root-shop .layout>aside.aside:after {
width: 0;
#root-shop .layout>aside.aside .gradient-bottom {
display: none;
}
#root-shop .layout>aside.aside + section.main {
@ -611,7 +624,7 @@
}
#root-shop .panel .summary>.summary-price table {
font-size: .7rem;
font-size: .8rem;
}
#root-shop .panel .summary>.summary-form form,
@ -620,7 +633,7 @@
}
#root-shop .panel .summary>.summary-price tfoot {
font-size: .85rem;
font-size: 1rem;
}
/*#root-shop .panel .summary>.summary-form form input[type="submit"] {
@ -633,7 +646,6 @@
border-top-right-radius: 30px;
width: 80px;
padding: 5px 0 5px 10px;
margin-bottom: 15px;
margin-left: -1.3rem;
}
@ -650,7 +662,7 @@
}
#root-shop table tr {
padding: .8em 0;
padding: .2em 0;
display: flex !important;
justify-content: space-between;
}
@ -659,7 +671,7 @@
overflow: initial;
}
#root-shop .layout>aside.aside.menu-opened > .backlog-container {
#root-shop .layout>aside.aside.menu-opened > .catalog-container {
overflow-y: scroll;
height: 100%;
}
@ -669,7 +681,7 @@
}
#accordion_categories button {
font-size: 1rem;
font-size: 1.5rem;
padding: .5rem 0.5rem;
}
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="512"
version="1.1"
viewBox="0 0 512 512"
id="svg1"
sodipodi:docname="icon-search.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.59375"
inkscape:cx="255.68627"
inkscape:cy="256"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M456.69,421.39,362.6,327.3a173.81,173.81,0,0,0,34.84-104.58C397.44,126.38,319.06,48,222.72,48S48,126.38,48,222.72s78.38,174.72,174.72,174.72A173.81,173.81,0,0,0,327.3,362.6l94.09,94.09a25,25,0,0,0,35.3-35.3ZM97.92,222.72a124.8,124.8,0,1,1,124.8,124.8A124.95,124.95,0,0,1,97.92,222.72Z"
fill="#fff"
id="path1"
style="fill:#ffffff;fill-opacity:0.5" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,68 @@
import React from 'react';
import {Droppable} from "@hello-pangea/dnd";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {CatalogGroups} from "./CatalogGroups";
import {SearchBar} from "./SearchBar";
import {CatalogSearchResult} from "./CatalogSearchResult";
import {GradientBottom} from "./GradientBottom";
/**
* Component that renders the catalog in the aside
*/
export function Catalog() {
// #!render_count
const renderCount = useRenderCount();
const data = useShopStore((state) => state.groups);
const items = useShopStore((state) => state.cards);
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
const isMobile = useShopStore((state) => state.isMobile);
const showSearch = useShopStore((state) => state.listed_cards.length > 0);
// #!render_count
console.log("Catalog renders: ", renderCount)
return (
<Droppable
droppableId={data.id}
isDropDisabled={false}>
{(provided) => (
<div
className="catalog-container"
ref={provided.innerRef}
{...provided.droppableProps}>
<div className="catalog-bar">
<SearchBar/>
{isMobile ? (
<div className="mobileCloseMenu">
<button onClick={onClickToggleMobileSideMenu}>
<img src="/images/shop/icon-close-white.svg" alt="add"/>
</button>
</div>
) : null}
</div>
{showSearch ? <CatalogSearchResult/> : <CatalogGroups/>}
{provided.placeholder && (
<div style={{display: 'none'}}>
{provided.placeholder}
</div>
)}
<GradientBottom/>
</div>
)}
</Droppable>
);
}

View File

@ -1,24 +1,10 @@
import React from 'react';
import {Droppable} from "@hello-pangea/dnd";
import {ProductItem} from "./ProductItem";
import React from "react";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that renders the backlog in the aside
*/
export function Backlog() {
// #!render_count
const renderCount = useRenderCount();
export function CatalogGroups() {
const data = useShopStore((state) => state.groups);
const items = useShopStore((state) => state.cards);
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
const isMobile = useShopStore((state) => state.isMobile);
// #!render_count
console.log("Backlog renders: ", renderCount)
const ordered_groups = data.categories.map(groupItem => ({
name: groupItem.name,
@ -50,39 +36,9 @@ export function Backlog() {
);
}
);
return (
<Droppable
droppableId={data.id}
isDropDisabled={false}>
{(provided) => (
<div
className="backlog-container"
ref={provided.innerRef}
{...provided.droppableProps}>
{isMobile ? (
<div className="mobileCloseMenu">
<button onClick={onClickToggleMobileSideMenu}>
<img src="/images/shop/icon-close-white.svg" alt="add"/>
</button>
</div>
) : null}
<div className="accordion accordion-flush" id="accordion_categories">
{groups}
</div>
{provided.placeholder && (
<div style={{display: 'none'}}>
{provided.placeholder}
</div>
)}
</div>
)}
</Droppable>
);
<div className="accordion accordion-flush" id="accordion_categories">
{groups}
</div>
)
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import {useShopStore} from "./shop_store";
import {ProductItem} from "./ProductItem";
export function CatalogSearchResult() {
const cards_to_display = useShopStore((state) => state.listed_cards);
return ( <>
{cards_to_display.map((item, _) => {
return (
<ProductItem card_index={item} key={"searched_" + item} />
)
})}
</>
)
}

View File

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

View File

@ -27,13 +27,16 @@ const JSONExample = JSON.stringify({
"options": null
}
],
"type": "rack"
"type": "rack",
"options": {}
},
{
"items": [],
"type": "no_crate"
"type": "no_crate",
"options": {}
}
]
],
"options": {}
});
export function ImportJSON() {

View File

@ -11,6 +11,11 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
const options_class = useShopStore((state) => state.crates[crate_index].items[card_index].options_class);
const sideMenuIsOpen = useShopStore((state) => state.sideMenuIsOpen);
const _notificationTimer = useShopStore((state) => state.notificationTimer);
const hideNotification = useShopStore((state) => state.hideNotification);
const displayNotification = useShopStore((state) =>
state.notificationCrateId === crate_id &&
(state.notificationCardIndex === card_index || (state.crates[crate_index].items.length + (state.notificationCardIndex || -1)) === card_index));
const onOptionsUpdate = useShopStore((state) => state.updateOptions);
@ -25,6 +30,8 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
first={first}
last={last}
sideMenuIsOpen={sideMenuIsOpen}
onHideNotification={hideNotification}
displayNotification={displayNotification}
target={{
construct: ((outvar, value) => {
// #!options_log

View File

@ -8,7 +8,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that renders a product.
* Used in the aside (e.g backlog of product)
* Used in the aside (e.g catalog of product)
*/
export function ProductItem({card_index}) {
// #!render_count
@ -16,7 +16,7 @@ export function ProductItem({card_index}) {
const getCardDescription = useShopStore((state) => state.getCardDescription);
const currency = useShopStore((state) => state.currency);
const onAddCard = useShopStore((state) => state.addCardFromBacklog);
const onAddCard = useShopStore((state) => state.addCardFromCatalog);
const card = getCardDescription(card_index);
// #!render_count

View File

@ -0,0 +1,18 @@
import React from 'react';
import {useShopStore} from "./shop_store";
export function SearchBar() {
const search_bar_value = useShopStore((state) => state.search_bar_value);
const updateSearchBar = useShopStore((state) => state.updateSearchBar);
return (
<div className="search-catalog form-outline">
<input type="search"
id="search_bar"
className="search-catalog-input form-control"
placeholder="Search through cards"
value={search_bar_value}
onChange={event => updateSearchBar(event.target.value)}
aria-label="Search"/>
</div>
)
}

View File

@ -6,7 +6,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
import {Layout} from "./Layout";
import {Backlog} from "./Backlog";
import {Catalog} from "./Catalog";
import {OrderPanel} from "./OrderPanel";
import {useShopStore} from "./shop_store";
@ -18,7 +18,7 @@ export function Shop() {
// #!render_count
const renderCount = useRenderCount();
const addCardFromBacklog = useShopStore((state) => state.addCardFromBacklog);
const addCardFromCatalog = useShopStore((state) => state.addCardFromCatalog);
const initExtData = useShopStore((state) => state.initExtData);
const moveCard = useShopStore((state) => state.moveCard);
const deleteCard = useShopStore((state) => state.deleteCard);
@ -30,16 +30,16 @@ export function Shop() {
console.log(drop_result)
return;
}
if (drop_result.source.droppableId === "backlog")
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
else if (drop_result.destination.droppableId === "backlog")
if (drop_result.source.droppableId === "catalog")
addCardFromCatalog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
else if (drop_result.destination.droppableId === "catalog")
deleteCard(drop_result.source.droppableId, drop_result.source.index);
else
moveCard(drop_result.source.droppableId, drop_result.source.index, drop_result.destination.droppableId, drop_result.destination.index)
}
useEffect(() => {
addCardFromBacklog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
addCardFromCatalog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
initExtData();
}, []);
@ -50,7 +50,7 @@ export function Shop() {
<DragDropContext onDragEnd={handleOnDragEnd}>
<Layout
aside={
<Backlog/>
<Catalog/>
}
main={(
<OrderPanel

View File

@ -41,11 +41,7 @@ export function SummaryCrateCard({crate_index, card_index}) {
onClick={() => setHighlight(crate_id, card_index)}
onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={() => resetHighlight()}>
<td className="item-card-name">
<span style={{
'display': 'inline-block',
'width': '16px',
}}>&nbsp;</span>
<td className="item-card-name tabbed">
<div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div>
</td>

View File

@ -23,10 +23,7 @@ export function SummaryCratePricedOptions({crate_index}) {
return options.map((option, _i) => (
<tr key={"summary_crate_" + crate_id +"option_" + option.id}>
<td className="item-card-name">
<span style={{
'display': 'inline-block', 'width': '16px',
}}>&nbsp;</span>
<td className="item-card-name tabbed">
<div>{option.title}</div>
</td>

View File

@ -3,7 +3,8 @@ import {useClickAway} from "./useClickAway";
import {ProcessOptions} from "./Options";
import {Notification} from "./Notification";
export function DialogPopup({options, data, target, id, big, first, last, options_class, sideMenuIsOpen}) {
export function DialogPopup({options, data, target, id, big, first, last, options_class,
sideMenuIsOpen, displayNotification, onHideNotification}) {
const [show, setShow] = useState(false);
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
@ -22,8 +23,11 @@ export function DialogPopup({options, data, target, id, big, first, last, option
id={"processed_options_notification" + id}
tip="Customization options available"
sideMenuIsOpen={sideMenuIsOpen}
show={displayNotification}
onHide={onHideNotification}
content={
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
<img className="alert-info"
src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
onClick={handleClick}/>
}
/>

View File

@ -1,30 +1,21 @@
import {OverlayTrigger, Tooltip} from "react-bootstrap";
import React, {useEffect, useState} from "react";
export function Notification({id, tip, content, sideMenuIsOpen}) {
const [show, setShow] = useState(false);
useEffect(() => {
setTimeout(() => {
setShow(true)
}, 100);
setTimeout(() => {
setShow(false)
}, 5000);
}, []);
import React from "react";
export function Notification({id, tip, content, sideMenuIsOpen, show, onHide}) {
return (
<OverlayTrigger
placement="top"
trigger={["click", "hover"]}
style={{display: 'inline'}}
show={show}
onToggle={() => setShow(false)}
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
rootClose={!sideMenuIsOpen}
>
{content}
</OverlayTrigger>
<OverlayTrigger
placement="top"
trigger={["click", "hover"]}
style={{display: 'inline'}}
show={show}
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
rootClose={!sideMenuIsOpen}
onToggle={onHide}
popperConfig={{
strategy: 'fixed'
}}
>
{content}
</OverlayTrigger>
)
}

View File

@ -1,22 +1,25 @@
import React, {Component} from "react";
import {Tip} from "./Tip";
import {Validation} from "../validation";
class Line extends Component {
constructor(props) {
super(props);
// Initialize the state object with the initial values from the props
this.state = {
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : "")
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : ""),
valid: true
};
// Bind the event handler to this
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
this.props.target.construct(this.props.outvar, this.state.text);
}
handleClick(element) {
handleChange(element) {
let text = element.target.value;
this.setState({
text: text
text: text,
valid: this.props.validator ? this.props.validator(text) : true
});
this.props.target.update(this.props.outvar, text);
}
@ -39,14 +42,14 @@ class Line extends Component {
{this.props.title}:
</label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
<input type="text" className="form-control form-control-sm" id={key} onChange={this.handleClick}
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key} onChange={this.handleChange}
value={this.state.text}/>
</div>
);
}
}
export function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
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}/>;
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
}

View File

@ -1,5 +1,6 @@
import React, {Component} from "react";
import {Tip} from "./Tip";
import {Validation} from "../validation";
class SwitchLine extends Component {
constructor(props) {
@ -7,7 +8,8 @@ class SwitchLine extends Component {
// Initialize the state object with the initial values from the props
this.state = {
text: props.outvar in props.data ? props.data[props.outvar].text : (props.fallback ? props.fallback.text : ""),
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false)
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false),
valid: true
};
// Bind the event handler to this
this.handleText = this.handleText.bind(this);
@ -18,7 +20,8 @@ class SwitchLine extends Component {
handleText(element) {
let new_state = {
...this.state,
text: element.target.value
text: element.target.value,
valid: this.props.validator ? this.props.validator(element.target.value) : true
}
this.setState(new_state);
this.props.target.update(this.props.outvar, new_state);
@ -39,6 +42,7 @@ class SwitchLine extends Component {
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
@ -64,14 +68,14 @@ class SwitchLine extends Component {
</label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div>
<input type="text" className="form-control form-control-sm" id={key + "line"} onChange={this.handleText}
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key + "line"} onChange={this.handleText}
value={this.state.text} disabled={!this.state.checked}/>
</div>
);
}
}
export function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
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}/>;
id={id} data={data} classes={classes} validator={validator && Validation[validator.name](validator.params)}/>;
}

View File

@ -0,0 +1,51 @@
const ipv4 = (params) => {
const ipv4WithMaskPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(0|1[0-9]|2[0-9]|3[0-2]|[0-9])$/;
return (text) => {
return ipv4WithMaskPattern.test(text);
}
}
const ipv6 = (params) => {
const ipv6WithMaskPattern = /(^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(\/(\d{1,2}|1[0-1]\d|12[0-8]))(%.+)?\s*$)/;
return (text) => {
return ipv6WithMaskPattern.test(text);
}
}
const ipv4or6 = (params) => {
const ipv4Local = ipv4(params);
const ipv6Local = ipv6(params);
return (text) => {
return ipv4Local(text) || ipv6Local(text);
}
}
const frequency = (params) => {
const factors = {
"mhz": 1e6,
"khz": 1e3,
"hz": 1e1,
"ghz": 1e9,
};
return (text) => {
const splited = text.split(/(\s+)/);
const numerator = parseFloat(splited[0]);
if (splited.length !== 3 || isNaN(numerator)) return false;
const factor = factors[splited[2].toLowerCase()];
if (!factor) return false;
const realFreq = factor * numerator;
return realFreq >= (params.min || 10*factors.mhz) && realFreq <= (params.max || 1*factors.ghz);
}
}
export const Validation = {
ipv4: ipv4,
ipv6: ipv6,
ipv4or6: ipv4or6,
frequency: frequency
};

View File

@ -18,9 +18,9 @@ const cards_to_pn_map = (cards) => {
return result;
};
const useBacklog = ((set, get) => ({
const useCatalog = ((set, get) => ({
cards: shared_data.items,
groups: shared_data.columns.backlog,
groups: shared_data.columns.catalog,
cards_list: itemsUnfoldedList,
currency: shared_data.currency,
pn_to_cards: cards_to_pn_map(shared_data.items),
@ -29,6 +29,43 @@ const useBacklog = ((set, get) => ({
cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
}));
const useOptionsNotification = ((set, get) => ({
notificationCrateId: null,
notificationCardIndex: null,
notificationTimer: null,
_showNotification: (crate_id, card_index) => set(state => ({
notificationCrateId: crate_id,
notificationCardIndex: card_index,
notificationTimer: setTimeout(() => {
state.hideNotification()
}, 5000)
})),
showNotification: (crate_id, card_index) => {
get().hideNotification()
setTimeout(() => get()._showNotification(crate_id, card_index), 100);
},
hideNotification: () => set(state => ({
notificationCrateId: null,
notificationCardIndex: null,
notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null,
}))
}));
const useSearch = ((set, get) => ({
search_index: Array.from(Object.values(shared_data.items)
.map((card, _) => (
[(card.name + " " + card.name_number + " " + card.name_codename).toLowerCase(), card.id]
))),
search_bar_value: "",
listed_cards: [],
updateSearchBar: text => set(state => ({
search_bar_value: text,
listed_cards: text.length > 0 ? Array.from(get().search_index
.filter((card, _) => card[0].includes(text.toLowerCase()))
.map(([index, card_id], _) => get().cards_list.findIndex(elem => elem === card_id))) : []
}))
}));
const useCrateModes = ((set, get) => ({
crate_modes: shared_data.crateModes,
modes_order: shared_data.crateModeOrder,
@ -114,9 +151,15 @@ const useLayout = ((set, get) => ({
showNoDestination: false,
timerAdded: null,
switchSideMenu: () => set(state => ({
_switchSideMenu: () => set(state => ({
sideMenuIsOpen: !state.sideMenuIsOpen
})),
switchSideMenu: () => {
if (!get().sideMenuIsOpen) {
get().hideNotification()
}
get()._switchSideMenu();
},
cardAdded: () => set(state => ({
showCardAddedFeedback: true,
showNoDestination: false,
@ -167,6 +210,7 @@ const useImportJSON = ((set, get) => ({
get().fillExtCrateData(crate.id);
});
get()._updateTotalOrderPrice();
get().showNotification(get().active_crate, null);
},
updateImportDescription: (new_description) => set(state => ({
importValue: {
@ -339,7 +383,7 @@ const useCart = ((set, get) => ({
})
})),
setActiveCrate: (id) => set(state => ({active_crate: id})),
_addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
_addCardFromCatalog: (crate_to, index_from, index_to) => set(state => {
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
const dest = crate_to || state.active_crate;
if (!dest) return {};
@ -510,14 +554,15 @@ const useCart = ((set, get) => ({
get().fillOrderExtData();
},
addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => {
addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => {
const dest = crate_to || get().active_crate;
if (!dest) {
console.warn("No destination");
get().noDestinationWarning();
return {};
}
get()._addCardFromBacklog(dest, index_from, index_to)
get().showNotification(dest, index_to);
get()._addCardFromCatalog(dest, index_from, index_to)
get().fillExtData(dest);
get().fillWarnings(dest);
get().setActiveCrate(dest);
@ -568,7 +613,9 @@ const useCart = ((set, get) => ({
export const useShopStore = createWithEqualityFn((...params) => ({
...useBacklog(...params),
...useOptionsNotification(...params),
...useCatalog(...params),
...useSearch(...params),
...useCrateModes(...params),
...useCart(...params),
...useSubmitForm(...params),

View File

@ -1,7 +1,7 @@
'use strict';
export const data = window.shop_data;
export const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat());
export const itemsUnfoldedList = Array.from(data.columns.catalog.categories.map(groupId => groupId.itemIds).flat());
export const productStyle = (style, snapshot, removeAnim, hovered, selected, cart=false) => {
const custom = {
@ -9,7 +9,7 @@ export const productStyle = (style, snapshot, removeAnim, hovered, selected, car
backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial',
};
if (!cart && snapshot.draggingOver == null && // hack for backlog
if (!cart && snapshot.draggingOver == null && // hack for catalog
((!snapshot.isDragging) // prevent next elements from animation
|| (snapshot.isDragging && snapshot.isDropAnimating))) { // prevent dragged element from weird animation
style.transform = "none";

View File

@ -194,9 +194,14 @@ const shop_data = {
]
},
[
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24",
tip: "Set up IPv4 address and mask used by core device", validator: {name: "ipv4"}}},
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6",
tip: "Set up IPv6 address and prefix used by core device",
validator: {name: "ipv6"}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false},
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
],
[
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
@ -262,9 +267,17 @@ const shop_data = {
]
},
[
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
{type: "Line", args: {title: "IPv4", outvar: "ipv4",
validator: {name: "ipv4"},
fallback: "192.168.1.75/24",
tip: "Set up IPv4 address used by core device"}},
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6",
tip: "Set up IPv6 address and prefix used by core device",
validator: {name: "ipv6"}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
fallback: {text: "125 MHz", checked: false},
tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
],
[
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
@ -658,7 +671,9 @@ const shop_data = {
"if": [
{"var": "mono_eem"},
[
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
fallback: {text: "125 MHz", checked: false}}},
],
[
{type: "Switch", args: {title: "Synchronization", outvar: "sync", tip: "Synchronize phases across Urukuls"}},
@ -667,7 +682,9 @@ const shop_data = {
{"var": "sync"},
null,
[
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
fallback: {text: "125 MHz", checked: false}}},
{
"if": [
{"var": "ext_data.has_sampler"},
@ -716,7 +733,9 @@ const shop_data = {
datasheet_name: '4410/4412 Urukul datasheet',
options: [
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
fallback: {text: "125 MHz", checked: false}}}
],
size: 'small',
type: 'urukul',
@ -747,7 +766,9 @@ const shop_data = {
'The upconverter is optional, if you would like the baseband version please leave us a note.'
],
options: [
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 1e9}},
fallback: {text: "125 MHz", checked: false}}},
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
],
size: 'small',
@ -1025,7 +1046,10 @@ const shop_data = {
'Can be controlled by Kasli or work stand-alone with PoE supply.'
],
options: [
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}},
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
{type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
{type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
@ -1057,7 +1081,9 @@ const shop_data = {
'Large frequency changes take several milliseconds.',
],
options: [
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 600e6}},
fallback: {text: "125 MHz", checked: false}}}
],
size: 'small',
type: null,
@ -1084,7 +1110,9 @@ const shop_data = {
'Each Almazny channel outputs twice the frequency of its corresponding Mirny channel.',
],
options: [
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
validator: {name: "frequency", params: {min: 10e6, max: 600e6}},
fallback: {text: "125 MHz", checked: false}}}
],
size: 'big',
type: null,
@ -1166,9 +1194,13 @@ const shop_data = {
'AD9959 DDS (500MSPS, 10-bit).'
],
options: [
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}},
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}},
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
],
@ -1213,12 +1245,12 @@ const shop_data = {
columns: {
/***
* backlog is the column containing all items on left aside,
* catalog is the column containing all items on left aside,
* name should not change
*/
'backlog': {
id: 'backlog',
title: 'Backlog',
'catalog': {
id: 'catalog',
title: 'Catalog',
/* itemIds define items order - change order to suit your need */
categories: [
{ name: 'Core',