@openmrs/esm-styleguide 10.0.1-pre.5702 → 10.0.1-pre.5707
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/config-schema.js +2 -2
- package/dist/diagnosis-tags/diagnosis-tags.component.d.ts +10 -9
- package/dist/diagnosis-tags/diagnosis-tags.component.d.ts.map +1 -1
- package/dist/diagnosis-tags/diagnosis-tags.component.js +96 -17
- package/dist/diagnosis-tags/diagnosis-tags.module.scss +25 -1
- package/dist/diagnosis-tags/diagnosis-tags.stories.js +24 -0
- package/dist/public.d.ts +1 -1
- package/dist/public.d.ts.map +1 -1
- package/dist/public.js +1 -1
- package/dist/workspaces2/active-workspace-window.component.d.ts +11 -1
- package/dist/workspaces2/active-workspace-window.component.d.ts.map +1 -1
- package/dist/workspaces2/active-workspace-window.component.js +182 -71
- package/dist/workspaces2/exported-workspace.component.d.ts +42 -0
- package/dist/workspaces2/exported-workspace.component.d.ts.map +1 -0
- package/dist/workspaces2/exported-workspace.component.js +110 -0
- package/dist/workspaces2/exported-workspace.module.scss +8 -0
- package/dist/workspaces2/index.d.ts +1 -0
- package/dist/workspaces2/index.d.ts.map +1 -1
- package/dist/workspaces2/index.js +1 -0
- package/dist/workspaces2/workspace-window-actions.d.ts +36 -0
- package/dist/workspaces2/workspace-window-actions.d.ts.map +1 -0
- package/dist/workspaces2/workspace-window-actions.js +69 -0
- package/dist/workspaces2/workspace-windows-and-menu.component.d.ts.map +1 -1
- package/dist/workspaces2/workspace-windows-and-menu.component.js +19 -2
- package/dist/workspaces2/workspace2.component.d.ts +10 -0
- package/dist/workspaces2/workspace2.component.d.ts.map +1 -1
- package/dist/workspaces2/workspace2.component.js +12 -140
- package/dist/workspaces2/workspace2.d.ts +39 -13
- package/dist/workspaces2/workspace2.d.ts.map +1 -1
- package/dist/workspaces2/workspace2.js +173 -100
- package/mock-jest.tsx +12 -7
- package/mock.tsx +12 -7
- package/package.json +25 -25
- package/src/config-schema.ts +2 -2
- package/src/diagnosis-tags/diagnosis-tags.component.tsx +103 -23
- package/src/diagnosis-tags/diagnosis-tags.module.scss +25 -1
- package/src/diagnosis-tags/diagnosis-tags.stories.tsx +11 -0
- package/src/diagnosis-tags/diagnosis-tags.test.tsx +174 -0
- package/src/public.ts +3 -0
- package/src/workspaces2/active-workspace-window.component.tsx +241 -66
- package/src/workspaces2/active-workspace-window.test.tsx +160 -34
- package/src/workspaces2/exported-workspace.component.tsx +140 -0
- package/src/workspaces2/exported-workspace.module.scss +8 -0
- package/src/workspaces2/exported-workspace.test.tsx +363 -0
- package/src/workspaces2/index.tsx +1 -0
- package/src/workspaces2/workspace-window-actions.test.ts +93 -0
- package/src/workspaces2/workspace-window-actions.ts +105 -0
- package/src/workspaces2/workspace-windows-and-menu.component.tsx +29 -2
- package/src/workspaces2/workspace2.component.test.tsx +72 -0
- package/src/workspaces2/workspace2.component.tsx +24 -150
- package/src/workspaces2/workspace2.test.ts +171 -106
- package/src/workspaces2/workspace2.ts +215 -109
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
[0] Successfully compiled:
|
|
1
|
+
[0] Successfully compiled: 117 files, copied 170 files with swc (252.19ms)
|
|
2
2
|
[0] swc --strip-leading-paths --copy-files src -d dist && svgo -rf dist --quiet exited with code 0
|
|
3
3
|
[1] tsc --project tsconfig.build.json exited with code 0
|
package/dist/config-schema.js
CHANGED
|
@@ -4,7 +4,7 @@ const diagnosisTagConfigSchema = {
|
|
|
4
4
|
primaryColor: {
|
|
5
5
|
_type: Type.String,
|
|
6
6
|
_description: 'The color for displaying primary diagnoses tags',
|
|
7
|
-
_default: '
|
|
7
|
+
_default: 'blue',
|
|
8
8
|
_validators: [
|
|
9
9
|
validators.oneOf(carbonTagColors)
|
|
10
10
|
]
|
|
@@ -12,7 +12,7 @@ const diagnosisTagConfigSchema = {
|
|
|
12
12
|
secondaryColor: {
|
|
13
13
|
_type: Type.String,
|
|
14
14
|
_description: 'The color for displaying secondary diagnoses tags',
|
|
15
|
-
_default: '
|
|
15
|
+
_default: 'gray',
|
|
16
16
|
_validators: [
|
|
17
17
|
validators.oneOf(carbonTagColors)
|
|
18
18
|
]
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
interface DiagnosisTagsProps {
|
|
4
|
-
diagnoses: Array<Diagnosis
|
|
2
|
+
import type { Diagnosis } from '@openmrs/esm-emr-api';
|
|
3
|
+
export interface DiagnosisTagsProps {
|
|
4
|
+
diagnoses: Array<Pick<Diagnosis, 'uuid' | 'display' | 'rank' | 'certainty'>>;
|
|
5
|
+
/**
|
|
6
|
+
* Mark provisional diagnoses with a leading "?". Confirmed diagnoses, and any with missing or
|
|
7
|
+
* unknown certainty, show just the name. Off by default because diagnoses saved before
|
|
8
|
+
* certainty was captured are stored as provisional.
|
|
9
|
+
*/
|
|
10
|
+
showCertainty?: boolean;
|
|
5
11
|
}
|
|
6
|
-
/**
|
|
7
|
-
* This component takes a list of diagnoses and displays them as
|
|
8
|
-
* Carbon tags, with colors configured base on whether the diagnoses are primary
|
|
9
|
-
* or secondary.
|
|
10
|
-
*/
|
|
12
|
+
/** Displays diagnoses with configured rank colours, optionally marking provisional ones. */
|
|
11
13
|
export declare const DiagnosisTags: React.FC<DiagnosisTagsProps>;
|
|
12
|
-
export {};
|
|
13
14
|
//# sourceMappingURL=diagnosis-tags.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnosis-tags.component.d.ts","sourceRoot":"","sources":["../../src/diagnosis-tags/diagnosis-tags.component.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"diagnosis-tags.component.d.ts","sourceRoot":"","sources":["../../src/diagnosis-tags/diagnosis-tags.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAKjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKtD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,4FAA4F;AAC5F,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgBtD,CAAC"}
|
|
@@ -1,28 +1,107 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button, Tooltip, TooltipTrigger } from "react-aria-components";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
4
6
|
import { useConfig } from "@openmrs/esm-react-utils";
|
|
5
7
|
import styles from "./diagnosis-tags.module.scss";
|
|
6
|
-
/**
|
|
7
|
-
* This component takes a list of diagnoses and displays them as
|
|
8
|
-
* Carbon tags, with colors configured base on whether the diagnoses are primary
|
|
9
|
-
* or secondary.
|
|
10
|
-
*/ export const DiagnosisTags = ({ diagnoses })=>{
|
|
8
|
+
/** Displays diagnoses with configured rank colours, optionally marking provisional ones. */ export const DiagnosisTags = ({ diagnoses, showCertainty = false })=>{
|
|
11
9
|
const { diagnosisTags } = useConfig({
|
|
12
10
|
externalModuleName: '@openmrs/esm-styleguide'
|
|
13
11
|
});
|
|
14
12
|
return /*#__PURE__*/ _jsx("div", {
|
|
15
13
|
className: styles.container,
|
|
16
14
|
children: diagnoses.map((diagnosis)=>{
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
children: display
|
|
25
|
-
}, uuid);
|
|
15
|
+
const isProvisional = showCertainty && diagnosis.certainty === 'PROVISIONAL';
|
|
16
|
+
const color = diagnosis.rank === 1 ? diagnosisTags?.primaryColor ?? 'blue' : diagnosisTags?.secondaryColor ?? 'gray';
|
|
17
|
+
return /*#__PURE__*/ _jsx(DiagnosisPill, {
|
|
18
|
+
color: color,
|
|
19
|
+
isProvisional: isProvisional,
|
|
20
|
+
name: diagnosis.display
|
|
21
|
+
}, diagnosis.uuid);
|
|
26
22
|
})
|
|
27
23
|
});
|
|
28
24
|
};
|
|
25
|
+
function DiagnosisPill({ color, isProvisional, name }) {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
28
|
+
const [isTruncated, setIsTruncated] = useState(false);
|
|
29
|
+
const isInteractive = isTruncated || isFocused;
|
|
30
|
+
const labelRef = useRef(null);
|
|
31
|
+
useLayoutEffect(()=>{
|
|
32
|
+
const label = labelRef.current;
|
|
33
|
+
if (!label) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const measure = ()=>{
|
|
37
|
+
const truncated = label.scrollWidth > label.clientWidth;
|
|
38
|
+
setIsTruncated(truncated);
|
|
39
|
+
if (!truncated) {
|
|
40
|
+
setIsOpen(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
measure();
|
|
44
|
+
const observer = new ResizeObserver(measure);
|
|
45
|
+
observer.observe(label);
|
|
46
|
+
return ()=>observer.disconnect();
|
|
47
|
+
}, [
|
|
48
|
+
name,
|
|
49
|
+
isProvisional,
|
|
50
|
+
isInteractive
|
|
51
|
+
]);
|
|
52
|
+
const className = classNames('cds--tag', 'cds--tag--md', 'cds--layout--size-md', `cds--tag--${color}`, styles.diagnosisTag);
|
|
53
|
+
const content = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
54
|
+
children: [
|
|
55
|
+
isProvisional && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
56
|
+
children: [
|
|
57
|
+
/*#__PURE__*/ _jsx("span", {
|
|
58
|
+
"aria-hidden": "true",
|
|
59
|
+
className: styles.provisionalMark,
|
|
60
|
+
children: "?"
|
|
61
|
+
}),
|
|
62
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
63
|
+
className: "cds--visually-hidden",
|
|
64
|
+
children: [
|
|
65
|
+
getCoreTranslation('provisional', 'Provisional'),
|
|
66
|
+
" "
|
|
67
|
+
]
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
}),
|
|
71
|
+
/*#__PURE__*/ _jsx("span", {
|
|
72
|
+
ref: labelRef,
|
|
73
|
+
className: "cds--tag__label",
|
|
74
|
+
children: name
|
|
75
|
+
})
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
if (!isInteractive) {
|
|
79
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
80
|
+
className: className,
|
|
81
|
+
"data-testid": "diagnosis-tag",
|
|
82
|
+
children: content
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return /*#__PURE__*/ _jsxs(TooltipTrigger, {
|
|
86
|
+
delay: 0,
|
|
87
|
+
closeDelay: 150,
|
|
88
|
+
isOpen: isOpen,
|
|
89
|
+
onOpenChange: setIsOpen,
|
|
90
|
+
children: [
|
|
91
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
92
|
+
className: className,
|
|
93
|
+
"data-testid": "diagnosis-tag",
|
|
94
|
+
onFocusChange: setIsFocused,
|
|
95
|
+
onPress: ()=>setIsOpen(true),
|
|
96
|
+
children: content
|
|
97
|
+
}),
|
|
98
|
+
/*#__PURE__*/ _jsx(Tooltip, {
|
|
99
|
+
className: styles.tagTooltip,
|
|
100
|
+
placement: "bottom",
|
|
101
|
+
offset: 4,
|
|
102
|
+
containerPadding: 8,
|
|
103
|
+
children: name
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -1,8 +1,32 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
1
2
|
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
2
4
|
|
|
3
5
|
.container {
|
|
4
6
|
display: flex;
|
|
5
|
-
flex-
|
|
7
|
+
flex-wrap: wrap;
|
|
6
8
|
align-items: center;
|
|
7
9
|
gap: layout.$spacing-02;
|
|
8
10
|
}
|
|
11
|
+
|
|
12
|
+
.diagnosisTag:focus-visible {
|
|
13
|
+
outline: 2px solid colors.$blue-60;
|
|
14
|
+
outline-offset: 1px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.tagTooltip {
|
|
18
|
+
@include type.type-style('body-compact-01');
|
|
19
|
+
max-width: min(18rem, calc(100vw - 1rem));
|
|
20
|
+
overflow-wrap: anywhere;
|
|
21
|
+
padding: layout.$spacing-01 layout.$spacing-03;
|
|
22
|
+
border-radius: 2px;
|
|
23
|
+
background-color: colors.$gray-80;
|
|
24
|
+
color: colors.$white-0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The name truncates; the "?" marking a provisional diagnosis must stay fully visible inside the tag
|
|
28
|
+
.provisionalMark {
|
|
29
|
+
margin-inline-end: layout.$spacing-01;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
}
|
|
@@ -71,3 +71,27 @@ export const MultipleDiagnoses = {
|
|
|
71
71
|
]
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
+
export const WithCertainty = {
|
|
75
|
+
args: {
|
|
76
|
+
showCertainty: true,
|
|
77
|
+
diagnoses: [
|
|
78
|
+
{
|
|
79
|
+
uuid: '1',
|
|
80
|
+
display: 'Extrinsic Asthma with Asthma Attack',
|
|
81
|
+
rank: 1,
|
|
82
|
+
certainty: 'CONFIRMED'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
uuid: '2',
|
|
86
|
+
display: 'Malaria due to Plasmodium falciparum',
|
|
87
|
+
rank: 2,
|
|
88
|
+
certainty: 'PROVISIONAL'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
uuid: '3',
|
|
92
|
+
display: 'Anemia',
|
|
93
|
+
rank: 2
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
};
|
package/dist/public.d.ts
CHANGED
|
@@ -23,5 +23,5 @@ export * from './pictograms/pictograms';
|
|
|
23
23
|
export * from './responsive-wrapper';
|
|
24
24
|
export { showSnackbar, type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars';
|
|
25
25
|
export { showToast, type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts';
|
|
26
|
-
export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, type Workspace2Definition, type Workspace2DefinitionProps, } from './workspaces2';
|
|
26
|
+
export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, type Workspace2Definition, type Workspace2DefinitionProps, ExportedWorkspace, type ExportedWorkspaceProps, type ExportedWorkspaceWindowInfo, } from './workspaces2';
|
|
27
27
|
//# sourceMappingURL=public.d.ts.map
|
package/dist/public.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,cAAc,SAAS,CAAC;AACxB,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,GAChC,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAClH,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,cAAc,SAAS,CAAC;AACxB,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,GAChC,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAClH,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,GACjC,MAAM,eAAe,CAAC"}
|
package/dist/public.js
CHANGED
|
@@ -20,4 +20,4 @@ export * from "./pictograms/pictograms.js";
|
|
|
20
20
|
export * from "./responsive-wrapper/index.js";
|
|
21
21
|
export { showSnackbar } from "./snackbars/index.js";
|
|
22
22
|
export { showToast } from "./toasts/index.js";
|
|
23
|
-
export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2 } from "./workspaces2/index.js";
|
|
23
|
+
export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, ExportedWorkspace } from "./workspaces2/index.js";
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type OpenedWindow } from '@openmrs/esm-extensions';
|
|
3
|
+
import { type WorkspaceWindowActions } from './workspace-window-actions';
|
|
3
4
|
interface WorkspaceWindowProps {
|
|
4
5
|
openedWindow: OpenedWindow;
|
|
6
|
+
groupProps: Record<string, any> | null;
|
|
7
|
+
actions: WorkspaceWindowActions;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to render the workspace window "chrome" (containers, title bar, maximize / hide / close).
|
|
10
|
+
* Only the global workspace window system renders it; an `<ExportedWorkspace>` renders the bare
|
|
11
|
+
* workspace content.
|
|
12
|
+
*/
|
|
13
|
+
renderChrome: boolean;
|
|
5
14
|
showActionMenu: boolean;
|
|
6
15
|
}
|
|
7
16
|
/**
|
|
8
|
-
* Renders an opened workspace window.
|
|
17
|
+
* Renders an opened workspace window. `actions` determines which store the window's workspaces
|
|
18
|
+
* navigate within: the global workspace window system, or an `<ExportedWorkspace>`'s local store.
|
|
9
19
|
*/
|
|
10
20
|
declare const ActiveWorkspaceWindow: React.FC<WorkspaceWindowProps>;
|
|
11
21
|
export default ActiveWorkspaceWindow;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAM5E,OAAO,EACL,KAAK,YAAY,EAKlB,MAAM,yBAAyB,CAAC;AAOjC,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGzE,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,EAAE,sBAAsB,CAAC;IAChC;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB;AAKD;;;GAGG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4DzD,CAAC;AAoRF,eAAe,qBAAqB,CAAC"}
|
|
@@ -2,15 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import React, { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
4
|
import Parcel from "single-spa-react/parcel";
|
|
5
|
-
import { InlineLoading } from "@carbon/react";
|
|
5
|
+
import { Header, HeaderGlobalAction, HeaderGlobalBar, HeaderName, InlineLoading } from "@carbon/react";
|
|
6
|
+
import { DownToBottom, Maximize, Minimize } from "@carbon/react/icons";
|
|
6
7
|
import { createParcelMounter, workspace2Store } from "@openmrs/esm-extensions";
|
|
8
|
+
import { isDesktop, useLayoutType, useStore } from "@openmrs/esm-react-utils";
|
|
7
9
|
import { loadLifeCycles } from "@openmrs/esm-routes";
|
|
8
10
|
import { getCoreTranslation } from "@openmrs/esm-translations";
|
|
9
|
-
import {
|
|
11
|
+
import { ArrowRightIcon, CloseIcon } from "../icons/index.js";
|
|
12
|
+
import { closeWorkspaceGroup2, useWorkspace2Store } from "./workspace2.js";
|
|
10
13
|
import styles from "./workspace2.module.scss";
|
|
14
|
+
// module-level so the store subscription's snapshot getter stays stable across renders
|
|
15
|
+
const selectRegisteredWorkspacesByName = (state)=>state.registeredWorkspacesByName;
|
|
11
16
|
/**
|
|
12
|
-
* Renders an opened workspace window.
|
|
13
|
-
|
|
17
|
+
* Renders an opened workspace window. `actions` determines which store the window's workspaces
|
|
18
|
+
* navigate within: the global workspace window system, or an `<ExportedWorkspace>`'s local store.
|
|
19
|
+
*/ const ActiveWorkspaceWindow = ({ openedWindow, groupProps, actions, renderChrome, showActionMenu })=>{
|
|
14
20
|
const { openedWorkspaces } = openedWindow;
|
|
15
21
|
// Keyed by opened workspace uuid rather than stack index. The lifecycles load
|
|
16
22
|
// asynchronously, so when the stack changes (a workspace at some position is
|
|
@@ -20,30 +26,39 @@ import styles from "./workspace2.module.scss";
|
|
|
20
26
|
// mounts whatever config it first receives, so the wrong component would then
|
|
21
27
|
// render with the new workspace's props.
|
|
22
28
|
const [lifeCyclesByUuid, setLifeCyclesByUuid] = useState({});
|
|
23
|
-
|
|
29
|
+
// Reactive so the effect retries when a workspace registers after mount.
|
|
30
|
+
const registeredWorkspacesByName = useStore(workspace2Store, selectRegisteredWorkspacesByName);
|
|
24
31
|
useEffect(()=>{
|
|
25
32
|
let cancelled = false;
|
|
26
33
|
Promise.all(openedWorkspaces.map(async (openedWorkspace)=>{
|
|
27
|
-
const
|
|
34
|
+
const workspaceDef = registeredWorkspacesByName[openedWorkspace.workspaceName];
|
|
35
|
+
if (!workspaceDef) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const { moduleName, component } = workspaceDef;
|
|
28
39
|
return [
|
|
29
40
|
openedWorkspace.uuid,
|
|
30
41
|
await loadLifeCycles(moduleName, component)
|
|
31
42
|
];
|
|
32
43
|
})).then((entries)=>{
|
|
33
44
|
if (!cancelled) {
|
|
34
|
-
setLifeCyclesByUuid(Object.fromEntries(entries));
|
|
45
|
+
setLifeCyclesByUuid(Object.fromEntries(entries.filter((entry)=>entry != null)));
|
|
35
46
|
}
|
|
36
47
|
});
|
|
37
48
|
return ()=>{
|
|
38
49
|
cancelled = true;
|
|
39
50
|
};
|
|
40
51
|
}, [
|
|
41
|
-
openedWorkspaces
|
|
52
|
+
openedWorkspaces,
|
|
53
|
+
registeredWorkspacesByName
|
|
42
54
|
]);
|
|
43
55
|
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
44
56
|
children: openedWorkspaces.map((openedWorkspace, i)=>/*#__PURE__*/ _jsx(ActiveWorkspace, {
|
|
45
57
|
openedWorkspace: openedWorkspace,
|
|
46
58
|
openedWindow: openedWindow,
|
|
59
|
+
groupProps: groupProps,
|
|
60
|
+
actions: actions,
|
|
61
|
+
renderChrome: renderChrome,
|
|
47
62
|
lifeCycle: lifeCyclesByUuid[openedWorkspace.uuid],
|
|
48
63
|
isRootWorkspace: i === 0,
|
|
49
64
|
isLeafWorkspace: i === openedWorkspaces.length - 1,
|
|
@@ -52,31 +67,21 @@ import styles from "./workspace2.module.scss";
|
|
|
52
67
|
});
|
|
53
68
|
};
|
|
54
69
|
const mountParcel = createParcelMounter();
|
|
55
|
-
const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorkspace, isLeafWorkspace, showActionMenu })=>{
|
|
56
|
-
const { openedGroup, closeWorkspace, openChildWorkspace } = useWorkspace2Store();
|
|
70
|
+
const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, groupProps, actions, renderChrome, isRootWorkspace, isLeafWorkspace, showActionMenu })=>{
|
|
57
71
|
const props = useMemo(()=>openedWorkspace && {
|
|
58
72
|
closeWorkspace: async (options = {})=>{
|
|
59
73
|
const { closeWindow = false, discardUnsavedChanges = false } = options;
|
|
60
74
|
if (closeWindow) {
|
|
61
|
-
const okToCloseWorkspaces = discardUnsavedChanges || await promptForClosingWorkspaces(
|
|
62
|
-
reason: 'CLOSE_WINDOW',
|
|
63
|
-
explicit: true,
|
|
64
|
-
windowName: openedWindow.windowName
|
|
65
|
-
});
|
|
75
|
+
const okToCloseWorkspaces = discardUnsavedChanges || await actions.promptForClosingWorkspaces();
|
|
66
76
|
if (okToCloseWorkspaces) {
|
|
67
|
-
closeWorkspace(openedWindow.openedWorkspaces[0].workspaceName);
|
|
77
|
+
actions.closeWorkspace(openedWindow.openedWorkspaces[0].workspaceName);
|
|
68
78
|
return true;
|
|
69
79
|
}
|
|
70
80
|
return false;
|
|
71
81
|
} else {
|
|
72
|
-
const okToCloseWorkspaces = discardUnsavedChanges || await promptForClosingWorkspaces(
|
|
73
|
-
reason: 'CLOSE_WORKSPACE',
|
|
74
|
-
explicit: true,
|
|
75
|
-
windowName: openedWindow.windowName,
|
|
76
|
-
workspaceName: openedWorkspace.workspaceName
|
|
77
|
-
});
|
|
82
|
+
const okToCloseWorkspaces = discardUnsavedChanges || await actions.promptForClosingWorkspaces(openedWorkspace.workspaceName);
|
|
78
83
|
if (okToCloseWorkspaces) {
|
|
79
|
-
closeWorkspace(openedWorkspace.workspaceName);
|
|
84
|
+
actions.closeWorkspace(openedWorkspace.workspaceName);
|
|
80
85
|
return true;
|
|
81
86
|
}
|
|
82
87
|
return false;
|
|
@@ -95,23 +100,20 @@ const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorks
|
|
|
95
100
|
// Prompt if any of them have unsaved changes.
|
|
96
101
|
const workspacesAboveParent = openedWorkspaces.slice(parentIndex + 1);
|
|
97
102
|
if (workspacesAboveParent.some((w)=>w.hasUnsavedChanges)) {
|
|
98
|
-
const okToClose = await promptForClosingWorkspaces(
|
|
99
|
-
reason: 'CLOSE_WORKSPACE',
|
|
100
|
-
explicit: true,
|
|
101
|
-
windowName: openedWindow.windowName,
|
|
102
|
-
workspaceName: openedWorkspaces[parentIndex + 1].workspaceName
|
|
103
|
-
});
|
|
103
|
+
const okToClose = await actions.promptForClosingWorkspaces(openedWorkspaces[parentIndex + 1].workspaceName);
|
|
104
104
|
if (!okToClose) {
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
openChildWorkspace(parentWorkspaceName, childWorkspaceName, childWorkspaceProps || {});
|
|
109
|
+
actions.openChildWorkspace(parentWorkspaceName, childWorkspaceName, childWorkspaceProps || {});
|
|
110
110
|
},
|
|
111
|
+
setWorkspaceTitle: (title)=>actions.setWorkspaceTitle(openedWorkspace.uuid, title),
|
|
112
|
+
setHasUnsavedChanges: (hasUnsavedChanges)=>actions.setHasUnsavedChanges(openedWorkspace.uuid, hasUnsavedChanges),
|
|
111
113
|
workspaceName: openedWorkspace.workspaceName,
|
|
112
114
|
workspaceProps: openedWorkspace.props,
|
|
113
115
|
windowProps: openedWindow.props,
|
|
114
|
-
groupProps
|
|
116
|
+
groupProps,
|
|
115
117
|
workspaceMeta: workspace2Store.getState().registeredWorkspacesByName[openedWorkspace.workspaceName]?.meta ?? {},
|
|
116
118
|
isRootWorkspace,
|
|
117
119
|
isLeafWorkspace,
|
|
@@ -119,51 +121,160 @@ const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorks
|
|
|
119
121
|
showActionMenu
|
|
120
122
|
}, [
|
|
121
123
|
openedWorkspace,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
openedWindow,
|
|
125
|
+
groupProps,
|
|
126
|
+
actions,
|
|
127
|
+
isRootWorkspace,
|
|
128
|
+
isLeafWorkspace,
|
|
129
|
+
showActionMenu
|
|
130
|
+
]);
|
|
131
|
+
// In bare mode (<ExportedWorkspace>) there is no chrome to stack workspaces, so every non-leaf
|
|
132
|
+
// workspace is hidden (not unmounted, so it keeps its state) and the leaf covers them. In chrome
|
|
133
|
+
// mode the absolute-positioned WorkspaceChrome containers handle stacking, so this class is not
|
|
134
|
+
// applied there.
|
|
135
|
+
const hideBehindLeaf = !renderChrome && !isLeafWorkspace;
|
|
136
|
+
const content = useMemo(()=>lifeCycle ? /*#__PURE__*/ _jsx(Parcel, {
|
|
137
|
+
config: lifeCycle,
|
|
138
|
+
mountParcel: mountParcel,
|
|
139
|
+
wrapWith: "div",
|
|
140
|
+
wrapClassName: classNames(styles.workspaceContent, {
|
|
141
|
+
[styles.hiddenExtraWorkspace]: hideBehindLeaf
|
|
142
|
+
}),
|
|
143
|
+
...props
|
|
144
|
+
}, openedWorkspace.workspaceName) : null, [
|
|
145
|
+
lifeCycle,
|
|
146
|
+
openedWorkspace.workspaceName,
|
|
147
|
+
props,
|
|
148
|
+
hideBehindLeaf
|
|
125
149
|
]);
|
|
126
|
-
if (!
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
150
|
+
if (!renderChrome) {
|
|
151
|
+
return content ?? /*#__PURE__*/ _jsx(InlineLoading, {
|
|
152
|
+
className: classNames(styles.loader, {
|
|
153
|
+
[styles.hiddenExtraWorkspace]: hideBehindLeaf
|
|
154
|
+
}),
|
|
155
|
+
description: `${getCoreTranslation('loading')}`
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return /*#__PURE__*/ _jsx(WorkspaceChrome, {
|
|
159
|
+
openedWorkspace: openedWorkspace,
|
|
160
|
+
openedWindow: openedWindow,
|
|
161
|
+
isRootWorkspace: isRootWorkspace,
|
|
162
|
+
showActionMenu: showActionMenu,
|
|
163
|
+
closeWindow: ()=>props.closeWorkspace({
|
|
164
|
+
closeWindow: true
|
|
165
|
+
}),
|
|
166
|
+
children: content
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Renders the containers, title bar and window buttons around a workspace in the global workspace
|
|
171
|
+
* window system.
|
|
172
|
+
*/ const WorkspaceChrome = ({ openedWorkspace, openedWindow, isRootWorkspace, showActionMenu, closeWindow, children })=>{
|
|
173
|
+
const layout = useLayoutType();
|
|
174
|
+
const { openedGroup, openedWindows, registeredWindowsByName, registeredGroupsByName, isMostRecentlyOpenedWindowHidden, setWindowMaximized, hideWindow } = useWorkspace2Store();
|
|
175
|
+
const windowName = openedWindow.windowName;
|
|
176
|
+
const { maximized } = openedWindow;
|
|
177
|
+
const title = openedWorkspace.title ?? '';
|
|
178
|
+
const windowDef = registeredWindowsByName[windowName];
|
|
179
|
+
const width = windowDef?.width ?? 'narrow';
|
|
180
|
+
const icon = windowDef?.icon;
|
|
181
|
+
const canMaximize = windowDef?.canMaximize;
|
|
182
|
+
const canCloseGroup = openedGroup ? registeredGroupsByName[openedGroup.groupName]?.persistence === 'closable' : false;
|
|
183
|
+
const canHide = !!icon && !canCloseGroup;
|
|
184
|
+
const openedWindowIndex = openedWindows.findIndex((w)=>w.windowName === windowName);
|
|
185
|
+
const isWindowHidden = openedWindowIndex < openedWindows.length - 1 || isMostRecentlyOpenedWindowHidden;
|
|
186
|
+
const isActionMenuOpened = Object.values(registeredWindowsByName).some((window)=>window.group === openedGroup?.groupName && window.icon !== undefined);
|
|
187
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
188
|
+
className: classNames(styles.workspaceOuterContainer, {
|
|
189
|
+
[styles.narrowWorkspace]: width === 'narrow',
|
|
190
|
+
[styles.widerWorkspace]: width === 'wider',
|
|
191
|
+
[styles.extraWideWorkspace]: width === 'extra-wide',
|
|
192
|
+
[styles.isActionMenuOpened]: isActionMenuOpened
|
|
193
|
+
}),
|
|
194
|
+
children: [
|
|
195
|
+
/*#__PURE__*/ _jsx("div", {
|
|
196
|
+
className: classNames(styles.workspaceSpacer, {
|
|
197
|
+
[styles.hidden]: isWindowHidden
|
|
198
|
+
})
|
|
140
199
|
}),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
200
|
+
/*#__PURE__*/ _jsx("div", {
|
|
201
|
+
className: classNames(styles.workspaceMiddleContainer, {
|
|
202
|
+
[styles.maximized]: maximized,
|
|
203
|
+
[styles.hidden]: isWindowHidden,
|
|
204
|
+
[styles.isRootWorkspace]: isRootWorkspace,
|
|
205
|
+
[styles.showActionMenu]: showActionMenu
|
|
144
206
|
}),
|
|
145
|
-
/*#__PURE__*/ _jsx("div", {
|
|
146
|
-
className: classNames(styles.
|
|
147
|
-
[styles.
|
|
148
|
-
[styles.
|
|
207
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
208
|
+
className: classNames(styles.workspaceInnerContainer, {
|
|
209
|
+
[styles.maximized]: maximized,
|
|
210
|
+
[styles.hidden]: isWindowHidden,
|
|
211
|
+
[styles.isRootWorkspace]: isRootWorkspace
|
|
149
212
|
}),
|
|
150
|
-
children: /*#__PURE__*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
213
|
+
children: children ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
214
|
+
children: [
|
|
215
|
+
/*#__PURE__*/ _jsxs(Header, {
|
|
216
|
+
"aria-label": getCoreTranslation('workspaceHeader'),
|
|
217
|
+
className: styles.header,
|
|
218
|
+
children: [
|
|
219
|
+
/*#__PURE__*/ _jsx(HeaderName, {
|
|
220
|
+
prefix: "",
|
|
221
|
+
children: title
|
|
222
|
+
}),
|
|
223
|
+
/*#__PURE__*/ _jsx("div", {
|
|
224
|
+
className: styles.overlayHeaderSpacer
|
|
225
|
+
}),
|
|
226
|
+
/*#__PURE__*/ _jsx(HeaderGlobalBar, {
|
|
227
|
+
className: styles.headerButtons,
|
|
228
|
+
children: isDesktop(layout) ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
229
|
+
children: [
|
|
230
|
+
(canMaximize || maximized) && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
|
|
231
|
+
"aria-label": maximized ? getCoreTranslation('minimize') : getCoreTranslation('maximize'),
|
|
232
|
+
onClick: ()=>setWindowMaximized(windowName, !maximized),
|
|
233
|
+
children: maximized ? /*#__PURE__*/ _jsx(Minimize, {}) : /*#__PURE__*/ _jsx(Maximize, {})
|
|
234
|
+
}),
|
|
235
|
+
canHide && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
|
|
236
|
+
"aria-label": getCoreTranslation('hide'),
|
|
237
|
+
onClick: ()=>hideWindow(),
|
|
238
|
+
children: /*#__PURE__*/ _jsx(ArrowRightIcon, {})
|
|
239
|
+
}),
|
|
240
|
+
!canCloseGroup && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
|
|
241
|
+
"aria-label": getCoreTranslation('close'),
|
|
242
|
+
onClick: closeWindow,
|
|
243
|
+
children: /*#__PURE__*/ _jsx(CloseIcon, {})
|
|
244
|
+
})
|
|
245
|
+
]
|
|
246
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
247
|
+
children: [
|
|
248
|
+
canHide && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
|
|
249
|
+
"aria-label": getCoreTranslation('hide'),
|
|
250
|
+
onClick: ()=>hideWindow(),
|
|
251
|
+
children: /*#__PURE__*/ _jsx(DownToBottom, {})
|
|
252
|
+
}),
|
|
253
|
+
/*#__PURE__*/ _jsx(HeaderGlobalAction, {
|
|
254
|
+
"aria-label": getCoreTranslation('close'),
|
|
255
|
+
onClick: ()=>{
|
|
256
|
+
if (canCloseGroup) {
|
|
257
|
+
void closeWorkspaceGroup2();
|
|
258
|
+
} else {
|
|
259
|
+
closeWindow();
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
children: /*#__PURE__*/ _jsx(CloseIcon, {})
|
|
263
|
+
})
|
|
264
|
+
]
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
}),
|
|
269
|
+
children
|
|
270
|
+
]
|
|
271
|
+
}) : /*#__PURE__*/ _jsx(InlineLoading, {
|
|
272
|
+
className: styles.loader,
|
|
273
|
+
description: `${getCoreTranslation('loading')} ...`
|
|
158
274
|
})
|
|
159
275
|
})
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
return /*#__PURE__*/ _jsx(Parcel, {
|
|
164
|
-
config: lifeCycle,
|
|
165
|
-
mountParcel: mountParcel,
|
|
166
|
-
...props
|
|
167
|
-
}, openedWorkspace.workspaceName);
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
});
|
|
168
279
|
};
|
|
169
280
|
export default ActiveWorkspaceWindow;
|