Compare commits

...

31 Commits

Author SHA1 Message Date
Egor Savkin b4989500ec Slightly promote new functionality and consider mono_eem modes on sampler/urukul for suservo
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 1244ac1775 Update shop bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 0762572956 Fix cards state being not updated on touchables
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin a8e3bae08e Fix not-disappearing warnings and clocker clock slots
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 11aac2323d Small duplication removal
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin a705feebc2 Remove redundant notifications about connectors and AD9910
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 718a4509f4 Integrate clock/slots configuration into the calculator
Also adjust sizes

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 1b1736ea15 Fixed initial display of empty options in the options summary
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 264e583d82 Still fixing the bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 3a6dfef9b7 Fix scroll issue and found another bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin c2cf323cf4 Add groups of options and some fixes for long lists of options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 4b5ea7c1db Add external data for use per every card. Apply it for TTL and Suservo
Signed-off-by: esavkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 832a538c22 Fix tooltip showed out when options overlay was closed
Signed-off-by: esavkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 3980ece5b5 Use tips on more cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 4e6447c3d2 Add tooltips with hints for the user
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin a4e7fce0f4 Add icons to options titles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 9b650b4a2c Remove MAC from options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 3454a1b4cd Reposition summary popup relative to icon respective to window width, hide on scroll
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 22372116ad Hide popups on clicking outside of them
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin a6a6d758e9 Update bundle file
Add custom options to more cards

Minor design adjustments: add margin-left to icon and optimize the icon

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 712d8664f1 Add popover for cart summary with options data
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin bd7e39927d Make overlay appear on button click
And fix options absense for non-carrier cards

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin b2250680c9 Make the design more compact
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 2eb4bc99dd Adjust styles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 759e41681f Fix add of useless options to JSON
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 4c64879fbc Add switchline compact design
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 6e54da2c71 Add switch and line
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 47d9af400d Fix JSON shop load
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin 7b394c8203 Make it update state
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:40 +08:00
Egor Savkin ae23c18b0c Add basic demo example
Kinda works, but buggy

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-09-25 16:47:39 +08:00
Sebastien Bourdeauducq a7c4c079a4 add link to UCLA AMO 2023-09-11 21:02:44 +08:00
10 changed files with 1160 additions and 74 deletions

View File

@ -146,6 +146,12 @@ 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>

239
package-lock.json generated
View File

@ -12,14 +12,17 @@
"@babel/core": "^7.22.8",
"@babel/preset-env": "^7.22.7",
"@babel/preset-react": "^7.22.5",
"@uidotdev/usehooks": "^2.1.1",
"axios": "^1.4.0",
"babel-loader": "^9.1.3",
"babel-preset-minify": "^0.5.2",
"bootstrap": "^5.3.0",
"jquery": "^3.7.0",
"json-logic-js": "^2.0.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-bootstrap": "^2.8.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.0",
"webpack": "^5.88.1",
@ -1922,12 +1925,77 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"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==",
"dev": true,
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
}
},
"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==",
"dev": true,
"dependencies": {
"dequal": "^2.0.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz",
"integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.21.0",
"@popperjs/core": "^2.11.6",
"@react-aria/ssr": "^3.5.0",
"@restart/hooks": "^0.4.9",
"@types/warning": "^3.0.0",
"dequal": "^2.0.3",
"dom-helpers": "^5.2.0",
"uncontrollable": "^8.0.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/@restart/ui/node_modules/uncontrollable": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
"dev": true,
"peerDependencies": {
"react": ">=16.14.0"
}
},
"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==",
"dev": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/eslint": {
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.0.tgz",
@ -2005,12 +2073,40 @@
"redux": "^4.0.0"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"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==",
"dev": true
},
"node_modules/@uidotdev/usehooks": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.1.1.tgz",
"integrity": "sha512-sVJ1jXU62Edh22dREEa3mHcHnH2MUgokuzLw9guNS30ADCkdhYASNZxg2V8iB83iwY5H+jSeSxjqGaFvsRrNBQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@ -2784,6 +2880,12 @@
"node": ">=6.0"
}
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==",
"dev": true
},
"node_modules/clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@ -2929,6 +3031,25 @@
"node": ">=0.4.0"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.454",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz",
@ -3393,6 +3514,15 @@
"node": ">=10.13.0"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -3540,6 +3670,12 @@
"node": ">=4"
}
},
"node_modules/json-logic-js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/json-logic-js/-/json-logic-js-2.0.2.tgz",
"integrity": "sha512-ZBtBdMJieqQcH7IX/LaBsr5pX+Y5JIW+EhejtM3Ffg2jdN9Iwf+Ht6TbHnvAZ/YtwyuhPaCBlnvzrwVeWdvGDQ==",
"dev": true
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@ -3860,6 +3996,25 @@
"react-is": "^16.13.1"
}
},
"node_modules/prop-types-extra": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
"dev": true,
"dependencies": {
"react-is": "^16.3.2",
"warning": "^4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/prop-types-extra/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -3927,6 +4082,36 @@
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-bootstrap": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.8.0.tgz",
"integrity": "sha512-e/aNtxl0Z2ozrIaR82jr6Zz7ss9GSoaXpQaxmvtDUsTZIq/XalkduR/ZXP6vbQHz2T4syvjA+4FbtwELxxmpww==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.21.0",
"@restart/hooks": "^0.4.9",
"@restart/ui": "^1.6.3",
"@types/react-transition-group": "^4.4.5",
"classnames": "^2.3.2",
"dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.8.1",
"prop-types-extra": "^1.1.0",
"react-transition-group": "^4.4.5",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"@types/react": ">=16.14.8",
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -3946,6 +4131,12 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"dev": true
},
"node_modules/react-redux": {
"version": "7.2.9",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
@ -3971,6 +4162,22 @@
}
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -4404,6 +4611,27 @@
"node": ">=8.0"
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true
},
"node_modules/uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.6.3",
"@types/react": ">=16.9.11",
"invariant": "^2.2.4",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0"
}
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@ -4501,6 +4729,15 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dev": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@ -16,18 +16,21 @@
"@babel/core": "^7.22.8",
"@babel/preset-env": "^7.22.7",
"@babel/preset-react": "^7.22.5",
"babel-preset-minify": "^0.5.2",
"axios": "^1.4.0",
"babel-loader": "^9.1.3",
"babel-preset-minify": "^0.5.2",
"bootstrap": "^5.3.0",
"jquery": "^3.7.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.8.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"uuid": "^9.0.0",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"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"
"json-logic-js": "^2.0.2",
"@uidotdev/usehooks": "^2.1.1"
},
"babel": {
"presets": [

View File

@ -255,12 +255,35 @@ button {
.item-card-name,
.price {
> .alert-warning {
> .alert-warning, .alert-info {
background-color: inherit;
height: inherit;
width: 20px;
padding-bottom: 3px;
}
.alert-info {
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;
}
}
thead, tbody, tfoot {
@ -382,7 +405,7 @@ button {
display: flex;
flex-direction: column;
/*max-width: 96px;*/
max-width: 130px;
max-width: 132px;
justify-content: flex-start;
align-content: center;
align-items: center;
@ -432,7 +455,7 @@ button {
height: 24px;
}
> .alert-warning {
> .alert-warning, .alert-info {
background-color: inherit;
height: inherit;
width: 20px;
@ -508,6 +531,60 @@ 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 {
right: -38.5px; // (card width (63) - overlay width (140)) / 2
}
.overlay-bigcard {
right: -7px; // (card width (126) - overlay width (140)) / 2
}
.hovered {

View File

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

After

Width:  |  Height:  |  Size: 689 B

View File

@ -0,0 +1 @@
<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.88,2.15l-1.2.4a13.84,13.84,0,0,1-6.41.64,11.87,11.87,0,0,0-6.68.9A7.23,7.23,0,0,0,3.3,9.5a8.65,8.65,0,0,0,1.47,6.6c-.06.21-.12.42-.17.63A22.6,22.6,0,0,0,4,22H6a30.69,30.69,0,0,1,.59-4.32,9.25,9.25,0,0,0,4.52,1.11,11,11,0,0,0,4.28-.87C23,14.67,22,3.86,22,3.41ZM14.61,16.08C12,17.19,8.88,17,7.13,15.63a13.79,13.79,0,0,1,1.21-2.84A10.17,10.17,0,0,1,9.73,11a9,9,0,0,1,1.81-1.42A12,12,0,0,1,16,8V7a11.43,11.43,0,0,0-5.26,1.08,10.28,10.28,0,0,0-4.12,3.65,15.07,15.07,0,0,0-1,1.87,7,7,0,0,1-.38-3.73,5.24,5.24,0,0,1,3.14-4,8.93,8.93,0,0,1,3.82-.84c.62,0,1.23.06,1.87.11a16.2,16.2,0,0,0,6-.35C20,7.55,19.5,14,14.61,16.08Z" fill="#3a887c" stroke="#4baf84"/></svg>

After

Width:  |  Height:  |  Size: 765 B

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ import { createRoot } from "react-dom/client";
import PropTypes from "prop-types";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { v4 as uuidv4 } from 'uuid';
import { OptionsDialogPopup, OptionsSummaryPopup, FilterOptions, FillExtData } from "./shop_components.jsx";
const data = window.shop_data;
const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat());
@ -254,6 +254,7 @@ class Layout extends React.PureComponent {
conf_obj.items = conf_obj.items.map(function (item) {
return {
pn: item.pn,
options: item.options ? item.options : null,
};
});
@ -534,10 +535,12 @@ class ProductCartItem extends React.PureComponent {
index: PropTypes.number.isRequired,
model: PropTypes.object.isRequired,
data: PropTypes.object,
ext_data: PropTypes.object,
onToggleProgress: PropTypes.func,
onToggleWarning: PropTypes.func,
onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func,
onCardUpdate: PropTypes.func,
shouldTooltipWarningClassInverted: PropTypes.bool,
};
}
@ -619,10 +622,12 @@ class ProductCartItem extends React.PureComponent {
model,
data,
index,
ext_data,
shouldTooltipWarningClassInverted,
onCardUpdate,
} = this.props;
let warning;
let warning, options, options_data;
if (data && data.warnings) {
const warningsKeys = Object.keys(data.warnings);
if (warningsKeys && warningsKeys.length > 0) {
@ -631,6 +636,15 @@ class ProductCartItem extends React.PureComponent {
}
}
if (data && data.options) {
options = data.options;
if (!data.options_data) data.options_data = {};
options_data = data.options_data;
options_data.ext_data = ext_data;
}
let render_progress;
if (model.showProgress && data) {
switch(model.type) {
@ -699,9 +713,28 @@ class ProductCartItem extends React.PureComponent {
onMouseEnter={this.handleOnMouseEnterWarningItem.bind(this, index, warning)}
onMouseLeave={this.handleOnMouseLeaveWarningItem.bind(this, index, warning)}>
{warning && (
<img className="alert-warning" src={warning ? `/images${warning.icon}` : null} />
)}
{warning ? (
<img className="alert-warning" src={`/images${warning.icon}`} />
) : (options ? (<OptionsDialogPopup
options={options}
data={options_data}
key={"popover" + index}
id={"popover" + index}
big={data.size === "big"}
target={{
construct: ((outvar, value) => {
// console.log("construct", outvar, value, options_data);
options_data[outvar] = value;
}),
update: ((outvar, value) => {
// console.log("update", outvar, value, options_data);
if (outvar in options_data) options_data[outvar] = value;
onCardUpdate();
})
}}
/>) : null) }
{warning && model.showWarning && (
<div className={`k-popup-warning ${shouldTooltipWarningClassInverted ? 'inverted': ''}`}>
@ -825,6 +858,7 @@ class Cart extends React.PureComponent {
onToggleWarning: PropTypes.func,
onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func,
onCardUpdate: PropTypes.func,
};
}
@ -838,6 +872,7 @@ class Cart extends React.PureComponent {
onToggleWarning,
onToggleOverlayRemove,
onClickRemoveItem,
onCardUpdate,
} = this.props;
const nbrOccupied = nbrOccupiedSlotsInCrate(data.items);
@ -846,21 +881,26 @@ class Cart extends React.PureComponent {
const products = data.items.map((item, index) => {
let itemData;
let ext_data = FillExtData(data.itemsData, index);
if (data.itemsData && index in data.itemsData) {
itemData = data.itemsData[index];
}
return (
<ProductCartItem
isMobile={isMobile}
hovered={item.id === itemHovered}
key={item.id}
id={item.id}
index={index}
data={itemData}
ext_data={ext_data}
shouldTooltipWarningClassInverted={shouldTooltipWarningClassInverted && index > 10}
onToggleProgress={onToggleProgress}
onToggleWarning={onToggleWarning}
onToggleOverlayRemove={onToggleOverlayRemove}
onClickRemoveItem={onClickRemoveItem}
onCardUpdate={onCardUpdate}
model={item}>
</ProductCartItem>
);
@ -1268,7 +1308,8 @@ class OrderForm extends React.PureComponent {
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
style={{'cursor': 'pointer', 'fontWeight': '700'}}
value="Show JSON"
onClick={onClickShow} />
onClick={onClickShow}
readOnly={true} />
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
</div>
@ -1398,16 +1439,17 @@ class OrderSumary extends React.PureComponent {
<tbody>
{summary.map((item, index) => {
let alert;
let warning;
let alert, warning, options, options_data;
if (itemsData[index]) {
if (itemsData[index] && itemsData[index].warnings) {
alert = itemsData[index];
const warningsKeys = Object.keys(alert.warnings);
if (warningsKeys && warningsKeys.length > 0) {
warning = alert.warnings[warningsKeys[0]];
}
}
options = itemsData[index] && itemsData[index].options;
options_data = itemsData[index] && itemsData[index].options_data;
return (
<tr key={item.id}
@ -1429,15 +1471,17 @@ class OrderSumary extends React.PureComponent {
</button>
</div>
{warning && (
{warning ? (
<img
style={{'marginLeft': '10px'}}
className="alert-warning"
src={`/images/${warning.icon}`}
/>
) : ( (options && options_data) ?
( <OptionsSummaryPopup id={item.id + "options"} options={options} data={options_data} /> ) : null
)}
{!warning && (
{(!warning && !options) && (
<span style={{
'display': 'inline-block',
'width': '30px',
@ -1628,6 +1672,7 @@ class Shop extends React.PureComponent {
this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);
this.handleCardsUpdated = this.handleCardsUpdated.bind(this);
this.timer = null;
}
@ -1662,9 +1707,7 @@ class Shop extends React.PureComponent {
(prevState.columns.cart.items !== this.state.columns.cart.items) ||
(prevState.currentMode !== this.state.currentMode)
) {
this.checkAlerts(
prevState.columns.cart.items,
this.state.columns.cart.items);
this.checkAlerts(this.state.columns.cart.items);
}
if (this.state.newCardJustAdded) {
@ -1680,6 +1723,10 @@ class Shop extends React.PureComponent {
clearTimeout(this.timer);
}
handleCardsUpdated() {
this.checkAlerts(this.state.columns.cart.items);
}
handleCrateModeChange(mode) {
this.setState({
currentMode: mode,
@ -1687,8 +1734,11 @@ class Shop extends React.PureComponent {
}
handleDeleteItem(index) {
const cloned = Array.from(this.state.columns.cart.items);
let cloned = Array.from(this.state.columns.cart.items);
let cloned_data = Array.from(this.state.columns.cart.itemsData);
cloned.splice(index, 1);
cloned_data.splice(index, 1);
this.setState({
...this.state,
columns: {
@ -1696,6 +1746,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: cloned,
itemsData: cloned_data,
},
},
});
@ -1709,6 +1760,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: [],
itemsData: []
},
},
});
@ -1833,10 +1885,13 @@ class Shop extends React.PureComponent {
type: this.state.currentMode,
};
const clonedCart = Array.from(this.state.columns.cart.items);
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
for (const i in clonedCart) {
const item = clonedCart[i];
const item_data = clonedCartData[i];
crate.items.push({
'pn': item.name_number,
'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null,
});
}
@ -1844,7 +1899,7 @@ class Shop extends React.PureComponent {
isProcessing: false,
shouldShowRFQFeedback: true,
RFQBodyType: 'show',
RFQBodyOrder: JSON.stringify(crate),
RFQBodyOrder: JSON.stringify(crate, null, 2),
});
}
@ -1860,9 +1915,9 @@ class Shop extends React.PureComponent {
if (!customconf) {return; }
const items = this.props.data.items;
const self = this;
let new_items = [];
let new_items_data = [];
this.setState({
@ -1883,14 +1938,15 @@ class Shop extends React.PureComponent {
...items[key],
}, {
id: uuidv4(),
options_data: item.options ? item.options : null,
}));
new_items_data.push({options_data: item.options ? item.options : null});
}
});
return item;
});
this.setState({
...this.state,
columns: {
@ -1898,6 +1954,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: new_items,
itemsData: new_items_data,
},
},
currentMode: customconf.type,
@ -1911,10 +1968,13 @@ class Shop extends React.PureComponent {
type: this.state.currentMode,
};
const clonedCart = Array.from(this.state.columns.cart.items);
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
for (const i in clonedCart) {
const item = clonedCart[i];
const item_data = clonedCartData[i];
crate.items.push({
'pn': item.name_number,
'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null,
});
}
@ -1961,6 +2021,10 @@ class Shop extends React.PureComponent {
this.state.columns[source.droppableId].items,
source.index,
),
itemsData: remove(
this.state.columns[source.droppableId].itemsData,
source.index,
)
},
},
});
@ -2006,6 +2070,11 @@ class Shop extends React.PureComponent {
source.index,
destination.index,
),
itemsData: reorder(
this.state.columns[destination.droppableId].itemsData,
source.index,
destination.index,
),
},
},
});
@ -2029,7 +2098,7 @@ class Shop extends React.PureComponent {
});
}
checkAlerts(prevItems, newItems) {
checkAlerts(newItems) {
console.log('--- START CHECKING CRATE WARNING ---');
const {
@ -2042,6 +2111,14 @@ class Shop extends React.PureComponent {
const itemsData = [];
const rules = {};
itemsCloned.forEach((elem, idx) => {
if (!(idx in itemsData)) itemsData[idx] = elem;
if (idx in this.state.columns.cart.itemsData && this.state.columns.cart.itemsData[idx].options_data) {
itemsCloned[idx].options_data = this.state.columns.cart.itemsData[idx].options_data;
}
itemsData[idx].warnings = {};
});
// check number of slot in crate
const nbrOccupied = nbrOccupiedSlotsInCrate(newItems);
@ -2091,14 +2168,31 @@ class Shop extends React.PureComponent {
}
}
const process_slots = (item) => {
if (!item.options_data
|| item.options_data.ext_pwr === false
|| item.options_data.mono_eem === false
)
return item.slotOccupied;
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.slotOccupied;
}
nbUsedSlot = slots
.filter(item => item.type !== 'idc-bnc')
.reduce((prev, next) => {
return prev + next.slotOccupied;
return prev + process_slots(next);
}, 0);
nbrCurrentClock = slots
.reduce((prev, next) => {
return next.type === 'clocker' ? prev + next.clockOccupied : prev;
return next.type === 'clocker' ? prev + ((next.options_data && next.options_data.ext_clk === true) ? 0 : next.clockOccupied) : prev;
}, 0);
if (idx in itemsData) {
@ -2174,11 +2268,11 @@ class Shop extends React.PureComponent {
}
nbrCurrentClock = slots.reduce((prev, next) => {
return prev + next.clockOccupied;
return prev + ((next.options_data && next.options_data.ext_clk && next.options_data.ext_clk.checked) ? 0 : next.clockOccupied);
}, 0);
if (idx in itemsData) {
if (itemsData[idx].nbrCurrentClock) {
if (itemsData[idx].nbrCurrentClock && itemsData[idx].type !== "clocker") {
itemsData[idx].nbrCurrentClock += nbrCurrentClock;
} else {
itemsData[idx].nbrCurrentClock = nbrCurrentClock;
@ -2196,15 +2290,6 @@ class Shop extends React.PureComponent {
}
// check for number of recommended EEM connectors
['novo', 'urukul', 'koster'].map(_type => {
if (itemsCloned.find(elem => elem.type === _type)) {
rules[this.state.items[_type].rules.connectors.type] = {...this.state.items[_type].rules.connectors};
}
return _type;
});
if (itemsCloned.find(elem => elem.type === 'urukul')) {
if (this.state.items['urukul'].rules.info) {
rules[this.state.items['urukul'].rules.info.type] = {...this.state.items['urukul'].rules.info};
@ -2315,7 +2400,6 @@ class Shop extends React.PureComponent {
}
}
// update state with rules
this.setState({
...this.state,
@ -2386,7 +2470,7 @@ class Shop extends React.PureComponent {
isMobile={isMobile}
title="Order hardware"
description="
Drag and drop the cards you want into the crate below to see how the combination would look like. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-****.hk. The price is estimated and must be confirmed by a quote."
Drag and drop the cards you want into the crate below to see how the combination would look like. Setup card's configuration by tapping at the top of the card. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-****.hk. The price is estimated and must be confirmed by a quote."
crateMode={
<CrateMode
items={crateModeItems}
@ -2404,7 +2488,8 @@ class Shop extends React.PureComponent {
onToggleProgress={this.handleToggleItemProgress}
onToggleWarning={this.handleToggleItemWarning}
onToggleOverlayRemove={this.handleToggleOverlayRemove}
onClickRemoveItem={this.handleDeleteItem}>
onClickRemoveItem={this.handleDeleteItem}
onCardUpdate={this.handleCardsUpdated}>
</Cart>
}
rules={Object.values(rules).filter(rule => rule)}>

View File

@ -0,0 +1,451 @@
'use strict';
import React, {Component} from "react";
import jsonLogic from 'json-logic-js';
import {useState, useEffect} from 'react';
import {useClickAway} from "@uidotdev/usehooks";
import {OverlayTrigger, Tooltip} from "react-bootstrap";
// https://stackoverflow.com/a/70511311
const true_type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
function Tip({id, tip}) {
return (
<OverlayTrigger
placement="auto"
trigger={['click', 'hover', 'focus']}
style={{display: 'inline'}}
overlay={<Tooltip id={id}>{tip}</Tooltip>}
>
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
</OverlayTrigger>
);
}
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);
}
render() {
let key = this.props.id + this.props.outvar;
return (
<div className="shop-radio" key={this.props.id}>
<div style={{"display": "inline"}}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}
</div>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
{this.props.variants.map((variant, _) => (
<div className="form-check" key={key + variant}>
<input
className="form-check-input"
type="radio"
name={key}
id={key + variant}
checked={this.state.variant === variant}
onClick={() => this.handleClick(variant)}
onChange={() => this.handleClick(variant)}
/>
<label className="form-check-label" htmlFor={key + variant}>
{variant}
</label>
</div>
))}
</div>
);
}
}
function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip}) {
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
fallback={fallback}
id={id} data={data}/>;
}
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);
}
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>
);
}
}
function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
id={id} data={data}/>;
}
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 : "")
};
// Bind the event handler to this
this.handleClick = this.handleClick.bind(this);
this.props.target.construct(this.props.outvar, this.state.text);
}
handleClick(element) {
let text = element.target.value;
this.setState({
text: text
});
this.props.target.update(this.props.outvar, text);
}
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" id={key} onChange={this.handleClick}
value={this.state.text}/>
</div>
);
}
}
function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
id={id} data={data}/>;
}
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)
};
// 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
}
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);
}
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" id={key + "line"} onChange={this.handleText}
value={this.state.text} disabled={!this.state.checked}/>
</div>
);
}
}
function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
id={id} data={data}/>;
}
function UnimplementedComponent(type, id) {
//console.error("Missing component with type:", type)
return <div key={type + id} style={{background: "red"}}>UNIMPLEMENTED</div>
}
const componentsList = {
"Radio": RadioWrapper,
"Switch": SwitchWrapper,
"Line": LineWrapper,
"SwitchLine": SwitchLineWrapper,
"Default": UnimplementedComponent,
};
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" key={id + "group"}>
{ProcessOptions({
options: jsonLogic.apply(options.items, data),
data: data,
target: target,
id: id
})}
</div>);
} else {
return componentsList["Default"](options.type, id + "missing");
}
} else {
return ProcessOptions({options: jsonLogic.apply(options, data), data: data, target: target, id: id});
}
}
}
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(jsonLogic.apply(options.items, data), data))
}
} else {
Object.assign(target, FilterOptions(jsonLogic.apply(options, data), data))
}
}
return target
}
export function OptionsDialogPopup({options, data, target, id, big}) {
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")
const handleClick = (event) => {
setShow(!show);
};
return (
<div ref={ref}>
<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>
);
}
export function OptionsSummaryPopup({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" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
style={{'marginLeft': '10px'}}
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>
);
}
export function FillExtData(data, index) {
return {
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name.endsWith("-TTL")).length > 0,
has_dds: data.filter(((value, _) => value.name === "DDS" && (!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
}
}

View File

@ -64,6 +64,29 @@ const shop_data = {
nbrCurrentClock: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["standalone", "master", "satellite"]}},
{
"if": [
{
"in": [
{"var": "variant"}, [
"master", "standalone"
]
]
},
[
{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: "Switch", args: {title: "Email device_db", outvar: "email_ddb", icon: "/shop/icon-leaf.svg", tip: "Send the device_db.py file by email instead of USB-drive"}},
],
[
{type: "Switch", args: {title: "Optic fiber", outvar: "optics", tip: "Use optic fiber instead of direct attach copper cable"}}
]
]
}
],
rules: {
maxSlot: {
type: 'kasli-max-slot',
@ -125,6 +148,29 @@ const shop_data = {
nbrCurrentClock: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["standalone", "master", "satellite"]}},
{
"if": [
{
"in": [
{"var": "variant"}, [
"master", "standalone"
]
]
},
[
{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: "Switch", args: {title: "Email device_db", outvar: "email_ddb", icon: "/shop/icon-leaf.svg", tip: "Send the device_db.py file by email instead of USB-drive"}},
],
[
{type: "Switch", args: {title: "Optic fiber", outvar: "optics", tip: "Use optic fiber instead of direct attach copper cable"}}
]
]
}
],
rules: {
maxSlot: {
type: 'kaslisoc-max-slot',
@ -240,6 +286,44 @@ const shop_data = {
nbrClockMax: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{
"if": [
{"var": "ext_data.has_other_dio"},
[
{type: "Switch", args: {title: "Output first group", fallback: true, outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first group", outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
]},
null
]
},
{type: "Switch", args: {title: "Output second group", outvar: "out_second_group", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
]},
null
]
}
],
rules: {
resources: {
type: 'bnc-dio',
@ -261,6 +345,44 @@ const shop_data = {
],
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet',
options: [
{
"if": [
{"var": "ext_data.has_other_dio"},
[
{type: "Switch", args: {title: "Output first group", fallback: true, outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first group", outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
]},
null
]
},
{type: "Switch", args: {title: "Output second group", outvar: "out_second_group", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
]},
null
]
}
],
size: 'small',
type: null,
hp: 4,
@ -294,6 +416,70 @@ const shop_data = {
],
datasheet_file: '/docs/sinara-datasheets/2238.pdf',
datasheet_name: '2238 MCX-TTL datasheet',
options: [
{
"if": [
{"var": "ext_data.has_other_dio"},
[
{type: "Switch", args: {title: "Output first group", fallback: true, outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first group", outvar: "out_first_group", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
]},
null
]
},
{type: "Switch", args: {title: "Output second group", outvar: "out_second_group", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
]},
null
]
},
{type: "Switch", args: {title: "Output third group", outvar: "out_third_group", fallback: true, tip: "Switch connectors 8-11 to output"}},
{
"if": [
{"!": {"var": "out_third_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #8", outvar: "term_8", tip: "Enable termination on channel #8"}},
{type: "Switch", args: {title: "Termination #9", outvar: "term_9", tip: "Enable termination on channel #9"}},
{type: "Switch", args: {title: "Termination #10", outvar: "term_10", tip: "Enable termination on channel #10"}},
{type: "Switch", args: {title: "Termination #11", outvar: "term_11", tip: "Enable termination on channel #11"}},
]},
null
]
},
{type: "Switch", args: {title: "Output fourth group", outvar: "out_fourth_group", fallback: true, tip: "Switch connectors 12-15 to output"}},
{
"if": [
{"!": {"var": "out_fourth_group"}},
{type: "Group", items: [
{type: "Switch", args: {title: "Termination #12", outvar: "term_12", tip: "Enable termination on channel #12"}},
{type: "Switch", args: {title: "Termination #13", outvar: "term_13", tip: "Enable termination on channel #13"}},
{type: "Switch", args: {title: "Termination #14", outvar: "term_14", tip: "Enable termination on channel #14"}},
{type: "Switch", args: {title: "Termination #15", outvar: "term_15", tip: "Enable termination on channel #15"}},
]},
null
]
}
],
size: 'small',
type: null,
hp: 4,
@ -364,6 +550,37 @@ const shop_data = {
],
datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf',
datasheet_name: '4410/4412 Urukul datasheet',
options: [
{type: "Switch", args: {title: "AD9912", outvar: "ad9912", tip: "AD9912 chip instead of AD9910"}},
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
{
"if": [
{"or": [{"var": "ad9912"}, {"var": "mono_eem"}]},
[
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
],
[
{type: "Switch", args: {title: "Synchronization", outvar: "sync", tip: "Synchronize phases across Urukuls"}},
{
"if": [
{"var": "sync"},
null,
[
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
{
"if": [
{"var": "ext_data.has_sampler"},
{type: "Switch", args: {title: "SUServo mode", outvar: "suservo"}},
null
]
}
]
]
}
]
]
}
],
size: 'small',
type: 'urukul',
hp: 4,
@ -373,18 +590,6 @@ const shop_data = {
slotOccupied: 2,
clockOccupied: 1,
rules: {
connectors: {
type: 'urukul',
icon: '/shop/icon-reminder.svg',
name: 'Urukul',
message: 'This configuration uses 2 EEM connectors as it is recommended. If you prefer to use 1 EEM connector, please inform us by leaving an additional note.',
},
info: {
type: 'urukul-info',
icon: '/shop/icon-reminder.svg',
name: 'Urukul',
message: 'The default chip is AD9910, which supports more features. If you need the higher frequency resolution of the AD9912, leave us a note.',
},
resources: {
type: 'urukul',
icon: '/shop/icon-warning.svg',
@ -409,13 +614,16 @@ const shop_data = {
'Internal MMCX clock from Kasli/Clocker and external SMA.',
'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}}}
],
size: 'small',
type: 'urukul',
hp: 4,
nbrSlotMin: 0,
nbrSlotMax: 0,
nbrClockMax: 0,
slotOccupied: 2,
slotOccupied: 1,
clockOccupied: 1,
rules: {
resources: {
@ -662,6 +870,16 @@ const shop_data = {
'Full-scale input ranges between +-10mV and +-10V.',
'Supports SU-Servo laser intensity stabilization servo in conjunction with Urukul.'
],
options: [
{type: "Switch", args: {title: "1 EEM mode", outvar: "mono_eem"}},
{
"if": [
{"and": [{"var": "ext_data.has_dds"}, {"!": {"var": "mono_eem"}}]},
{type: "Switch", args: {title: "SUServo mode", outvar: "suservo"}},
null
]
}
],
size: 'big',
type: 'novo',
hp: 8,
@ -671,12 +889,6 @@ const shop_data = {
slotOccupied: 2,
clockOccupied: 0,
rules: {
connectors: {
type: 'novo',
icon: '/shop/icon-reminder.svg',
name: 'Sampler',
message: 'This configuration uses 2 EEM connectors as it is recommended. If you prefer to use 1 EEM connector, please inform us by leaving an additional note.',
},
resources: {
type: 'novo',
icon: '/shop/icon-warning.svg',
@ -699,6 +911,9 @@ const shop_data = {
'Existing stack supports summing over rectangular ROIs and reporting the result to ARTIQ kernels.',
'Camera signal is entirely processed in the Kasli FPGA.',
],
options: [
{type: "Radio", args: {title: "Connectors", outvar: "n_eem", variants: ["1 EEM", "2 EEM", "3 EEM"], tip: "Number of EEM ports to use.", fallback: 1}},
],
size: 'small',
type: 'koster',
hp: 4,
@ -708,12 +923,6 @@ const shop_data = {
slotOccupied: 2,
clockOccupied: 0,
rules: {
connectors: {
type: 'koster',
icon: '/shop/icon-reminder.svg',
name: 'Grabber',
message: 'This configuration uses 2 EEM connectors. If you prefer to use 1 or 3 EEM connectors, please inform us by leaving an additional note.',
},
resources: {
type: 'koster',
icon: '/shop/icon-warning.svg',
@ -736,6 +945,10 @@ const shop_data = {
'Frequency up to 1GHz.',
'Low jitter <100fs RMS.'
],
options: [
{type: "Switch", args: {title: "Ext CLK", outvar: "ext_clk"}},
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
],
size: 'small',
type: 'clocker',
hp: 4,
@ -774,6 +987,9 @@ const shop_data = {
'100Base-T Ethernet.',
'Can be controlled by Kasli or work stand-alone with PoE supply.'
],
options: [
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
],
size: 'small',
type: null,
hp: 4,
@ -798,6 +1014,9 @@ const shop_data = {
'Lower jitter and phase noise.',
'Large frequency changes take several milliseconds.',
],
options: [
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
],
size: 'small',
type: null,
hp: 4,
@ -827,6 +1046,9 @@ const shop_data = {
'Additional 4 channels up to 12 GHz.',
'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}}}
],
size: 'big',
type: null,
hp: 8,
@ -880,6 +1102,9 @@ const shop_data = {
'2-channel Pound Drever Hall (PDH) lock generator.',
'AD9959 DDS (500MSPS, 10-bit).'
],
options: [
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
],
size: 'big',
type: null,
hp: 8,