Skip to content

Commit

Permalink
ENH MutliLinkField sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Jan 11, 2024
1 parent 5db919e commit 494f23f
Show file tree
Hide file tree
Showing 10 changed files with 6,773 additions and 192 deletions.
6,400 changes: 6,399 additions & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

161 changes: 160 additions & 1 deletion client/dist/styles/bundle.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion client/lang/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
"LinkField.CANNOT_CREATE_LINK": "Cannot create link",
"LinkField.FAILED_TO_LOAD_LINKS": "Failed to load links",
"LinkField.FAILED_TO_SAVE_LINK": "Failed to save link",
"LinkField.SAVE_RECORD_FIRST": "Cannot add links until the record has been saved"
"LinkField.SAVE_RECORD_FIRST": "Cannot add links until the record has been saved",
"LinkField.SORT_SUCCESS": "Updated link sort order",
"LinkField.SORT_ERROR": "Unable to sort links"
});
}
4 changes: 3 additions & 1 deletion client/lang/src/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
"LinkField.CANNOT_CREATE_LINK": "Cannot create link",
"LinkField.FAILED_TO_LOAD_LINKS": "Failed to load links",
"LinkField.FAILED_TO_SAVE_LINK": "Failed to save link",
"LinkField.SAVE_RECORD_FIRST": "Cannot add links until the record has been saved"
"LinkField.SAVE_RECORD_FIRST": "Cannot add links until the record has been saved",
"LinkField.SORT_SUCCESS": "Updated link sort order",
"LinkField.SORT_ERROR": "Unable to sort links"
}
66 changes: 65 additions & 1 deletion client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ import PropTypes from 'prop-types';
import i18n from 'i18n';
import url from 'url';
import qs from 'qs';
import { arrayMoveImmutable } from 'array-move';

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

export const LinkFieldContext = createContext(null);

Expand Down Expand Up @@ -48,6 +66,14 @@ const LinkField = ({
const [editingID, setEditingID] = useState(0);
const [loading, setLoading] = useState(false);

const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
delay: 250
}
})
);

// Ensure we have a valid array
let linkIDs = value;
if (!Array.isArray(linkIDs)) {
Expand Down Expand Up @@ -172,10 +198,34 @@ const LinkField = ({
return links;
};

/**
* Drag and drop handler for MultiLinkField's
*/
const handleDragEnd = (event) => {
const {active, over} = event;
if (active.id === over.id) {
return;
}
const fromIndex = linkIDs.indexOf(active.id);
const toIndex = linkIDs.indexOf(over.id);
const newLinkIDs = arrayMoveImmutable(linkIDs, fromIndex, toIndex);
let endpoint = `${Config.getSection(section).form.linkForm.sortUrl}`;
// CSRF token 'X-SecurityID' headers needs to be present
backend.post(endpoint, { newLinkIDs }, { 'X-SecurityID': Config.get('SecurityID') })
.then(() => {
onChange(newLinkIDs);
actions.toasts.success(i18n._t('LinkField.SORT_SUCCESS', 'Updated link sort order'));
})
.catch(() => {
actions.toasts.error(i18n._t('LinkField.SORT_ERROR', 'Failed to sort links'));
});
}

const saveRecordFirst = ownerID === 0;
const renderPicker = !saveRecordFirst && (isMulti || Object.keys(data).length === 0);
const renderModal = !saveRecordFirst && Boolean(editingID);
const saveRecordFirstText = i18n._t('LinkField.SAVE_RECORD_FIRST', 'Cannot add links until the record has been saved');
const links = renderLinks();

if (loading && !saveRecordFirst) {
return <div className="link-field__loading"><Loading/></div>;
Expand All @@ -189,7 +239,21 @@ const LinkField = ({
types={types}
canCreate={canCreate}
/> }
<div> { renderLinks() } </div>
{ isMulti && <div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={linkIDs}
strategy={verticalListSortingStrategy}
>
{links}
</SortableContext>
</DndContext>
</div> }
{ !isMulti && <div>{ renderLinks() }</div>}
{ renderModal && <LinkModalContainer
types={types}
typeKey={data[editingID]?.typeKey}
Expand Down
30 changes: 25 additions & 5 deletions client/src/components/LinkPicker/LinkPickerTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import classnames from 'classnames';
import i18n from 'i18n';
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from 'reactstrap';
import { Button } from 'reactstrap';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

const stopPropagation = (fn) => (e) => {
e.nativeEvent.stopImmediatePropagation();
Expand Down Expand Up @@ -37,21 +39,39 @@ const LinkPickerTitle = ({
typeTitle,
onDelete,
onClick,
canDelete
canDelete,
}) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({id});
console.log('transform is ', transform);
console.log('transition is ', transition);
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const classes = {
'link-picker__link': true,
'form-control': true,
};
if (versionState) {
classes[` link-picker__link--${versionState}`] = true;
classes[`link-picker__link--${versionState}`] = true;
}
const className = classnames(classes);
const deleteText = ['unversioned', 'unsaved'].includes(versionState)
? i18n._t('LinkField.DELETE', 'Delete')
: i18n._t('LinkField.ARCHIVE', 'Archive');
return <div className={className}>
<Button className="link-picker__button font-icon-link" color="secondary" onClick={stopPropagation(onClick)}>
return <div
className={className}
ref={setNodeRef}
style={style} {...attributes}
{...listeners}
>
<Button className="link-picker__button font-icon-link" color="secondary" onClick={stopPropagation(onClick)}>
<div className="link-picker__link-detail">
<div className="link-picker__title">
<span className="link-picker__title-text">{title}</span>
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "silverstripe-link",
"name": "silverstripe-linkfield",
"version": "0.0.0",
"description": "Link management for the SilverStripe CMS",
"main": "./client/src/boot/index.js",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git+https://github.com/silverstripe/silverstripe-link.git"
"url": "git+https://github.com/silverstripe/silverstripe-linkfield.git"
},
"homepage": "https://github.com/silverstripe/silverstripNe-link",
"homepage": "https://github.com/silverstripe/silverstripNe-linkfield",
"bugs": {
"url": "https://github.com/silverstripe/silverstripe-link/issues"
"url": "https://github.com/silverstripe/silverstripe-linkfield/issues"
},
"author": "SilverStripe Ltd",
"engines": {
Expand Down Expand Up @@ -61,14 +61,15 @@
},
"dependencies": {
"@apollo/client": "^3.7.1",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"array-move": "^4.0.0",
"bootstrap": "^4.6.2",
"classnames": "^2.2.5",
"core-js": "^3.26.0",
"prop-types": "^15.8.1",
"qs": "^6.11.0",
"react": "^18.2.0",
"react-dnd": "^5.0.0",
"react-dnd-html5-backend": "^5.0.1",
"react-dom": "^18.2.0",
"react-redux": "^8.0.4",
"react-router": "^6.4.2",
Expand Down
Loading

0 comments on commit 494f23f

Please sign in to comment.