Hooking dropzones
This commit is contained in:
@ -33,6 +33,7 @@ class Dragger extends React.Component {
|
||||
handleMouseMove(e) {
|
||||
const { dragger, uiActions: { dragActive } } = this.props;
|
||||
const { target } = e;
|
||||
console.debug('mouseMove', e)
|
||||
if (!dragger) return null;
|
||||
|
||||
const threshold = 10;
|
||||
@ -48,11 +49,11 @@ class Dragger extends React.Component {
|
||||
|
||||
const dropzones = document.getElementsByClassName('dropzone');
|
||||
for (let i = 0; i < dropzones.length; i++) {
|
||||
dropzones[i].classList.remove('hover');
|
||||
dropzones[i].classList.remove('drag-over');
|
||||
}
|
||||
|
||||
if (target.classList && target.classList.contains('dropzone') && !target.classList.contains('hover')) {
|
||||
target.className += ' hover';
|
||||
if (target.classList && target.classList.contains('dropzone') && !target.classList.contains('drag-over')) {
|
||||
target.className += ' drag-over';
|
||||
}
|
||||
|
||||
// if not already, activate
|
||||
@ -74,7 +75,7 @@ class Dragger extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="dragger"
|
||||
className="dragger"
|
||||
style={{
|
||||
left: position_x,
|
||||
top: position_y,
|
||||
|
||||
@ -1,81 +1,75 @@
|
||||
import React from 'react';
|
||||
import Sortable from 'react-sortablejs';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ReactSortable } from 'react-sortablejs';
|
||||
import Icon from '../Icon';
|
||||
import Link from '../Link';
|
||||
import { sortItems } from '../../util/arrays';
|
||||
import { sortItems, indexToArray } from '../../util/arrays';
|
||||
|
||||
export default class Commands extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
const Commands = ({
|
||||
commands,
|
||||
runCommand,
|
||||
onChange,
|
||||
}) => {
|
||||
const [list, setList] = useState([]);
|
||||
|
||||
onChange(order) {
|
||||
const commands = {};
|
||||
for (let i = 0; i <= order.length; i++) {
|
||||
const command = this.props.commands[order[i]];
|
||||
if (command) {
|
||||
commands[command.id] = { ...command, ...{ sort_order: i } };
|
||||
useEffect(() => {
|
||||
setList(sortItems(indexToArray(commands), 'sort_order'));
|
||||
}, [commands]);
|
||||
|
||||
useEffect(() => {
|
||||
setList(sortItems(indexToArray(commands), 'sort_order'));
|
||||
}, []);
|
||||
|
||||
const onSort = () => {
|
||||
const nextCommands = {};
|
||||
if (!list || !list.length) return;
|
||||
|
||||
list.forEach((item, index) => {
|
||||
nextCommands[item.id] = {
|
||||
...item,
|
||||
sort_order: index,
|
||||
};
|
||||
});
|
||||
|
||||
onChange(nextCommands);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactSortable
|
||||
options={{
|
||||
handle: '.commands-setup__item__drag-handle',
|
||||
animation: 150,
|
||||
}}
|
||||
className="list commands-setup"
|
||||
list={list}
|
||||
setList={setList}
|
||||
onSort={onSort}
|
||||
>
|
||||
{
|
||||
list.map((command) => (
|
||||
<div className="list__item commands-setup__item list__item--no-interaction" key={command.id} data-id={command.id}>
|
||||
<div className="commands-setup__item__details">
|
||||
<Icon className="commands-setup__item__drag-handle" name="drag_indicator" />
|
||||
<div className="commands-setup__item__command-item commands__item commands__item--small">
|
||||
<Icon className="commands__item__icon" name={command.icon} />
|
||||
<span className={`${command.colour}-background commands__item__background`} />
|
||||
</div>
|
||||
<div className="commands-setup__item__url commands__item__url">
|
||||
{command.name ? command.name : <span className="grey-text">{command.url}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="commands-setup__item__actions">
|
||||
<a className="commands-setup__item__run-button action" onClick={() => runCommand(command.id, true)}>
|
||||
<Icon name="play_arrow" />
|
||||
</a>
|
||||
<Link className="commands-setup__item__edit-button action" to={`/modal/edit-command/${command.id}`}>
|
||||
<Icon name="edit" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onChange(commands);
|
||||
}
|
||||
|
||||
commands() {
|
||||
let commands = [];
|
||||
|
||||
if (this.props.commands) {
|
||||
for (const key of Object.keys(this.props.commands)) {
|
||||
commands.push({ ...this.props.commands[key] });
|
||||
}
|
||||
}
|
||||
|
||||
commands = sortItems(commands, 'sort_order');
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
render() {
|
||||
const commands = this.commands();
|
||||
|
||||
if (!commands) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Sortable
|
||||
options={{
|
||||
handle: '.commands-setup__item__drag-handle',
|
||||
animation: 150,
|
||||
}}
|
||||
className="list commands-setup"
|
||||
onChange={(order, sortable, e) => { this.onChange(order); }}
|
||||
>
|
||||
{
|
||||
commands.map((command) => (
|
||||
<div className="list__item commands-setup__item list__item--no-interaction" key={command.id} data-id={command.id}>
|
||||
<div className="commands-setup__item__details">
|
||||
<Icon className="commands-setup__item__drag-handle" name="drag_indicator" />
|
||||
<div className="commands-setup__item__command-item commands__item commands__item--small">
|
||||
<Icon className="commands__item__icon" name={command.icon} />
|
||||
<span className={`${command.colour}-background commands__item__background`} />
|
||||
</div>
|
||||
<div className="commands-setup__item__url commands__item__url">
|
||||
{command.name ? command.name : <span className="grey-text">{command.url}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="commands-setup__item__actions">
|
||||
<a className="commands-setup__item__run-button action" onClick={(e) => this.props.runCommand(command.id, true)}>
|
||||
<Icon name="play_arrow" />
|
||||
</a>
|
||||
<Link className="commands-setup__item__edit-button action" to={`/modal/edit-command/${command.id}`}>
|
||||
<Icon name="edit" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</Sortable>
|
||||
);
|
||||
}
|
||||
</ReactSortable>
|
||||
);
|
||||
}
|
||||
|
||||
export default Commands;
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export default class Dropzone extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hover: false,
|
||||
};
|
||||
|
||||
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||
this.handleMouseOut = this.handleMouseOut.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('mouseover', this.handleMouseOver, false);
|
||||
window.addEventListener('mouseout', this.handleMouseOut, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('mouseover', this.handleMouseOver, false);
|
||||
window.removeEventListener('mouseout', this.handleMouseOut, false);
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({ hover: true });
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({ hover: false });
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
data,
|
||||
handleMouseUp,
|
||||
} = this.props;
|
||||
const { hover } = this.state;
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={hover ? 'dropzone hover' : 'dropzone'}
|
||||
onMouseUp={() => handleMouseUp(data)}
|
||||
>
|
||||
<Icon name={data.icon} />
|
||||
<span className="title">{ data.title }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,51 +1,65 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Dropzone from './Dropzone';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import Icon from '../Icon';
|
||||
import { arrayOf } from '../../util/arrays';
|
||||
import { i18n } from '../../locale';
|
||||
import { encodeUri } from '../../util/format';
|
||||
|
||||
const zones = [
|
||||
{
|
||||
action: 'enqueue',
|
||||
title: i18n('actions.add_to_queue'),
|
||||
icon: 'play_arrow',
|
||||
},
|
||||
{
|
||||
action: 'enqueue_next',
|
||||
title: i18n('actions.play_next'),
|
||||
icon: 'play_arrow',
|
||||
},
|
||||
{
|
||||
action: 'add_to_playlist',
|
||||
title: i18n('actions.add_to_playlist'),
|
||||
icon: 'playlist_add',
|
||||
accepts: ['tltrack', 'track', 'album', 'playlist', 'artist'],
|
||||
},
|
||||
{
|
||||
action: 'create_playlist_and_add',
|
||||
title: i18n('modal.edit_playlist.create_playlist'),
|
||||
icon: 'playlist_add',
|
||||
accepts: ['tltrack', 'track', 'album', 'playlist', 'artist'],
|
||||
},
|
||||
];
|
||||
|
||||
const Dropzones = () => {
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const [dropTarget, setDropTarget] = useState();
|
||||
const {
|
||||
victims,
|
||||
from_uri,
|
||||
active,
|
||||
} = useSelector((state) => state.ui.dragger || {});
|
||||
const zones = [
|
||||
{
|
||||
title: i18n('actions.add_to_queue'),
|
||||
icon: 'play_arrow',
|
||||
action: 'enqueue',
|
||||
},
|
||||
{
|
||||
title: i18n('actions.play_next'),
|
||||
icon: 'play_arrow',
|
||||
action: 'enqueue_next',
|
||||
},
|
||||
{
|
||||
title: i18n('actions.add_to_playlist'),
|
||||
icon: 'playlist_add',
|
||||
action: 'add_to_playlist',
|
||||
accepts: ['tltrack', 'track', 'album', 'playlist', 'artist'],
|
||||
},
|
||||
{
|
||||
title: i18n('modal.edit_playlist.create_playlist'),
|
||||
icon: 'playlist_add',
|
||||
action: 'create_playlist_and_add',
|
||||
accepts: ['tltrack', 'track', 'album', 'playlist', 'artist'],
|
||||
},
|
||||
];
|
||||
|
||||
if (!active) return null;
|
||||
|
||||
const handleMouseUp = (zone) => {
|
||||
const onDragEnter = (e, action) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDropTarget(action);
|
||||
}
|
||||
|
||||
const onDragOver = (e, action) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDropTarget(action);
|
||||
}
|
||||
|
||||
const onDrop = (e, action) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const uris = arrayOf('uri', victims);
|
||||
switch (zone.action) {
|
||||
switch (action) {
|
||||
case 'enqueue':
|
||||
dispatch(mopidyActions.enqueueURIs(uris, from_uri));
|
||||
break;
|
||||
@ -65,15 +79,18 @@ const Dropzones = () => {
|
||||
|
||||
return (
|
||||
<div className="dropzones">
|
||||
{
|
||||
zones.map((zone) => (
|
||||
<Dropzone
|
||||
key={zone.action}
|
||||
data={zone}
|
||||
handleMouseUp={handleMouseUp}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{zones.map(({ title, icon, action }) => (
|
||||
<div
|
||||
key={action}
|
||||
className={`dropzones__item ${dropTarget === action ? ' hover' : ''}`}
|
||||
onDragEnter={(e) => onDragEnter(e, action)}
|
||||
onDragOver={(e) => onDragOver(e, action)}
|
||||
onDrop={(e) => onDrop(e, action)}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
<span className="title">{title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,55 +1,67 @@
|
||||
import React from 'react';
|
||||
import Sortable from 'react-sortablejs';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { ReactSortable } from 'react-sortablejs';
|
||||
import { set as uiSet } from '../../services/ui/actions';
|
||||
import Icon from '../Icon';
|
||||
import { titleCase } from '../../util/helpers';
|
||||
|
||||
export default class SourcesPriority extends React.Component {
|
||||
handleSort(order) {
|
||||
this.props.uiActions.set({ uri_schemes_priority: order });
|
||||
}
|
||||
const SourcesPriority = ({
|
||||
uri_schemes,
|
||||
uri_schemes_priority,
|
||||
}) => {
|
||||
const [list, setList] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
render() {
|
||||
const className = 'sources-priority-field';
|
||||
const ordered_schemes = [];
|
||||
const unordered_schemes = [];
|
||||
useEffect(() => {
|
||||
processList();
|
||||
}, []);
|
||||
|
||||
for (var i = 0; i < this.props.uri_schemes.length; i++) {
|
||||
const index = this.props.uri_schemes_priority.indexOf(this.props.uri_schemes[i]);
|
||||
useEffect(() => {
|
||||
processList();
|
||||
}, [uri_schemes]);
|
||||
|
||||
const processList = () => {
|
||||
let seen = [];
|
||||
let unseen = [];
|
||||
uri_schemes.forEach((uri) => {
|
||||
const index = uri_schemes_priority.indexOf(uri);
|
||||
if (index > -1) {
|
||||
ordered_schemes[index] = this.props.uri_schemes[i];
|
||||
seen[index] = { uri };
|
||||
} else {
|
||||
unordered_schemes.push(this.props.uri_schemes[i]);
|
||||
unseen.push({ uri });
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < unordered_schemes.length; i++) {
|
||||
ordered_schemes.push(unordered_schemes[i]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Sortable
|
||||
options={{
|
||||
animation: 150,
|
||||
}}
|
||||
className={className}
|
||||
onChange={(order, sortable, e) => {
|
||||
this.handleSort(order);
|
||||
}}
|
||||
>
|
||||
{
|
||||
ordered_schemes.map((scheme) => {
|
||||
const name = titleCase(scheme.replace(':', '').replace('+', ' '));
|
||||
|
||||
return (
|
||||
<span className="source flag flag--grey" key={scheme} data-id={scheme}>
|
||||
<Icon name="drag_indicator" />
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Sortable>
|
||||
);
|
||||
});
|
||||
setList([ ...seen, ...unseen ]);
|
||||
}
|
||||
|
||||
const onSort = () => {
|
||||
dispatch(uiSet({ uri_schemes_priority: list.map(({ uri }) => uri) }));
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactSortable
|
||||
options={{
|
||||
animation: 150,
|
||||
}}
|
||||
className="sources-priority-field"
|
||||
list={list}
|
||||
setList={setList}
|
||||
onSort={onSort}
|
||||
>
|
||||
{
|
||||
list.map(({ uri }) => {
|
||||
const name = titleCase(uri.replace(':', '').replace('+', ' '));
|
||||
|
||||
return (
|
||||
<span className="source flag flag--grey" key={`uri_scheme_${uri}`}>
|
||||
<Icon name="drag_indicator" />
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ReactSortable>
|
||||
);
|
||||
}
|
||||
|
||||
export default SourcesPriority;
|
||||
|
||||
@ -58,7 +58,7 @@ const Track = ({
|
||||
stream_title,
|
||||
play_state,
|
||||
is_selected,
|
||||
is_dropping,
|
||||
// is_dropping,
|
||||
can_sort,
|
||||
show_source_icon,
|
||||
getItemIndex,
|
||||
@ -113,7 +113,7 @@ const Track = ({
|
||||
|
||||
const track_middle_column = <MiddleColumn context={context} item={item} />;
|
||||
if (is_selected(index)) className += ' list__item--selected';
|
||||
if (is_dropping(index)) className += ' list__item--dropping';
|
||||
// if (is_dropping(index)) className += ' list__item--dropping';
|
||||
if (can_sort) className += ' list__item--can-sort';
|
||||
if (item.type !== undefined) className += ` list__item--${item.type}`;
|
||||
if (item.playing) className += ' list__item--playing';
|
||||
@ -126,21 +126,23 @@ const Track = ({
|
||||
const onDoubleClick = (e) => events.onDoubleClick(item, index, e);
|
||||
const onContextMenu = (e) => events.onContextMenu(item, index, e);
|
||||
const onDragStart = (e) => events.onDragStart(item, index, e);
|
||||
const onDragEnd = (e) => events.onDragEnd(item, index, e);
|
||||
const onDragOver = (e) => events.onDragOver(item, index, e);
|
||||
const onDragEnter = (e) => events.onDragEnter(item, index, e);
|
||||
const onDragLeave = (e) => events.onDragLeave(item, index, e);
|
||||
const onDrop = (e) => events.onDrop(item, index, e);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div
|
||||
className={className}
|
||||
className={`${className} dropzone`}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onContextMenu={onContextMenu}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={() => console.debug("DROP")}
|
||||
onDrop={onDrop}
|
||||
draggable="true"
|
||||
>
|
||||
<div className="list__item__column list__item__column--name">
|
||||
|
||||
@ -29,6 +29,7 @@ const TrackList = ({
|
||||
createNotification,
|
||||
showContextMenu,
|
||||
dragStart,
|
||||
dragEnd,
|
||||
},
|
||||
mopidyActions: {
|
||||
playURIs,
|
||||
@ -39,10 +40,10 @@ const TrackList = ({
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onKeyDown, false);
|
||||
window.addEventListener('dragend', events.onDragEnd, false);
|
||||
window.addEventListener('dragend', onDragEnd, false);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown, false);
|
||||
window.removeEventListener('dragend', events.onDragEnd, false);
|
||||
window.removeEventListener('dragend', onDragEnd, false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -94,25 +95,24 @@ const TrackList = ({
|
||||
const events = {
|
||||
onDragStart: (item, index, e) => {
|
||||
const items = getOrUpdateSelected(item, index, e).map(({ item: selectedItem }) => selectedItem);
|
||||
console.debug('DRAGGING', items.length)
|
||||
dragStart(e, context, context, items, selected);
|
||||
},
|
||||
onDrop: (item, index, e) => {
|
||||
console.debug('DROP')
|
||||
reorderTracks(arrayOf('index', selected), index);
|
||||
setSelected([]);
|
||||
},
|
||||
onDragEnd: (item, index, e) => {
|
||||
dragEnd();
|
||||
},
|
||||
onDragEnter: (item, index, e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
console.debug('onDragEnter', index);
|
||||
setDropTarget(index);
|
||||
// e.preventDefault();
|
||||
},
|
||||
onDragLeave: (item, index, e) => {
|
||||
console.debug('onDragLeave', index);
|
||||
// e.preventDefault();
|
||||
},
|
||||
onDragEnd: (e) => {
|
||||
console.debug('onDragEnd');
|
||||
setDropTarget(null);
|
||||
onDragOver: (item, index, e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
onClick: (item, index, e) => {
|
||||
// e.preventDefault();
|
||||
@ -142,6 +142,37 @@ const TrackList = ({
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* NEXT STEPS
|
||||
* Collate these events into a coherant package, possibly using hooks?
|
||||
* Create redux handlers to push dragging items into state. This allows a nice drag shadow and
|
||||
* activation of dropzones in sidebar.
|
||||
* Figure out where to leave touch events. Perhaps dragging one-by-one with a simple handle is
|
||||
* sufficient.
|
||||
* Apply drag-ability to other assets, like album tiles, etc.
|
||||
*
|
||||
* Also, fix shuffle play; first track goes first, but all subsequent ones go to end of tracklist
|
||||
*/
|
||||
|
||||
const onDragLeave = (e) => {
|
||||
e.persist();
|
||||
const { target, relatedTarget } = e;
|
||||
e.stopPropagation();
|
||||
// The element that we just left is the element with the listener
|
||||
// Unfortunately this event triggers for EVERY element that is 'left' during dragging, not just
|
||||
// the one bound to the event. This check allows us to only act when we leave the bound element.
|
||||
// if (relatedTarget.id === 'tracklist_container') {
|
||||
console.debug('onDragLeave', e)
|
||||
if (target.id === 'tracklist-container') {
|
||||
console.debug('OUTSIDE, WHEE!');
|
||||
setDropTarget(null);
|
||||
}
|
||||
};
|
||||
const onDragEnd = (e) => {
|
||||
console.debug('onDragEnd');
|
||||
setDropTarget(null);
|
||||
};
|
||||
|
||||
const onRemoveTracks = () => {
|
||||
if (!removeTracks) {
|
||||
createNotification({
|
||||
@ -218,7 +249,7 @@ const TrackList = ({
|
||||
selected_tracks,
|
||||
can_sort: context?.can_edit,
|
||||
is_selected,
|
||||
is_dropping,
|
||||
// is_dropping,
|
||||
mini_zones: slim_mode || isTouchDevice(),
|
||||
events,
|
||||
}}
|
||||
|
||||
@ -80,7 +80,7 @@ export default function reducer(ui = {}, action) {
|
||||
...ui,
|
||||
dragger: {
|
||||
dragging: true,
|
||||
active: false,
|
||||
active: true,
|
||||
context: action.context,
|
||||
from_uri: action.from_uri,
|
||||
victims: action.victims,
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
background: colour('faint_grey');
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
&__item {
|
||||
@include animate();
|
||||
@include gradient_overlay(3px,0);
|
||||
margin: 15px;
|
||||
@ -61,7 +61,7 @@
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
&.hover {
|
||||
&.drag-over {
|
||||
border-color: colour('blue');
|
||||
|
||||
&:before {
|
||||
|
||||
@ -288,7 +288,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&--dropping {
|
||||
&.drag-over {
|
||||
&:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user