Compare commits

...

15 Commits

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

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

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

156
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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