Compare commits

...

38 Commits

Author SHA1 Message Date
Egor Savkin d3a5ee8a3d Add edge counter for the options to TTL
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 7e7905e522 Add HD68 cable options and fix first small cards options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 67a6e772f3 Leave opt-out option only to the first kasli
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 893ca829e1 Change ttl groups to banks and remove green leaf
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 9da249aa95 Slightly promote new functionality and consider mono_eem modes on sampler/urukul for suservo
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 44bed4a4e1 Update shop bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 0126a663ea Fix cards state being not updated on touchables
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 2fee470e11 Fix not-disappearing warnings and clocker clock slots
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 4c584efdf3 Small duplication removal
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 631e1dd6b1 Remove redundant notifications about connectors and AD9910
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 248fd9782e Integrate clock/slots configuration into the calculator
Also adjust sizes

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin eddf408f26 Fixed initial display of empty options in the options summary
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 9cdf563d26 Still fixing the bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 4838f50b60 Fix scroll issue and found another bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 3d8f82fcd6 Add groups of options and some fixes for long lists of options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 65f896f517 Add external data for use per every card. Apply it for TTL and Suservo
Signed-off-by: esavkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin ba054ee2a2 Fix tooltip showed out when options overlay was closed
Signed-off-by: esavkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin c95ce76b00 Use tips on more cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin c57a27f762 Add tooltips with hints for the user
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 55492c5090 Add icons to options titles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin a6994416d0 Remove MAC from options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin a92ec4289c Reposition summary popup relative to icon respective to window width, hide on scroll
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin fa406b7d49 Hide popups on clicking outside of them
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 8483d101d6 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-10-11 11:03:19 +08:00
Egor Savkin 45c5b388aa Add popover for cart summary with options data
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin e5c3c0bada Make overlay appear on button click
And fix options absense for non-carrier cards

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin ef64c3ce6c Make the design more compact
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin adac15f803 Adjust styles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 019a6f384b Fix add of useless options to JSON
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 02f8e916a7 Add switchline compact design
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 577fc6520c Add switch and line
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 0e556c270a Fix JSON shop load
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin a6f131e781 Make it update state
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin 2eb2c37178 Add basic demo example
Kinda works, but buggy

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:03:19 +08:00
Egor Savkin da74bb1d8d Add Shuttler to the shop (#98)
Closes #95

Reviewed-on: #98
Co-authored-by: Egor Savkin <es@m-labs.hk>
Co-committed-by: Egor Savkin <es@m-labs.hk>
2023-10-11 11:01:26 +08:00
Sebastien Bourdeauducq 320665c3bd cleanup 2023-10-10 17:53:25 +08:00
Sebastien Bourdeauducq a6faaecb8b clarify external PSU 2023-10-10 17:51:18 +08:00
Egor Savkin 393fc8e3c9 Add EEM power module to the shop
Co-authored-by: Egor Savkin <es@m-labs.hk>
Co-committed-by: Egor Savkin <es@m-labs.hk>
2023-10-10 17:34:52 +08:00
10 changed files with 1264 additions and 85 deletions

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,65 @@ 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;
}
&.hd68-idc, &.stabilizer, &.clocker {
width: 85px;
left: -11px; // (card width (63) - overlay width (85)) / 2
}
}
}
.overlay-smallcard {
left: -38.5px; // (card width (63) - overlay width (140)) / 2
}
.overlay-bigcard {
left: -7px; // (card width (126) - overlay width (140)) / 2
}
.hovered {

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

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

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());
@ -86,10 +86,12 @@ const copy = (
) => {
const destClone = Array.from(destination.items);
destClone.splice(droppableDestination.index, 0, {
...model[draggableSource],
id: uuidv4(),
});
destClone.splice(droppableDestination.index, 0, ...draggableSource.map((dragged_item, _) => {
return {
...model[dragged_item],
id: uuidv4(),
}
}));
return destClone;
};
@ -254,6 +256,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 +537,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 +624,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 +638,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 +715,29 @@ 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}
options_class={model.options_class}
key={"popover" + index}
id={"popover" + index}
big={model.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 +861,7 @@ class Cart extends React.PureComponent {
onToggleWarning: PropTypes.func,
onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func,
onCardUpdate: PropTypes.func,
};
}
@ -838,6 +875,7 @@ class Cart extends React.PureComponent {
onToggleWarning,
onToggleOverlayRemove,
onClickRemoveItem,
onCardUpdate,
} = this.props;
const nbrOccupied = nbrOccupiedSlotsInCrate(data.items);
@ -846,21 +884,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 +1311,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 +1442,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 +1474,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,15 +1675,18 @@ 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;
}
componentDidMount() {
// index 0 is a Kasli, we place it as a default conf on the crate.
const source = {
droppableId: 'backlog',
index: 0,
indexes: [
itemsUnfoldedList.findIndex(element => element === "eem_pwr_mod"),
itemsUnfoldedList.findIndex(element => element === "kasli")
],
};
const destination = {
droppableId: 'cart',
@ -1662,9 +1712,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 +1728,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 +1739,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 +1751,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: cloned,
itemsData: cloned_data,
},
},
});
@ -1709,6 +1765,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: [],
itemsData: []
},
},
});
@ -1833,10 +1890,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 +1904,7 @@ class Shop extends React.PureComponent {
isProcessing: false,
shouldShowRFQFeedback: true,
RFQBodyType: 'show',
RFQBodyOrder: JSON.stringify(crate),
RFQBodyOrder: JSON.stringify(crate, null, 2),
});
}
@ -1860,9 +1920,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 +1943,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 +1959,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: new_items,
itemsData: new_items_data,
},
},
currentMode: customconf.type,
@ -1911,10 +1973,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,
});
}
@ -1945,7 +2010,14 @@ class Shop extends React.PureComponent {
source,
destination,
} = result;
let dragged_item = itemsUnfoldedList[source.index];
let dragged_items = [];
if (source.indexes) {
source.indexes.forEach((card_index, _) => {
dragged_items.push(itemsUnfoldedList[card_index]);
})
} else if (source.index) {
dragged_items.push(itemsUnfoldedList[source.index]);
}
if (!destination) {
@ -1961,6 +2033,10 @@ class Shop extends React.PureComponent {
this.state.columns[source.droppableId].items,
source.index,
),
itemsData: remove(
this.state.columns[source.droppableId].itemsData,
source.index,
)
},
},
});
@ -1984,7 +2060,7 @@ class Shop extends React.PureComponent {
this.state.items,
this.state.columns[source.droppableId],
this.state.columns[destination.droppableId],
dragged_item,
dragged_items,
destination,
),
},
@ -2006,6 +2082,11 @@ class Shop extends React.PureComponent {
source.index,
destination.index,
),
itemsData: reorder(
this.state.columns[destination.droppableId].itemsData,
source.index,
destination.index,
),
},
},
});
@ -2029,7 +2110,7 @@ class Shop extends React.PureComponent {
});
}
checkAlerts(prevItems, newItems) {
checkAlerts(newItems) {
console.log('--- START CHECKING CRATE WARNING ---');
const {
@ -2042,6 +2123,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 +2180,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 +2280,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 +2302,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 +2412,6 @@ class Shop extends React.PureComponent {
}
}
// update state with rules
this.setState({
...this.state,
@ -2386,7 +2482,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 +2500,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,452 @@
'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, options_class}) {
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 ") + (options_class || "");
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_previous_carrier: data.filter((value, item_index) => index > item_index && value.name === "Carrier").length > 0,
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name &&value.name.endsWith("-TTL")).length > 0,
has_dds: data.filter(((value, _) => value.name === "DDS" && (!value.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,32 @@ 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."}},
{"if": [
{"!": {"var": "ext_data.has_previous_carrier"}},
{type: "Switch", args: {title: "Opt out from promotional USB-stick", outvar: "usb_opt_out", tip: "Do not ship a promotional USB-stick"}}
]}
],
[
{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 +151,32 @@ 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."}},
{"if": [
{"!": {"var": "ext_data.has_previous_carrier"}},
{type: "Switch", args: {title: "Opt out from promotional USB-stick", outvar: "usb_opt_out", tip: "Do not ship a promotional USB-stick"}}
]}
],
[
{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 +292,50 @@ const shop_data = {
nbrClockMax: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{
"if": [
{"var": "ext_data.has_other_dio"},
[
{type: "Switch", args: {title: "Output first bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_bank"}},
{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 bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_bank"}},
{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
]
},
{
"if": [
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}]}},
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
]
}
],
rules: {
resources: {
type: 'bnc-dio',
@ -261,6 +357,50 @@ 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 bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_bank"}},
{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 bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_bank"}},
{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
]
},
{
"if": [
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}]}},
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
]
}
],
size: 'small',
type: null,
hp: 4,
@ -294,6 +434,72 @@ 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 bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
],
[
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
]
]
},
{
"if": [
{"!": {"var": "out_first_bank"}},
{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"}},
]}
]
},
{type: "Switch", args: {title: "Output second bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
{
"if": [
{"!": {"var": "out_second_bank"}},
{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"}},
]}
]
},
{type: "Switch", args: {title: "Output third bank", outvar: "out_third_bank", fallback: true, tip: "Switch connectors 8-11 to output"}},
{
"if": [
{"!": {"var": "out_third_bank"}},
{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"}},
]}
]
},
{type: "Switch", args: {title: "Output fourth bank", outvar: "out_fourth_bank", fallback: true, tip: "Switch connectors 12-15 to output"}},
{
"if": [
{"!": {"var": "out_fourth_bank"}},
{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"}},
]}
]
},
{
"if": [
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}, {"var": "out_third_bank"}, {"var": "out_fourth_bank"}]}},
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
]
}
],
size: 'small',
type: null,
hp: 4,
@ -364,6 +570,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 +610,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 +634,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: {
@ -619,6 +847,10 @@ const shop_data = {
size: 'small',
type: 'hd68',
hp: 4,
options: [
{type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
],
options_class: "hd68-idc",
nbrSlotMin: 1,
nbrSlotMax: 4,
nbrCurrentSlot: 0,
@ -662,6 +894,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 +913,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 +935,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 +947,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 +969,11 @@ 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"}}
],
options_class: "clocker",
size: 'small',
type: 'clocker',
hp: 4,
@ -774,6 +1012,10 @@ 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"}}
],
options_class: "stabilizer",
size: 'small',
type: null,
hp: 4,
@ -798,6 +1040,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 +1072,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,
@ -868,6 +1116,30 @@ const shop_data = {
slotOccupied: 1,
clockOccupied: 0
},
'shuttler': {
id: 'shuttler',
name: 'DAC',
name_number: '5716',
name_codename: 'Shuttler',
price: 8500,
image: '/shop/graphic-03_Shuttler.svg',
specs: [
'16-ch, 125 MSPS DAC in FMC form factor, with remote analog front end board.',
'High dc resolution (~18 bits) for trap electrode bias.',
'~15 MHz analog bandwidth.',
'AD9117 DACs (14 bits @ 125 MSPS, <1 LSB DNL).',
'Differential analog output (+/-5 V differential) via COTS connector with shielded impedance-matched pairs (mini-SAS HD).',
'Included remote analog front-end (AFE) board converts differential signals on mini-SAS HD cables to single-ended, with additional gain/filtering as desired.',
],
size: 'big',
type: null,
hp: 8,
nbrSlotMin: 0,
nbrSlotMax: 0,
nbrClockMax: 0,
slotOccupied: 0,
clockOccupied: 0,
},
'pounder': {
id: 'pounder',
name: 'PDH lock generator',
@ -880,6 +1152,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,
@ -888,7 +1163,34 @@ const shop_data = {
nbrClockMax: 0,
slotOccupied: 1,
clockOccupied: 1,
}
},
'eem_pwr_mod': {
id: 'eem_pwr_mod',
name: 'EEM AC Power Module',
name_number: '1106',
name_codename: '',
price: 750,
image: '/shop/graphic-03_eem_pwr_mod.svg',
specs: [
"EEM AC power module.",
"400W with forced cooling (25CFM), 200W with free air convection.",
"Universal input.",
"IEC inlet on front panel.",
"EMC filter.",
"LED current indicator.",
"6 rear side outputs.",
"Mains circuit protected with steel cover.",
"Optional - external power brick will be shipped free of charge if removed."
],
size: 'big',
type: null,
hp: 8,
nbrSlotMin: 0,
nbrSlotMax: 0,
nbrClockMax: 0,
slotOccupied: 0,
clockOccupied: 0,
},
},
columns: {
@ -923,7 +1225,8 @@ const shop_data = {
itemIds: [
'zotino',
'fastino',
'novo']},
'novo',
'shuttler']},
{ name: 'Adapters',
itemIds: [
'idc-bnc-adapter',
@ -936,7 +1239,9 @@ const shop_data = {
'koster',
'stabilizer',
'pounder',
'thermostat-eem']}
'thermostat-eem',
'eem_pwr_mod',
]}
],
},