docs(ui): add base for ui component showcase in docs (#4414)

This commit is contained in:
Alireza 2024-10-15 09:53:26 -04:00 committed by GitHub
parent 9125cec333
commit 465df5c573
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 5160 additions and 4740 deletions

View File

@ -22,7 +22,6 @@ const cssToJavaScript = require('./rules/cssToJavaScript.js');
// const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// ~~ ENV VARS
const NODE_ENV = process.env.NODE_ENV;
const QUICK_BUILD = process.env.QUICK_BUILD;
@ -101,14 +100,18 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
module: {
noParse: [/(codec)/, /(dicomicc)/],
rules: [
...(isProdBuild ? [] : [{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: ['react-refresh/babel'],
},
}]),
...(isProdBuild
? []
: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: ['react-refresh/babel'],
},
},
]),
{
test: /\.svg?$/,
oneOf: [
@ -123,11 +126,11 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
removeViewBox: false,
},
},
},
]
],
},
prettier: false,
svgo: true,

View File

@ -68,7 +68,7 @@
},
"optionalDependencies": {
"@percy/cypress": "^3.1.1",
"@playwright/test": "^1.44.0",
"@playwright/test": "^1.48.0",
"cypress": "13.7.2",
"cypress-file-upload": "^3.5.3"
},

View File

@ -69,21 +69,21 @@ API, this would be `['Project', 'Location', 'Data set', 'DICOM store']`.
Besides the configurable item labels themselves, several other string look ups
are used base on EACH of the labels returned by this method.
For instance, for the label `{itemLabel}``, the following strings are fetched for
For instance, for the label `{itemLabel}`, the following strings are fetched for
translation...
1. `No {itemLabel} available`
1. No `{itemLabel}` available
- used to indicate no such items are available
- for example, for Google, `No Project available` would be 'No projects available'
2. `Select {itemLabel}`
- for example, for Google, No Project available would be 'No projects available'
2. Select `{itemLabel}`
- used to direct selection of the item
- for example, for Google, `Select Project` would be 'Select a project'
3. `Error fetching {itemLabel} list`
- for example, for Google, Select Project would be 'Select a project'
3. Error fetching `{itemLabel}` list
- used to indicate an error occurred fetching the list of items
- usually accompanied by the error itself
- for example, for Google, `Error fetching Project list` would be 'Error fetching projects'
4. `Search {itemLabel} list`
- for example, for Google, Error fetching Project list would be 'Error fetching projects'
4. Search `{itemLabel}` list
- used as the placeholder text for filtering a list of items
- for example, for Google, `Search Project list` would be 'Search projects'
- for example, for Google, Search Project list would be 'Search projects'
#### `initialize`

View File

@ -36,7 +36,7 @@ Once the Docker image has been built, it can be run as a container from the comm
|Flag|Description|
|----|-----------|
|-d|Run the container in the background and print the container ID|
|-p {host-port}:{nginx-port}/tcp|Publish the `nginx` listen port on the given host port|
|-p `{host-port}:{nginx-port}/tcp`|Publish the `nginx` listen port on the given host port|
|--name|An arbitrary name for the container.|
@ -58,7 +58,7 @@ Simply replace `latest` at the end of the command with any of the tags for a spe
### Configuring the `nginx` Listen Port
The Dockerfile and entry point use the `${PORT}` environment variable as the port that the `nginx` server uses to serve the web server. The default value for `${PORT}` is `80`. One way to set this environment variable is to use the `-e` switch when running the container with `docker run`. The block below gives an example where the listen port is set to `8080` and published on the host as `3000`.
The Dockerfile and entry point use the `{PORT}` environment variable as the port that the `nginx` server uses to serve the web server. The default value for `{PORT}` is `80`. One way to set this environment variable is to use the `-e` switch when running the container with `docker run`. The block below gives an example where the listen port is set to `8080` and published on the host as `3000`.
```sh
docker run -d -e PORT=8080 -p 3000:8080/tcp --name ohif-viewer-container ohif-viewer-image
@ -83,13 +83,13 @@ docker run -d -p 3000:80/tcp -v /path/to/config/file.js:/usr/share/nginx/html/ap
:::
#### Environment Variable
In certain scenarios, such as deploying the Docker container to Google Cloud, it might be convenient to specify the configuration file (contents) as an environment variable. That environment variable is `${APP_CONFIG}` and it can be set in the `docker run` command using the `-e` switch.
In certain scenarios, such as deploying the Docker container to Google Cloud, it might be convenient to specify the configuration file (contents) as an environment variable. That environment variable is `{APP_CONFIG}` and it can be set in the `docker run` command using the `-e` switch.
:::tip
It is important to stress here that the environment variable is the contents of the configuration file and NOT the path to the config file as is [typically specified](https://docs.ohif.org/configuration/configurationFiles#configuration-files) for development and build environments or for the [volume mounting method](#volume-mounting).
:::
Below the `cat` command is used to convert the configuration file to a string and its result set as the `${APP_CONFIG}` environment variable.
Below the `cat` command is used to convert the configuration file to a string and its result set as the `{APP_CONFIG}` environment variable.
```sh
docker run -d -p 3000:80/tcp -e APP_CONFIG="$(cat /path/to/the/config/file)" --name ohif-viewer-container ohif-viewer-image
@ -100,7 +100,7 @@ To be safe, remove single line comments (i.e. `//`) from the configuration file
:::
:::tip
As an alternative to the `cat` command, convert the configuration file to a single line and copy and paste it as the value to the `${APP_CONFIG}` environment variable on the `docker run` line. Editors such as [Visual Studio Code](https://stackoverflow.com/questions/46491061/shortcut-for-joining-two-lines) and [Notepad++](https://superuser.com/questions/518229/how-do-i-remove-linebreaks-in-notepad) have 'Join Lines' commands to facilitate this.
As an alternative to the `cat` command, convert the configuration file to a single line and copy and paste it as the value to the `{APP_CONFIG}` environment variable on the `docker run` line. Editors such as [Visual Studio Code](https://stackoverflow.com/questions/46491061/shortcut-for-joining-two-lines) and [Notepad++](https://superuser.com/questions/518229/how-do-i-remove-linebreaks-in-notepad) have 'Join Lines' commands to facilitate this.
:::
:::tip
@ -139,7 +139,7 @@ Consideration must be given as to whether OHIF should be deployed in a secure co
### Specifying the SSL Port, Certificate and Private Key
For convenience, the [built Docker image](#building-the-docker-image) can be run over SSL by
- setting the `${SSL_PORT}` environment variable
- setting the `{SSL_PORT}` environment variable
- volume mounting the SSL certificate
- volume mounting the SSL private key
@ -147,7 +147,7 @@ For convenience, the [built Docker image](#building-the-docker-image) can be run
The volume mounted SSL certificate and private key are mapped to the [`ssl_certificate`](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) and [`ssl_certificate_key`](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) `nginx` directives respectively.
:::
Similar to the [`nginx` listen port](#configuring-the-nginx-listen-port), the `${SSL_PORT}` environment variable is the internal port that `nginx` listens on to serve the OHIF web server over SSL and has to be likewise published via the `-p` switch.
Similar to the [`nginx` listen port](#configuring-the-nginx-listen-port), the `{SSL_PORT}` environment variable is the internal port that `nginx` listens on to serve the OHIF web server over SSL and has to be likewise published via the `-p` switch.
The following is an example command running the Docker container over SSL. Note that depending on the version of Docker, an absolute path to the certificate and private key files might be required.
@ -164,7 +164,7 @@ The private key is a secure entity and should have restricted access. Keep it sa
:::
:::caution
The presence of the `${SSL_PORT}` environment variable is used to trigger to deploy over SSL as opposed to HTTP. If `${SSL_PORT}` is NOT defined, then HTTP is used even if the certificate and private key volumes are mounted.
The presence of the `{SSL_PORT}` environment variable is used to trigger to deploy over SSL as opposed to HTTP. If `{SSL_PORT}` is NOT defined, then HTTP is used even if the certificate and private key volumes are mounted.
:::
:::tip

View File

@ -67,14 +67,14 @@ You can check out the studies that we have put in this [Dropbox link](https://ww
<!--
Links
-->
[general]: ../faq.md#general
[technical]: ../faq.md#technical
[report-bug]: ../faq.md#how-do-i-report-a-bug
[new-feature]: ../faq.md#how-can-i-request-a-new-feature
[commercial-support]: ../faq.md#does-ohif-offer-commercial-support
[academic]: ../faq.md#who-should-i-contact-about-academic-collaborations
[fda-clearance]: ../faq.md#does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission
[hipaa]: ../faq.md#is-the-ohif-viewer-hipaa-compliant
[general]: general
[technical]: technical
[report-bug]: how-do-i-report-a-bug
[new-feature]: how-can-i-request-a-new-feature
[commercial-support]: does-ohif-offer-commercial-support
[academic]: who-should-i-contact-about-academic-collaborations
[fda-clearance]: does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission
[hipaa]: is-the-ohif-viewer-hipaa-compliant
[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/
[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en
[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act

View File

@ -3,17 +3,6 @@ id: index
---
- [General FAQ](../faq.md#general-faq)
- [How do I report a bug?](../faq.md#how-do-i-report-a-bug)
- [How can I request a new feature?](../faq.md#how-can-i-request-a-new-feature)
- [Who should I contact about Academic Collaborations?](../faq.md#who-should-i-contact-about-academic-collaborations)
- [Does OHIF offer support?](../faq.md#does-ohif-offer-support)
- [Does The OHIF Viewer have 510(k) Clearance from the U.S. F.D.A or CE Marking from the European Commission?](../faq.md#does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission)
- [Is there a DICOM Conformance Statement for the OHIF Viewer?](../faq.md#is-there-a-dicom-conformance-statement-for-the-ohif-viewer)
- [Is The OHIF Viewer HIPAA Compliant?](../faq.md#is-the-ohif-viewer-hipaa-compliant)
- [Could you provide me with a particular study from the OHIF Viewer Demo?](../faq.md#could-you-provide-me-with-a-particular-study-from-the-ohif-viewer-demo)
# General FAQ
@ -77,14 +66,14 @@ You can check out the studies that we have put in this [Dropbox link](https://ww
<!--
Links
-->
[general]: ../faq.md#general
[technical]: ../faq.md#technical
[report-bug]: ../faq.md#how-do-i-report-a-bug
[new-feature]: ../faq.md#how-can-i-request-a-new-feature
[commercial-support]: ../faq.md#does-ohif-offer-commercial-support
[academic]: ../faq.md#who-should-i-contact-about-academic-collaborations
[fda-clearance]: ../faq.md#does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission
[hipaa]: ../faq.md#is-the-ohif-viewer-hipaa-compliant
[general]: #general
[technical]: #technicalß˚
[report-bug]: #how-do-i-report-a-bug
[new-feature]: #how-can-i-request-a-new-feature
[commercial-support]: #does-ohif-offer-commercial-support
[academic]: #who-should-i-contact-about-academic-collaborations
[fda-clearance]: #does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission
[hipaa]: #is-the-ohif-viewer-hipaa-compliant
[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/
[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en
[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act

View File

@ -209,8 +209,8 @@ Remember that there is no strict rule for deciding between modes and extensions.
In OHIF v2 a study was loaded and mounted on `/viewer/:studyInstanceUID` route. In OHIF v3
we have reworked the route registration to enable more sophisticated routing. Now, Modes are tied to specific routes in the viewer, and multiple modes/routes can be present within a single application, making "routes" configuration the most important part of mode configuration.
- Routes with a dataSourceName: ${mode.id}/${dataSourceName}
- Routes without a dataSourceName: ${mode.id} which uses the default dataSourceName
- Routes with a dataSourceName: `{mode.id}/{dataSourceName}`
- Routes without a dataSourceName: `{mode.id}` which uses the default dataSourceName
This makes a mode flexible enough to be able to connect to multiple datasources
without rebuild of the app for use cases such as reading from one PACS and

View File

@ -249,22 +249,22 @@ A list of criteria for the protocol along with the provided points for ranking.
- `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be
[`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`]
- | Rule | Single Value | Array Value | Example |
|--- |--- |--- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| equals | === | All members are === in same order | value = ['abc', 'def', 'GHI']<br/>testValue = 'abc' (Fail)<br/><br/> = ['abc'] (Fail)<br/><br/> = ['abc', 'def', 'GHI'] (Valid)<br/><br/> = ['abc', 'GHI', 'def'] (Fail)<br/><br/> = ['abc', 'def'] (Fail)<br/><br/>value = 'Attenuation Corrected'<br/>testValue = 'Attenuation Corrected' (Valid)<br/> = 'Attenuation' (Fail)<br/><br/> value = ['Attenuation Corrected'] <br/> testValue = ['Attenuation Corrected'] (Valid)<br/> = 'Attenuation Corrected' (Valid) <br/> = 'Attenuation' (Fail) <br/> |
| doesNotEqual | !== | Any member is !== for the array, either in value, order, or length | value = ['abc', 'def', 'GHI']<br/>testValue = 'abc' (Valid)<br/> = ['abc'] (Valid)<br/> = ['abc', 'def', 'GHI'] (Fail)<br/> = ['abc', 'GHI', 'def'] (Valid)<br/> = ['abc', 'def'] (Valid)<br/><br/>value = 'Attenuation Corrected'<br/>testValue = 'Attenuation Corrected' (Fail)Valid<br/><br/> = 'Attenuation' (Valid) <br/><br/> value = ['Attenuation Corrected']<br/> testValue = ['Attenuation Corrected'] (Fail) <br/> = 'Attenuation Corrected' (Fail) <br/> = 'Attenuation' (Fail) |
| includes | Not allowed | Value is equal to one of the values of the array | value = ['abc', 'def', 'GHI']<br/>testValue = ['abc'] (Valid)<br/><br/> = abc (Fail)<br/><br/>= [abc] (Fail)<br/><br/> = dog (Fail)<br/><br/> = = [att, abc] (Valid)<br/><br/> = ['abc', 'def', 'dog'] (Valid)<br/><br/> = ['cat', 'dog'] (Fail)<br/><br/><br/> value = 'Attenuation Corrected' <br/> testValue = ['Attenuation Corrected', 'Corrected'] (Valid)<br/>= ['Attenuation', 'Corrected'] (Fail)<br/><br/><br/>value = ['Attenuation Corrected']<br/>testValue = 'Attenuation Corrected' (Fail)<br/> = ['Attenuation Corrected', 'Corrected'] (Valid)<br/> = ['Attenuation', 'Corrected'] (Fail) |
| doesNotInclude | Not allowed | Value is not in one of the values of the array | value = ['abc', 'def', 'GHI']<br/>testValue = Corr (Valid)<br/><br/> = abc (Fail)<br/><br/> = [att, cor] (Valid)<br/><br/> = ['abc', 'def', 'dog'] (Fail)<br/><br/><br/>value = 'Attenuation Corrected' <br/> testValue = ['Attenuation Corrected', 'Corrected'] (Fail) <br/> = ['Attenuation', 'Corrected'] (Valid) <br/><br/><br/>value = ['Attenuation Corrected'] <br/> testValue = 'Attenuation' (Fail)<br/> = ['Attenuation Corrected', 'Corrected'] (Fail) <br/>= ['Attenuation', 'Corrected'] (Valid) |
| containsI | String containment (case insensitive) | String containment (case insensitive) is OK for one of the rule values | value = 'Attenuation Corrected'<br/>testValue = Corr (Valid)<br/> = corr (Valid)<br/><br/> = [att, cor] (Valid)<br/><br/> = [Att, Wall] (Valid)<br/><br/> = [cat, dog] (Fail)<br/><br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'def' (Valid)<br/><br/> = 'dog' (Fail)<br/><br/> = ['gh', 'de'] (Valid)<br/><br/> = ['cat', 'dog'] (Fail)<br/> |
| contains | String containment (case sensitive) | String containment (case sensitive) is OK for one of the rule values | value = 'Attenuation Corrected'<br/>testValue = Corr (Valid)<br/> = corr (Fail)<br/> = [att, cor] (Fail)<br/> = [Att, Wall] (Valid)<br/><br/> = [cat, dog] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/><br/>testValue = 'def' (Valid)<br/> = 'dog' (Fail)<br/><br/> = ['cat', 'de'] (Valid)<br/><br/> = ['cat', 'dog'] (Fail) |
| doesNotContain | String containment is false | String containment is false for all values of the array | value = 'Attenuation Corrected'<br/>testValue = Corr (Fail)<br/><br/><br/> = corr (Valid)<br/><br/> = [att, cor] (Valid)<br/><br/> = [Att, Wall] (Fail)<br/><br/> = [cat, dog] (Valid)<br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'def' (Fail)<br/><br/><br/> = 'dog' (Valid)<br/><br/> = ['cat', 'de'] (Fail)<br/><br/> = ['cat', 'dog'] (Valid) |
| doesNotContainI | String containment is false (case insensitive) | String containment (case insensitive) is false for all values of the array | value = 'Attenuation Corrected'<br/>testValue = Corr (Fail)<br/><br/> = corr (Fail)<br/><br/> = [att, cor] (Fail)<br/><br/> = [Att, Wall] (Fail)<br/><br/> = [cat, dog] (Valid)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'DEF' (Fail)<br/><br/> = 'dog' (Valid)<br/><br/> = ['cat', 'gh'] (Fail)<br/><br/> = ['cat', 'dog'] (Valid) |
| startsWith | Value begins with characters | Starts with one of the values of the array | value = 'Attenuation Corrected'<br/>testValue = Corr (Fail)<br/><br/> = Att (Fail)<br/><br/> = ['cat', 'dog', 'Att'] (Valid)<br/><br/> = [cat, dog] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'deg' (Valid)<br/><br/> = ['cat', 'GH'] (Valid)<br/><br/> = ['cat', 'gh'] (Fail)<br/><br/> = ['cat', 'dog'] (Fail) |
| endsWith | Value ends with characters | ends with one of the value of the array | value = 'Attenuation Corrected'<br/>testValue = TED (Fail)<br/><br/> = ted (Valid)<br/><br/> = ['cat', 'dog', 'ted'] (Valid)<br/><br/> = [cat, dog] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'deg' (Valid)<br/><br/> = ['cat', 'HI'] (Valid)<br/><br/> = ['cat', 'hi'] (Fail)<br/><br/> = ['cat', 'dog'] (Fail) |
| greaterThan | value is => to rule | Not applicable | value = 30<br/><br/>testValue = 20 (Valid)<br/> = 40 (Fail)<br/><br/> |
| lessThan | value is <= to rule | Not applicable | value = 30<br/><br/>testValue = 40 (Valid)<br/> = 20 (Fail)<br/> |
| range | Not applicable | 2 value requested (min and max) | value = 50<br/><br/>testValue = [10,60] (Valid)<br/> = [60, 10] (Valid)<br/><br/> = [0, 10] (Fail)<br/><br/> = [70, 80] (Fail)<br/><br/> = 45 (Fail)<br/><br/> = [45] (Fail) |
| notNull | Not Applicable | Not Applicable | No value |
| Rule | Single Value | Array Value | Example |
|------|--------------|-------------|---------|
| equals | &equals;&equals;&equals; | All members are &equals;&equals;&equals; in same order | value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'abc' (Fail)<br/>&equals; ['abc'] (Fail)<br/>&equals; ['abc', 'def', 'GHI'] (Valid)<br/>&equals; ['abc', 'GHI', 'def'] (Fail)<br/>&equals; ['abc', 'def'] (Fail)<br/><br/>value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Attenuation Corrected' (Valid)<br/>&equals; 'Attenuation' (Fail)<br/><br/>value &equals; ['Attenuation Corrected']<br/>testValue &equals; ['Attenuation Corrected'] (Valid)<br/>&equals; 'Attenuation Corrected' (Valid)<br/>&equals; 'Attenuation' (Fail) |
| doesNotEqual | !&equals;&equals; | Any member is !&equals;&equals; for the array, either in value, order, or length | value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'abc' (Valid)<br/>&equals; ['abc'] (Valid)<br/>&equals; ['abc', 'def', 'GHI'] (Fail)<br/>&equals; ['abc', 'GHI', 'def'] (Valid)<br/>&equals; ['abc', 'def'] (Valid)<br/><br/>value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Attenuation Corrected' (Fail)<br/>&equals; 'Attenuation' (Valid)<br/><br/>value &equals; ['Attenuation Corrected']<br/>testValue &equals; ['Attenuation Corrected'] (Fail)<br/>&equals; 'Attenuation Corrected' (Fail)<br/>&equals; 'Attenuation' (Fail) |
| includes | Not allowed | Value is equal to one of the values of the array | value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; ['abc'] (Valid)<br/>&equals; 'abc' (Fail)<br/>&equals; ['abc'] (Fail)<br/>&equals; 'dog' (Fail)<br/>&equals; ['att', 'abc'] (Valid)<br/>&equals; ['abc', 'def', 'dog'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail)<br/><br/>value &equals; 'Attenuation Corrected'<br/>testValue &equals; ['Attenuation Corrected', 'Corrected'] (Valid)<br/>&equals; ['Attenuation', 'Corrected'] (Fail)<br/><br/>value &equals; ['Attenuation Corrected']<br/>testValue &equals; 'Attenuation Corrected' (Fail)<br/>&equals; ['Attenuation Corrected', 'Corrected'] (Valid)<br/>&equals; ['Attenuation', 'Corrected'] (Fail) |
| doesNotInclude | Not allowed | Value is not in one of the values of the array | value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'Corr' (Valid)<br/>&equals; 'abc' (Fail)<br/>&equals; ['att', 'cor'] (Valid)<br/>&equals; ['abc', 'def', 'dog'] (Fail)<br/><br/>value &equals; 'Attenuation Corrected'<br/>testValue &equals; ['Attenuation Corrected', 'Corrected'] (Fail)<br/>&equals; ['Attenuation', 'Corrected'] (Valid)<br/><br/>value &equals; ['Attenuation Corrected']<br/>testValue &equals; 'Attenuation' (Fail)<br/>&equals; ['Attenuation Corrected', 'Corrected'] (Fail)<br/>&equals; ['Attenuation', 'Corrected'] (Valid) |
| containsI | String containment (case insensitive) | String containment (case insensitive) is OK for one of the rule values | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Corr' (Valid)<br/>&equals; 'corr' (Valid)<br/>&equals; ['att', 'cor'] (Valid)<br/>&equals; ['Att', 'Wall'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'def' (Valid)<br/>&equals; 'dog' (Fail)<br/>&equals; ['gh', 'de'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail) |
| contains | String containment (case sensitive) | String containment (case sensitive) is OK for one of the rule values | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Corr' (Valid)<br/>&equals; 'corr' (Fail)<br/>&equals; ['att', 'cor'] (Fail)<br/>&equals; ['Att', 'Wall'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'def' (Valid)<br/>&equals; 'dog' (Fail)<br/>&equals; ['cat', 'de'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail) |
| doesNotContain | String containment is false | String containment is false for all values of the array | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Corr' (Fail)<br/>&equals; 'corr' (Valid)<br/>&equals; ['att', 'cor'] (Valid)<br/>&equals; ['Att', 'Wall'] (Fail)<br/>&equals; ['cat', 'dog'] (Valid)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'def' (Fail)<br/>&equals; 'dog' (Valid)<br/>&equals; ['cat', 'de'] (Fail)<br/>&equals; ['cat', 'dog'] (Valid) |
| doesNotContainI | String containment is false (case insensitive) | String containment (case insensitive) is false for all values of the array | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Corr' (Fail)<br/>&equals; 'corr' (Fail)<br/>&equals; ['att', 'cor'] (Fail)<br/>&equals; ['Att', 'Wall'] (Fail)<br/>&equals; ['cat', 'dog'] (Valid)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'DEF' (Fail)<br/>&equals; 'dog' (Valid)<br/>&equals; ['cat', 'gh'] (Fail)<br/>&equals; ['cat', 'dog'] (Valid) |
| startsWith | Value begins with characters | Starts with one of the values of the array | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'Corr' (Fail)<br/>&equals; 'Att' (Fail)<br/>&equals; ['cat', 'dog', 'Att'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'deg' (Valid)<br/>&equals; ['cat', 'GH'] (Valid)<br/>&equals; ['cat', 'gh'] (Fail)<br/>&equals; ['cat', 'dog'] (Fail) |
| endsWith | Value ends with characters | ends with one of the value of the array | value &equals; 'Attenuation Corrected'<br/>testValue &equals; 'TED' (Fail)<br/>&equals; 'ted' (Valid)<br/>&equals; ['cat', 'dog', 'ted'] (Valid)<br/>&equals; ['cat', 'dog'] (Fail)<br/><br/>value &equals; ['abc', 'def', 'GHI']<br/>testValue &equals; 'deg' (Valid)<br/>&equals; ['cat', 'HI'] (Valid)<br/>&equals; ['cat', 'hi'] (Fail)<br/>&equals; ['cat', 'dog'] (Fail) |
| greaterThan | value is &gt;&equals; to rule | Not applicable | value &equals; 30<br/>testValue &equals; 20 (Valid)<br/>&equals; 40 (Fail) |
| lessThan | value is &lt;&equals; to rule | Not applicable | value &equals; 30<br/>testValue &equals; 40 (Valid)<br/>&equals; 20 (Fail) |
| range | Not applicable | 2 value requested (min and max) | value &equals; 50<br/>testValue &equals; [10,60] (Valid)<br/>&equals; [60, 10] (Valid)<br/>&equals; [0, 10] (Fail)<br/>&equals; [70, 80] (Fail)<br/>&equals; 45 (Fail)<br/>&equals; [45] (Fail) |
| notNull | Not Applicable | Not Applicable | No value | |
A sample of the matching rule is above which matches against the study description to be PETCT
```js

View File

@ -11,39 +11,7 @@ const path = require('path');
const fs = require('fs');
const versions = fs.readFileSync('../../version.txt', 'utf8').split('\n');
// This probably only makes sense for the beta phase, temporary
// function getNextBetaVersionName() {
// const expectedPrefix = '';
// const lastReleasedVersion = versions[0];
// if (!lastReleasedVersion.includes(expectedPrefix)) {
// throw new Error(
// 'this code is only meant to be used during the 2.0 beta phase.'
// );
// }
// const version = parseInt(lastReleasedVersion.replace(expectedPrefix, ''), 10);
// return `${expectedPrefix}${version + 1}`;
// }
// const allDocHomesPaths = [
// '/docs/',
// '/docs/next/',
// ...versions.slice(1).map(version => `/docs/${version}/`),
// ];
const isDev = process.env.NODE_ENV === 'development';
const isDeployPreview = process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview';
const baseUrl = process.env.BASE_URL || '/';
const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap';
// Special deployment for staging locales until they get enough translations
// https://app.netlify.com/sites/docusaurus-i18n-staging
// https://docusaurus-i18n-staging.netlify.app/
const isI18nStaging = process.env.I18N_STAGING === 'true';
// const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
@ -56,22 +24,11 @@ module.exports = {
url: 'https://docs.ohif.org',
i18n: {
defaultLocale: 'en',
locales: isDeployPreview
? // Deploy preview: keep it fast!
['en']
: isI18nStaging
? // Staging locales: https://docusaurus-i18n-staging.netlify.app/
['en']
: // Production locales
['en'],
locales: ['en'],
},
onBrokenLinks: 'warn',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
// customFields: {
// description:
// 'An optimized site generator in React. Docusaurus helps you to move fast and write content. Build documentation websites, blogs, marketing pages, and more.',
// },
themes: ['@docusaurus/theme-live-codeblock'],
plugins: [
() => ({
@ -87,7 +44,8 @@ module.exports = {
};
},
}),
path.resolve(__dirname, './pluginOHIFWebpackConfig.js'),
// path.resolve(__dirname, './pluginOHIFWebpackConfig.js'),
// /path.resolve(__dirname, './postcss.js'),
'docusaurus-plugin-image-zoom', // 3rd party plugin for image click to pop
[
'@docusaurus/plugin-client-redirects',
@ -103,34 +61,6 @@ module.exports = {
],
},
],
// createRedirects: function(path) {
// // redirect to /docs from /docs/introduction,
// // as introduction has been made the home doc
// // if (allDocHomesPaths.includes(path)) {
// // return [`${path}/introduction`];
// // }
// if (path.includes("/connecting-to-image-archives/google-cloud-healthcare")) {
// return ["/deployment/recipes/google-cloud-healthcare"]
// }
// },
// redirects: [
// {
// from: ['/'],
// to: '/docs',
// },
// {
// from: ['/docs/support', '/docs/next/support'],
// to: '/community/support',
// },
// {
// from: ['/docs/team', '/docs/next/team'],
// to: '/community/team',
// },
// {
// from: ['/docs/resources', '/docs/next/resources'],
// to: '/community/resources',
// },
// ],
},
],
[
@ -142,73 +72,11 @@ module.exports = {
steps: 2, // the max number of images generated between min and max (inclusive)
},
],
// [
// '@docusaurus/plugin-pwa',
// {
// debug: isDeployPreview,
// offlineModeActivationStrategies: [
// 'appInstalled',
// 'standalone',
// 'queryString',
// ],
// // swRegister: false,
// // swCustom: path.resolve(__dirname, 'src/sw.js'),
// pwaHead: [
// {
// tagName: 'link',
// rel: 'icon',
// href: 'img/docusaurus.png',
// },
// {
// tagName: 'link',
// rel: 'manifest',
// href: `${baseUrl}manifest.json`,
// },
// {
// tagName: 'meta',
// name: 'theme-color',
// content: 'rgb(37, 194, 160)',
// },
// {
// tagName: 'meta',
// name: 'apple-mobile-web-app-capable',
// content: 'yes',
// },
// {
// tagName: 'meta',
// name: 'apple-mobile-web-app-status-bar-style',
// content: '#000',
// },
// {
// tagName: 'link',
// rel: 'apple-touch-icon',
// href: 'img/docusaurus.png',
// },
// {
// tagName: 'link',
// rel: 'mask-icon',
// href: 'img/docusaurus.svg',
// color: 'rgb(62, 204, 94)',
// },
// {
// tagName: 'meta',
// name: 'msapplication-TileImage',
// content: 'img/docusaurus.png',
// },
// {
// tagName: 'meta',
// name: 'msapplication-TileColor',
// content: '#000',
// },
// ],
// },
// ]
],
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
{
debug: true, // force debug plugin usage
docs: {
routeBasePath: '/',
@ -247,7 +115,7 @@ module.exports = {
trackingID: 'G-DDBJFE34EG',
anonymizeIP: true,
},
}),
},
],
],
themeConfig:
@ -273,24 +141,14 @@ module.exports = {
'🎉 OHIF 3.8 has landed! Explore 4D and volume rendering, enhanced layout menus, streamlined visualization controls, workflow steps, and more. You can find the release notes by following this <a target="_blank" rel="noopener noreferrer" href="https://ohif.org/release-notes/3p8/">Link!</a> 🌟',
},
prism: {
theme: require('prism-react-renderer/themes/github'),
darkTheme: require('prism-react-renderer/themes/dracula'),
theme: require('prism-react-renderer').themes.github,
darkTheme: require('prism-react-renderer').themes.dracula,
},
algolia: {
appId: 'EFLT6YIHHZ',
apiKey: 'c220dd24fe4f86248eea3b1238a1fb60',
indexName: 'ohif',
},
// zoom: {
// selector: '.markdown > img',
// background: {
// light: 'rgb(255, 255, 255)',
// dark: 'rgb(50, 50, 50)',
// },
// config: {
// // options you can specify via https://github.com/francoischalifour/medium-zoom#usage
// },
// },
navbar: {
hideOnScroll: false,
logo: {
@ -319,8 +177,8 @@ module.exports = {
position: 'left',
},
{
to: '/migration-guide',
label: 'Migration Guides',
to: '/playground',
label: 'UI Playground',
position: 'left',
className: 'new-badge',
},

View File

@ -18,7 +18,7 @@
"clean": "shx rm -rf dist",
"clean:deep": "yarn run clean && shx rm -rf node_modules",
"start": "docusaurus start --port 8001",
"dev": "docusaurus start --port 8001",
"dev": "docusaurus clear && docusaurus start --port 8001",
"docs": "docusaurus start --port 8001",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
@ -28,28 +28,6 @@
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"optionalDependencies": {
"@docusaurus/core": "2.4.3",
"@docusaurus/plugin-client-redirects": "2.4.3",
"@docusaurus/plugin-google-gtag": "2.4.3",
"@docusaurus/plugin-ideal-image": "2.4.3",
"@docusaurus/plugin-pwa": "2.4.3",
"@docusaurus/preset-classic": "2.4.3",
"@docusaurus/remark-plugin-npm2yarn": "2.4.3",
"@docusaurus/theme-classic": "2.4.3",
"@docusaurus/theme-live-codeblock": "2.4.3",
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"classnames": "^2.3.2",
"clsx": "^1.1.1",
"docusaurus-plugin-image-zoom": "^1.0.1",
"file-loader": "^6.2.0",
"url-loader": "^4.1.1"
},
"peerDependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"browserslist": {
"production": [
">0.5%",
@ -62,8 +40,66 @@
"last 1 safari version"
]
},
"devDependencies": {
"dependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"postcss-import": "^14.0.2",
"postcss-preset-env": "^7.4.3"
"postcss-preset-env": "^7.4.3",
"tailwindcss": "^3.4.13",
"@docusaurus/core": "3.5.2",
"@docusaurus/plugin-client-redirects": "3.5.2",
"@docusaurus/plugin-google-gtag": "3.5.2",
"@docusaurus/plugin-ideal-image": "3.5.2",
"@docusaurus/plugin-pwa": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/remark-plugin-npm2yarn": "3.5.2",
"@docusaurus/theme-classic": "3.5.2",
"@docusaurus/theme-live-codeblock": "3.5.2",
"prism-react-renderer": "^2.1.0",
"@mdx-js/react": "3.0.1",
"@svgr/webpack": "^5.5.0",
"classnames": "^2.3.2",
"clsx": "^1.1.1",
"docusaurus-plugin-image-zoom": "^1.0.1",
"file-loader": "^6.2.0",
"url-loader": "^4.1.1",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"framer-motion": "6.2.4",
"lucide-react": "^0.379.0",
"next-themes": "^0.3.0",
"react-day-picker": "^8.10.1",
"react-shepherd": "6.1.1",
"shepherd.js": "13.0.3",
"sonner": "^1.4.41",
"tailwind-merge": "^2.3.0"
},
"peerDependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@docusaurus/tsconfig": "3.0.0",
"@types/react": "^18.2.29",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0",
"typescript": "~5.2.2"
}
}

View File

@ -21,17 +21,5 @@ module.exports = function (context, options) {
},
};
},
configurePostCss(postcssOptions) {
postcssOptions.plugins.push(
require('postcss-import')
/*require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 4,
})*/
);
return postcssOptions;
},
};
};

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -1,3 +1,241 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
/* OHIF Theme */
@layer base {
:root {
--highlight: 191 74% 63%;
--background: 236 62% 5%;
--foreground: 0 0% 98%;
--card: 236 62% 5%;
--card-foreground: 0 0% 98%;
--popover: 219 90% 15%;
--popover-foreground: 0 0% 98%;
--primary: 214 98% 60%;
--primary-foreground: 0 0% 98%;
--secondary: 214 66% 48%;
--secondary-foreground: 200 50% 84%;
--muted: 234 64% 10%;
--muted-foreground: 200 46% 65%;
--accent: 217 79% 24%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 236 45% 21%;
--ring: 214 98% 60%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 214 98% 60%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 236 45% 21%;
--ring: 214 98% 60%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
/* ORIGINAL THEME for comparison and testing
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
*/
/* Theme Copy Example
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 262.1 83.3% 57.8%;
--radius: 0.5rem;
--chart-1: ;
--chart-2: ;
--chart-3: ;
--chart-4: ;
--chart-5: ;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--primary: 263.4 70% 50.4%;
--primary-foreground: 210 20% 98%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 263.4 70% 50.4%;
--chart-1: ;
--chart-2: ;
--chart-3: ;
--chart-4: ;
--chart-5: ;
}
}
*/
h2.section-header {
@apply py-4 text-2xl font-normal text-white;
}
h3.section-header {
@apply py-3 text-xl text-white;
}
.playground-row {
@apply bg-card mb-6 flex flex-row flex-wrap rounded-md border py-10;
}
.example {
@apply flex-initial px-6;
}
.example2 {
@apply flex-initial px-4;
}
/* Additional CSS edits to components */
/* Tooltip */
.TooltipContent[data-side='bottom'] {
animation-name: slideDown;
}
/* Custom CSS to hide default number input arrows */
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
@apply appearance-none;
}
input[type='number'] {
-moz-appearance: textfield; /* For Firefox */
}
.navbar__item {
display: flex;
align-items: center;
}
.navbar__item svg {
margin-left: 5px;
}
/* stylelint-disable docusaurus/copyright-header */
/**
* Any CSS included here will be global. The classic template
@ -98,8 +336,6 @@ html[data-theme='dark'] {
/* Typography updates */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
html {
font-size: 1em;
}

View File

@ -0,0 +1,25 @@
import React from 'react';
import Layout from '@theme/Layout';
import { useHistory } from '@docusaurus/router';
export default function Patterns() {
const history = useHistory();
return (
<Layout>
<h1>Patterns</h1>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns/segmentation-panel')}
>
{'Segmentation Panel'}
</button>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns/measurement-panel')}
>
{'Measurements Panel'}
</button>
</Layout>
);
}

View File

@ -1,51 +1,14 @@
// src/_pages/patterns.tsx
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
import { Button } from '../components/Button';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuPortal,
} from '../components/DropdownMenu';
import { Icons } from '../components/Icons/Icons';
import DataRow from '../_prototypes/DataRow/DataRow';
import dataList from '../_prototypes/DataRow/dataList.json';
import actionOptionsMap from '../_prototypes/DataRow/actionOptionsMap';
import { Button } from '../../../../ui-next/src/components/Button';
import { Icons } from '../../../../ui-next/src/components/Icons';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '../components/Accordion/Accordion';
import { Slider } from '../components/Slider';
import { Switch } from '../components/Switch';
import { Label } from '../components/Label';
import { Input } from '../components/Input';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import { ChevronDownIcon } from '@radix-ui/react-icons';
} from '../../../../ui-next/src/components/Accordion';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
interface DataItem {
id: number;
@ -62,7 +25,7 @@ interface ListGroup {
items: DataItem[];
}
function Patterns() {
export default function Measurements() {
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
const handleAction = (id: string, action: string) => {
console.log(`Action "${action}" triggered for item with id: ${id}`);
@ -86,13 +49,13 @@ function Patterns() {
}
return (
<div className="my-4 flex max-w-6xl justify-end py-6">
<div className="my-4 flex max-w-6xl justify-end bg-black py-6">
{/* Simulated Panel List for "Segmentation" */}
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['measurements-list', 'measurements-additional']}
collapsible
tabIndex={0}
>
{/* Segmentation Tools */}
<AccordionItem value="measurements-list">
@ -165,7 +128,3 @@ function Patterns() {
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<Patterns />);

View File

@ -1,49 +1,27 @@
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
import { Button } from '../components/Button';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
import { Button } from '../../../../ui-next/src/components/Button';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuPortal,
} from '../components/DropdownMenu';
import { Icons } from '../components/Icons/Icons';
import DataRow from '../_prototypes/DataRow/DataRow';
import dataList from '../_prototypes/DataRow/dataList.json';
import actionOptionsMap from '../_prototypes/DataRow/actionOptionsMap';
} from '../../../../ui-next/src/components/Select';
import { Icons } from '../../../../ui-next/src/components/Icons';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '../components/Accordion/Accordion';
import { Slider } from '../components/Slider';
import { Switch } from '../components/Switch';
import { Label } from '../components/Label';
import { Input } from '../components/Input';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import { ChevronDownIcon } from '@radix-ui/react-icons';
} from '../../../../ui-next/src/components/Accordion';
import { Slider } from '../../../../ui-next/src/components/Slider';
import { Switch } from '../../../../ui-next/src/components/Switch';
import { Label } from '../../../../ui-next/src/components/Label';
import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
interface DataItem {
id: number;
@ -60,7 +38,7 @@ interface ListGroup {
items: DataItem[];
}
function Patterns() {
export default function SegmentationPanel() {
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
const handleAction = (id: string, action: string) => {
@ -82,12 +60,11 @@ function Patterns() {
}
return (
<div className="my-4 flex max-w-6xl justify-end py-6">
<div className="my-4 flex h-full w-full max-w-6xl justify-end bg-black py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list']}
collapsible
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
@ -108,45 +85,6 @@ function Patterns() {
<div className="mb-0">
{/* Header Controls */}
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Create New Segmentation</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Current Segmentation</DropdownMenuLabel>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Delete className="text-foreground" />
<span className="pl-2">Delete</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
<Select>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder="Segmentation 1" />
@ -197,7 +135,7 @@ function Patterns() {
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center ">
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
@ -244,7 +182,7 @@ function Patterns() {
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center ">
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
@ -310,7 +248,3 @@ function Patterns() {
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<Patterns />);

View File

@ -0,0 +1,27 @@
import React from 'react';
import Layout from '@theme/Layout';
import { useHistory } from '@docusaurus/router';
export default function Hello() {
const history = useHistory();
return (
<Layout
title="Hello"
description="Hello React Page"
>
<button
className="bg-slate-400"
onClick={() => history.push('/ui-playground')}
>
ui-playground
</button>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns')}
>
patterns
</button>
</Layout>
);
}

View File

@ -0,0 +1,522 @@
import React, { useState } from 'react';
import '../css/custom.css';
import { Label } from '../../../ui-next/src/components/Label';
import { Input } from '../../../ui-next/src/components/Input';
import { Separator } from '../../../ui-next/src/components/Separator';
import { Tabs, TabsList, TabsTrigger } from '../../../ui-next/src/components/Tabs';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '../../../ui-next/src/components/Select';
import { Button } from '../../../ui-next/src/components/Button';
import { Switch } from '../../../ui-next/src/components/Switch';
import { Checkbox } from '../../../ui-next/src/components/Checkbox';
import { Toggle } from '../../../ui-next/src/components/Toggle';
import { Slider } from '../../../ui-next/src/components/Slider';
import { ScrollArea } from '../../../ui-next/src/components/ScrollArea';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '../../../ui-next/src/components/DropdownMenu';
import { Icons } from '../../../ui-next/src/components/Icons';
interface ShowcaseRowProps {
title: string;
description?: string;
children: React.ReactNode;
code: string;
}
export default function ComponentShowcase() {
return (
<div className="bg-background text-foreground h-full">
<div className="mx-auto my-4 max-w-6xl py-6">
<ShowcaseRow
title="Buttons"
description="Various button styles and sizes"
code={`
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button variant="ghost" size="icon">?</Button>
<Button variant="link">Link</Button>
`}
>
<div className="flex flex-wrap gap-4">
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button
variant="ghost"
size="icon"
>
?
</Button>
<Button variant="link">Link</Button>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Small Buttons"
description="Various small button styles"
code={`
<Button variant="default" size="sm">Primary Button</Button>
<Button variant="secondary" size="sm">Secondary Button</Button>
<Button variant="ghost" size="sm">Ghost Button</Button>
<Button variant="ghost" size="icon">?</Button>
<Button variant="link" size="sm">Link</Button>
`}
>
<div className="flex flex-wrap gap-4">
<Button
variant="default"
size="sm"
>
Primary Button
</Button>
<Button
variant="secondary"
size="sm"
>
Secondary Button
</Button>
<Button
variant="ghost"
size="sm"
>
Ghost Button
</Button>
<Button
variant="ghost"
size="icon"
>
?
</Button>
<Button
variant="link"
size="sm"
>
Link
</Button>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Button large"
description="Various large button styles"
code={`
<Button variant="default" size="lg">Primary Button</Button>
<Button variant="secondary" size="lg">Secondary Button</Button>
<Button variant="ghost" size="lg">Ghost Button</Button>
<Button variant="ghost" size="icon">?</Button>
<Button variant="link" size="lg">Link</Button>
`}
>
<div className="flex flex-wrap gap-4">
<Button
variant="default"
size="lg"
>
Primary Button
</Button>
<Button
variant="secondary"
size="lg"
>
Secondary Button
</Button>
<Button
variant="ghost"
size="lg"
>
Ghost Button
</Button>
<Button
variant="ghost"
size="icon"
>
?
</Button>
<Button
variant="link"
size="lg"
>
Link
</Button>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Input"
description="Input field with label"
code={`
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
`}
>
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Color swatches"
description="Various color swatches"
code={`
<div className="bg-actions h-16 w-16 rounded"></div>
<div className="bg-infosecondary h-16 w-16 rounded"></div>
<div className="bg-highlight h-16 w-16 rounded"></div>
<div className="h-16 w-16 rounded bg-white"></div>
<div className="h-16 w-16 rounded bg-white"></div>
<div className="h-16 w-16 rounded bg-white"></div>
`}
>
<div className="flex flex-wrap gap-4">
<div className="bg-actions h-16 w-16 rounded"></div>
<div className="bg-infosecondary h-16 w-16 rounded"></div>
<div className="bg-highlight h-16 w-16 rounded"></div>
<div className="h-16 w-16 rounded bg-white"></div>
<div className="h-16 w-16 rounded bg-white"></div>
<div className="h-16 w-16 rounded bg-white"></div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Slider"
description="Adjustable slider component"
code={`
<Slider defaultValue={[50]} max={100} step={1} />
`}
>
<div className="w-full max-w-sm">
<Slider
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Typography"
description="Various text sizes"
code={`
<div className="text-base text-white">Standard text size (text-base) 14px</div>
<div className="text-sm text-white">Small text size (text-sm) 13px</div>
<div className="text-xs text-white">Extra small text size (text-xs) 12px</div>
`}
>
<div className="flex flex-col gap-2">
<div className="text-base text-white">Standard text size (text-base) 14px</div>
<div className="text-sm text-white">Small text size (text-sm) 13px</div>
<div className="text-xs text-white">Extra small text size (text-xs) 12px</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Typography large"
description="Various large text sizes"
code={`
<div className="text-lg text-white">Large text size (text-lg) 16px</div>
<div className="text-xl text-white">Extra large text size (text-xl) 18px</div>
<div className="text-2xl text-white">Double extra large text size (text-2xl) 20px</div>
`}
>
<div className="flex flex-col gap-2">
<div className="text-lg text-white">Large text size (text-lg) 16px</div>
<div className="text-xl text-white">Extra large text size (text-xl) 18px</div>
<div className="text-2xl text-white">Double extra large text size (text-2xl) 20px</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Tabs"
description="Tabs component"
code={`
<Tabs className="w-[400px]">
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
`}
>
<Tabs className="w-[400px]">
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
</ShowcaseRow>
<ShowcaseRow
title="Select"
description="Select component"
code={`
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
`}
>
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</ShowcaseRow>
<ShowcaseRow
title="Dropdown Menu"
description="Various dropdown menu examples"
code={`
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem onSelect={() => console.debug('Item 1')}>Item 1</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 2')}>Item 2</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 3')}>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
`}
>
<div className="flex flex-wrap gap-4">
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
>
<DropdownMenuItem onSelect={() => console.debug('Item 1')}>Item 1</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 2')}>Item 2</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 3')}>
Long name Item 3
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Switch"
description="Switch component"
code={`
<Switch />
`}
>
<Switch />
</ShowcaseRow>
<ShowcaseRow
title="Checkbox"
description="Checkbox with label"
code={`
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
`}
>
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Toggle"
description="Toggle component"
code={`
<Toggle>Hello</Toggle>
`}
>
<Toggle>Hello</Toggle>
</ShowcaseRow>
<ShowcaseRow
title="Slider"
description="Slider component"
code={`
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
`}
>
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Scroll Area"
description="Scroll Area component"
code={`
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</ScrollArea>
`}
>
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</ScrollArea>
</ShowcaseRow>
</div>
</div>
);
}
function ShowcaseRow({ title, description, children, code }: ShowcaseRowProps) {
const [showCode, setShowCode] = useState(false);
return (
<div className="bg-secondary/10 mb-8 rounded-lg p-6">
<div className="mb-4 flex items-start justify-between">
<div>
<h2 className="text-2xl font-bold">{title}</h2>
{description && <p className="text-muted-foreground mt-1">{description}</p>}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowCode(!showCode)}
>
{showCode ? 'Hide Code' : 'Show Code'} <Icons.Code className="ml-2 h-4 w-4" />
</Button>
</div>
<div className="showcase-content mb-4">{children}</div>
{showCode && (
<pre className="mt-4 overflow-x-auto rounded-md bg-black p-4 text-sm">
<code>{code}</code>
</pre>
)}
</div>
);
}

View File

@ -1,320 +1,31 @@
module.exports = {
prefix: '',
separator: ':',
// Don't purge any tailwind classes, usefull for debugging
// ...(process.env.NODE_ENV === 'development' && {
// safelist: [{ pattern: /.*/ }],
// }),
content: [
'./docs/**/*.jsx',
'./docs/**/*.mdx',
'./node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
'../ui-next/**/*.{ts,tsx,js, jsx}',
],
prefix: '',
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
colors: {
overlay: 'rgba(0, 0, 0, 0.8)',
transparent: 'transparent',
black: '#000',
white: '#fff',
initial: 'initial',
inherit: 'inherit',
indigo: {
dark: '#0b1a42',
},
aqua: {
pale: '#7bb2ce',
},
primary: {
light: '#5acce6',
main: '#0944b3',
dark: '#090c29',
active: '#348cfd',
},
secondary: {
light: '#3a3f99',
main: '#2b166b',
dark: '#041c4a',
active: '#1f1f27',
},
common: {
bright: '#e1e1e1',
light: '#a19fad',
main: '#fff',
dark: '#726f7e',
active: '#2c3074',
},
customgreen: {
100: '#05D97C',
200: '#0FD97C',
},
customblue: {
100: '#c4fdff',
200: '#38daff',
300: '#1D204D',
},
customgray: {
100: '#262943',
},
gray: {
100: '#f7fafc',
200: '#edf2f7',
300: '#e2e8f0',
400: '#cbd5e0',
500: '#a0aec0',
600: '#718096',
700: '#4a5568',
800: '#2d3748',
900: '#1a202c',
},
red: {
100: '#fff5f5',
200: '#fed7d7',
300: '#feb2b2',
400: '#fc8181',
500: '#f56565',
600: '#e53e3e',
700: '#c53030',
800: '#9b2c2c',
900: '#742a2a',
},
orange: {
100: '#fffaf0',
200: '#feebc8',
300: '#fbd38d',
400: '#f6ad55',
500: '#ed8936',
600: '#dd6b20',
700: '#c05621',
800: '#9c4221',
900: '#7b341e',
},
yellow: {
100: '#fffff0',
200: '#fefcbf',
300: '#faf089',
400: '#f6e05e',
500: '#ecc94b',
600: '#d69e2e',
700: '#b7791f',
800: '#975a16',
900: '#744210',
},
green: {
100: '#f0fff4',
200: '#c6f6d5',
300: '#9ae6b4',
400: '#68d391',
500: '#48bb78',
600: '#38a169',
700: '#2f855a',
800: '#276749',
900: '#22543d',
},
teal: {
100: '#e6fffa',
200: '#b2f5ea',
300: '#81e6d9',
400: '#4fd1c5',
500: '#38b2ac',
600: '#319795',
700: '#2c7a7b',
800: '#285e61',
900: '#234e52',
},
blue: {
100: '#ebf8ff',
200: '#bee3f8',
300: '#90cdf4',
400: '#63b3ed',
500: '#4299e1',
600: '#3182ce',
700: '#2b6cb0',
800: '#2c5282',
900: '#2a4365',
},
indigo: {
100: '#ebf4ff',
200: '#c3dafe',
300: '#a3bffa',
400: '#7f9cf5',
500: '#667eea',
600: '#5a67d8',
700: '#4c51bf',
800: '#434190',
900: '#3c366b',
},
purple: {
100: '#faf5ff',
200: '#e9d8fd',
300: '#d6bcfa',
400: '#b794f4',
500: '#9f7aea',
600: '#805ad5',
700: '#6b46c1',
800: '#553c9a',
900: '#44337a',
},
pink: {
100: '#fff5f7',
200: '#fed7e2',
300: '#fbb6ce',
400: '#f687b3',
500: '#ed64a6',
600: '#d53f8c',
700: '#b83280',
800: '#97266d',
900: '#702459',
},
},
spacing: {
px: '1px',
0: '0',
1: '0.15rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
10: '2.5rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
18: '4.5rem',
20: '5rem',
24: '6rem',
32: '8rem',
40: '10rem',
48: '12rem',
56: '14rem',
64: '16rem',
72: '18rem',
80: '20rem',
88: '22rem',
96: '24rem',
104: '26rem',
112: '28rem',
'250px': '250px',
},
backgroundColor: theme => theme('colors'),
backgroundPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain',
},
borderColor: theme => ({
...theme('colors'),
DEFAULT: theme('colors.gray.300', 'currentColor'),
}),
borderRadius: {
none: '0',
sm: '0.125rem',
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
full: '9999px',
},
borderWidth: {
DEFAULT: '1px',
0: '0',
2: '2px',
4: '4px',
8: '8px',
},
boxShadow: {
xs: '0 0 0 1px rgba(0, 0, 0, 0.05)',
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
none: 'none',
},
container: {},
cursor: {
auto: 'auto',
default: 'default',
pointer: 'pointer',
wait: 'wait',
text: 'text',
move: 'move',
'not-allowed': 'not-allowed',
},
fill: {
current: 'currentColor',
},
flex: {
1: '1 1 0%',
0.3: '0.3 0.3 0%',
0.5: '0.5 0.5 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none',
},
flexGrow: {
0: '0',
DEFAULT: '1',
},
flexShrink: {
0: '0',
DEFAULT: '1',
},
fontFamily: {
sans: [
'Inter',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
inter: ['Inter', 'sans-serif'],
},
fontSize: {
xs: '0.65rem',
sm: '0.75rem',
base: '0.875rem',
lg: '1rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '4rem',
xxs: '0.6875rem', // 11px
xs: '0.75rem', // 12px
sm: '0.8125rem', // 13px
base: '0.875rem', // 14px
lg: '1rem', // 16px
xl: '1.125rem', // 18px
'2xl': '1.25rem', // 20px
'3xl': '1.375rem', // 22px
'4xl': '1.5rem', // 24px
'5xl': '1.875rem', // 30px
},
fontWeight: {
hairline: '100',
@ -327,488 +38,77 @@ module.exports = {
extrabold: '800',
black: '900',
},
height: theme => ({
auto: 'auto',
...theme('spacing'),
full: '100%',
screen: '100vh',
}),
inset: theme => ({
...theme('spacing'),
0: '0',
auto: 'auto',
full: '100%',
viewport: '0.5rem',
'1/2': '50%',
'viewport-scrollbar': '1.3rem',
}),
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
3: '.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
}),
maxHeight: theme => ({
full: '100%',
screen: '100vh',
...theme('spacing'),
}),
maxWidth: (theme, { breakpoints }) => ({
none: 'none',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
full: '100%',
...breakpoints(theme('screens')),
...theme('spacing'),
}),
minHeight: theme => ({
...theme('spacing'),
0: '0',
full: '100%',
screen: '100vh',
}),
minWidth: theme => ({
...theme('spacing'),
0: '0',
xs: '2rem',
sm: '4rem',
md: '6rem',
lg: '8rem',
xl: '10rem',
full: '100%',
}),
objectPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
opacity: {
0: '0',
5: '.5',
10: '.10',
15: '.15',
20: '.20',
25: '.25',
30: '.30',
35: '.35',
40: '.40',
45: '.45',
50: '.50',
55: '.55',
60: '.60',
65: '.65',
70: '.70',
75: '.75',
80: '.80',
85: '.85',
90: '.90',
95: '.95',
100: '1',
},
order: {
first: '-9999',
last: '9999',
none: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
},
padding: theme => theme('spacing'),
placeholderColor: theme => theme('colors'),
stroke: theme => ({
...theme('colors'),
current: 'currentColor',
}),
strokeWidth: {
0: '0',
1: '1',
2: '2',
},
textColor: theme => theme('colors'),
width: theme => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
'1/24': '4.166666667%',
'2/24': '8.333333333%',
'3/24': '12.5%',
'4/24': '16.66666667%',
'5/24': '20.83333333%',
'6/24': '25%',
'7/24': '29.16666667%',
'8/24': '33.33333333%',
'9/24': '37.5%',
'10/24': '41.66666667%',
'11/24': '45.83333333%',
'12/24': '50%',
'13/24': '54.16666667%',
'14/24': '58.33333333%',
'15/24': '62.5%',
'16/24': '66.66666667%',
'17/24': '70.83333333%',
'18/24': '75%',
'19/24': '79.16666667%',
'20/24': '83.33333333%',
'21/24': '87.5%',
'22/24': '91.66666667%',
'23/24': '95.83333333%',
full: '100%',
screen: '100vw',
'max-content': 'max-content',
}),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
},
gap: theme => theme('spacing'),
gridTemplateColumns: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))',
},
gridColumn: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-7': 'span 7 / span 7',
'span-8': 'span 8 / span 8',
'span-9': 'span 9 / span 9',
'span-10': 'span 10 / span 10',
'span-11': 'span 11 / span 11',
'span-12': 'span 12 / span 12',
},
gridColumnStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridColumnEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridTemplateRows: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
},
gridRow: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
},
gridRowStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
gridRowEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
transformOrigin: {
center: 'center',
top: 'top',
'top-right': 'top right',
right: 'right',
'bottom-right': 'bottom right',
bottom: 'bottom',
'bottom-left': 'bottom left',
left: 'left',
'top-left': 'top left',
},
scale: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5',
},
rotate: {
'-180': '-180deg',
'-90': '-90deg',
'-45': '-45deg',
0: '0',
45: '45deg',
90: '90deg',
180: '180deg',
},
translate: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
'-full': '-100%',
'-1/2': '-50%',
'1/2': '50%',
full: '100%',
}),
skew: {
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
0: '0',
3: '3deg',
6: '6deg',
12: '12deg',
},
transitionProperty: {
none: 'none',
all: 'all',
height: 'height',
DEFAULT:
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
colors: 'background-color, border-color, color, fill, stroke',
opacity: 'opacity',
shadow: 'box-shadow',
transform: 'transform',
},
transitionTimingFunction: {
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
},
transitionDuration: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
extend: {
colors: {
highlight: 'hsl(var(--highlight))',
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
bkg: {
low: '#050615',
med: '#090C29',
full: '#041C4A',
},
info: {
primary: '#FFFFFF',
secondary: '#7BB2CE',
},
actions: {
primary: '#348CFD',
highlight: '#5ACCE6',
hover: 'rgba(52, 140, 253, 0.2)',
},
},
},
variants: {
accessibility: ['responsive', 'focus'],
alignContent: ['responsive'],
alignItems: ['responsive'],
alignSelf: ['responsive'],
appearance: ['responsive'],
backgroundAttachment: ['responsive'],
backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
borderCollapse: ['responsive'],
borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'],
borderRadius: ['responsive', 'focus', 'first', 'last'],
borderStyle: ['responsive', 'focus'],
borderWidth: ['responsive', 'focus', 'first', 'last'],
boxShadow: ['responsive', 'hover', 'focus'],
boxSizing: ['responsive'],
cursor: ['responsive'],
display: ['responsive'],
fill: ['responsive'],
flex: ['responsive'],
flexDirection: ['responsive'],
flexGrow: ['responsive'],
flexShrink: ['responsive'],
flexWrap: ['responsive'],
float: ['responsive'],
clear: ['responsive'],
fontFamily: ['responsive'],
fontSize: ['responsive'],
fontSmoothing: ['responsive'],
fontStyle: ['responsive'],
fontWeight: ['responsive', 'hover', 'focus'],
height: ['responsive'],
inset: ['responsive'],
justifyContent: ['responsive'],
letterSpacing: ['responsive'],
lineHeight: ['responsive'],
listStylePosition: ['responsive'],
listStyleType: ['responsive'],
margin: ['responsive'],
maxHeight: ['responsive'],
maxWidth: ['responsive'],
minHeight: ['responsive'],
minWidth: ['responsive'],
objectFit: ['responsive'],
objectPosition: ['responsive'],
opacity: ['responsive', 'hover', 'focus', 'active'],
order: ['responsive'],
outline: ['responsive', 'focus'],
overflow: ['responsive'],
padding: ['responsive', 'first'],
placeholderColor: ['responsive', 'focus'],
pointerEvents: ['responsive'],
position: ['responsive'],
resize: ['responsive'],
stroke: ['responsive'],
strokeWidth: ['responsive'],
tableLayout: ['responsive'],
textAlign: ['responsive'],
textColor: ['responsive', 'hover', 'focus', 'active', 'group-hover'],
textDecoration: ['responsive', 'hover', 'focus'],
textTransform: ['responsive'],
userSelect: ['responsive'],
verticalAlign: ['responsive'],
visibility: ['responsive'],
whitespace: ['responsive'],
width: ['responsive'],
wordBreak: ['responsive'],
zIndex: ['responsive'],
gap: ['responsive'],
gridAutoFlow: ['responsive'],
gridTemplateColumns: ['responsive'],
gridColumn: ['responsive'],
gridColumnStart: ['responsive'],
gridColumnEnd: ['responsive'],
gridTemplateRows: ['responsive'],
gridRow: ['responsive'],
gridRowStart: ['responsive'],
gridRowEnd: ['responsive'],
transform: ['responsive'],
transformOrigin: ['responsive'],
scale: ['responsive', 'hover', 'focus'],
rotate: ['responsive', 'hover', 'focus'],
translate: ['responsive', 'hover', 'focus'],
skew: ['responsive', 'hover', 'focus'],
transitionProperty: ['responsive'],
transitionTimingFunction: ['responsive'],
transitionDuration: ['responsive'],
},
corePlugins: {},
plugins: [],
plugins: [require('tailwindcss-animate')],
};

View File

@ -1,59 +1,61 @@
# OHIF Standalone Installation Instructions For Windows Server 2016:
**Note: Turn-Off Windows Defender Realtime protection during this process*
**Note: Turn-Off Windows Defender Realtime protection during this process**
1. Install Chocolatey
a. Go to this URL for instructions:
https://chocolatey.org/install#install-with-cmdexe
b. Execute this command in the cmd line as Admin:
`@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"`
```
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
```
c. Type `choco -?` to ensure the installation is OK.
2. Install Meteor
a. Type the command: `choco install meteor`
3. Install node.js
a. Download MSI from: https://nodejs.org/en/download/
4. Install MongoDB
a. Download MSI from:
a. Download MSI from:
https://www.mongodb.com/download-center/community
5. Install Git
a. https://git-scm.com/download/win
6. Download the OHIF Viewer repository from GitHub, or use Git to clone it (recommended)
a. GitHub Repo: https://github.com/OHIF/Viewers
b. GIT Clone command: `git clone https://github.com/OHIF/Viewers`
7. Set the Meteor Packages folder environment variable
a. Go to Advanced System Settings
b. Under Advanced, click on the "Environment Variables..." button
c. Under System Variables, click "New..."
d. Set the following:
i. Variable name: METEOR_PACKAGE_DIRS
ii. Variable Value: {path to the OHIF Viewer Packages Folder}
ii. Variable Value:
example: C:\OHIF\Viewers\Packages
8. Using the command line, navigate/cs to the standalone viewer folder, example: `cd C:\OHIF\Viewers\StandaloneViewer\StandaloneViewer`
9. run the command: `meteor npm install`
10. Run the command: `meteor`
a. if you get this error "Error: EPERM: operation not permitted, unlink" Or,
b. You feel the build is stale for a very long time > 10min with no visual indication, restart the cmd prompt and repeat this step (`meteor`)
# Troubleshooting:
1. If you get an error: Error: EPERM: operation not permitted, unlink 'c:\xxxxx'
1. If you get an error: Error: EPERM: operation not permitted, unlink `c:\xxxxx`
a. https://github.com/phoenixframework/phoenix/issues/2464

View File

@ -29,16 +29,16 @@ sidebar_position: 4
## Web Service URLs from DCM4CHEE:
Original source here: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker#web-service-urls
- Archive UI: <http://localhost:8080/dcm4chee-arc/ui> - if secured, login with
- Archive UI: `http://localhost:8080/dcm4chee-arc/ui` - if secured, login with
Username | Password | Role
--- | --- | ---
`user` | `user` | `user`
`admin` | `admin` | `user` + `admin`
- Keycloak Administration Console: <http://localhost:8080/auth>, login with Username: `admin`, Password: `admin`.
- Wildfly Administration Console: <http://localhost:9990>, login with Username: `admin`, Password: `admin`.
- Kibana UI: <http://localhost:5601>
- DICOM QIDO-RS Base URL: <http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs>
- DICOM STOW-RS Base URL: <http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs>
- DICOM WADO-RS Base URL: <http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs>
- DICOM WADO-URI: <http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado>
- Keycloak Administration Console: `http://localhost:8080/auth`, login with Username: `admin`, Password: `admin`.
- Wildfly Administration Console: `http://localhost:9990`, login with Username: `admin`, Password: `admin`.
- Kibana UI: `http://localhost:5601`
- DICOM QIDO-RS Base URL: `http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs`
- DICOM STOW-RS Base URL: `http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs`
- DICOM WADO-RS Base URL: `http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs`
- DICOM WADO-URI: `http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado`

View File

@ -113,7 +113,7 @@ module.exports = {
},
hot: true,
open: true,
port: 8000,
port: 8002,
historyApiFallback: true,
devMiddleware: {
writeToDisk: true,

View File

@ -0,0 +1,210 @@
const actionOptionsMap: { [key: string]: string[] } = {
Measurement: ['Rename', 'Lock', 'Delete'],
Segmentation: ['Rename', 'Lock', 'Export', 'Delete'],
'ROI Tools': ['Rename', 'Lock', 'Delete'],
'Organ Segmentation': ['Rename', 'Lock', 'Export', 'Delete'],
// Add more types and their corresponding actions as needed
};
const dataList = [
{
type: 'Measurement',
items: [
{
id: 1,
title: 'Measurement Label',
description: 'Description for Measurement One.',
optionalField: 'Optional Info 1',
details: 'Data',
},
{
id: 2,
title: 'Measurement Label',
description: 'Description for Measurement Two.',
details: 'Data',
},
],
},
{
type: 'Segmentation',
items: [
{
id: 3,
title: 'Segmentation One',
colorHex: '#FF5733',
description: 'Description for Segmentation One.',
},
{
id: 4,
title: 'Segmentation Two',
colorHex: '#FF5733',
description: 'Description for Segmentation Two.',
},
{
id: 5,
title: 'Segmentation Three',
colorHex: '#FF5733',
description: 'Description for Segmentation Three.',
},
],
},
{
type: 'ROI Tools',
items: [
{
id: 6,
title: 'Linear',
description: 'Description for Linear.',
details: '49.2 mm',
series: 'S:2 I:1',
},
{
id: 7,
title: 'Bidirectional',
description: 'Description for Bidirectional.',
details: 'L: 34.5 mm\nW: 23.0 mm',
series: 'S:2 I:2',
},
{
id: 8,
title: 'Ellipse',
description: 'Description for Ellipse.',
details: '2641 mm²\nMax: 1087 HU',
series: 'S:2 I:4',
},
{
id: 9,
title: 'Rectangle',
description: 'Description for Rectangle.',
details: '1426 mm²\nMax: 718 HU',
series: 'S:2 I:5',
},
{
id: 10,
title: 'Circle',
description: 'Description for Circle.',
details: '7339 mm²\nMax: 871 HU',
series: 'S:2 I:6',
},
{
id: 11,
title: 'Freehand ROI',
description: 'Description for Freehand ROI.',
details: 'Mean: 215 HU\nMax: 947 HU\nArea: 839 mm²',
series: 'S:2 I:7',
},
{
id: 12,
title: 'Spline Tool',
description: 'Description for Spline Tool.',
details: 'Area: 203 mm²',
series: 'S:2 I:8',
},
{
id: 13,
title: 'Livewire Tool',
description: 'Description for Livewire Tool.',
details: '',
series: 'S:2 I:3',
},
{
id: 14,
title: 'Annotation Lorem ipsum dolor sit amet long measurement name continues here',
description: 'Description for Annotation.',
details: '',
series: 'S:2 I:3',
},
],
},
{
type: 'Organ Segmentation',
items: [
{
id: 15,
title: 'Spleen',
description: 'Description for Spleen.',
colorHex: '#6B8E23',
},
{
id: 16,
title: 'Kidney',
description: 'Description for Kidney.',
colorHex: '#4682B4',
},
{
id: 17,
title: 'Kidney very long title name lorem ipsum dolor sit amet segmentation',
description: 'Description for Kidney.',
colorHex: '#9ACD32',
},
{
id: 18,
title: 'Gallbladder',
description: 'Description for Gallbladder.',
colorHex: '#20B2AA',
},
{
id: 19,
title: 'Esophagus',
description: 'Description for Esophagus.',
colorHex: '#DAA520',
},
{
id: 20,
title: 'Liver',
description: 'Description for Liver.',
colorHex: '#CD5C5C',
},
{
id: 21,
title: 'Stomach',
description: 'Description for Stomach.',
colorHex: '#778899',
},
{
id: 22,
title: 'Abdominal aorta',
description: 'Description for Abdominal Aorta.',
colorHex: '#B8860B',
},
{
id: 23,
title: 'Inferior vena cava',
description: 'Description for Inferior Vena Cava.',
colorHex: '#556B2F',
},
{
id: 24,
title: 'Portal vein',
description: 'Description for Portal Vein.',
colorHex: '#8B4513',
},
{
id: 25,
title: 'Pancreas',
description: 'Description for Pancreas.',
colorHex: '#2F4F4F',
},
{
id: 26,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#708090',
},
{
id: 27,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#6A5ACD',
},
{
id: 28,
title: 'New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test ',
description: 'Description for New Seg Test.',
colorHex: '#4682B4',
},
],
},
];
export { actionOptionsMap, dataList };

View File

@ -1 +0,0 @@
This is our playground for designing and developing the next generation of the platform's UI. You probably don't need to be here.

View File

@ -1,134 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
function Colors() {
return (
<main>
<h2>Primary color</h2>
<div className="row">
<div className="example2">
<div className="bg-primary h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary/80 h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary/60 h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary/40 h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary/20 h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-infosecondary h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-highlight h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-highlight h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
</div>
<h2>New colors</h2>
<div className="row">
<div className="example2">
<div className="bg-highlight h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-bkg-low h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-bkg-med h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-bkg-full h-16 w-16 rounded"></div>
</div>
</div>
<h2>Core colors</h2>
<div className="row">
<div className="example2">
<div className="bg-background h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-card h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-card-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-popover h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-popover-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-primary-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-secondary h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-secondary-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-muted h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-muted-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-accent h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-accent-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="destructive h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="destructive-foreground h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-border h-16 w-16 rounded"></div>
</div>
</div>
<h2>Borders</h2>
<div className="row">
<div className="example2">
<div className="bg-border h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-input h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-ring h-16 w-16 rounded"></div>
</div>
</div>
</main>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(Colors));

View File

@ -1,40 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
const App: React.FC = () => (
<div className="container mx-auto p-4">
<h1 className="mb-4 text-2xl font-bold">Home Page</h1>
<nav className="space-x-4">
<a
href="playground.html"
className="text-blue-500 hover:text-blue-700"
>
Playground
</a>
<a
href="patterns.html"
className="text-blue-500 hover:text-blue-700"
>
Patterns
</a>
<a
href="viewer.html"
className="text-blue-500 hover:text-blue-700"
>
Viewer
</a>
<a
href="colors.html"
className="text-blue-500 hover:text-blue-700"
>
Colors
</a>
</nav>
</div>
);
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(App));

View File

@ -1,43 +0,0 @@
// src/_pages/patterns.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
// component imports
import { Button } from '../components/Button';
import { Input } from '../components/Input';
import { Label } from '../components/Label';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import Separator from '../components/Separator';
import { Switch } from '../components/Switch';
import { Checkbox } from '../components/Checkbox';
import { Toggle, toggleVariants } from '../components/Toggle';
import { Slider } from '../components/Slider';
import { ScrollArea, ScrollBar } from '../components/ScrollArea';
import { PanelSplit } from '../_prototypes/PanelSplit'; // Updated import path
function Patterns() {
return (
<div className="my-4 mx-auto max-w-6xl py-6">
<div className="text-3xl"> Patterns 1 </div>
<PanelSplit />;
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(Patterns));

View File

@ -1,316 +0,0 @@
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
import { Button } from '../components/Button';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuPortal,
} from '../components/DropdownMenu';
import { Icons } from '../components/Icons/Icons';
import DataRow from '../_prototypes/DataRow/DataRow';
import dataList from '../_prototypes/DataRow/dataList.json';
import actionOptionsMap from '../_prototypes/DataRow/actionOptionsMap';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '../components/Accordion/Accordion';
import { Slider } from '../components/Slider';
import { Switch } from '../components/Switch';
import { Label } from '../components/Label';
import { Input } from '../components/Input';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import { ChevronDownIcon } from '@radix-ui/react-icons';
interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: string;
series?: string;
}
interface ListGroup {
type: string;
items: DataItem[];
}
function Patterns() {
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
const handleAction = (id: string, action: string) => {
console.log(`Action "${action}" triggered for item with id: ${id}`);
// Implement actual action logic here
};
// Handle row selection
const handleRowSelect = (id: string) => {
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
};
const organSegmentationGroup = dataList.find(
(listGroup: ListGroup) => listGroup.type === 'Organ Segmentation'
);
if (!organSegmentationGroup) {
return <div className="text-red-500">Organ Segmentation data not found.</div>;
}
return (
<div className="my-4 flex max-w-6xl justify-end py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list']}
collapsible
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
<div className="mb-0">
{/* Header Controls */}
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Create New Segmentation</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Current Segmentation</DropdownMenuLabel>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Delete className="text-foreground" />
<span className="pl-2">Delete</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
<Select>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder="Segmentation 1" />
</SelectTrigger>
<SelectContent>
<SelectItem value="seg1">Segmentation 1</SelectItem>
<SelectItem value="seg2">Segmentation 2</SelectItem>
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
{/* Action Buttons */}
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
<Button
size="sm"
variant="ghost"
className="pr pl-0.5"
>
<Icons.Add />
Add Segment
</Button>
<Button
size="icon"
variant="ghost"
>
<Icons.Hide className="h-6 w-6" />
</Button>
</div>
</div>
{/* Data Rows */}
<div className="space-y-px">
{organSegmentationGroup.items.map((item, index) => {
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`;
return (
<DataRow
key={`panel-${compositeId}`}
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[organSegmentationGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<Patterns />);

View File

@ -1,411 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
import { Button } from '../components/Button';
import { Input } from '../components/Input';
import { Label } from '../components/Label';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import { Separator } from '../components/Separator';
import { Switch } from '../components/Switch';
import { Checkbox } from '../components/Checkbox';
import { Toggle, toggleVariants } from '../components/Toggle';
import { Slider } from '../components/Slider';
import { ScrollArea, ScrollBar } from '../components/ScrollArea';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} from '../components/DropdownMenu';
import { BackgroundColorSelect } from '../components/BackgroundColorSelect';
// import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../components/Tooltip';
// import type { Metadata } from 'next';
// import Script from 'next/script';
// import BackgroundColorSelector from '@/components/backgroundcolor';
// import { useState, useEffect } from 'react';
// import { Inter } from 'next/font/google';
// import { ThemeProvider } from '@/components/themeprovider';
// import { Button2 } from '@/components/ui/button2';
// import { Switch } from '@/components/ui/switch';
// import { Slider } from '@/components/ui/slider';
// import { ScrollArea } from '@/components/ui/scroll-area';
// import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
// import { Separator } from '@/components/ui/separator';
// import { Toggle } from '@/components/ui/toggle';
// import { Checkbox } from '@/components/ui/checkbox';
// import {
// Select,
// SelectContent,
// SelectItem,
// SelectTrigger,
// SelectValue,
// } from '@/components/ui/select';
// import { Input } from '@/components/ui/input';
// import { Label } from '@/components/ui/label';
// import icons from '../../components/icons';
// import SegRow from '@/components/segrow';
export default function Playground() {
return (
<main className="my-4 mx-auto max-w-6xl py-6">
<BackgroundColorSelect />
<h2 className="section-header">Button default</h2>
<div className="row">
<div className="example">
<Button
className=""
variant="default"
>
Primary Button
</Button>
</div>
<div className="example">
<Button variant="secondary">Secondary Button</Button>
</div>
<div className="example">
<Button variant="ghost">Ghost Button</Button>
</div>
<div className="example">
<Button
variant="ghost"
size="icon"
>
?
</Button>
</div>
<div className="example">
<Button variant="link">Link</Button>
</div>
</div>
<h2 className="section-header">Button small</h2>
<div className="row">
<div className="example">
<Button
className=""
variant="default"
size="sm"
>
Primary Button
</Button>
</div>
<div className="example">
<Button
variant="secondary"
size="sm"
>
Secondary Button
</Button>
</div>
<div className="example">
<Button
variant="ghost"
size="sm"
>
Ghost Button
</Button>
</div>
<div className="example">
<Button
variant="ghost"
size="icon"
>
?
</Button>
</div>
<div className="example">
<Button
variant="link"
size="sm"
>
Link
</Button>
</div>
</div>
<h2 className="section-header">Button large</h2>
<div className="row">
<div className="example">
<Button
className=""
variant="default"
size="lg"
>
Primary Button
</Button>
</div>
<div className="example">
<Button
variant="secondary"
size="lg"
>
Secondary Button
</Button>
</div>
<div className="example">
<Button
variant="ghost"
size="lg"
>
Ghost Button
</Button>
</div>
<div className="example">
<Button
variant="ghost"
size="icon"
>
?
</Button>
</div>
<div className="example">
<Button
variant="link"
size="lg"
>
Link
</Button>
</div>
</div>
<h2 className="section-header">Input</h2>
<div className="row">
<div className="example">
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
</div>
</div>
<h2 className="section-header">Color swatches</h2>
<div className="row">
<div className="example2">
<div className="bg-actions h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-infosecondary h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="bg-highlight h-16 w-16 rounded"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
<div className="example2">
<div className="h-16 w-16 rounded bg-white"></div>
</div>
</div>
<h2 className="section-header">Typography</h2>
<div className="row">
<div className="example text-base text-white">Standard text size (text-base) 14px</div>
<div className="example text-sm text-white">Small text size (text-sm) 13px</div>
<div className="example text-xs text-white">Extra small text size (text-xs) 12px</div>
</div>
<h2 className="section-header">Typography large</h2>
<div className="row">
<div className="example text-lg text-white">Large text size (text-lg) 16px</div>
<div className="example text-xl text-white">Small text size (text-xl) 18px</div>
<div className="example text-2xl text-white">Extra small text size (text-2xl) 20px</div>
</div>
<h2 className="section-header">Tabs</h2>
<div className="row">
<div className="example">
<Tabs className="w-[400px]">
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
</div>
</div>
<h2 className="section-header">Select</h2>
<div className="row">
<div className="example">
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<h2 className="section-header">Dropdown Menu</h2>
<div className="row">
<div className="example">
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="example">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="example">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="example">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
>
<DropdownMenuItem onSelect={() => console.log('Item 1')}>Item 1</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.log('Item 2')}>Item 2</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.log('Item 3')}>
Long name Item 3
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<h2 className="section-header">Switch</h2>
<div className="row">
<div className="example">
<Switch />
</div>
</div>
<h2 className="section-header">Checkbox</h2>
<div className="row">
<div className="example">
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
</div>
</div>
<h2 className="section-header">Toggle</h2>
<div className="row">
<div className="example">
<Toggle>Hello</Toggle>
</div>
</div>
<h2 className="section-header">Slider</h2>
<div className="row">
<div className="example">
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</div>
</div>
<h2 className="section-header">Accordion</h2>
<div className="row">
<div className="example">
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</div>
</div>
<h2 className="section-header">Scroll Area</h2>
<div className="row">
<div className="example">
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</ScrollArea>
</div>
</div>
</main>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(Playground));

View File

@ -1,17 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
// component imports
function Viewer() {
return (
<div className="h-full w-full">
<div className="text-3xl"> Viewer </div>
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(Viewer));

View File

@ -1,11 +0,0 @@
// src/_prototypes/DataRow/actionOptionsMap.ts
const actionOptionsMap: { [key: string]: string[] } = {
Measurement: ['Rename', 'Lock', 'Delete'],
Segmentation: ['Rename', 'Lock', 'Export', 'Delete'],
'ROI Tools': ['Rename', 'Lock', 'Delete'],
'Organ Segmentation': ['Rename', 'Lock', 'Export', 'Delete'],
// Add more types and their corresponding actions as needed
};
export default actionOptionsMap;

View File

@ -1,200 +0,0 @@
[
{
"type": "Measurement",
"items": [
{
"id": 1,
"title": "Measurement Label",
"description": "Description for Measurement One.",
"optionalField": "Optional Info 1",
"details": "Data"
},
{
"id": 2,
"title": "Measurement Label",
"description": "Description for Measurement Two.",
"details": "Data"
}
]
},
{
"type": "Segmentation",
"items": [
{
"id": 3,
"title": "Segmentation One",
"colorHex": "#FF5733",
"description": "Description for Segmentation One."
},
{
"id": 4,
"title": "Segmentation Two",
"colorHex": "#FF5733",
"description": "Description for Segmentation Two."
},
{
"id": 5,
"title": "Segmentation Three",
"colorHex": "#FF5733",
"description": "Description for Segmentation Three."
}
]
},
{
"type": "ROI Tools",
"items": [
{
"id": 6,
"title": "Linear",
"description": "Description for Linear.",
"details": "49.2 mm",
"series": "S:2 I:1"
},
{
"id": 7,
"title": "Bidirectional",
"description": "Description for Bidirectional.",
"details": "L: 34.5 mm\nW: 23.0 mm",
"series": "S:2 I:2"
},
{
"id": 8,
"title": "Ellipse",
"description": "Description for Ellipse.",
"details": "2641 mm²\nMax: 1087 HU",
"series": "S:2 I:4"
},
{
"id": 9,
"title": "Rectangle",
"description": "Description for Rectangle.",
"details": "1426 mm²\nMax: 718 HU",
"series": "S:2 I:5"
},
{
"id": 10,
"title": "Circle",
"description": "Description for Circle.",
"details": "7339 mm²\nMax: 871 HU",
"series": "S:2 I:6"
},
{
"id": 11,
"title": "Freehand ROI",
"description": "Description for Freehand ROI.",
"details": "Mean: 215 HU\nMax: 947 HU\nArea: 839 mm²",
"series": "S:2 I:7"
},
{
"id": 12,
"title": "Spline Tool",
"description": "Description for Spline Tool.",
"details": "Area: 203 mm²",
"series": "S:2 I:8"
},
{
"id": 13,
"title": "Livewire Tool",
"description": "Description for Livewire Tool.",
"details": "",
"series": "S:2 I:3"
},
{
"id": 14,
"title": "Annotation Lorem ipsum dolor sit amet long measurement name continues here",
"description": "Description for Annotation.",
"details": "",
"series": "S:2 I:3"
}
]
},
{
"type": "Organ Segmentation",
"items": [
{
"id": 15,
"title": "Spleen",
"description": "Description for Spleen.",
"colorHex": "#6B8E23"
},
{
"id": 16,
"title": "Kidney",
"description": "Description for Kidney.",
"colorHex": "#4682B4"
},
{
"id": 17,
"title": "Kidney very long title name lorem ipsum dolor sit amet segmentation",
"description": "Description for Kidney.",
"colorHex": "#9ACD32"
},
{
"id": 18,
"title": "Gallbladder",
"description": "Description for Gallbladder.",
"colorHex": "#20B2AA"
},
{
"id": 19,
"title": "Esophagus",
"description": "Description for Esophagus.",
"colorHex": "#DAA520"
},
{
"id": 20,
"title": "Liver",
"description": "Description for Liver.",
"colorHex": "#CD5C5C"
},
{
"id": 21,
"title": "Stomach",
"description": "Description for Stomach.",
"colorHex": "#778899"
},
{
"id": 22,
"title": "Abdominal aorta",
"description": "Description for Abdominal Aorta.",
"colorHex": "#B8860B"
},
{
"id": 23,
"title": "Inferior vena cava",
"description": "Description for Inferior Vena Cava.",
"colorHex": "#556B2F"
},
{
"id": 24,
"title": "Portal vein",
"description": "Description for Portal Vein.",
"colorHex": "#8B4513"
},
{
"id": 25,
"title": "Pancreas",
"description": "Description for Pancreas.",
"colorHex": "#2F4F4F"
},
{
"id": 26,
"title": "Adrenal gland",
"description": "Description for Adrenal Gland.",
"colorHex": "#708090"
},
{
"id": 27,
"title": "Adrenal gland",
"description": "Description for Adrenal Gland.",
"colorHex": "#6A5ACD"
},
{
"id": 28,
"title": "New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test ",
"description": "Description for New Seg Test.",
"colorHex": "#4682B4"
}
]
}
]

View File

@ -1,15 +0,0 @@
// src/_prototypes/DataRow/types.ts
export interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: string;
}
export interface ListGroup {
type: string;
items: DataItem[];
}

View File

@ -1,213 +0,0 @@
import React from 'react';
import { Item, VisibilityState, AvailabilityState } from './types';
import { Button } from '../../components/Button'; // Updated import path
interface ItemListProps {
items: Item[];
onSelectItem: (item: Item) => void;
selectedItem: Item | null;
onToggleVisibility: (itemId: number) => void; // Prop for visibility toggle
onAddItem: (itemId: number) => void; // Prop for add button
}
/**
* ItemList Component
*
* Displays a list of items that can be selected, toggled for visibility,
* and added (changing availability).
*
* @param items - Array of items to display.
* @param onSelectItem - Callback when an item is selected.
* @param selectedItem - The currently selected item.
* @param onToggleVisibility - Callback when an item's visibility is toggled.
* @param onAddItem - Callback when an item's availability is changed to 'loaded'.
*/
const ItemList: React.FC<ItemListProps> = ({
items,
onSelectItem,
selectedItem,
onToggleVisibility,
onAddItem,
}) => {
return (
<ul
aria-label="Item List"
className="space-y-1"
>
{items.map(item => (
<li key={item.id}>
<div className="flex items-center">
{/* Conditionally render the Add button for 'available' items */}
{item.availability === 'available' && (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation(); // Prevent parent onClick
onAddItem(item.id);
}}
aria-label={`Add ${item.name}`}
className="ml-1 mr-1"
>
{/* Plus Icon */}
<svg
width="16px"
height="16px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 5V19"
stroke="#348CFD"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 12H19"
stroke="#348CFD"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
)}
{/* All items are now rendered as clickable rows */}
<button
onClick={() => onSelectItem(item)}
className={`text-foreground flex h-7 w-full flex-grow cursor-pointer items-center justify-between rounded p-1 text-sm ${
item.id === selectedItem?.id ? 'bg-primary/20' : 'bg-muted hover:bg-primary/30'
} focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-1`}
aria-pressed={item.id === selectedItem?.id}
>
<span className="ml-1">{item.name}</span>
{/* Conditionally render the "Available" label for 'available' items */}
{item.availability === 'available' && (
<span className="text-muted-foreground text-xs">Available</span>
)}
{/* Conditionally render the visibility icon for 'loaded' items */}
{item.availability === 'loaded' && (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation(); // Prevent parent onClick
onToggleVisibility(item.id);
}}
aria-label={
item.visibility === 'Visible' ? `Hide ${item.name}` : `Show ${item.name}`
}
>
{item.visibility === 'Visible' ? (
// SVG Icon for "Visible"
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
>
<g
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
x="0"
y="0"
width="24"
height="24"
></rect>
<circle
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
cx="12.4986195"
cy="11.8041442"
r="2.58684689"
></circle>
<path
d="M20.906611,11.5617197 C20.0470387,10.5861089 16.6094888,7 12.4986195,7
C8.38775024,7 4.95020027,10.5861089 4.090628,11.5617197
C3.96979067,11.7007491 3.96979067,11.9075393 4.090628,12.0465687
C4.95020027,13.0221796 8.38775024,16.6082885 12.4986195,16.6082885
C16.6094888,16.6082885 20.0470387,13.0221796 20.906611,12.0465687
C21.0274483,11.9075393 21.0274483,11.7007491 20.906611,11.5617197 Z"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</g>
</svg>
) : (
// SVG Icon for "Hidden"
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
>
<g
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<path
d="M18.0567826,8.96286957
C19.1471229,9.75269568 20.1356859,10.674229 21,11.7065217
C21,11.7065217 17.1949565,16.5108696 12.5,16.5108696
C11.7479876,16.5066962 11.0007435,16.3911225 10.2826087,16.167913"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M6.93286957,14.4413043
C5.84666081,13.6535964 4.86162018,12.7350857 4,11.7065217
C4,11.7065217 7.80504348,6.90217391 12.5,6.90217391
C13.1235541,6.90480509 13.7443251,6.98550531 14.3478261,7.1423913"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M9.54347826,11.7065217
C9.54347826,10.0736799 10.8671581,8.75 12.5,8.75"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M15.4565217,11.7065217
C15.4565217,13.3393636 14.1328419,14.6630435 12.5,14.6630435"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<line
x1="19.7065217"
y1="4.5"
x2="5.29347826"
y2="18.9130435"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></line>
</g>
</svg>
)}
</Button>
)}
</button>
</div>
</li>
))}
</ul>
);
};
export default ItemList;

View File

@ -1,331 +0,0 @@
import React, { useState, useEffect } from 'react';
import ItemList from './ItemList';
import PropertiesPanel from './PropertiesPanel';
import { Item, DisplayMode, VisibilityState, AvailabilityState } from './types';
import { ScrollArea } from '../../components/ScrollArea';
const PanelSplit: React.FC = () => {
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
const [items, setItems] = useState<Item[]>([
{
id: 1,
name: 'All Items',
controlsAll: true, // Master item
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 50,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 5,
min: 1,
max: 10,
step: 1,
},
{
key: 'displayInactiveSegments',
label: 'Display inactive segments',
type: 'boolean',
value: false,
},
],
},
{
id: 2,
name: 'List item 1',
series: 'Series A',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 70,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 7,
min: 1,
max: 10,
step: 1,
},
],
},
{
id: 3,
name: 'List item 2',
series: 'Series B',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 70,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 7,
min: 1,
max: 10,
step: 1,
},
],
},
{
id: 4,
name: 'List item 3',
series: 'Series C',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Visibility will not be shown initially
availability: 'available', // Set to 'available'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 80,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 8,
min: 1,
max: 10,
step: 1,
},
],
},
// Add more items as needed
]);
// Set the master item as selected by default on mount
useEffect(() => {
if (items.length > 0 && !selectedItem) {
setSelectedItem(items.find(item => item.controlsAll) || items[0]);
}
}, [items, selectedItem]);
/**
* Handles updating a property's value.
*
* @param itemId - The ID of the item being updated.
* @param propertyKey - The key identifying the property.
* @param newValue - The new value for the property.
*/
const handleUpdateProperty = (itemId: number, propertyKey: string, newValue: any) => {
const masterItem = items.find(item => item.controlsAll);
if (masterItem && itemId === masterItem.id) {
if (propertyKey === 'displayMode') {
// Update displayMode for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
displayMode: newValue, // Set to the new displayMode
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
displayMode: newValue,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
} else if (propertyKey === 'visibility') {
// Update visibility for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
visibility: newValue, // Set to the new visibility
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
visibility: newValue,
}
: prevSelected
);
} else {
// Update other properties for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
}
} else {
if (propertyKey === 'displayMode') {
// Update displayMode only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, displayMode: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, displayMode: newValue } : prevSelected
);
} else if (propertyKey === 'visibility') {
// Toggle visibility only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, visibility: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, visibility: newValue } : prevSelected
);
} else if (propertyKey === 'availability') {
// Update availability only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, availability: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, availability: newValue } : prevSelected
);
} else {
// Update only the selected item's properties
setItems(prevItems =>
prevItems.map(item =>
item.id === itemId
? {
...item,
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: item
)
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
}
}
};
/**
* Handles selecting an item from the list.
*
* @param item - The item being selected.
*/
const handleSelectItem = (item: Item) => {
setSelectedItem(item);
};
/**
* Handles toggling the visibility of an item.
*
* @param itemId - The ID of the item being toggled.
*/
const handleToggleVisibility = (itemId: number) => {
const item = items.find(item => item.id === itemId);
if (item) {
const newVisibility: VisibilityState = item.visibility === 'Visible' ? 'Hidden' : 'Visible';
handleUpdateProperty(itemId, 'visibility', newVisibility);
}
};
/**
* Handles adding an item (changing availability from 'available' to 'loaded').
*
* @param itemId - The ID of the item being added.
*/
const handleAddItem = (itemId: number) => {
handleUpdateProperty(itemId, 'availability', 'loaded');
};
return (
<div className="flex h-full w-[262px] flex-col">
{/* Top: List of Selectable Items */}
<ScrollArea className="bg-muted h-[132px] rounded-t border-gray-300 p-1">
<ItemList
items={items}
onSelectItem={handleSelectItem}
selectedItem={selectedItem}
onToggleVisibility={handleToggleVisibility} // Pass the toggle handler
onAddItem={handleAddItem} // Pass the add handler
/>
</ScrollArea>
{/* Bottom: Properties of Selected Item */}
<ScrollArea className="bg-popover max-h-[400px] flex-grow overflow-auto rounded p-1">
<PropertiesPanel
selectedItem={selectedItem}
onUpdateProperty={handleUpdateProperty}
onAddItem={handleAddItem} // Pass the add handler
/>
</ScrollArea>
</div>
);
};
export default PanelSplit;

View File

@ -1,337 +0,0 @@
import React from 'react';
import { Item, Property, DisplayMode, AvailabilityState } from './types';
import { Label } from '../../components/Label';
import { Slider } from '../../components/Slider';
import { Input } from '../../components/Input';
import { Switch } from '../../components/Switch';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/Tabs';
import { Button } from '../../components/Button';
interface PropertiesPanelProps {
selectedItem: Item | null;
onUpdateProperty: (itemId: number, propertyKey: string, newValue: any) => void;
onAddItem: (itemId: number) => void; // Prop for adding item
}
/**
* PropertiesPanel Component
*
* Displays and manages the properties of the selected item.
* Renders different content based on the item's availability state.
*
* @param selectedItem - The currently selected item.
* @param onUpdateProperty - Callback to handle property updates.
* @param onAddItem - Callback to handle adding the item.
*/
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
selectedItem,
onUpdateProperty,
onAddItem,
}) => {
if (!selectedItem) {
return (
<div className="text-gray-500">
<p>No item selected.</p>
</div>
);
}
if (selectedItem.availability === 'available') {
return (
<div className="flex flex-col items-center p-1.5 text-sm">
{/* "Add this item" Button */}
<Button
onClick={() => onAddItem(selectedItem.id)}
className="mb-4 mt-2"
variant="default"
>
Add this item
</Button>
{/* Divider */}
<div className="border-primary/30 mb-2 w-full border-b"></div>
{/* Series Name */}
<div className="text-foreground w-full text-left">
Series: <span className="text-muted-foreground">{selectedItem.series}</span>
</div>
</div>
);
}
if (selectedItem.availability === 'loaded') {
/**
* Handles changes to a property's value.
*
* @param property - The property being updated.
* @param newValue - The new value for the property.
*/
const handleChange = (property: Property, newValue: any) => {
console.log(`Updating property '${property.key}' to`, newValue); // Debug log
onUpdateProperty(selectedItem.id, property.key, newValue);
};
// Determine if the selected item is the master
const isMaster = selectedItem.controlsAll;
/**
* Handles changes to the display mode via Tabs.
*
* @param newDisplayMode - The new display mode selected.
*/
const handleDisplayModeChange = (newDisplayMode: DisplayMode) => {
console.log(`Display mode changed to`, newDisplayMode); // Debug log
onUpdateProperty(selectedItem.id, 'displayMode', newDisplayMode);
};
return (
<div className="p-1.5 text-sm">
<div className="items-top mb-2.5 flex justify-between">
<div className="text-foreground text-sm font-semibold">
Properties <br />
<span className="text-muted-foreground font-normal">{selectedItem.name}</span>
</div>
{/* Tabs component for Outline and Fill control */}
<Tabs
value={selectedItem.displayMode}
onValueChange={handleDisplayModeChange}
className="ml-auto"
>
<TabsList>
<TabsTrigger value="Fill & Outline">
{/* SVG Icon for Fill & Outline */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-outline-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
stroke="#348CFD"
x="1.5"
y="1.5"
width="15"
height="15"
rx="1"
></rect>
<rect
id="Rectangle"
fill="#348CFD"
x="3.5"
y="3.5"
width="11"
height="11"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
<TabsTrigger value="Outline Only">
{/* SVG Icon for Outline Only */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-outline"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
stroke="#348CFD"
x="1.5"
y="1.5"
width="15"
height="15"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
<TabsTrigger value="Fill Only">
{/* SVG Icon for Fill Only */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
fill="#348CFD"
x="2"
y="2"
width="14"
height="14"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
</TabsList>
{/* Display dynamic text under the tabs */}
<div className="mt-0">
<TabsContent value="Fill & Outline">
<p className="text-muted-foreground text-xxs text-center">Fill & Outline</p>
</TabsContent>
<TabsContent value="Outline Only">
<p className="text-muted-foreground text-xxs text-center">Outline Only</p>
</TabsContent>
<TabsContent value="Fill Only">
<p className="text-muted-foreground text-xxs text-center">Fill Only</p>
</TabsContent>
</div>
</Tabs>
</div>
{/* Properties List */}
<div className="mb-3 space-y-3">
{selectedItem.properties.map(prop => (
<div
key={prop.key}
className="flex items-center justify-between space-x-4"
>
{/* Label takes up space and doesn't wrap */}
<Label
htmlFor={prop.key}
className="flex-grow whitespace-nowrap"
>
{prop.label}
</Label>
{/* Flex container for input elements, with spacing */}
<div className="flex items-center space-x-3">
{renderPropertyInput(prop, handleChange)}
</div>
</div>
))}
</div>
{/* Conditionally render the details section for non-master items */}
{!isMaster && (
<div className="text-foreground mb-1">
<div className="border-primary/30 mb-2 w-full border-b"></div>
Series: <span className="text-muted-foreground">{selectedItem.series}</span>
</div>
)}
</div>
);
}
// For other availability states, you can add additional conditions if needed
return (
<div className="text-gray-500">
<p>No properties available for the selected item.</p>
</div>
);
};
/**
* Renders the appropriate input component based on the property's type.
*
* @param prop - The property to render.
* @param handleChange - Function to handle value changes.
* @returns JSX Element corresponding to the property type.
*/
const renderPropertyInput = (
prop: Property,
handleChange: (prop: Property, value: any) => void
) => {
switch (prop.type) {
case 'slider':
return (
<>
<Slider
id={prop.key}
value={[prop.value as number]} // Pass as an array for Radix UI Slider
min={prop.min}
max={prop.max}
step={prop.step}
onValueChange={values => {
console.log(`Slider '${prop.key}' changed to`, values[0]); // Debug log
handleChange(prop, values[0]);
}}
className="w-28"
/>
<Input
type="number"
id={prop.key}
value={prop.value as number}
min={prop.min}
max={prop.max}
step={prop.step}
onChange={e => {
const newVal = Number(e.target.value);
console.log(`Input '${prop.key}' changed to`, newVal); // Debug log
handleChange(prop, newVal);
}}
className="w-14"
/>
</>
);
case 'boolean':
return (
<Switch
id={prop.key}
checked={prop.value as boolean}
onCheckedChange={checked => {
console.log(`Switch '${prop.key}' toggled to`, checked); // Debug log
handleChange(prop, checked);
}}
/>
);
// Add more cases if you have other property types
default:
return null;
}
};
export default PropertiesPanel;

View File

@ -1,7 +0,0 @@
import PanelSplit from './PanelSplit';
import ItemList from './ItemList';
import PropertiesPanel from './PropertiesPanel';
export { default as PanelSplit } from './PanelSplit';
export { default as PropertiesPanel } from './PropertiesPanel';
export { default as ItemList } from './ItemList';

View File

@ -1,24 +0,0 @@
export interface Property {
key: string;
label: string;
type: 'slider' | 'boolean' | string; // Extend types as needed
value: any;
min?: number;
max?: number;
step?: number;
}
export type DisplayMode = 'Fill & Outline' | 'Outline Only' | 'Fill Only';
export type VisibilityState = 'Visible' | 'Hidden';
export type AvailabilityState = 'loaded' | 'available' | 'not available';
export interface Item {
id: number;
name: string;
controlsAll?: boolean; // Indicates if the item is the master
series?: string; // Optional, only for non-master items
displayMode: DisplayMode; // Existing property for display mode
visibility: VisibilityState; // Existing property for visibility
availability: AvailabilityState; // New property for availability
properties: Property[];
}

View File

@ -1,2 +1 @@
// src/components/BackgroundColorSelect/index.ts
export { default as BackgroundColorSelect } from './BackgroundColorSelect';

View File

@ -0,0 +1 @@
export { default as DataRow } from './DataRow';

View File

@ -1,10 +1,11 @@
import React from 'react';
import { Code } from 'lucide-react';
type IconProps = React.HTMLAttributes<SVGElement>;
export const Icons = {
// Usage example: <Icons.ArrowLeft />
Code: Code,
Add: (props: IconProps) => (
<svg
width="24px"

View File

@ -19,6 +19,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
);
}
);
Input.displayName = 'Input';
export { Input };

View File

@ -23,6 +23,7 @@ import {
DialogTitle,
DialogDescription,
} from './Dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select';
import { Combobox } from './Combobox';
import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './Popover';
@ -68,6 +69,8 @@ import {
DropdownMenuRadioGroup,
} from './DropdownMenu';
import { Onboarding } from './Onboarding';
import { BackgroundColorSelect } from './BackgroundColorSelect';
import { DataRow } from './DataRow';
export {
Button,
@ -147,4 +150,11 @@ export {
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
Onboarding,
BackgroundColorSelect,
DataRow,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
};

View File

@ -1,4 +1,5 @@
import {
BackgroundColorSelect,
Button,
buttonVariants,
ThemeWrapper,
@ -33,7 +34,37 @@ import {
DisplaySetMessageListTooltip,
Toolbox,
ToolboxUI,
ScrollArea,
Label,
Slider,
Input,
Switch,
Checkbox,
Onboarding,
PopoverAnchor,
PopoverContent,
PopoverTrigger,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
DataRow,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} from './components';
import { useNotification, NotificationProvider } from './contextProviders';
@ -45,6 +76,7 @@ export {
Command,
Popover,
Combobox,
Checkbox,
buttonVariants,
ThemeWrapper,
Calendar,
@ -77,5 +109,35 @@ export {
DisplaySetMessageListTooltip,
Toolbox,
ToolboxUI,
ScrollArea,
Label,
Slider,
Input,
Switch,
Onboarding,
PopoverAnchor,
PopoverContent,
PopoverTrigger,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
DataRow,
BackgroundColorSelect,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View File

@ -11,10 +11,6 @@
}
}
body {
background-color: #000;
}
/* Truncate 2 lines utility */
.truncate-2-lines {
overflow: hidden;

5191
yarn.lock

File diff suppressed because it is too large Load Diff