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 16, 2024
1 parent f18b830 commit ac95eea
Show file tree
Hide file tree
Showing 15 changed files with 7,189 additions and 208 deletions.
6,572 changes: 6,571 additions & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

191 changes: 190 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"
}
99 changes: 97 additions & 2 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import React, { useState, useEffect, createContext } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { restrictToVerticalAxis, restrictToParentElement } from '@dnd-kit/modifiers';
import { injectGraphql } from 'lib/Injector';
import fieldHolder from 'components/FieldHolder/FieldHolder';
import LinkPicker from 'components/LinkPicker/LinkPicker';
import LinkPickerTitle from 'components/LinkPicker/LinkPickerTitle';
Expand All @@ -15,6 +19,7 @@ import PropTypes from 'prop-types';
import i18n from 'i18n';
import url from 'url';
import qs from 'qs';
import classnames from 'classnames';

export const LinkFieldContext = createContext(null);

Expand Down Expand Up @@ -46,6 +51,16 @@ const LinkField = ({
const [data, setData] = useState({});
const [editingID, setEditingID] = useState(0);
const [loading, setLoading] = useState(false);
const [forceFetch, setForceFetch] = useState(0);
const [linksClassName, setLinksClassName] = useState(classnames({'link-picker-links': true}));

const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 10
}
})
);

// Ensure we have a valid array
let linkIDs = value;
Expand All @@ -60,6 +75,7 @@ const LinkField = ({

// Read data from endpoint and update component state
// This happens any time a link is added or removed and triggers a re-render

useEffect(() => {
if (!editingID && linkIDs.length > 0) {
setLoading(true);
Expand All @@ -79,7 +95,7 @@ const LinkField = ({
setLoading(false);
});
}
}, [editingID, value && value.length]);
}, [editingID, value && value.length, forceFetch]);

/**
* Unset the editing ID when the editing modal is closed
Expand Down Expand Up @@ -167,15 +183,79 @@ const LinkField = ({
onDelete={onDelete}
onClick={() => { setEditingID(linkID); }}
canDelete={data[linkID]?.canDelete ? true : false}
isMulti={isMulti}
/>);
}
return links;
};

const handleDragStart = (event) => {
setLinksClassName(classnames({
'link-picker__links': true,
'link-picker__links--dragging': true,
}));
}

/**
* Drag and drop handler for MultiLinkField's
*/
const handleDragEnd = (event) => {
const {active, over} = event;
setLinksClassName(classnames({
'link-picker__links': true,
'link-picker__links--dragging': false,
}));
if (active.id === over.id) {
return;
}
const fromIndex = linkIDs.indexOf(active.id);
const toIndex = linkIDs.indexOf(over.id);
const newLinkIDs = arrayMove(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'));
// Force a rerender so that links are retched so that versionState badges are up to date
setForceFetch(forceFetch + 1);
})
.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();

// return <LinkFieldContext.Provider value={{ ownerID, ownerClass, ownerRelation, actions, loading }}>
// <div className="link-field__container">
// { saveRecordFirst && <div className="link-field__save-record-first">{saveRecordFirstText}</div>}
// { loading && !saveRecordFirst && <Loading containerClass="link-field__loading"/> }
// { renderPicker && <LinkPicker
// onModalSuccess={onModalSuccess}
// onModalClosed={onModalClosed}
// types={types}
// canCreate={canCreate}
// /> }
// <div> { renderLinks() } </div>
// { renderModal && <LinkModalContainer
// types={types}
// typeKey={data[editingID]?.typeKey}
// isOpen={Boolean(editingID)}
// onSuccess={onModalSuccess}
// onClosed={onModalClosed}
// linkID={editingID}
// />
// }
// </div>

if (loading && !saveRecordFirst) {
return <div className="link-field__loading"><Loading/></div>;
}

return <LinkFieldContext.Provider value={{ ownerID, ownerClass, ownerRelation, actions, loading }}>
<div className="link-field__container">
Expand All @@ -187,7 +267,22 @@ const LinkField = ({
types={types}
canCreate={canCreate}
/> }
<div> { renderLinks() } </div>
{ isMulti && <div className={linksClassName}>
<DndContext modifiers={[restrictToVerticalAxis, restrictToParentElement]}
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
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
Loading

0 comments on commit ac95eea

Please sign in to comment.