Merging develop
This commit is contained in:
5569
package-lock.json
generated
5569
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,86 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ReactSortable } from 'react-sortablejs';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useDrop, useDrag } from 'react-dnd';
|
||||
import Icon from '../Icon';
|
||||
import Link from '../Link';
|
||||
import { sortItems, indexToArray } from '../../util/arrays';
|
||||
|
||||
const DRAGGABLE_TYPE = 'COMMAND';
|
||||
|
||||
const Command = ({
|
||||
index,
|
||||
onExecute,
|
||||
onDrag,
|
||||
onDragEnd,
|
||||
command: {
|
||||
id,
|
||||
url,
|
||||
name,
|
||||
icon,
|
||||
colour,
|
||||
} = {},
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const [{ isDragging }, dragRef] = useDrag({
|
||||
type: DRAGGABLE_TYPE,
|
||||
item: { index },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
end: onDragEnd,
|
||||
});
|
||||
|
||||
const [_dropProps, dropRef] = useDrop({
|
||||
accept: DRAGGABLE_TYPE,
|
||||
hover: (item, monitor) => {
|
||||
const dragIndex = item.index;
|
||||
const hoverIndex = index;
|
||||
const hoverBoundingRect = ref.current?.getBoundingClientRect()
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
const hoverActualY = monitor.getClientOffset().y - hoverBoundingRect.top;
|
||||
|
||||
// if dragging down, continue only when hover is smaller than middle Y
|
||||
if (dragIndex < hoverIndex && hoverActualY < hoverMiddleY) return;
|
||||
// if dragging up, continue only when hover is bigger than middle Y
|
||||
if (dragIndex > hoverIndex && hoverActualY > hoverMiddleY) return;
|
||||
|
||||
onDrag(dragIndex, hoverIndex);
|
||||
item.index = hoverIndex;
|
||||
},
|
||||
})
|
||||
|
||||
let className = 'list__item commands-setup__item';
|
||||
if (isDragging) className += ' list__item--dragging';
|
||||
|
||||
return (
|
||||
<div className={className} ref={dropRef(ref)}>
|
||||
<div className="commands-setup__item__details">
|
||||
<div ref={dragRef}>
|
||||
<Icon
|
||||
className="commands-setup__item__drag-handle"
|
||||
name="drag_indicator"
|
||||
/>
|
||||
</div>
|
||||
<div className="commands-setup__item__command-item commands__item commands__item--small">
|
||||
<Icon className="commands__item__icon" name={icon} />
|
||||
<span className={`${colour}-background commands__item__background`} />
|
||||
</div>
|
||||
<div className="commands-setup__item__url commands__item__url">
|
||||
{name || <span className="grey-text">{url}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="commands-setup__item__actions">
|
||||
<a className="commands-setup__item__run-button action" onClick={() => onExecute(id, true)}>
|
||||
<Icon name="play_arrow" />
|
||||
</a>
|
||||
<Link className="commands-setup__item__edit-button action" to={`/modal/edit-command/${id}`}>
|
||||
<Icon name="edit" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Commands = ({
|
||||
commands,
|
||||
runCommand,
|
||||
@ -18,58 +95,44 @@ const Commands = ({
|
||||
useEffect(() => {
|
||||
setList(sortItems(indexToArray(commands), 'sort_order'));
|
||||
}, []);
|
||||
|
||||
const onSort = () => {
|
||||
const nextCommands = {};
|
||||
if (!list || !list.length) return;
|
||||
|
||||
const onDrag = useCallback((dragIndex, hoverIndex) => {
|
||||
setList((prev) => {
|
||||
const next = [...prev];
|
||||
const dragItem = next[dragIndex];
|
||||
const hoverItem = next[hoverIndex];
|
||||
next[dragIndex] = hoverItem;
|
||||
next[hoverIndex] = dragItem;
|
||||
return next;
|
||||
});
|
||||
}, [list]);
|
||||
|
||||
const onDragEnd = () => {
|
||||
if (!list || !list.length) return;
|
||||
const nextCommands = {};
|
||||
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>
|
||||
))
|
||||
}
|
||||
</ReactSortable>
|
||||
<div className="commands-setup__item__drag-handle list commands-setup">
|
||||
{list.map((command, index) => (
|
||||
<Command
|
||||
key={`command_${command.id}`}
|
||||
index={index}
|
||||
command={command}
|
||||
onExecute={runCommand}
|
||||
onDrag={onDrag}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Commands;
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
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';
|
||||
|
||||
const SourcesPriority = ({
|
||||
uri_schemes,
|
||||
uri_schemes_priority,
|
||||
}) => {
|
||||
const [list, setList] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
processList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
processList();
|
||||
}, [uri_schemes]);
|
||||
|
||||
const processList = () => {
|
||||
let seen = [];
|
||||
let unseen = [];
|
||||
uri_schemes.forEach((uri) => {
|
||||
const index = uri_schemes_priority.indexOf(uri);
|
||||
if (index > -1) {
|
||||
seen[index] = { uri };
|
||||
} else {
|
||||
unseen.push({ uri });
|
||||
}
|
||||
});
|
||||
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;
|
||||
@ -99,6 +99,36 @@ const Hotkeys = () => {
|
||||
});
|
||||
});
|
||||
|
||||
useHotkeys('1', (e) => {
|
||||
prepare({
|
||||
e,
|
||||
label: 'Now playing',
|
||||
callback: () => {
|
||||
history.push('/queue');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
useHotkeys('2', (e) => {
|
||||
prepare({
|
||||
e,
|
||||
label: 'Search',
|
||||
callback: () => {
|
||||
history.push('/search');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
useHotkeys('3', (e) => {
|
||||
prepare({
|
||||
e,
|
||||
label: 'Kiosk mode',
|
||||
callback: () => {
|
||||
history.push('/modal/kiosk-mode');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
useHotkeys('space,p', (e) => prepare({
|
||||
e,
|
||||
label: 'Play/pause',
|
||||
|
||||
@ -303,9 +303,6 @@ settings:
|
||||
touch_events: Aktivieren Berührungsereignissen zur Steuerungen
|
||||
touch_events_tooltip: Ermöglicht Links- und Rechts-Wischen zum Wechseln der Titeln
|
||||
wide_scrollbars: Breite Bildlaufleisten verwenden
|
||||
sources_priority:
|
||||
label: Quellen Priorität
|
||||
description: Drag-and-Drop zur Priorisierung von Suchanbietern und Ergebnissen
|
||||
reporting:
|
||||
label: Berichterstattung
|
||||
sublabel: Erlaubt die Berichterstattung anonymer Nutzungsstatistiken
|
||||
|
||||
@ -394,9 +394,6 @@ settings:
|
||||
wide: Wide scrollbars
|
||||
hidden: Hidden scrollbars
|
||||
smooth_scrolling: Smooth scroll
|
||||
sources_priority:
|
||||
label: Sources priority
|
||||
description: Drag-and-drop to prioritize search providers and results
|
||||
reporting:
|
||||
label: Reporting
|
||||
sublabel: Allow reporting of anonymous usage statistics
|
||||
@ -635,6 +632,9 @@ modal:
|
||||
title: Hotkeys
|
||||
keys:
|
||||
info: Hotkeys info (this dialog)
|
||||
now_playing: Now playing
|
||||
search: Search
|
||||
kiosk_mode: Kiosk mode
|
||||
exit: Cancel the current interaction
|
||||
play_pause: Play/pause
|
||||
stop: Stop
|
||||
|
||||
@ -366,9 +366,6 @@ settings:
|
||||
wide_scrollbars: Usar barras de desplazamiento anchas
|
||||
grid_glow: Efecto resplandeciente en miniaturas
|
||||
grid_glow_tooltip: Deshabilitar el efecto para dispositivos lentos o navegadores antiguos
|
||||
sources_priority:
|
||||
label: Prioridad de fuentes
|
||||
description: Arrastre y suelte para priorizar proveedores y resultados
|
||||
reporting:
|
||||
label: Reporte
|
||||
sublabel: Permitir reporte de estadísticas de uso anónimas
|
||||
|
||||
@ -379,9 +379,6 @@ settings:
|
||||
wide: Larges
|
||||
hidden: Cachées
|
||||
smooth_scrolling: Défilement doux
|
||||
sources_priority:
|
||||
label: Priorité des sources
|
||||
description: Glisser-déposer pour réordonner les sources et les résultats
|
||||
reporting:
|
||||
label: Rapport
|
||||
sublabel: Autoriser la création de rapports anonymisés sur les statistiques d'utilisation
|
||||
|
||||
@ -366,9 +366,6 @@ settings:
|
||||
wide_scrollbars: Usa barra di scorrimento larga
|
||||
grid_glow: Effetto bagliore miniature
|
||||
grid_glow_tooltip: Disattiva l'effetto per dispositivi poco potenti o browser non recenti
|
||||
sources_priority:
|
||||
label: Priorità fonti
|
||||
description: Trascina e rilascia per dare la priorità ai provider di ricerca e ai risultati
|
||||
reporting:
|
||||
label: Segnalazione
|
||||
sublabel: Consenti statistiche di utilizzo anonime
|
||||
|
||||
@ -331,9 +331,6 @@ settings:
|
||||
touch_events: 再生コントロールにタッチイベントを有効化
|
||||
touch_events_tooltip: 左右スワイプでトラックを変更可能
|
||||
wide_scrollbars: 広いスクロールバーを使用
|
||||
sources_priority:
|
||||
label: ソース優先順位
|
||||
description: ソースの優先順位をドラッグ&ドロップで決める
|
||||
reporting:
|
||||
label: リポート
|
||||
sublabel: 匿名で使用レポートを送信
|
||||
|
||||
@ -364,9 +364,6 @@ settings:
|
||||
touch_events_tooltip: Maakt het mogelijk om naar links of rechts te vegen om van tracks te veranderen
|
||||
grid_glow: Miniatuur gloei effect
|
||||
wide_scrollbars: Gebruik brede schuifbalken
|
||||
sources_priority:
|
||||
label: Prioriteit bronnen
|
||||
description: Verslepen om voorrang van zoekmachines en resultaten te wijzigen
|
||||
reporting:
|
||||
label: Rapportering
|
||||
sublabel: Sta het versturen van anonieme gebruiksstatistieken toe
|
||||
|
||||
@ -331,9 +331,6 @@ settings:
|
||||
touch_events: Włącz obsługę zdarzeń dotykowych
|
||||
touch_events_tooltip: Umożliwia zmienianie utworów poprzez przesunięcia palcem po ekranie
|
||||
wide_scrollbars: Używaj szerokich pasków przewijania
|
||||
sources_priority:
|
||||
label: Priorytety źródeł
|
||||
description: Przesuń i upuść aby zmienić priorytety dostawców
|
||||
reporting:
|
||||
label: Raportowanie
|
||||
sublabel: Włącz anonimowe raportowanie statystyk
|
||||
|
||||
@ -391,9 +391,6 @@ settings:
|
||||
wide: Широкие полосы прокрутки
|
||||
hidden: Скрытые полосы прокрутки
|
||||
smooth_scrolling: Плавная прокрутка
|
||||
sources_priority:
|
||||
label: Приоритет источников
|
||||
description: Перетаскивание для определения приоритетов поисковых систем и результатов
|
||||
reporting:
|
||||
label: Отчетность
|
||||
sublabel: Разрешить отчеты об анонимной статистике использования
|
||||
|
||||
@ -367,9 +367,6 @@ settings:
|
||||
wide_scrollbars: Use wide scrollbars
|
||||
grid_glow: Thumbnail glow effect
|
||||
grid_glow_tooltip: Inaktivera effekten för lågenergi enheter eller äldre webbläsare
|
||||
sources_priority:
|
||||
label: Sources priority
|
||||
description: Dra och släpp för att prioritera sök resultaten från olika utgivningar
|
||||
reporting:
|
||||
label: Rapportering
|
||||
sublabel: Tillåt rapportering av anonym användarstatistik
|
||||
|
||||
@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import Header from '../components/Header';
|
||||
import Icon from '../components/Icon';
|
||||
import Button from '../components/Button';
|
||||
import LinksSentence from '../components/LinksSentence';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as pusherActions from '../services/pusher/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
@ -75,6 +76,7 @@ class Debug extends React.Component {
|
||||
log_pusher,
|
||||
log_snapcast,
|
||||
access_token,
|
||||
uri_schemes = [],
|
||||
} = this.props;
|
||||
const {
|
||||
mopidy_call,
|
||||
@ -216,6 +218,14 @@ class Debug extends React.Component {
|
||||
</label>
|
||||
|
||||
<h4 className="underline"><I18n path="services.mopidy.title" /></h4>
|
||||
<label className="field">
|
||||
<div className="name">Enabled sources</div>
|
||||
<div className="input">
|
||||
<span className="text">
|
||||
<LinksSentence items={uri_schemes.map((name) => ({ name, uri: name }))} nolinks />
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<form onSubmit={this.callMopidy}>
|
||||
<label className="field">
|
||||
<div className="name"><I18n path="debug.call" /></div>
|
||||
@ -336,6 +346,7 @@ const mapStateToProps = (state) => ({
|
||||
test_mode: (state.ui.test_mode ? state.ui.test_mode : false),
|
||||
debug_info: (state.ui.debug_info ? state.ui.debug_info : false),
|
||||
debug_response: state.ui.debug_response,
|
||||
uri_schemes: state.mopidy.uri_schemes,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@ -6,6 +6,10 @@ import * as uiActions from '../../services/ui/actions';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
|
||||
const hotkeys = [
|
||||
{ label: 'now_playing', keysets: [['1']] },
|
||||
{ label: 'search', keysets: [['2']] },
|
||||
{ label: 'kiosk_mode', keysets: [['3']] },
|
||||
{ label: 'info', keysets: [['i']] },
|
||||
{ label: 'info', keysets: [['i']] },
|
||||
{ label: 'play_pause', keysets: [['p'], ['spacebar']] },
|
||||
{ label: 'stop', keysets: [['s']] },
|
||||
|
||||
@ -157,7 +157,6 @@ class Search extends React.Component {
|
||||
{ value: 'name', label: i18n('common.name') },
|
||||
{ value: 'artist', label: i18n('common.artist') },
|
||||
{ value: 'duration', label: i18n('common.duration') },
|
||||
{ value: 'uri', label: i18n('common.source') },
|
||||
];
|
||||
|
||||
const provider_options = uri_schemes.map((item) => ({
|
||||
|
||||
@ -3,7 +3,6 @@ import { connect, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import PusherConnectionList from '../components/PusherConnectionList';
|
||||
import SourcesPriority from '../components/Fields/SourcesPriority';
|
||||
import Commands from '../components/Fields/Commands';
|
||||
import TextField from '../components/Fields/TextField';
|
||||
import SelectField from '../components/Fields/SelectField';
|
||||
@ -333,22 +332,6 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field sources-priority">
|
||||
<div className="name">
|
||||
<I18n path="settings.interface.sources_priority.label" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<SourcesPriority
|
||||
uri_schemes={mopidy.uri_schemes ? mopidy.uri_schemes : []}
|
||||
uri_schemes_priority={ui.uri_schemes_priority ? ui.uri_schemes_priority : []}
|
||||
uiActions={uiActions}
|
||||
/>
|
||||
<div className="description">
|
||||
<I18n path="settings.interface.sources_priority.description" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isHosted() ? null : (
|
||||
<div className="field checkbox">
|
||||
<div className="name">
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
@import 'components/dropdown-field';
|
||||
@import 'components/autocomplete-field';
|
||||
@import 'components/filter-field';
|
||||
@import 'components/sources-priority-field';
|
||||
@import 'components/sub-views';
|
||||
@import 'components/sub-tabs';
|
||||
@import 'components/debug';
|
||||
@ -36,7 +35,6 @@
|
||||
@import 'components/commands';
|
||||
@import 'components/related-artists';
|
||||
@import 'components/error-message';
|
||||
@import 'components/sortable';
|
||||
@import 'components/mute-control';
|
||||
@import 'components/select-field';
|
||||
@import 'components/pin-list';
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
|
||||
.sortable {
|
||||
&-ghost {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
|
||||
.sources-priority-field {
|
||||
padding: 7px 0;
|
||||
|
||||
.source {
|
||||
@include animate();
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
padding: 6px 8px 4px 3px;
|
||||
margin: 0 5px 5px 0;
|
||||
cursor: move;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: colour('white');
|
||||
|
||||
&.sortable-chosen {
|
||||
background: colour('grey');
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 14px;
|
||||
padding-right: 0.1em;
|
||||
color: colour('darkest_grey');
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
@include theme('light') {
|
||||
background: colour('faint_grey');
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
background: darken(colour('faint_grey'), 4%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user