Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/apps/esm-implementer-tools-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"@carbon/react": "^1.83.0",
"@dnd-kit/sortable": "^10.0.0",
"lodash-es": "^4.17.21"
},
"peerDependencies": {
Expand All @@ -51,6 +52,7 @@
"swr": "2.x"
},
"devDependencies": {
"@dnd-kit/core": "^6.3.1",
"@openmrs/esm-framework": "workspace:*",
"@openmrs/webpack-config": "workspace:*",
"ace-builds": "^1.4.14",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ import React from 'react';
import { StructuredListCell, StructuredListRow } from '@carbon/react';
import styles from './display-value.scss';

export type CustomValueType = 'add' | 'remove' | 'order' | 'configure';

export interface DisplayValueProps {
value: any;
customType?: CustomValueType;
}

export function DisplayValue({ value }: DisplayValueProps) {
export function DisplayValue({ value, customType }: DisplayValueProps) {
if (Array.isArray(value) && (customType === 'add' || customType === 'remove' || customType === 'order')) {
return (
<div>
{value.map((slot, idx) => (
<div key={slot}>
{idx + 1}. <code>{slot}</code> {customType === 'remove' ? 'removed' : customType === 'add' ? 'added' : ''}
</div>
))}
</div>
);
}
// TODO: Make this show the concept name for ConceptUUID type values
return (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default function EditableValue({ path, element, customType }: EditableVal
</>
) : (
<div className={styles.elementValue}>
<DisplayValue value={element._value} />
<DisplayValue value={element._value} customType={customType} />
<Button
kind="ghost"
size="sm"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@use '@carbon/colors';
@use '@carbon/layout';
@use '@carbon/type';

.extension {
display: flex;
flex-direction: column;
}

.iconAndName {
display: flex;
align-items: center;
}

.dragContainer {
display: flex;
min-height: layout.$spacing-09;
justify-content: space-between;
width: 100%;
}

.dragContainerWhenDragging {
@extend .dragContainer;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
background-color: rgba(255, 255, 255, 0.552);
transform: scale(0.75);
}

.dragIcon {
margin-right: layout.$spacing-02;
:focus {
border: none !important;
}
}

.droppable {
border: layout.$spacing-01 solid transparent;
}

.dropZone {
border: 2px solid colors.$teal-40;
&::before {
opacity: 0.4;
}
}

.extensionsList {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,117 @@
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import classNames from 'classnames';
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useTranslation } from 'react-i18next';
import { IconButton } from '@carbon/react';
import { Draggable } from '@carbon/react/icons';
import { useAssignedExtensions } from '@openmrs/esm-framework';
import styles from './extension-slot-order.scss';

export function ExtensionSlotOrder({ slotName, slotModuleName, value, setValue }) {
return <div>Todo: compose array with extension lookup</div>;
interface ExtensionSlotOrderProps {
slotName: string;
slotModuleName: string;
value: string[];
setValue: (value: string[]) => void;
}

interface DraggableExtensionProps {
extension: { id: string; label: string };
index: number;
extensionCount: number;
}

const DraggableExtension: React.FC<DraggableExtensionProps> = ({ extension, index, extensionCount }) => {
const { t } = useTranslation();
const defaultEnterDelayInMs = 300;

const { attributes, listeners, setNodeRef, active, isDragging, over } = useSortable({
id: extension.id,
data: { type: 'extension' },
disabled: extensionCount <= 1,
});

return (
<div
ref={setNodeRef}
className={classNames(styles.extension, {
[styles.dragContainer]: true,
[styles.dragContainerWhenDragging]: isDragging,
[styles.dropZone]: over?.id === extension.id,
[styles.droppable]: active?.id === extension.id,
})}
>
<div>
<div className={styles.iconAndName}>
<div {...attributes} {...listeners}>
<IconButton
className={styles.dragIcon}
enterDelayMs={over ? 6000 : defaultEnterDelayInMs}
label={t('dragToReorder', 'Drag to reorder')}
kind="ghost"
size="md"
>
<Draggable />
</IconButton>
</div>
<p>{extension.label}</p>
</div>
</div>
</div>
);
};

export function ExtensionSlotOrder({ slotName, slotModuleName, value, setValue }: ExtensionSlotOrderProps) {
const { t } = useTranslation();
const assignedExtensions = useAssignedExtensions(slotName);
const [orderedExtensions, setOrderedExtensions] = useState<string[]>(
value && value.length > 0 ? value : assignedExtensions.map((e) => e.id),
);

const sensors = useSensors(useSensor(PointerSensor));

const extensionItems = orderedExtensions.map((extensionId) => {
const extension = assignedExtensions.find((e) => e.id === extensionId);
return {
id: extensionId,
label: extension?.id || extensionId,
};
});

const handleDragEnd = useCallback(
(event) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = orderedExtensions.indexOf(active.id);
const newIndex = orderedExtensions.indexOf(over.id);
const newOrder = arrayMove(orderedExtensions, oldIndex, newIndex);
setOrderedExtensions(newOrder);
setValue(newOrder);
}
},
[orderedExtensions, setValue],
);

if (!assignedExtensions || assignedExtensions.length === 0) {
return <p>{t('noExtensionsToOrderText', 'No extensions found in this slot.')}</p>;
}

return (
<div>
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={extensionItems.map((item) => item.id)} strategy={verticalListSortingStrategy}>
<div className={styles.extensionsList}>
{extensionItems.map((extension, index) => (
<DraggableExtension
key={extension.id}
extension={extension}
index={index}
extensionCount={extensionItems.length}
/>
))}
</div>
</SortableContext>
</DndContext>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React from 'react';
import { FilterableMultiSelect } from '@carbon/react';
import { useAssignedExtensions } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';

export function ExtensionSlotRemove({ slotName, slotModuleName, value, setValue }) {
const assignedIds = useAssignedExtensions(slotName).map((e) => e.id);
const { t } = useTranslation();
const assignedExtensions = useAssignedExtensions(slotName);
const assignedExtensionIds = assignedExtensions.map((extension) => extension.id);

if (!assignedExtensions || assignedExtensions.length === 0) {
return <p>{t('noExtensionsToRemoveText', 'No extensions available to remove.')}</p>;
}

return (
<FilterableMultiSelect
id={`add-select`}
items={assignedIds.map((id) => ({ id, label: id }))}
items={assignedExtensionIds.map((id) => ({ id, label: id }))}
placeholder="Select extensions"
onChange={(value) => setValue(value.selectedItems.map((v) => v.id))}
initialSelectedItems={value}
Expand Down
2 changes: 2 additions & 0 deletions packages/apps/esm-implementer-tools-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"missing": "Missing",
"moduleName": "Module name",
"modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies",
"noExtensionsToOrder": "No extensions found in this slot.",
"noExtensionsToRemoveText": "No extensions available to remove.",
"numberValidationMessage": "Value must be a number",
"objectValidationMessage": "Value must be an object",
"requiredVersion": "Required Version",
Expand Down
51 changes: 51 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,55 @@ __metadata:
languageName: node
linkType: hard

"@dnd-kit/accessibility@npm:^3.1.1":
version: 3.1.1
resolution: "@dnd-kit/accessibility@npm:3.1.1"
dependencies:
tslib: "npm:^2.0.0"
peerDependencies:
react: ">=16.8.0"
checksum: 10/961000456a36700a9cd13be51147a818bc100f7dfabb332b80438d02e06f3b556aa0ff46ddf13bdff3b70bc8f9b63dd5a392cc285597ab1f7026e672660c54b6
languageName: node
linkType: hard

"@dnd-kit/core@npm:^6.3.1":
version: 6.3.1
resolution: "@dnd-kit/core@npm:6.3.1"
dependencies:
"@dnd-kit/accessibility": "npm:^3.1.1"
"@dnd-kit/utilities": "npm:^3.2.2"
tslib: "npm:^2.0.0"
peerDependencies:
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 10/a5ae6fa8404765712aa80e308f58cb79bac9a306c274ec8272c405c2a59dd277d24b966348fe8ca6340bb3f0d75f90b8a021fa781edcf65255114d3cf2bef891
languageName: node
linkType: hard

"@dnd-kit/sortable@npm:^10.0.0":
version: 10.0.0
resolution: "@dnd-kit/sortable@npm:10.0.0"
dependencies:
"@dnd-kit/utilities": "npm:^3.2.2"
tslib: "npm:^2.0.0"
peerDependencies:
"@dnd-kit/core": ^6.3.0
react: ">=16.8.0"
checksum: 10/bc61c25e76905204a53f91294b8116bf106fa27247eebca2c66478450b2051d7177115a384054e7e5639e6c4430083ade63056f79ee45f549da537cf05bc5288
languageName: node
linkType: hard

"@dnd-kit/utilities@npm:^3.2.2":
version: 3.2.2
resolution: "@dnd-kit/utilities@npm:3.2.2"
dependencies:
tslib: "npm:^2.0.0"
peerDependencies:
react: ">=16.8.0"
checksum: 10/6cfe46a5fcdaced943982e7ae66b08b89235493e106eb5bc833737c25905e13375c6ecc3aa0c357d136cb21dae3966213dba063f19b7a60b1235a29a7b05ff84
languageName: node
linkType: hard

"@esbuild/aix-ppc64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/aix-ppc64@npm:0.20.2"
Expand Down Expand Up @@ -3878,6 +3927,8 @@ __metadata:
resolution: "@openmrs/esm-implementer-tools-app@workspace:packages/apps/esm-implementer-tools-app"
dependencies:
"@carbon/react": "npm:^1.83.0"
"@dnd-kit/core": "npm:^6.3.1"
"@dnd-kit/sortable": "npm:^10.0.0"
"@openmrs/esm-framework": "workspace:*"
"@openmrs/webpack-config": "workspace:*"
ace-builds: "npm:^1.4.14"
Expand Down
Loading