mirror of
https://github.com/mue/mue.git
synced 2026-07-09 13:35:35 +02:00
Modernize to 2025 standards: React 19, Vite 6, ESLint 9, @dnd-kit migration
This commit is contained in:
@@ -96,17 +96,31 @@ class Favourite extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateTooltip();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.favourited !== this.state.favourited) {
|
||||
this.updateTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
updateTooltip() {
|
||||
if (this.props.tooltipText) {
|
||||
this.props.tooltipText(
|
||||
localStorage.getItem('favourite')
|
||||
? variables.getMessage('widgets.quote.unfavourite')
|
||||
: variables.getMessage('widgets.quote.favourite'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (localStorage.getItem('backgroundType') === 'colour') {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.props.tooltipText(
|
||||
localStorage.getItem('favourite')
|
||||
? variables.getMessage('widgets.quote.unfavourite')
|
||||
: variables.getMessage('widgets.quote.favourite'),
|
||||
);
|
||||
|
||||
return this.state.favourited;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { MdOutlineDragIndicator } from 'react-icons/md';
|
||||
import { sortableContainer, sortableElement } from '@muetab/react-sortable-hoc';
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import variables from 'config/variables';
|
||||
@@ -22,16 +37,21 @@ const widget_name = {
|
||||
message: variables.getMessage('modals.main.settings.sections.message.title'),
|
||||
};
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className="sortableItem">
|
||||
{widget_name[value]}
|
||||
<MdOutlineDragIndicator />
|
||||
</li>
|
||||
));
|
||||
const SortableItem = ({ id }) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
|
||||
const SortableContainer = sortableContainer(({ children }) => (
|
||||
<ul className="sortableContainer">{children}</ul>
|
||||
));
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<li ref={setNodeRef} style={style} {...attributes} {...listeners} className="sortableItem">
|
||||
{widget_name[id]}
|
||||
<MdOutlineDragIndicator />
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const Overview = () => {
|
||||
const [items, setItems] = useState(
|
||||
@@ -55,15 +75,23 @@ const Overview = () => {
|
||||
const [newsDone, setNewsDone] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const arrayMove = (array, oldIndex, newIndex) => {
|
||||
const result = Array.from(array);
|
||||
const [removed] = result.splice(oldIndex, 1);
|
||||
result.splice(newIndex, 0, removed);
|
||||
return result;
|
||||
};
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
const onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
setItems((prevItems) => arrayMove(prevItems, oldIndex, newIndex));
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (active.id !== over.id) {
|
||||
setItems((items) => {
|
||||
const oldIndex = items.indexOf(active.id);
|
||||
const newIndex = items.indexOf(over.id);
|
||||
return arrayMove(items, oldIndex, newIndex);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@@ -159,19 +187,18 @@ const Overview = () => {
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.order.title')}
|
||||
</span>
|
||||
<SortableContainer
|
||||
onSortEnd={onSortEnd}
|
||||
lockAxis="y"
|
||||
lockToContainerEdges
|
||||
disableAutoscroll
|
||||
>
|
||||
{items.map((value, index) => {
|
||||
if (!enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
return <SortableItem key={`item-${value}`} index={index} value={value} />;
|
||||
})}
|
||||
</SortableContainer>
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
<ul className="sortableContainer">
|
||||
{items.map((value) => {
|
||||
if (!enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
return <SortableItem key={value} id={value} />;
|
||||
})}
|
||||
</ul>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -14,12 +14,65 @@ import { Tooltip } from 'components/Elements';
|
||||
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { shift, useFloating } from '@floating-ui/react-dom';
|
||||
import { sortableContainer, sortableElement, sortableHandle } from '@muetab/react-sortable-hoc';
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => <div>{value}</div>);
|
||||
const SortableContainer = sortableContainer(({ children }) => <div>{children}</div>);
|
||||
const SortableHandle = sortableHandle(() => <MdOutlineDragIndicator />);
|
||||
const DragHandle = () => (
|
||||
<div className="todo-drag-handle" {...arguments[0]}>
|
||||
<MdOutlineDragIndicator />
|
||||
</div>
|
||||
);
|
||||
|
||||
const SortableItem = ({ id, children }) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style}>
|
||||
{typeof children === 'function' ? children({ attributes, listeners }) : children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SortableList = ({ items, onDragEnd, children }) => {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 5,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
<div>{children}</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
class Todo extends PureComponent {
|
||||
constructor() {
|
||||
@@ -70,10 +123,17 @@ class Todo extends PureComponent {
|
||||
return result;
|
||||
}
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
this.setState({
|
||||
todo: this.arrayMove(this.state.todo, oldIndex, newIndex),
|
||||
});
|
||||
handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
const oldIndex = Number(active.id);
|
||||
const newIndex = Number(over.id);
|
||||
|
||||
this.setState({
|
||||
todo: arrayMove(this.state.todo, oldIndex, newIndex),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
showTodo() {
|
||||
@@ -189,31 +249,25 @@ class Todo extends PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<SortableContainer
|
||||
onSortEnd={this.onSortEnd}
|
||||
lockAxis="y"
|
||||
lockToContainerEdges
|
||||
disableAutoscroll
|
||||
useDragHandle
|
||||
<SortableList
|
||||
items={this.state.todo.map((_, index) => index)}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
>
|
||||
{this.state.todo.map((_value, index) => (
|
||||
<SortableItem
|
||||
key={`item-${index}`}
|
||||
index={index}
|
||||
value={
|
||||
{this.state.todo.map((todo, index) => (
|
||||
<SortableItem key={index} id={index}>
|
||||
{({ attributes, listeners }) => (
|
||||
<div
|
||||
className={'todoRow' + (this.state.todo[index].done ? ' done' : '')}
|
||||
key={index}
|
||||
className={'todoRow' + (todo.done ? ' done' : '')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={this.state.todo[index].done}
|
||||
checked={todo.done}
|
||||
onClick={() => this.updateTodo('done', index)}
|
||||
/>
|
||||
<TextareaAutosize
|
||||
placeholder={variables.getMessage('widgets.navbar.notes.placeholder')}
|
||||
value={this.state.todo[index].value}
|
||||
value={todo.value}
|
||||
onChange={(data) => this.updateTodo('set', index, data)}
|
||||
readOnly={this.state.todo[index].done}
|
||||
readOnly={todo.done}
|
||||
/>
|
||||
<Tooltip
|
||||
title={variables.getMessage(
|
||||
@@ -222,12 +276,12 @@ class Todo extends PureComponent {
|
||||
>
|
||||
<MdDelete onClick={() => this.updateTodo('remove', index)} />
|
||||
</Tooltip>
|
||||
<SortableHandle />
|
||||
<DragHandle {...attributes} {...listeners} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SortableItem>
|
||||
))}
|
||||
</SortableContainer>
|
||||
</SortableList>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import { MdAddLink, MdLinkOff, MdOutlineDragIndicator, MdEdit, MdDelete } from 'react-icons/md';
|
||||
import { sortableContainer, sortableElement } from '@muetab/react-sortable-hoc';
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragOverlay,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||
import { Checkbox, Dropdown } from 'components/Form/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
@@ -30,13 +46,31 @@ const DragHandle = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const SortableItem = sortableElement(({ value, enabled, startEditLink, deleteLink }) => {
|
||||
const SortableItem = ({ value, enabled, startEditLink, deleteLink }) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: value.key,
|
||||
disabled: !enabled,
|
||||
});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
};
|
||||
|
||||
const getIconUrl = (item) => {
|
||||
return item.icon || 'https://icon.horse/icon/' + item.url.replace('https://', '').replace('http://', '');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`quicklink-item ${!enabled ? 'disabled' : ''}`} role="listitem">
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`quicklink-item ${!enabled ? 'disabled' : ''}`}
|
||||
role="listitem"
|
||||
>
|
||||
<DragHandle />
|
||||
<div className="quicklink-icon">
|
||||
<img src={getIconUrl(value)} alt={value.name} draggable={false} />
|
||||
@@ -77,11 +111,38 @@ const SortableItem = sortableElement(({ value, enabled, startEditLink, deleteLin
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const SortableContainer = sortableContainer(({ children }) => (
|
||||
<div className="quicklinks-list" role="list">{children}</div>
|
||||
));
|
||||
const SortableList = ({ items, enabled, onDragEnd, startEditLink, deleteLink }) => {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 6,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||
<SortableContext items={items.map((item) => item.key)} strategy={verticalListSortingStrategy}>
|
||||
<div className="quicklinks-list" role="list">
|
||||
{items.map((item) => (
|
||||
<SortableItem
|
||||
key={item.key}
|
||||
value={item}
|
||||
enabled={enabled}
|
||||
startEditLink={startEditLink}
|
||||
deleteLink={deleteLink}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
class QuickLinksOptions extends PureComponent {
|
||||
constructor() {
|
||||
@@ -188,24 +249,28 @@ class QuickLinksOptions extends PureComponent {
|
||||
return result;
|
||||
};
|
||||
|
||||
onSortStart = () => {
|
||||
if (this.quicklinksContainer && this.quicklinksContainer.current) {
|
||||
this.quicklinksContainer.current.classList.add('dragging');
|
||||
}
|
||||
};
|
||||
handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (!over || !this.state.enabled) return;
|
||||
if (active.id === over.id) return;
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
if (!this.state.enabled) return;
|
||||
if (oldIndex === newIndex) return;
|
||||
const newItems = this.arrayMove(this.state.items, oldIndex, newIndex);
|
||||
const oldIndex = this.state.items.findIndex((item) => item.key === active.id);
|
||||
const newIndex = this.state.items.findIndex((item) => item.key === over.id);
|
||||
|
||||
this.silenceEvent = true;
|
||||
this.setState({ items: newItems }, () => {
|
||||
localStorage.setItem('quicklinks', JSON.stringify(newItems));
|
||||
EventBus.emit('refresh', 'quicklinks');
|
||||
setTimeout(() => { this.silenceEvent = false; }, 0);
|
||||
});
|
||||
};
|
||||
if (oldIndex === -1 || newIndex === -1) return;
|
||||
|
||||
const newItems = arrayMove(this.state.items, oldIndex, newIndex);
|
||||
|
||||
this.silenceEvent = true;
|
||||
this.setState({ items: newItems }, () => {
|
||||
localStorage.setItem('quicklinks', JSON.stringify(newItems));
|
||||
EventBus.emit('refresh', 'quicklinks');
|
||||
setTimeout(() => {
|
||||
this.silenceEvent = false;
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
@@ -339,27 +404,13 @@ onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
aria-hidden={!enabled}
|
||||
>
|
||||
<div className={`messagesContainer ${!enabled ? 'disabled' : ''}`}>
|
||||
<SortableContainer
|
||||
onSortStart={this.onSortStart}
|
||||
onSortEnd={this.onSortEnd}
|
||||
lockAxis="y"
|
||||
lockToContainerEdges
|
||||
disableAutoscroll
|
||||
helperClass="sortable-helper"
|
||||
distance={6}
|
||||
disabled={!enabled}
|
||||
>
|
||||
{this.state.items.map((item, index) => (
|
||||
<SortableItem
|
||||
key={item.key}
|
||||
index={index}
|
||||
value={item}
|
||||
enabled={enabled}
|
||||
startEditLink={(data) => this.startEditLink(data)}
|
||||
deleteLink={(key, e) => this.deleteLink(key, e)}
|
||||
/>
|
||||
))}
|
||||
</SortableContainer>
|
||||
<SortableList
|
||||
items={this.state.items}
|
||||
enabled={enabled}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
startEditLink={(data) => this.startEditLink(data)}
|
||||
deleteLink={(key, e) => this.deleteLink(key, e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user