scicat-widget 26.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ # PythonBuild artifacts
2
+ build
3
+ dist
4
+ node_modules
5
+ *.egg-info
6
+ .DS_Store
7
+ src/*/_static
8
+
9
+ # Documentation
10
+ html
11
+ docs/generated
12
+
13
+ # Caches
14
+ *.ipynb_checkpoints
15
+ __pycache__/
16
+ .virtual_documents
17
+ .hypothesis
18
+ .pytest_cache
19
+ .ruff_cache
20
+ .mypy_cache
21
+
22
+ # Editor config / caches
23
+ .idea/
24
+ .vscode/
25
+ .vs/
26
+
27
+ # Environments
28
+ Pipfile
29
+ venv
30
+ .venv
31
+
32
+ # Ontologies
33
+ *.jsonld
34
+
35
+ # Data files
36
+ *.data
37
+ *.dat
38
+ *.csv
39
+ *.xye
40
+ *.h5
41
+ *.hdf5
42
+ *.hdf
43
+ *.nxs
44
+ *.raw
45
+ *.cif
46
+ *.rcif
47
+ *.ort
48
+ *.zip
49
+ *.sqw
50
+ *.nxspe
51
+ *.mtz
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, SciCat Project
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: scicat_widget
3
+ Version: 26.2.0
4
+ Summary: SciCat dataset widget
5
+ Project-URL: Bug Tracker, https://github.com/jl-wynen/scicat_widget/issues
6
+ Project-URL: Source, https://github.com/jl-wynen/scicat_widget
7
+ License-File: LICENSE
8
+ Classifier: Framework :: Jupyter
9
+ Classifier: Framework :: Jupyter :: JupyterLab
10
+ Classifier: Framework :: Jupyter :: JupyterLab :: 4
11
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
12
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: anywidget>=0.9.21
22
+ Requires-Dist: ipykernel>=6.30.1
23
+ Requires-Dist: jupyter-host-file-picker>=26.2.1
24
+ Requires-Dist: pydantic>=2.12
25
+ Requires-Dist: scitacean>=26.2.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # widget
29
+
30
+ ## Installation
31
+
32
+ ```sh
33
+ pip install widget
34
+ ```
35
+
36
+ or with [uv](https://github.com/astral-sh/uv):
37
+
38
+ ```sh
39
+ uv add widget
40
+ ```
41
+
42
+ ## Development
43
+
44
+ We recommend using [uv](https://github.com/astral-sh/uv) for development.
45
+ It will automatically manage virtual environments and dependencies for you.
46
+
47
+ ```sh
48
+ uv run jupyter lab example.ipynb
49
+ ```
50
+
51
+ Alternatively, create and manage your own virtual environment:
52
+
53
+ ```sh
54
+ python -m venv .venv
55
+ source .venv/bin/activate
56
+ pip install -e ".[dev]"
57
+ jupyter lab example.ipynb
58
+ ```
59
+
60
+ The widget front-end code bundles it's JavaScript dependencies. After setting up Python,
61
+ make sure to install these dependencies locally:
62
+
63
+ ```sh
64
+ npm install
65
+ ```
66
+
67
+ While developing, you can run the following in a separate terminal to automatically
68
+ rebuild JavaScript as you make changes:
69
+
70
+ ```sh
71
+ npm run dev
72
+ ```
73
+
74
+ Open `example.ipynb` in JupyterLab, VS Code, or your favorite editor
75
+ to start developing. Changes made in `js/` will be reflected
76
+ in the notebook.
@@ -0,0 +1,49 @@
1
+ # widget
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ pip install widget
7
+ ```
8
+
9
+ or with [uv](https://github.com/astral-sh/uv):
10
+
11
+ ```sh
12
+ uv add widget
13
+ ```
14
+
15
+ ## Development
16
+
17
+ We recommend using [uv](https://github.com/astral-sh/uv) for development.
18
+ It will automatically manage virtual environments and dependencies for you.
19
+
20
+ ```sh
21
+ uv run jupyter lab example.ipynb
22
+ ```
23
+
24
+ Alternatively, create and manage your own virtual environment:
25
+
26
+ ```sh
27
+ python -m venv .venv
28
+ source .venv/bin/activate
29
+ pip install -e ".[dev]"
30
+ jupyter lab example.ipynb
31
+ ```
32
+
33
+ The widget front-end code bundles it's JavaScript dependencies. After setting up Python,
34
+ make sure to install these dependencies locally:
35
+
36
+ ```sh
37
+ npm install
38
+ ```
39
+
40
+ While developing, you can run the following in a separate terminal to automatically
41
+ rebuild JavaScript as you make changes:
42
+
43
+ ```sh
44
+ npm run dev
45
+ ```
46
+
47
+ Open `example.ipynb` in JupyterLab, VS Code, or your favorite editor
48
+ to start developing. Changes made in `js/` will be reflected
49
+ in the notebook.
@@ -0,0 +1,128 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "scicat_widget"
7
+ dynamic = ["version"]
8
+ readme = "README.md"
9
+ description = "SciCat dataset widget"
10
+ classifiers = [
11
+ "Framework :: Jupyter",
12
+ "Framework :: Jupyter :: JupyterLab",
13
+ "Framework :: Jupyter :: JupyterLab :: 4",
14
+ "Framework :: Jupyter :: JupyterLab :: Extensions",
15
+ "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
16
+ "Intended Audience :: Science/Research",
17
+ "Natural Language :: English",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Scientific/Engineering",
22
+ "Typing :: Typed",
23
+ ]
24
+ requires-python = ">=3.11"
25
+ dependencies = [
26
+ "anywidget>=0.9.21",
27
+ "ipykernel>=6.30.1",
28
+ "jupyter-host-file-picker>=26.2.1",
29
+ "pydantic>=2.12",
30
+ "scitacean>=26.2.0",
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ { include-group = "test" },
36
+ "anywidget[dev]>=0.9.21",
37
+ "ipykernel>=6.30.1",
38
+ "jupyterlab>=4.5.0",
39
+ "mypy>=1.17",
40
+ "ruff>=0.14",
41
+ "watchfiles>=1.1"
42
+ ]
43
+ test = [
44
+ "pytest>=9",
45
+ ]
46
+
47
+ [project.urls]
48
+ # "Documentation" = "https://scicatproject.github.io/scitacean"
49
+ "Bug Tracker" = "https://github.com/jl-wynen/scicat_widget/issues"
50
+ "Source" = "https://github.com/jl-wynen/scicat_widget"
51
+
52
+ [tool.hatch.build]
53
+ only-packages = true
54
+ artifacts = ["src/scicat_widget/_static/*"]
55
+
56
+ [tool.hatch.build.hooks.jupyter-builder]
57
+ build-function = "hatch_jupyter_builder.npm_builder"
58
+ ensured-targets = ["src/scicat_widget/_static/datasetUploadWidget.js"]
59
+ skip-if-exists = ["src/scicat_widget/_static/datasetUploadWidget.js"]
60
+ dependencies = ["hatch-jupyter-builder>=0.5.0"]
61
+
62
+ [tool.hatch.build.hooks.jupyter-builder.build-kwargs]
63
+ npm = "npm"
64
+ build_cmd = "build"
65
+
66
+ [tool.hatch.version]
67
+ source = "vcs"
68
+
69
+ [tool.pytest.ini_options]
70
+ minversion = "7.0"
71
+ testpaths = "tests"
72
+ addopts = """
73
+ --strict-config
74
+ --strict-markers
75
+ -ra
76
+ -v
77
+ """
78
+ filterwarnings = [
79
+ "error",
80
+ ]
81
+
82
+ [tool.mypy]
83
+ plugins = "pydantic.mypy"
84
+ mypy_path = "src"
85
+ exclude = ["docs/conf.py", "venv", ".venv"]
86
+ ignore_missing_imports = true
87
+ enable_error_code = [
88
+ "ignore-without-code",
89
+ "redundant-expr",
90
+ "truthy-bool",
91
+ ]
92
+ strict = true
93
+ show_error_codes = true
94
+ warn_unreachable = true
95
+
96
+ [tool.pydantic-mypy]
97
+ init_forbid_extra = true
98
+ init_typed = true
99
+ warn_required_dynamic_aliases = true
100
+
101
+ [tool.ruff]
102
+ line-length = 88
103
+ extend-include = ["*.ipynb"]
104
+ extend-exclude = [".*", "__pycache__", "build", "dist", "venv"]
105
+
106
+ [tool.ruff.lint]
107
+ select = ["B", "C4", "D", "DTZ", "E", "F", "G", "I", "FBT003", "FURB", "PERF", "PGH", "PT", "PYI", "RUF", "S", "T20", "UP", "W"]
108
+ ignore = [
109
+ "D105", # most magic methods don't need docstrings as their purpose is always the same
110
+ "E741", "E742", "E743", # do not use names ‘l’, ‘O’, or ‘I’; they are not a problem with a proper font
111
+ # Conflict with ruff format, see
112
+ # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
113
+ "COM812", "COM819", "D206", "D300", "E111", "E114", "E117", "ISC001", "ISC002", "Q000", "Q001", "Q002", "Q003", "W191",
114
+ ]
115
+ fixable = ["B010", "I001", "PT001", "RUF022"]
116
+ isort.known-first-party = ["scicat_widget"]
117
+ pydocstyle.convention = "numpy"
118
+
119
+ [tool.ruff.lint.per-file-ignores]
120
+ "tests/*" = [
121
+ "S101", # asserts are fine in tests
122
+ "D10", # no docstrings required in tests
123
+ ]
124
+ "docs/*" = [
125
+ "D", "E402", "F811", "F841", "RUF015", "S101", "T201",
126
+ ]
127
+ "*.ipynb" = ["I"]
128
+ "docs/conf.py" = ["D10"]
@@ -0,0 +1,12 @@
1
+ """Jupyter widget for uploading datasets to SciCat."""
2
+
3
+ import importlib.metadata
4
+
5
+ from ._widgets import DatasetUploadWidget, dataset_upload_widget
6
+
7
+ try:
8
+ __version__ = importlib.metadata.version("scicat_widget")
9
+ except importlib.metadata.PackageNotFoundError:
10
+ __version__ = "0.0.0"
11
+
12
+ __all__ = ["DatasetUploadWidget", "dataset_upload_widget"]
@@ -0,0 +1,9 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2026 SciCat Project (https://github.com/SciCatProject/scitacean)
3
+
4
+ import logging
5
+
6
+
7
+ # TODO does not show up in Jupyter (need to configure handler)
8
+ def get_logger() -> logging.Logger:
9
+ return logging.getLogger("scicat-widget")
@@ -0,0 +1,24 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2026 SciCat Project (https://github.com/SciCatProject/scitacean)
3
+
4
+ from pydantic import BaseModel, EmailStr
5
+ from scitacean.model import Instrument
6
+
7
+
8
+ class ProposalOverview(BaseModel):
9
+ id_: str
10
+ title: str
11
+ instrument_ids: list[str]
12
+
13
+
14
+ class UserInfo(BaseModel):
15
+ user_id: str
16
+ display_name: str | None
17
+ email: EmailStr | None
18
+ access_groups: list[str]
19
+ orcid_id: str | None
20
+ # The proposals we know the user is a member of, they may have access to more:
21
+ proposals: list[ProposalOverview]
22
+
23
+
24
+ __all__ = ["Instrument", "ProposalOverview", "UserInfo"]
@@ -0,0 +1,68 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2026 SciCat Project (https://github.com/SciCatProject/scitacean)
3
+
4
+ from scitacean import Client
5
+
6
+ # Not great but there is no need to reimplement this here,
7
+ # and handling of ORCID iDs is not part of the core functionality of Scitacean.
8
+ from scitacean._internal.orcid import parse_orcid_id
9
+
10
+ from ._model import Instrument, ProposalOverview, UserInfo
11
+
12
+
13
+ def get_user_and_scicat_info(client: Client) -> tuple[UserInfo, list[Instrument]]:
14
+ user_info = get_user_info(client)
15
+ instruments = get_instruments(client)
16
+ return user_info, instruments
17
+
18
+
19
+ def get_user_info(client: Client) -> UserInfo:
20
+ identity = client.scicat.call_endpoint(
21
+ cmd="GET", url="users/my/identity", operation="get_user_info"
22
+ )
23
+ profile = identity["profile"]
24
+
25
+ access_groups = sorted(profile.get("accessGroups", []))
26
+ proposals = get_proposals(client, access_groups) if access_groups else []
27
+
28
+ return UserInfo(
29
+ user_id=identity["userId"],
30
+ display_name=profile.get("displayName", None),
31
+ email=profile.get("email", None),
32
+ access_groups=access_groups,
33
+ orcid_id=_maybe_parse_orcid_id(
34
+ profile.get("oidcClaims", {}).get("orcid", None)
35
+ ),
36
+ proposals=proposals,
37
+ )
38
+
39
+
40
+ def get_proposals(client: Client, access_groups: list[str]) -> list[ProposalOverview]:
41
+ proposals = client.scicat.call_endpoint(
42
+ cmd="GET", url="proposals", operation="get_proposals"
43
+ )
44
+ # the API call returns all proposals, select only the ones the user has access to:
45
+ # assuming the access groups match proposals (the case at ESS)
46
+ return [
47
+ ProposalOverview(
48
+ id_=p["proposalId"], title=p["title"], instrument_ids=p["instrumentIds"]
49
+ )
50
+ for p in proposals
51
+ if p["proposalId"] in access_groups
52
+ ]
53
+
54
+
55
+ def get_instruments(client: Client) -> list[Instrument]:
56
+ return [
57
+ Instrument.from_download_model(instrument)
58
+ for instrument in client.scicat.get_all_instrument_models()
59
+ ]
60
+
61
+
62
+ def _maybe_parse_orcid_id(orcid_id: str | None) -> str | None:
63
+ if orcid_id is None:
64
+ return None
65
+ try:
66
+ return parse_orcid_id(orcid_id)
67
+ except ValueError:
68
+ return None
@@ -0,0 +1 @@
1
+ var e={accessGroups:{label:"Access groups",description:"Groups with access to the dataset."},checksumAlgorithm:{label:"Checksum algorithm",description:"Algorithm for computing checksums of files. The default is a good choice for most cases."},creationLocation:{label:"Creation location",description:"Identifier for the place where data was taken, usually of the form 'site:facility:instrument'."},datasetName:{label:"Name",description:"The name of the dataset."},description:{label:"Description",description:"Free text explanation of the contents of the dataset."},endTime:{label:"End",description:"End time of data acquisition for the dataset."},instrumentId:{label:"Instrument",description:"The instrument where the data was taken."},isPublished:{label:"Published",description:"Check to make the dataset publicly available immediately."},keywords:{label:"Keywords",description:"Tags associated with the dataset for search and categorization. Values should ideally come from defined vocabularies, taxonomies, ontologies or knowledge graphs."},license:{label:"License",description:"Name of the license under which the data can be used."},owners:{label:"Owners",description:"People who own the dataset and its data. The owners typically have full access to the data and decide who can use it and how."},ownerGroup:{label:"Owner group",description:"The group that owns the dataset in SciCat and the data on disk. As uploader, you need to be a member of this group."},principalInvestigator:{label:"PI",description:"Principal investigator and contact person for the dataset."},proposalId:{label:"Proposal",description:"The proposal, if any, under which this data was recorded or computed."},relationships:{label:"Relationships",description:"Relationships with other datasets. In particular the 'input' relationship indicates datasets that were processed to obtain the dataset you are uploading."},runNumber:{label:"Run number",description:"Facility or instrument run number that this data belongs to."},sampleId:{label:"Sample ID",description:"SciCat identifier for the sample that was used to record the data."},scientificMetadata:{label:"Scientific metadata",description:"Arbitrary metadata describing the dataset. Choose what to include based on how you want to search for and identify the dataset later. The unit can be omitted if it does not apply."},sourceFolder:{label:"Source folder",description:"Absolute file path on the file server for a folder where the data files will be uploaded."},startTime:{label:"Start",description:"Start time of data acquisition for the dataset."},techniques:{label:"Techniques",description:"Techniques (experimental, analysis) used to produce the dataset."},type:{label:"Type",description:"Characterize the type of the dataset."},usedSoftware:{label:"Used software",description:"Software used to produce the dataset."}};function n(t){return e[t]??null}export{n as fieldInfo};
@@ -0,0 +1 @@
1
+ var o=class{constructor(e){this.callbacks=new Map;this.model=e,this.model.on("msg:custom",t=>{if(t.hasOwnProperty("type")){let s=t.key,r=this.callbacks.get(t.type)?.get(s);r&&r(t.payload);return}console.warn(`Unknown message type: ${t}`)})}sendReqInspectFile(e,t){this.model.send({type:"req:inspect-file",key:e,payload:t})}onResInspectFile(e,t){this.getForMethod("res:inspect-file").set(e,t)}offResInspectFile(e){this.getForMethod("res:inspect-file").delete(e)}sendReqBrowseFiles(e,t){this.model.send({type:"req:browse-files",key:e,payload:t})}onResBrowseFiles(e,t){this.getForMethod("res:browse-files").set(e,t)}offResBrowseFiles(e){this.getForMethod("res:browse-files").delete(e)}sendReqUploadDataset(e,t){this.model.send({type:"req:upload-dataset",key:e,payload:t})}onResUploadDataset(e,t){this.getForMethod("res:upload-dataset").set(e,t)}getForMethod(e){let t=this.callbacks.get(e);if(t!==void 0)return t;let s=new Map;return this.callbacks.set(e,s),s}};export{o as BackendComm};
@@ -0,0 +1 @@
1
+ .cean-ds-general-info{display:grid;grid-template-columns:max-content 1fr max-content 1fr;column-gap:12px;row-gap:10px;align-items:start;>*:nth-child(2n){min-width:0}& input,select,textarea{width:100%;box-sizing:border-box}>.cean-span-3{grid-column:span 3}}.cean-run-row{display:grid;grid-template-columns:1fr max-content max-content max-content max-content;column-gap:12px;row-gap:10px;align-items:start;>*:nth-child(odd){min-width:0}}.cean-ds-owner-columns{display:grid;grid-template-columns:3fr 2fr;column-gap:12px;row-gap:10px;align-items:start}.cean-ds-human-owners,.cean-ds-technical-owners{display:grid;grid-template-columns:max-content 1fr;column-gap:12px;row-gap:10px;align-items:start;>*:nth-child(2n){min-width:0}}.cean-single-owner{margin:.3em 0;display:grid;grid-template-columns:1fr max-content;column-gap:12px;row-gap:10px;align-items:start}.cean-ds-misc-columns{display:grid;grid-template-columns:3fr 2fr;column-gap:12px;row-gap:10px;align-items:start}.cean-ds-misc-left,.cean-ds-misc-right{display:grid;grid-template-columns:max-content 1fr;column-gap:12px;row-gap:10px;align-items:start;& input{width:100%;box-sizing:border-box}}table.cean-scientific-metadata-table{width:100%;border-collapse:collapse;& th:last-child,td:last-child{width:1%;white-space:nowrap;border:none;padding:0}& td:not(:last-child){border:1px solid var(--jp-border-color1);&:hover{box-shadow:inset 0 0 0 var(--jp-border-width) var(--jp-input-active-border-color)}&:focus-within{background:var(--jp-input-active-background);box-shadow:inset 0 0 0 var(--jp-border-width) var(--jp-input-active-border-color)}}& input{width:100%;border:none!important;background:unset!important;&:focus-visible{outline:none}}& button{margin:0 0 0 1rem}}label.cean-required{&:after{content:" *";color:var(--jp-error-color2)}}.cean-person-widget{display:grid;grid-template-columns:max-content 1fr;column-gap:12px;row-gap:10px;align-items:start}.cean-same-as-container{display:grid;grid-template-columns:max-content max-content 1fr;column-gap:5px;align-items:start}.cean-ds textarea{resize:vertical}.cean-datetime-input{display:grid;grid-template-columns:max-content max-content;column-gap:0;row-gap:10px;align-items:start}.cean-tab-buttons{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--jp-border-color1)}.cean-tab-buttons-left{padding:0 1em;& svg{height:1.8rem;width:auto;transform:translateY(.4em)}}.cean-tab-buttons-middle{display:flex;position:relative}.cean-tab-buttons-right{display:flex;align-items:center;padding-right:16px;margin-left:auto;& a{margin-right:1em}& a:hover{text-decoration:underline}}.cean-tab-button{background:none;border:none;padding:8px 16px;cursor:pointer;color:var(--jp-ui-font-color2)}.cean-tab-button:hover{background-color:var(--jp-layout-color2)}.cean-tab-button-active{color:var(--jp-ui-font-color1);background-color:var(--jp-layout-color1);font-weight:700;border-bottom:2px solid var(--jp-brand-color1)}.cean-tab-content{display:grid}.cean-tab-pane{grid-column:1;grid-row:1;padding:10px 0;visibility:hidden}.cean-input{background:var(--jp-cell-editor-background);color:var(--jp-content-font-color1);&::selection{background-color:var(--jp-editor-selected-focused-background)}}input.cean-input,select.cean-input,textarea.cean-input{width:100%;box-sizing:border-box;border:var(--jp-border-width) solid var(--jp-cell-editor-border-color);&:focus-visible{outline:none}&:focus{border-color:var(--jp-input-active-border-color);background-color:var(--jp-input-active-background)}&:hover:not(:disabled){border-color:var(--jp-input-active-border-color)}}input[type=checkbox].cean-input{width:auto;justify-self:start}.cean-input-status{display:none;font-size:var(--jp-ui-font-size0);min-height:1.2em;line-height:1.2em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.cean-input[data-valid=false]{border-color:var(--jp-error-color2);~.cean-input-status{display:block;color:var(--jp-error-color2)}}.cean-file-input+.cean-input-status{display:block}.cean-input-panel{background:rgba(from var(--jp-layout-color0) r g b / .7);border:1px solid var(--jp-border-color2);border-radius:6px;padding:6px;margin:.25em .5em}.cean-upload-button{background:#27aae1;color:var(--jp-ui-inverse-font-color1);&:hover{background:#3ec2fa;box-shadow:none!important}}.cean-modal-dialog{border:1px solid var(--jp-border-color1);border-radius:var(--jp-border-radius);box-shadow:var(--jp-elevation-z8);background:var(--jp-layout-color1);color:var(--jp-ui-font-color1);padding:0;min-width:min(30%,30em);max-width:50%;width:max-content;>div{padding:1em 2em}&::backdrop{backdrop-filter:brightness(80%)}& a{color:var(--jp-content-link-color)}& a:hover{color:var(--jp-content-link-color);text-decoration:underline}}.cean-dialog-header{margin-bottom:15px;font-size:var(--jp-ui-font-size3);font-weight:400;color:var(--jp-ui-font-color1)}.cean-dialog-body{margin-bottom:20px}.cean-dialog-footer{display:flex;justify-content:flex-end;gap:10px}.cean-warning{color:var(--jp-warn-color1)}.cean-warning-severe{background:var(--jp-warn-color2);color:var(--jp-ui-inverse-font-color0);padding:.3em 0;font-variant:all-small-caps;font-size:var(--jp-ui-font-size2)}.cean-button{background:var(--jp-input-background);color:var(--jp-ui-font-color1);border-radius:var(--jp-border-radius);border:none;cursor:pointer;&:hover{background:var(--jp-input-hover-background);color:var(--jp-ui-font-color0)}&[disabled=true]{background:var(--jp-input-background);color:var(--jp-ui-font-color2);cursor:unset}&:active{box-shadow:0 0 0 1px var(--jp-input-active-border-color)}}.cean-icon-button{&:active{box-shadow:none;background:var(--jp-input-active-background)}}.cean-button i+span{margin-left:.5em}button.cean-button-remove{background:none;&:hover{color:var(--jp-error-color1);background:none}&[disabled=true]{visibility:hidden}&:active{color:var(--jp-error-color2)}}.cean-combox-dropdown{position:relative}.cean-combox-search,.cean-combox-display{display:block;width:100%;box-sizing:border-box;border:var(--jp-border-width) solid var(--jp-cell-editor-border-color);color:var(--jp-content-font-color1);padding:.1rem .5rem;min-height:1.5rem;&:focus-visible{outline:none}&:focus{border-color:var(--jp-input-active-border-color)}}.cean-combox-display{cursor:text;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cean-combox-arrow{position:absolute;right:8px;top:50%;transform:translateY(-50%);cursor:default;color:var(--jp-ui-font-color2);font-size:.8rem}.cean-combox-list{position:absolute;display:none;top:100%;left:0;width:100%;box-sizing:border-box;z-index:100;border:var(--jp-border-width) solid var(--jp-input-border-color);border-top:none;background-color:var(--jp-notebook-select-background);max-height:16rem;overflow-y:scroll;overflow-x:hidden}.cean-combox-item{cursor:pointer;user-select:none;padding:.1em .5em;&:hover{background-color:var(--jp-editor-selected-background)}& span{padding-right:.5em}}.cean-combox-selected{background-color:var(--jp-layout-color2);font-weight:700}.cean-item-id{color:var(--jp-content-font-color3);font-family:monospace}.cean-file-input{display:grid;grid-template-columns:1fr max-content;& button{margin-left:1em}}.cean-browse-files-button{background:var(--jp-layout-color3);&:hover{background:var(--jp-layout-color4)}}.cean-files-widget{& input{width:100%;box-sizing:border-box}.cean-input-panel{margin-top:.5em}}.cean-files-summary{margin:0 .5em;padding:6px;font-size:.85rem;& span{margin-right:.5em}& span:nth-child(2){margin-right:2em}}.cean-single-file-widget{grid-template-columns:max-content 1fr max-content;margin:1em .5em .5em}.cean-single-file-widget,.cean-single-relationship-widget,.cean-single-owner{background:rgb(from var(--jp-layout-color2) r g b / .8);border:1px solid var(--jp-border-color2);padding:.5em}.cean-single-relationship-widget{grid-template-columns:max-content 1fr max-content;margin:.3em 0}.cean-relationships{grid-column:span 2}.cean-input-grid{display:grid;column-gap:12px;row-gap:10px;align-items:start}select.cean-chk-alg{width:10em!important}.cean-string-list-widget{display:flex;flex-direction:column;gap:8px}.cean-string-list-input-row{display:grid;column-gap:8px;grid-template-columns:1fr max-content}.cean-string-list-items{display:flex;flex-wrap:wrap;gap:4px}.cean-string-list-item{display:flex;align-items:center;background:var(--jp-layout-color2);padding:2px 8px;gap:4px;border:var(--jp-border-width) solid var(--jp-layout-color3);border-radius:var(--jp-border-radius)}.cean-string-list-item span{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cean-string-list-item .cean-button{padding:0;background:none;min-height:unset;font-size:.8em}.cean-techniques-widget{display:flex;flex-direction:column;gap:8px}.cean-techniques-items{display:flex;flex-direction:column;gap:4px}.cean-techniques-item{display:flex;background:var(--jp-layout-color2);padding:2px 8px;border:var(--jp-border-width) solid var(--jp-layout-color3);border-radius:var(--jp-border-radius)}.cean-techniques-item-content{flex-grow:1}.cean-techniques-item .cean-button{padding:0;background:none;min-height:unset;font-size:.8em}.cean-techniques-selected-item{display:flex;flex-direction:column;.cean-item-id{color:var(--jp-content-link-color);font-family:unset}.cean-item-id:hover{color:var(--jp-content-link-color);text-decoration:underline}}.cean-output-anchor{display:none}.cean-validation-error-list{& strong{color:var(--jp-error-color2);margin-right:.5em}}.cean-spinner{width:48px;height:48px;border:2px solid var(--jp-ui-font-color0);border-radius:50%;display:inline-block;position:relative;box-sizing:border-box;&:after{content:"";box-sizing:border-box;position:absolute;left:0;top:0;width:44px;height:44px;border-radius:50%;border:4px solid transparent;border-bottom-color:#ed6c6b;animation:cean-rotation 1.5s linear infinite}&:before{content:"";box-sizing:border-box;position:absolute;left:-6px;top:-6px;width:56px;height:56px;border-radius:50%;border:4px solid transparent;border-bottom-color:#58caed;animation:cean-rotation 2s linear infinite}}@keyframes cean-rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}