Merge pull request #887 from jaedb/develop

Develop
This commit is contained in:
James Barnsley
2023-02-12 07:56:52 +13:00
committed by GitHub
16 changed files with 1738 additions and 1174 deletions

View File

@ -10,7 +10,6 @@ services:
- /tmp/snapserver:/tmp
- ./docker/snapserver/snapserver.conf:/etc/snapserver.conf
- ./docker/snapserver/snapserver.json:/root/.config/snapserver/server.json
- ./docker/snapserver/meta_mopidy.py:/usr/share/snapserver/plug-ins/meta_mopidy.py
mopidy:
image: jaedb/iris
environment:

577
src/js/locale/br.yaml Normal file
View File

@ -0,0 +1,577 @@
name: Português
##
# Common and reusable phrases
##
errors:
uri_not_found: 'Não posso encotrar naeda com a URI %{uri}'
could_not_load: 'Não posso carregar %{name}'
could_not_load_library: 'Não posso carregar %{name} de %{provider}'
no_results: Sem resultados
authorization_required: 'devo autorizar %{provider} antes'
enable_first: 'Habilite %{provider} antes'
no_service_worker: Serviço não suportado
required: Requerido
unknown_error: Erro desconhecido
need_to_be_online: Você precisa estar online para carregar este recurso
nothing_selected: Nada seleccionado
cannot_reorder:
title: Não consigo reordenar Não consigo reordenar itens
description: Limpe a ordem/filtros e tente novamente
actions:
play: Reproduzir
play_all: Reproduzir tudo
play_next: Reproduzir próximo
shuffle_play: Reprodução aleatoria
pause: Pausa
stop: Parar
add_to_playlist: Adicionar à Playlist
add_to_library: Adicionar à Biblioteca
added_tracks_to: 'Adicionar %{count} faixas a '
remove_from_library: Removido da Biblioteca
add_to_queue: Adicionar à fila
reset: Reiniciar
back: Voltar
refresh: Atualizar
enable: Habilitar
disable: Desabilitar
send: Enviar
save: Guardar
start: Iniciar
edit: Editar
add: Adicionar
confirm: Deseja confirmar?
delete: Apagar
remove: Remover
cancel: Cancelar
created: '%{name} Criado'
saved: '%{name} guardado'
deleted: '%{name} apagado'
common:
popularity: Popularidade
name: Nome
artist: Artista
duration: Duração
source: Fonte
sources: Fontes
sort: Ordenar
filter: Filtrar
by: ' por '
loading_library: 'Carregando %{provider} %{type}'
fields:
start_typing: 'Começar a escrever...'
search: 'Buscar...'
sort: Ordenar
view: Ver
source: Fonte
uri: URI(s)
items_to_add:
label: Etiquetas a adicionar
placeholder: Sem Etiquetas
filter: Filtrar
filters:
all: Todo
thumbnails: Miniaturas
list: Lista
as_loaded: Como carregar
name: Nome
artist: Artista
album: Album
updated: Atualizando
tracks: Faixas
source: Fonte
editable: Editável
owner: Proprietário
followers: Seguidores
popularity: Popularidade
time:
seconds:
short: seg
singular: segundo
plural: segundos
minutes:
short: min
singular: minuto
plural: minutos
hours:
short: hor
singular: hora
plural: horas
days:
short: día
singular: día
plural: días
weeks:
short: sem
singular: semana
plural: semanas
years:
short: ano
singular: ano
plural: anos
specs:
tracks: '%{count} faixas'
albums: '%{count} álbuns'
followers: '%{count} seguidores'
listeners: '%{count} ouvintes'
playlists: '%{count} playlists'
plays: '%{count} reproduções'
popularity: '%{percent}% popularidade'
added: 'Adicionado '
added_ago: 'Adicionado á %{time}'
edited: 'Editado '
edited_ago: 'Editado á %{time}'
played_ago: 'Reproduzido á %{time}'
services:
mopidy:
title: Mopidy
local: Local
adding_uris: 'Adicionando %{count} URI(s)'
adding_uris_broadcast: '%{username} está adicionando %{count} URI(s)'
searching: 'Buscando %{provider} %{type}'
searching_providers: 'Buscando %{count} provedores Mopidy'
loading_albums: 'Carregando %{count} álbuns locais'
spotify:
title: Spotify
pusher:
title: Pusher
snapcast:
title: Snapcast
genius:
title: Genius
switch_lyrics_result: Alterar para outro resultado de pesquisa de letras
want_lyrics: 'Quer ver as letras das músicas? Autorizar o Genius em '
google:
title: Google
lastfm:
title: LastFM
love: Love
unlove: Unlove
##
# Components
##
sidebar:
now_playing: Tocando
search: Buscar
discover: Encontrar
genre: Género
featured_playlists: Playlist destacadas
new_releases: Novos lançamentos
my_music: Minha música
playlists: Playlists
artists: Artistas
albums: Álbuns
tracks: Faixas
browse: Navegar
settings: Ajustes
not_connected: '%{name} não conectado'
browser_offline: Navegador offline
update_available: Atualização disponível
context_menu:
add_to_playlist:
title: Adicionar à Playlist
no_playlists: Sem Playlists editáveis
play_next: Reproduzir a seguir
play_top_tracks: Reproduzir as faixas superiores
play_all_tracks: Reproduzir todas as faixas
love_track: Faixa favorita
unlove_track: Faixa descartada
pin: Fixar
unpin: Desfixar
go_to_artist: Ir a artista
go_to_user: Ir a usuario
track_info: Informações da faixa
discover_similar: Encontrar similares
start_radio: Começar rádio
copy_uri: Copiar URI(s)
refresh: Atualizar
playback_controls:
consume: Usar
shuffle: Aleatória
repeat: Repetir
no_outputs: Sem saídas
mute: Mute
unmute: Desmute
snapcast:
enabled: Habilitado
streaming_enabled: Streaming habilitado
show_disconnected_clients: Mostrar clientes desconectados
no_connected_clients: Nenhum cliente conectado
host: Host
port: Porta
name: Nome
name_disconnected: Nome (desconectado)
group: Grupo
new_group: '+ Novo grupo'
latency: Latência
stream: Stream
volume: Volúme
authentication:
popup_blocked: Pop-up bloqueado. Permita janelas pop-up e tente novamente.
failed: Falha na autorização. %{origin} não é o URL de autorização configurado.
log_out: Sair
log_in: Entrar
##
# Views
##
now_playing:
title: Reproduzir
context_actions:
radio: Radio
history: Historico
add_uri: Adicionar URI
current_track:
playing_from: Reproduzindo de
shuffle: Aleatória
clear: Limpar
radio: Rádio
album:
title: Álbum
title_window: '%{name} por %{artist} (album)'
wiki:
title: Acerca de
published: 'Publicado: %{date}'
tracks:
title: Faixas
sort:
disc_track: Número da faixa
name: Nome
artist:
title: Artista
title_window: '%{name} (artista)'
overview:
title: Visão geral
top_tracks: Faixas superiores
albums: Sem álbum
related_artists:
title: Artistas relacionados
more: Todos os artistas relacionados
tracks:
title: Faixas
sort:
disc_track: Número de Faixas
name: Nome
album: Álbum
related_artists:
title: Artistas relacionados
about:
title: Acerca de
source: '%{source} artista'
wiki:
published: 'Publicado: %{date}'
origin: 'Origem: '
albums:
sort:
default: Por padrão
name: Nome
release_date: Lançado
track_count: Faixa
filter:
all: Todo
albums: Álbuns
singles: Singles
compilations: Copilação
debug:
title: Depurar
ui:
title: Interface de usuário
debug:
title: Depurar
overlay: Mostrar capa de depuração
logging:
title: Iniciando sessão
mopidy: Log Mopidy
pusher: Log Notificações
actions: Log Ações
snapcast: Log Snapcast
create_notification: Criado notificação
create_process: Criado notificação de proceso
run_test: Executar teste
call: Chamar
data: Data
examples: Exemplo
playlist:
title: Playlist
title_window: '%{name} (playlist)'
title_plural: Playlists
tracks:
title: Faixas
sort:
default: Por Padrão
sort_id: Como cargado
added_at: Data adição
name: Nome
artist: Artista
album: Álbum
queue_history:
title: Histórico de reprodução
search:
title: Buscar
title_window: 'Buscar: %{term}'
context_actions:
sort: Ordenar
source: 'Fontes (%{count})'
placeholder: Buscar
all:
title: Todo
artists:
title: Artistas
more: 'Todos os artistas (%{count})'
albums:
title: Álbumes
more: 'Todos os álbuns (%{count})'
playlists:
title: Playlists
more: 'Todas as playlists (%{count})'
tracks:
title: Faixas
track:
title: Faixa
title_window: '%{name} por %{artists} (track)'
lyrics_origin: 'Origem: '
explicit: Explícito
disc_number: 'Disco %{number}'
track_number: 'Faixa %{number}'
unknown_album: Álbum desconhecido
want_lyrics: 'Quer ver a letra da faixa? Autorize Genius baixo '
settings:
title: Ajustes
help: Ajuda
server:
title: Servidor
username:
label: Nome de usuário
description: Uma string não exclusiva usada para identificar este cliente (sem caracteres especiais)
interface:
title: Interface
language:
label: Idioma
theme:
label: Tema
auto: Auto
auto_tooltip: Detectar a sua preferência do navegador do sistema operacional
dark: Escuro
light: Claro
behavior:
label: Comportamento
clear_tracklist: Limpar lista de faixas da reprodução URI(s)
clear_tracklist_tooltip: Reproduzir uma ou mias URIs irá limpar a fila de reprodução atual
hotkeys: Habilitar teclas de atalho
smooth_scrolling: Ativar rolagem suave
touch_events: Ativar eventos de toque nos controles de reprodução
touch_events_tooltip: Permitir deslizar para a esquerda e para a direita para mudar de faixa
wide_scrollbars: Use barras de rolagem largas
grid_glow: Efeito brilhante nas miniaturas
grid_glow_tooltip: Desative o efeito para dispositivos lentos ou navegadores antigos
reporting:
label: Etiquetas
sublabel: Permitir etiquetas de estatisticas de uso anônimo
description: 'Esses dados de uso anônimos são importantes para identificar bugs e possíveis recursos que tornam o Iris melhor. você quer saber mais? Ler '
privacy_policy: Política de privacidade
are_you_sure: Tem certeza de que não deseja esse recurso?
commands:
label: Comandos
advanced:
title: Avançado
start_local_scan: Iniciar pesquisa local
artist_uri:
label: URI da biblioteca de artistas
description: URI usado para armazenar a biblioteca do artista
album_uri:
label: URI da biblioteca de álbuns
description: URI usado para armazenar a biblioteca de álbuns
track_uri:
label: URI da biblioteca de faixas
description: URI usado para armazenar a biblioteca de faixas
connections:
label: Conexões
version:
label: Versão
upgrade_available: 'Atualização disponível (%{version})'
upgrade: 'Aztualizar a %{version}'
up_to_date: Atualizada
share_configuration: Compartilhar configurações
restart: Reiniciar servidor
reset: Reiniciar ajustes
about:
title: Acerca de
blurb_1: ' é um projeto open-source de '
blurb_2: '. É grátis e não tem garantia. Se você pagou a alguém por este software, informe-nos.'
donate: Doar
github: GitHub
pusher_connection_list:
not_connected: Não conectado
no_connections: Sem conexão
you: ' (Você)'
servers:
inactive: Inativo
connecting: Conectando
disconnected: Desconectado
connected: Conectado
new_server: Novo servidor
name: Nome
host: Host
port: Porta
encryption:
label: criptografia
sublabel: Usar SSL
description: Requer um proxy SSL
switch: Trocar este servidor
reconnect: Reconectar
services:
title: Serviços
limited_access: Acceso limitado
unknown: Desconhecido
current_user: Usuario atual
authorization: Autorização
refresh_token: Atualizar o token
authorized: Autorizado
read_only: Somente leitura
unauthorized: Sem autorização
enabled: Habilitado
disabled: Deshabilitado
connected: Conectado
disconnected: Desconectado
spotify:
mopidy_spotify_not_running: A extensão Mopidy-Spotify não está funcionando. Não será possível reproduzir faixas do Spotify
country:
label: País
description: Código do país ISO 3166-1 alpha-2 (eg 'PT')
locale:
title: Local
description: Código de idioma em minúsculas ISO 639 e código do país em maiúsculas ISO 3166-1 alpha-2, acompanhado por um sublinhado (eg 'pt_BR')
user:
title: Usuário
title_window: '%{name} (usuario)'
you: Vocês
discover:
title: Encontrar
categories:
title: 'Género / Estilo'
category:
title: 'Género / Estilo'
category: Categoría
featured:
title: Playlists destacadas
new_releases:
title: Novos lançamentos
recommendations:
title: Encontrar
tracks: Faixas
artists: Artistas
albums: Álbuns
body_title: Explorar nova música
body_subtitle: Adicione propriedades musicais para construir seu som
too_many_seeds: Muitas faixas! Você pode usar até 5 faixas entre músicas, artistas e gêneros.
find_recommendations: Encontrar recomendações
library:
albums:
title: Meus álbuns
artists:
title: Meus artistas
browse:
title: Navegar
browse_directory:
title: Diretório
playlists:
title: Meinhas playlists
tracks:
title: Meinhas faixas
modal:
add_to_playlist:
title: adicionar à Playlist
subtitle: 'Selecione a lista de reprodução para adicionar %{count} faixa%{plural}'
no_playlists: Nenhuma lista de reprodução disponível
no_editable_playlists: Sem listas de reprodução editáveis
add_to_queue:
title: Adicionar à fila
subtitle: Adicione uma lista separada por vírgulas de URIs à fila de reprodução
uris: URI(s)
add_random: Encontrar faixas aleatorias
position:
label: Posição
end: Fim
next: Depois da faixa atual
edit_playlist:
title: Editar playlist
title_create: Crear playlist
name_required: Requier um nome
name: Nome
description: Descrição
provider: Provedor
image:
label: Arte da capa
description: Só JPEG, 256kB máx. Deixe-o vazio para manter a imagem inalterada.
options:
label: Opções
public: Público
collaborative: Colaborativa
create_playlist: Crio a playlist
edit_command:
title: Editar comando
title_create: Criar comando
name: Nome
colour: Cor
icon: Icone
url: URL
method:
label: Método
get: GET
post: POST
data: Dados
headers: Cabeçalhos adicionais
edit_radio:
title: Rádio
subtitle: Adicione e remova faixas para moldar o som do seu rádio. O rádio usa as recomendações do Spotify para sugerir músicas semelhantes às suas faixas.
invalid_seed_uri: URI(s) inválida
too_many_seeds: Mais de 5 faixas, ignorando o resto
only_spotify_uris: Só URIs do Spotify são suportados
already_added: URI já adicionado
no_seeds: Sem faixas
initial_setup:
title: Bem-vindo a Iris
username: Usuário
kiosk:
title: Reproduzindo
title_window: '%{name} por %{artist} (reproduzindo agora)'
lyrics: Letras
share_configuration:
title: Compartilhar configurações
subtitle: Transfira suas autorizações e configurações de interface para outro usuário conectado
no_peers: Sem conexões de mesmo nível
recipients: Recipientes
configurations: ConfiguraçÕes
authorization: '%{service} autorização'
logged_in_as: 'Registrado como %{name}'
snapcast_description: Detalhes da conexão do servidor
interface: Ajustes de Interface
interface_description: Tema, ordem, filtros, etc
import:
title: Configuração compartilhad
subtitle: 'Outro usuário compartilhou suas configurações com você. Isso inclui:'
do_you_want_to_import: Você quer importar?
import_now: Importar agora
successful: Importada com sucesso
reset:
title: Reiniciar Iris
subtitle: Limpe suas configurações locais, informações de armazenamento ou cache
items: Reiniciar de fábrica
preferences:
label: Preferencias
description: Costumização de Interface
database:
label: Base de dados
description: Base de dados local de bibliotecas e ajustes
service_worker:
label: Service worker
description: Reinstalar service worker
cache:
label: Extrair cacher
description: Uma API externa requer cache (requer Service Worker)
test_mode:
label: Modo Teste
description: Log detalhado para fins de diagnóstico

View File

@ -9,6 +9,7 @@ import _pl from './pl.yaml';
import _it from './it.yaml';
import _es from './es.yaml';
import _ru from './ru.yaml';
import _br from './br.yaml';
// Merge languages with English. This provides English fallbacks to untranslated
// fields without breaking the UI (with blanks)
@ -30,6 +31,8 @@ const es = {};
merge(es, en, _es);
const ru = {};
merge(ru, en, _ru);
const br = {};
merge(br, en, _br);
const available = [
{ key: 'en', name: en.name },
@ -39,6 +42,7 @@ const available = [
{ key: 'it', name: it.name },
{ key: 'ja', name: ja.name },
{ key: 'nl', name: nl.name },
{ key: 'br', name: br.name },
{ key: 'pl', name: pl.name },
{ key: 'ru', name: ru.name },
{ key: 'sv', name: sv.name },
@ -46,6 +50,7 @@ const available = [
export default {
en,
br,
es,
de,
fr,

View File

@ -19,10 +19,10 @@ errors:
title: Невозможно изменить порядок элементов
description: Удалить сортировку/фильтры и повторить попытку
actions:
play: Играть
play: Воспроизвести
play_all: Воспроизвести все
play_next: Играть дальше
shuffle_play: Играть в случайном порядке
play_next: Воспроизвести следующим
shuffle_play: Воспроизвести в случайном порядке
pause: Пауза
stop: Стоп
add_to_playlist: Добавить в плейлист
@ -31,7 +31,7 @@ actions:
remove_from_library: Удалить из библиотеки
add_to_queue: Добавить в очередь
reset: Сброс
back: Отменить
back: Назад
refresh: Oбновить
enable: Включить
disable: Выключить
@ -59,7 +59,7 @@ common:
by: 'после'
loading_library: 'Загружается %{provider} %{type}'
play_state:
playing: Играет
playing: Воспроизведение
paused: Приостановлено
stopped: Остановлено
idle: Бездействие
@ -78,7 +78,7 @@ fields:
all: Все
thumbnails: Миниатюры
list: Список
as_loaded: Как загружено
as_loaded: Время добавления
name: Имя
artist: Исполнитель
album: Альбом
@ -132,7 +132,7 @@ services:
restore_defaults: Восстановить значения по умолчанию
mopidy:
title: Mopidy
local: Local
local: Локальные
adding_uris: 'Добавление %{count} URI(s)'
adding_uris_broadcast: '%{username} добавил %{count} URI(s)'
searching: 'Поиск %{provider} %{type}'
@ -165,8 +165,8 @@ services:
sidebar:
now_playing: Сейчас играет
search: Поиск
discover: Обнаружить
genre: Жанр / Настроение
discover: Найти новое
moods: Жанр / Настроение
featured_playlists: Избранные плейлисты.
new_releases: Новые релизы
my_music: Моя музыка
@ -174,7 +174,7 @@ sidebar:
artists: Исполнитель
albums: Альбомы
tracks: Треки
browse: Смотреть
browse: Сервисы
settings: Настройки
servers: Серверы
not_connected: '%{name} не подключен'
@ -185,7 +185,7 @@ context_menu:
title: Добавить в плейлист
new_playlist: Создать плейлист
no_playlists: Нет доступных для записи плейлистов
play_next: Играть дальше
play_next: Воспроизвести следующим
play_top_tracks: Воспроизвести лучшие треки
play_all_tracks: Воспроизвести все треки
love_track: Любимые треки
@ -213,7 +213,7 @@ snapcast:
show_disconnected_clients: Показать отключенные клиенты
no_connected_clients: Нет подключенных клиентов
host: Host
port: Port
port: Порт
name: Имя
name_disconnected: Имя (Отключен)
group: Группа
@ -229,9 +229,9 @@ snapcast:
incompatible: ' (требуется при подключении из сеанса браузера HTTPS)'
authentication:
popup_blocked: Всплывающее окно заблокировано. Разрешите всплывающие окна и повторите попытку.
failed: Авторизация не удалась. %{origin} is not the configured authorization_url.
log_out: Log out
log_in: Log in
failed: Авторизация не удалась. %{origin}, не настроен authorization_url.
log_out: Выйти
log_in: Войти
##
@ -255,7 +255,7 @@ album:
title: О
published: 'Опубликовано: %{date}'
tracks:
title: Треке
title: Треки
sort:
disc_track: Номер трека
name: Имя
@ -418,14 +418,17 @@ settings:
upgrade_available: 'Доступно обновление (%{version})'
upgrade: 'Обновить до %{version}'
up_to_date: Актуально
share_configuration: Поделиться конфигурацией
shared_configuration:
label: Конфигурация
share: Поделиться
import: Импортировать
restart: Перезапустить сервер
reset: Сброс настроек
about:
title: О
blurb_1: ' это проект с открытым исходным кодом от '
blurb_2: '. It is provided free and with absolutely no warranty. If you paid someone for this software, please let me know.'
donate: Donate
donate: Поддержать
github: GitHub
pusher_connection_list:
not_connected: Не подключен
@ -454,7 +457,7 @@ settings:
unknown: Неизвестно
current_user: Текущий пользователь
authorization: Авторизация
refresh_token: RОбновить токен
refresh_token: Обновить токен
authorized: Авторизованный
read_only: Только для чтения
unauthorized: Неавторизованный
@ -563,9 +566,45 @@ modal:
title: Добро пожаловать в Ирис
username: Пользователь
kiosk:
title: Сейчас играет
title: Воспроизведение
title_window: '%{name} by %{artist} (сейчас воспроизводится)'
lyrics: Текст песни
shared_config:
no_peers: Нет соединений
recipients: Получатели
configurations: Настройки
import_now: Импортировать
imported: Импортирование завершено
share: Поделиться
shared: Настройки предоставлены
push:
title: Поделиться настройками
subtitle: Поделиться авторизациями и настройками интерфейса с другими подключенными пользователями
pushed:
title: Настройки получены
subtitle: Другой пользователь поделилился настройками с вами
server:
title: Импортировать настройки
subtitle: Импортировать авторизации сторонних серверов и настройки интерфейса
label: Сервер
description: Сохранить на сервер чтобы другие пользователи могли импортировать
config:
label: Настройки
ui:
label: Пользовательский интерфейс
description: Тема, сортировка, фильтры и т.д.
spotify:
label: Авторизация Spotify
description: 'Вы вошли в систему как %{name}'
lastfm:
label: Авторизация LastFM
description: 'Вы вошли в систему как %{name}'
genius:
label: Авторизация Genius
description: 'Вы вошли в систему как %{name}'
snapcast:
label: Сервер Snapcast
description: Настройки подключения
share_configuration:
title: Поделиться конфигурацией
subtitle: Передайте свои авторизации и настройки интерфейса другому подключенному пользователю

View File

@ -258,7 +258,9 @@ export function findTrackLyrics(uri) {
// Immediately go and get the first result's lyrics
const lyrics_result = lyrics_results[0];
dispatch(getTrackLyrics(track.uri, lyrics_result.path));
if (lyrics_result) {
dispatch(getTrackLyrics(track.uri, lyrics_result.path));
}
}
dispatch(uiActions.stopLoading(loader_key));
},

View File

@ -1899,11 +1899,15 @@ const MopidyMiddleware = (function () {
playlistsToLoad.map((uri) => {
request(store, 'playlists.lookup', { uri })
.then((playlist) => {
playlists[uri] = formatPlaylist({
name: playlist.name,
uri: playlist.uri,
tracks: formatTracks(playlist.tracks),
});
playlists[uri] = {
uri,
loading: false,
...playlist ? formatPlaylist({
name: playlist.name,
uri: playlist.uri,
tracks: formatTracks(playlist.tracks),
}) : {},
};
loaded += 1;
if (loaded === toLoad) {
resolve(playlists);

View File

@ -15,10 +15,15 @@ import { SourceIcon } from '../components/Icon';
import DropdownField from '../components/Fields/DropdownField';
import FilterField from '../components/Fields/FilterField';
import { i18n, I18n } from '../locale';
import * as coreActions from '../services/core/actions';
import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as lastfmActions from '../services/lastfm/actions';
import { loadAlbum } from '../services/core/actions';
import { playURIs } from '../services/mopidy/actions';
import { getAlbum } from '../services/lastfm/actions';
import {
setSort,
setWindowTitle,
showContextMenu,
hideContextMenu,
} from '../services/ui/actions';
import { uriSource } from '../util/helpers';
import Button from '../components/Button';
import { makeItemSelector, makeSortSelector } from '../util/selectors';
@ -28,15 +33,6 @@ import { decodeUri, formatContext } from '../util/format';
const SORT_KEY = 'album_tracks';
const Album = () => {
const { loadAlbum } = coreActions;
const { getAlbum } = lastfmActions;
const { playURIs } = mopidyActions;
const {
setSort,
setWindowTitle,
showContextMenu,
hideContextMenu,
} = uiActions;
const dispatch = useDispatch();
const { uri: encodedUri } = useParams();
const uri = decodeUri(encodedUri);

View File

@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import Loader from '../../components/Loader';
import Header from '../../components/Header';
import TrackList from '../../components/TrackList';
@ -10,8 +10,15 @@ import DropdownField from '../../components/Fields/DropdownField';
import FilterField from '../../components/Fields/FilterField';
import Icon from '../../components/Icon';
import ErrorBoundary from '../../components/ErrorBoundary';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import {
set,
setWindowTitle,
hideContextMenu,
} from '../../services/ui/actions';
import {
getDirectory,
playURIs,
} from '../../services/mopidy/actions';
import * as spotifyActions from '../../services/spotify/actions';
import { arrayOf, sortItems, applyFilter } from '../../util/arrays';
import { i18n, I18n } from '../../locale';
@ -19,7 +26,8 @@ import Button from '../../components/Button';
import { encodeUri, decodeUri } from '../../util/format';
import { makeLoadingSelector } from '../../util/selectors';
import ErrorMessage from '../../components/ErrorMessage';
import { withRouter } from '../../util';
const loadingSelector = makeLoadingSelector(['mopidy_library.(browse|lookup)']);
const Breadcrumbs = ({ uri }) => {
let parent_uri = uri || null;
@ -64,221 +72,154 @@ const Subdirectories = ({ items, view }) => {
);
};
class BrowseDirectory extends React.Component {
constructor(props) {
super(props);
const BrowseDirectory = () => {
const dispatch = useDispatch();
const [filter, setFilter] = useState('');
const directoryInState = useSelector(({ mopidy }) => mopidy?.directory || {});
const view = useSelector(({ ui }) => ui?.library_directory_view);
const loading = useSelector(loadingSelector);
this.state = {
filter: '',
};
}
componentDidMount() {
const {
uiActions: {
setWindowTitle,
},
} = this.props;
setWindowTitle(i18n('library.browse_directory.title'));
this.loadDirectory();
}
componentDidUpdate = ({ uri: prevUri }) => {
const { uri } = this.props;
if (uri && uri !== prevUri) {
this.loadDirectory();
}
}
loadDirectory = () => {
const {
uri,
mopidyActions: {
getDirectory,
},
} = this.props;
getDirectory(uri);
}
playAll = () => {
const {
uri,
mopidyActions: {
playURIs,
},
uiActions: {
hideContextMenu,
},
directory: {
tracks,
} = {},
} = this.props;
if (!tracks || !tracks.length) return;
playURIs({
uris: arrayOf('uri', sortItems(tracks, 'name')),
from: {
name: 'Browse',
type: 'browse',
uri,
},
});
hideContextMenu();
}
goBack = () => {
const { uiActions: { hideContextMenu } } = this.props;
window.history.back();
hideContextMenu();
}
render = () => {
const {
uri,
directory,
uiActions,
loading,
view,
name,
} = this.props;
const { filter } = this.state;
if (!directory || (!directory.subdirectories && !directory.tracks)) {
if (loading) {
return <Loader body loading />;
}
return (
<ErrorMessage type="not-found" title="Not found">
<p>
<I18n path="errors.uri_not_found" uri={uri} />
</p>
</ErrorMessage>
);
}
let subdirectories = directory?.subdirectories;
let tracks = directory?.tracks;
subdirectories = sortItems(subdirectories, 'name');
tracks = sortItems(tracks, 'name');
if (filter && filter !== '') {
subdirectories = applyFilter('name', filter, subdirectories);
tracks = applyFilter('name', filter, tracks);
}
const view_options = [
{
value: 'thumbnails',
label: i18n('fields.filters.thumbnails'),
},
{
value: 'list',
label: i18n('fields.filters.list'),
},
];
const options = (
<>
<FilterField
initialValue={filter}
handleChange={(value) => this.setState({ filter: value })}
onSubmit={() => uiActions.hideContextMenu()}
/>
<DropdownField
icon="visibility"
name="View"
value={view}
valueAsLabel
options={view_options}
handleChange={(value) => { uiActions.set({ library_directory_view: value }); uiActions.hideContextMenu(); }}
/>
{tracks && (
<Button
onClick={this.playAll}
noHover
discrete
tracking={{ category: 'Directory', action: 'Play' }}
>
<Icon name="play_circle_filled" />
<I18n path="actions.play_all" />
</Button>
)}
<Button
onClick={(e) => { uiActions.hideContextMenu(); this.goBack(e); }}
noHover
tracking={{ category: 'Directory', action: 'Back' }}
>
<Icon name="keyboard_backspace" />
<I18n path="actions.back" />
</Button>
</>
);
return (
<div className="view library-local-view">
<Header options={options} uiActions={uiActions}>
<Icon name="folder" type="material" />
<div className="header__text">
{name || i18n('library.browse_directory.title')}
<Breadcrumbs uri={uri} />
</div>
</Header>
<section className="content-wrapper">
<ErrorBoundary>
<Subdirectories items={subdirectories} view={view} />
<TrackList
context={{
uri,
name: 'Browse',
type: 'browse',
}}
tracks={tracks}
className="library-local-track-list"
/>
</ErrorBoundary>
</section>
</div>
);
}
}
const loadingSelector = makeLoadingSelector(['mopidy_library.(browse|lookup)']);
const mapStateToProps = (state, ownProps) => {
console.debug('browser directory', ownProps)
const {
mopidy: {
directory: _directory = {},
},
ui: {
library_directory_view: view,
},
} = state;
const uri = decodeUri(ownProps.params.uri);
let { uri, name } = useParams();
uri = decodeUri(uri);
name = decodeURIComponent(name);
const uriMatcher = [uri, decodeURIComponent(uri)]; // Lenient matching due to encoding diffs
const directory = _directory && uriMatcher.includes(_directory.uri)
? _directory
const directory = directoryInState && uriMatcher.includes(directoryInState.uri)
? directoryInState
: undefined;
return {
uri,
name: decodeURIComponent(ownProps.params.name),
loading: loadingSelector(state),
directory,
view,
};
};
useEffect(() => {
dispatch(setWindowTitle(i18n('library.browse_directory.title')));
dispatch(getDirectory(uri));
}, []);
const mapDispatchToProps = (dispatch) => ({
uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch),
});
useEffect(() => {
dispatch(getDirectory(uri));
}, [uri]);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BrowseDirectory));
const onViewChange = (value) => {
dispatch(set({ library_directory_view: value }));
dispatch(hideContextMenu());
}
const playAll = () => {
if (!tracks || !tracks.length) return;
dispatch(
playURIs({
uris: arrayOf('uri', sortItems(tracks, 'name')),
from: {
name: 'Browse',
type: 'browse',
uri,
},
})
);
dispatch(hideContextMenu());
}
const goBack = () => {
dispatch(hideContextMenu());
window.history.back();
}
if (!directory || (!directory.subdirectories && !directory.tracks)) {
if (loading) {
return <Loader body loading />;
}
return (
<ErrorMessage type="not-found" title="Not found">
<p>
<I18n path="errors.uri_not_found" uri={uri} />
</p>
</ErrorMessage>
);
}
let subdirectories = directory?.subdirectories;
let tracks = directory?.tracks;
subdirectories = sortItems(subdirectories, 'name');
tracks = sortItems(tracks, 'name');
if (filter && filter !== '') {
subdirectories = applyFilter('name', filter, subdirectories);
tracks = applyFilter('name', filter, tracks);
}
const view_options = [
{
value: 'thumbnails',
label: i18n('fields.filters.thumbnails'),
},
{
value: 'list',
label: i18n('fields.filters.list'),
},
];
const options = (
<>
<FilterField
initialValue={filter}
handleChange={setFilter}
onSubmit={() => dispatch(hideContextMenu())}
/>
<DropdownField
icon="visibility"
name="View"
value={view}
valueAsLabel
options={view_options}
handleChange={onViewChange}
/>
{tracks && (
<Button
onClick={playAll}
noHover
discrete
tracking={{ category: 'Directory', action: 'Play' }}
>
<Icon name="play_circle_filled" />
<I18n path="actions.play_all" />
</Button>
)}
<Button
onClick={goBack}
tracking={{ category: 'Directory', action: 'Back' }}
noHover
>
<Icon name="keyboard_backspace" />
<I18n path="actions.back" />
</Button>
</>
);
return (
<div className="view library-local-view">
<Header options={options}>
<Icon name="folder" type="material" />
<div className="header__text">
{name || i18n('library.browse_directory.title')}
<Breadcrumbs uri={uri} />
</div>
</Header>
<section className="content-wrapper">
<ErrorBoundary>
<Subdirectories items={subdirectories} view={view} />
<TrackList
context={{
uri,
name: 'Browse',
type: 'browse',
}}
tracks={tracks}
className="library-local-track-list"
/>
</ErrorBoundary>
</section>
</div>
);
}
export default BrowseDirectory;

View File

@ -106,7 +106,7 @@ const KioskMode = () => {
if (current_track.lyrics) return;
// We got results, but failed to load the lyrics, so re-try
if (current_track.lyrics_results && current_track.lyrics === null) {
if (current_track?.lyrics_results && current_track.lyrics === null) {
dispatch(geniusActions.getTrackLyrics(current_track.uri, current_track.path));
return;
}

View File

@ -1,375 +1,375 @@
@include responsive($bp_medium){
body.context-menu-open {
.body {
@include blur(10px);
}
}
}
.context-menu {
position: fixed;
z-index: 99;
font-size: 13px;
font-weight: 500;
font-family: "Overpass";
&__section {
@include animate();
overflow-y: scroll;
&--submenu {
z-index: 3;
border-radius: 3px;
transform: translateX(100%);
opacity: 0;
.context-menu {
&__item {
width: 100%;
&--functional {
color: colour('mid_grey');
&:last-of-type {
border-bottom: 1px solid colour('middle_grey');
}
}
&__label {
.icon {
margin-bottom: 0.4em;
padding-right: 10px;
vertical-align: baseline;
}
}
}
}
}
&--items {
opacity: 1;
position: relative;
z-index: 2;
}
}
&__item {
display: block;
overflow-x: hidden;
&--disabled {
pointer-events: none;
cursor: default;
opacity: 0.5;
}
&__link {
@include gradient_overlay(3px);
cursor: pointer;
display: block;
padding: 9px 12px 8px;
box-sizing: border-box;
position: relative;
text-decoration: none;
color: inherit;
&--inactive {
cursor: default;
opacity: 0.5;
&:before {
display: none;
}
}
.notouch &:hover {
&:before {
opacity: 0.15;
}
}
.notouch &:active {
background: colour('turquoise');
color: colour('white');
}
.icon {
font-size: 12px;
&.submenu-icon {
display: inline-block;
padding-top: 4px;
margin-left: 10px;
vertical-align: baseline
}
}
}
&--loader {
padding-top: 75%;
}
}
em {
font-style: normal;
&.context-menu__item:last-of-type {
border-bottom: 1px solid colour('middle_grey');
}
}
&__divider {
height: 1px;
padding: 0;
margin: 0;
background: colour('middle_grey');
opacity: 0.5;
}
&--submenu-expanded {
.context-menu {
&__section {
transform: translateX(-100%);
opacity: 0;
&--submenu {
transform: translateX(0);
opacity: 1;
}
}
}
}
@include responsive(null, $bp_medium){
width: 200px;
overflow-x: hidden;
border-radius: 3px;
box-shadow: 2px 3px 6px rgba(0,0,0,0.2);
@include theme('light') {
background: colour('white');
}
@include theme('dark') {
background: colour('grey');
}
&--closing {
display: none;
}
&__section {
background: none;
overflow: visible;
&--submenu {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
@include theme('dark') {
background: colour('grey');
}
@include theme('light') {
background: colour('white');
}
}
}
&__title {
display: none;
}
}
@include responsive($bp_medium){
@include fadein(0.2s, 40px);
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
margin: 0 !important;
text-align: center;
&--closing {
@include fadeout(0.2s, 40px);
}
&__background {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: linear-gradient(rgba(24,24,24,0) 0%, rgba(24,24,24,1) 20%);
z-index: 1;
@include theme('light') {
background-image: linear-gradient(rgba(255,255,255,0) 0%, rgba(255,255,255,1) 20%);
}
}
&__title {
@include clearfix();
color: colour('mid_grey');
padding: 16px 22px;
@include theme('light') {
color: colour('darkest_grey');
}
.thumbnail {
float: left;
width: 36px;
margin-right: 20px;
}
&__text {
position: relative;
z-index: 2;
font-size: 1.2rem;
font-weight: 800;
padding-top: 8px;
}
&__type {
display: none;
position: relative;
z-index: 2;
opacity: 0.3;
font-weight: 200;
text-transform: capitalize;
}
&__deselect {
font-size: 0.86em;
}
a {
color: inherit;
}
}
&__section {
position: absolute;
top: 20%;
left: 0;
right: 0;
padding: 0 10%;
max-height: 80%;
overflow: auto;
&--submenu {
background: none;
border-radius: 0;
opacity: 0;
}
&--items {
/**
* The following is for buttons automatically pulled from
* a page header, so we don't have classname control
**/
.button {
display: block;
text-decoration: none;
display: block;
margin: 0;
width: 100% !important;
box-sizing: border-box;
text-align: center;
text-transform: none;
font-weight: 700;
font-size: 1.2rem;
color: colour('white');
background: none;
transform: none;
padding: 16px 22px;
border: 0;
border-radius: 3px;
&,
&:hover {
box-shadow: none;
}
@include theme('light') {
color: colour('darkest_grey');
}
.icon {
display: none;
}
&:before {
display: none !important;
}
&:focus,
&:active {
@include noanimate();
color: colour('white') !important;
background: colour('turquoise') !important;
}
}
}
}
&__divider {
display: none;
}
&__item {
font-size: 1.2rem;
font-weight: 800;
opacity: 1;
&__link {
@include animate();
padding: 16px 22px;
box-sizing: border-box;
@include theme('light') {
color: colour('darkest_grey');
}
&:before {
display: none !important;
}
&:focus,
&:active {
@include noanimate();
color: colour('white') !important;
background: colour('turquoise') !important;
}
}
}
}
}
.context-menu-trigger {
position: relative;
.actions & {
font-size: 24px;
padding: 2px 10px 1px;
}
.icon {
pointer-events: none;
}
h4 &,
h4 &:active {
margin: -8px 0 0 10px;
}
@include responsive($bp_medium){
padding: 12px;
}
}
@include responsive($bp_medium){
body.context-menu-open {
.body {
@include blur(10px);
}
}
}
.context-menu {
position: fixed;
z-index: 99;
font-size: 13px;
font-weight: 500;
font-family: $font-family-alt;
&__section {
@include animate();
overflow-y: scroll;
&--submenu {
z-index: 3;
border-radius: 3px;
transform: translateX(100%);
opacity: 0;
.context-menu {
&__item {
width: 100%;
&--functional {
color: colour('mid_grey');
&:last-of-type {
border-bottom: 1px solid colour('middle_grey');
}
}
&__label {
.icon {
margin-bottom: 0.4em;
padding-right: 10px;
vertical-align: baseline;
}
}
}
}
}
&--items {
opacity: 1;
position: relative;
z-index: 2;
}
}
&__item {
display: block;
overflow-x: hidden;
&--disabled {
pointer-events: none;
cursor: default;
opacity: 0.5;
}
&__link {
@include gradient_overlay(3px);
cursor: pointer;
display: block;
padding: 9px 12px 8px;
box-sizing: border-box;
position: relative;
text-decoration: none;
color: inherit;
&--inactive {
cursor: default;
opacity: 0.5;
&:before {
display: none;
}
}
.notouch &:hover {
&:before {
opacity: 0.15;
}
}
.notouch &:active {
background: colour('turquoise');
color: colour('white');
}
.icon {
font-size: 12px;
&.submenu-icon {
display: inline-block;
padding-top: 4px;
margin-left: 10px;
vertical-align: baseline
}
}
}
&--loader {
padding-top: 75%;
}
}
em {
font-style: normal;
&.context-menu__item:last-of-type {
border-bottom: 1px solid colour('middle_grey');
}
}
&__divider {
height: 1px;
padding: 0;
margin: 0;
background: colour('middle_grey');
opacity: 0.5;
}
&--submenu-expanded {
.context-menu {
&__section {
transform: translateX(-100%);
opacity: 0;
&--submenu {
transform: translateX(0);
opacity: 1;
}
}
}
}
@include responsive(null, $bp_medium){
width: 200px;
overflow-x: hidden;
border-radius: 3px;
box-shadow: 2px 3px 6px rgba(0,0,0,0.2);
@include theme('light') {
background: colour('white');
}
@include theme('dark') {
background: colour('grey');
}
&--closing {
display: none;
}
&__section {
background: none;
overflow: visible;
&--submenu {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
@include theme('dark') {
background: colour('grey');
}
@include theme('light') {
background: colour('white');
}
}
}
&__title {
display: none;
}
}
@include responsive($bp_medium){
@include fadein(0.2s, 40px);
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
margin: 0 !important;
text-align: center;
&--closing {
@include fadeout(0.2s, 40px);
}
&__background {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: linear-gradient(rgba(24,24,24,0) 0%, rgba(24,24,24,1) 20%);
z-index: 1;
@include theme('light') {
background-image: linear-gradient(rgba(255,255,255,0) 0%, rgba(255,255,255,1) 20%);
}
}
&__title {
@include clearfix();
color: colour('mid_grey');
padding: 16px 22px;
@include theme('light') {
color: colour('darkest_grey');
}
.thumbnail {
float: left;
width: 36px;
margin-right: 20px;
}
&__text {
position: relative;
z-index: 2;
font-size: 1.2rem;
font-weight: 800;
padding-top: 8px;
}
&__type {
display: none;
position: relative;
z-index: 2;
opacity: 0.3;
font-weight: 200;
text-transform: capitalize;
}
&__deselect {
font-size: 0.86em;
}
a {
color: inherit;
}
}
&__section {
position: absolute;
top: 20%;
left: 0;
right: 0;
padding: 0 10%;
max-height: 80%;
overflow: auto;
&--submenu {
background: none;
border-radius: 0;
opacity: 0;
}
&--items {
/**
* The following is for buttons automatically pulled from
* a page header, so we don't have classname control
**/
.button {
display: block;
text-decoration: none;
display: block;
margin: 0;
width: 100% !important;
box-sizing: border-box;
text-align: center;
text-transform: none;
font-weight: 700;
font-size: 1.2rem;
color: colour('white');
background: none;
transform: none;
padding: 16px 22px;
border: 0;
border-radius: 3px;
&,
&:hover {
box-shadow: none;
}
@include theme('light') {
color: colour('darkest_grey');
}
.icon {
display: none;
}
&:before {
display: none !important;
}
&:focus,
&:active {
@include noanimate();
color: colour('white') !important;
background: colour('turquoise') !important;
}
}
}
}
&__divider {
display: none;
}
&__item {
font-size: 1.2rem;
font-weight: 800;
opacity: 1;
&__link {
@include animate();
padding: 16px 22px;
box-sizing: border-box;
@include theme('light') {
color: colour('darkest_grey');
}
&:before {
display: none !important;
}
&:focus,
&:active {
@include noanimate();
color: colour('white') !important;
background: colour('turquoise') !important;
}
}
}
}
}
.context-menu-trigger {
position: relative;
.actions & {
font-size: 24px;
padding: 2px 10px 1px;
}
.icon {
pointer-events: none;
}
h4 &,
h4 &:active {
margin: -8px 0 0 10px;
}
@include responsive($bp_medium){
padding: 12px;
}
}

View File

@ -1,208 +1,208 @@
.dropdown-field {
@include noselect();
font-size: 13px;
font-weight: 500;
font-family: "Overpass";
position: relative;
display: inline-block;
&__label {
display: flex;
font-weight: 800;
&__name {
display: none;
}
.icon {
padding-right: 8px;
}
&:not(.button) {
@include animate();
padding: 14px 10px;
cursor: pointer;
.notouch &:hover {
color: colour('blue');
}
}
}
&--expanded {
.label:not(.button) {
color: colour('blue');
}
.dropdown-field__options {
display: block;
}
}
&--no-status-icon {
.dropdown-field__options {
&__item {
padding-left: 12px;
}
}
}
&--no-label {
.label > .text {
display: none;
}
}
&__options {
position: absolute;
top: 46px;
right: 10px;
z-index: 97;
display: none;
box-shadow: 2px 3px 6px rgba(0,0,0,0.2);
&:before {
content: '';
display: block;
width: 6px;
height: 6px;
background: colour('white');
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
top: -3px;
right: 9px;
@include theme('light') {
background: colour('white');
}
}
&__liner {
color: colour('black');
background: colour('white');
min-width: 120px;
max-height: 70vh;
overflow-y: auto;
border-radius: 3px;
@include theme('light') {
background: colour('white');
}
}
&__item {
@include animate();
@include gradient_overlay(3px);
position: relative;
padding: 9px 12px 8px 24px;
cursor: pointer;
color: colour('darkest_grey');
.icon {
position: absolute;
top: 8px;
left: 6px;
font-size: 16px;
}
.notouch &:hover {
color: colour('darkest_grey');
&:before {
opacity: 0.15;
}
}
}
}
}
#context-menu {
.dropdown-field {
padding: 16px 0;
display: block;
&:active,
&:focus {
background: none !important;
}
&__label {
padding: 0 0 3px 0;
opacity: 0.5;
font-size: 1rem;
display: block;
cursor: default;
&__value {
display: none;
}
&__name {
display: inline-block;
}
&,
&:hover {
color: colour('white');
@include theme('light') {
color: colour('dark_grey') !important;
}
}
}
&__options {
display: block;
position: static;
background: transparent;
border-radius: 0;
border: 0;
box-shadow: none;
font-weight: 700;
font-size: 1.2rem;
&:before {
display: none !important;
}
&__liner {
background: transparent;
color: colour('white') !important;
margin: -3px -5px;
@include theme('light') {
color: colour('dark_grey') !important;
}
&:before {
display: none !important;
}
}
&__item {
display: block;
padding: 3px 5px;
border-radius: 0;
color: colour('white') !important;
@include theme('light') {
color: colour('dark_grey') !important;
}
&:before {
display: none !important;
}
.icon {
display: inline-block;
position: static;
padding-right: 2px;
padding-bottom: 3px;
font-size: 0.9em;
}
}
}
}
}
.dropdown-field {
@include noselect();
font-size: 13px;
font-weight: 500;
font-family: $font-family-alt;
position: relative;
display: inline-block;
&__label {
display: flex;
font-weight: 800;
&__name {
display: none;
}
.icon {
padding-right: 8px;
}
&:not(.button) {
@include animate();
padding: 14px 10px;
cursor: pointer;
.notouch &:hover {
color: colour('blue');
}
}
}
&--expanded {
.label:not(.button) {
color: colour('blue');
}
.dropdown-field__options {
display: block;
}
}
&--no-status-icon {
.dropdown-field__options {
&__item {
padding-left: 12px;
}
}
}
&--no-label {
.label > .text {
display: none;
}
}
&__options {
position: absolute;
top: 46px;
right: 10px;
z-index: 97;
display: none;
box-shadow: 2px 3px 6px rgba(0,0,0,0.2);
&:before {
content: '';
display: block;
width: 6px;
height: 6px;
background: colour('white');
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
top: -3px;
right: 9px;
@include theme('light') {
background: colour('white');
}
}
&__liner {
color: colour('black');
background: colour('white');
min-width: 120px;
max-height: 70vh;
overflow-y: auto;
border-radius: 3px;
@include theme('light') {
background: colour('white');
}
}
&__item {
@include animate();
@include gradient_overlay(3px);
position: relative;
padding: 9px 12px 8px 24px;
cursor: pointer;
color: colour('darkest_grey');
.icon {
position: absolute;
top: 8px;
left: 6px;
font-size: 16px;
}
.notouch &:hover {
color: colour('darkest_grey');
&:before {
opacity: 0.15;
}
}
}
}
}
#context-menu {
.dropdown-field {
padding: 16px 0;
display: block;
&:active,
&:focus {
background: none !important;
}
&__label {
padding: 0 0 3px 0;
opacity: 0.5;
font-size: 1rem;
display: block;
cursor: default;
&__value {
display: none;
}
&__name {
display: inline-block;
}
&,
&:hover {
color: colour('white');
@include theme('light') {
color: colour('dark_grey') !important;
}
}
}
&__options {
display: block;
position: static;
background: transparent;
border-radius: 0;
border: 0;
box-shadow: none;
font-weight: 700;
font-size: 1.2rem;
&:before {
display: none !important;
}
&__liner {
background: transparent;
color: colour('white') !important;
margin: -3px -5px;
@include theme('light') {
color: colour('dark_grey') !important;
}
&:before {
display: none !important;
}
}
&__item {
display: block;
padding: 3px 5px;
border-radius: 0;
color: colour('white') !important;
@include theme('light') {
color: colour('dark_grey') !important;
}
&:before {
display: none !important;
}
.icon {
display: inline-block;
position: static;
padding-right: 2px;
padding-bottom: 3px;
font-size: 0.9em;
}
}
}
}
}

View File

@ -1,118 +1,118 @@
main {
header {
box-sizing: border-box;
padding: 40px;
margin-bottom: -40px;
display: flex;
flex-direction: row;
h1 {
& > .icon {
display: inline-block;
padding-right: 14px;
vertical-align: bottom;
margin-bottom: 8px;
font-size: 0.9em;
}
}
&__text {
display: flex;
flex-flow: column;
}
h4 {
line-height: 1em;
opacity: 0.5;
}
.header__options {
margin-left: auto;
&__wrapper {
display: flex;
}
.context-menu-trigger {
display: none;
font-size: 18px;
padding: 16px 14px;
}
.button,
button {
font-size: 13px;
font-weight: 800;
font-family: "Overpass";
background: transparent;
text-transform: none;
padding: 13px 10px;
margin: 0;
min-width: 0;
color: colour('light_grey');
vertical-align: unset;
border: none;
&:hover {
color: colour('blue');
box-shadow: none;
&:before {
display: none;
}
}
@include theme('light') {
color: colour('darkest_grey');
&:hover {
color: colour('blue');
}
&:active {
background: transparent;
}
}
}
}
&.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 98;
}
@include responsive($bp_medium){
padding: 10px 10px 10px 0;
margin-bottom: -10px;
h1 {
vertical-align: top;
padding-top: 6px;
& > .icon {
margin-bottom: 2px;
padding-right: 6px;
margin-left: 10px;
}
}
.header__options {
top: 1rem;
right: 1rem;
.context-menu-trigger {
display: block;
font-size: 1.4rem;
}
&__wrapper {
display: none;
}
}
}
}
}
main {
header {
box-sizing: border-box;
padding: 40px;
margin-bottom: -40px;
display: flex;
flex-direction: row;
h1 {
& > .icon {
display: inline-block;
padding-right: 14px;
vertical-align: bottom;
margin-bottom: 8px;
font-size: 0.9em;
}
}
&__text {
display: flex;
flex-flow: column;
}
h4 {
line-height: 1em;
opacity: 0.5;
}
.header__options {
margin-left: auto;
&__wrapper {
display: flex;
}
.context-menu-trigger {
display: none;
font-size: 18px;
padding: 16px 14px;
}
.button,
button {
font-size: 13px;
font-weight: 800;
font-family: $font-family-alt;
background: transparent;
text-transform: none;
padding: 13px 10px;
margin: 0;
min-width: 0;
color: colour('light_grey');
vertical-align: unset;
border: none;
&:hover {
color: colour('blue');
box-shadow: none;
&:before {
display: none;
}
}
@include theme('light') {
color: colour('darkest_grey');
&:hover {
color: colour('blue');
}
&:active {
background: transparent;
}
}
}
}
&.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 98;
}
@include responsive($bp_medium){
padding: 10px 10px 10px 0;
margin-bottom: -10px;
h1 {
vertical-align: top;
padding-top: 6px;
& > .icon {
margin-bottom: 2px;
padding-right: 6px;
margin-left: 10px;
}
}
.header__options {
top: 1rem;
right: 1rem;
.context-menu-trigger {
display: block;
font-size: 1.4rem;
}
&__wrapper {
display: none;
}
}
}
}
}

View File

@ -1,211 +1,211 @@
.sidebar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
z-index: 96;
width: 220px;
overflow: hidden;
color: colour('white');
box-sizing: border-box;
transform-style: preserve-3d;
&__liner {
overflow-y: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 60px;
box-sizing: border-box;
z-index: 2;
overflow-y: auto;
}
&__menu {
z-index: 2;
position: relative;
padding-top: 20px;
line-height: 1em;
&__item {
font-family: "Overpass";
font-weight: 600;
font-size: 1.05rem;
color: colour('white');
text-decoration: none;
display: flex;
padding: 0.75em 0.85em;
margin: 0 1.1em;
border-radius: 3px;
line-break: after-white-space;
@include theme('dark') {
@include gradient_overlay(3px);
}
@include theme('light') {
@include animate();
color: colour('darkest_grey');
font-weight: 600;
}
.icon {
padding-right: 8px;
font-size: 1em;
}
.status {
margin-left: auto;
.icon {
padding-right: 0;
padding-left: 8px;
}
}
&--submenu {
font-size: 1rem;
padding: 0.6em 0.7em;
font-weight: 400 !important;
margin: 0 1.1em 0 3em;
&:before {
display: none;
}
&.sidebar__menu__item {
color: colour('mid_grey');
&:before,
&:after {
display: none;
}
@include theme('light') {
color: colour('soft_grey');
}
&.active {
background: transparent !important;
color: colour('turquoise') !important;
}
}
}
&--active {
background: colour('turquoise');
color: colour('white') !important;
.status {
.icon {
color: colour('white') !important;
}
}
&:before {
opacity: 0.15;
}
}
&[disabled] {
opacity: 0.3;
cursor: not-allowed;
}
&:hover:not([disabled]) {
&:before {
opacity: 0.15;
}
@include theme('light') {
color: lighten(colour('darkest_grey'), 20%);
}
}
&:focus:not([disabled]):not(.sidebar__menu__item--active) {
background: rgba(128, 128, 128, 0.25);
}
&:active {
@include noanimate();
transform: translate(1px, 1px);
}
}
&__section {
padding-bottom: 25px;
&__title {
display: block;
padding: 0 26px 5px 26px;
font-size: 12px;
text-transform: uppercase;
color: colour('middle_grey');
}
}
}
.close {
@include click_feedback();
display: none;
position: absolute;
bottom: 4px;
right: 4px;
padding: 10px;
display: inline-block;
z-index: 99;
font-size: 1.2rem;
@include theme('light') {
color: colour('darkest_grey');
}
}
@include responsive($bp_medium) {
@include animate(0.15s);
width: 250px;
left: auto;
right: -250px;
padding-bottom: 0;
position: fixed;
z-index: 100;
@include theme('light') {
background: lighten(colour('faint_grey'), 2%);
}
@include theme('dark') {
background: colour('darkest_grey');
}
.sidebar-open & {
-ms-transform: translate(-250px, 0);
-webkit-transform: translate(-250px, 0);
transform: translate(-250px, 0);
}
&__liner {
bottom: 0 !important;
padding-bottom: 30px;
}
.thumbnail {
display: none;
}
.close {
display: block;
}
&__menu {
&__section {
padding-bottom: 10px;
&__title {
padding-bottom: 2px;
}
}
}
}
}
.sidebar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
z-index: 96;
width: 220px;
overflow: hidden;
color: colour('white');
box-sizing: border-box;
transform-style: preserve-3d;
&__liner {
overflow-y: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 60px;
box-sizing: border-box;
z-index: 2;
overflow-y: auto;
}
&__menu {
z-index: 2;
position: relative;
padding-top: 20px;
line-height: 1em;
&__item {
font-family: $font-family-alt;
font-weight: 600;
font-size: 1.05rem;
color: colour('white');
text-decoration: none;
display: flex;
padding: 0.75em 0.85em;
margin: 0 1.1em;
border-radius: 3px;
line-break: after-white-space;
@include theme('dark') {
@include gradient_overlay(3px);
}
@include theme('light') {
@include animate();
color: colour('darkest_grey');
font-weight: 600;
}
.icon {
padding-right: 8px;
font-size: 1em;
}
.status {
margin-left: auto;
.icon {
padding-right: 0;
padding-left: 8px;
}
}
&--submenu {
font-size: 1rem;
padding: 0.6em 0.7em;
font-weight: 400 !important;
margin: 0 1.1em 0 3em;
&:before {
display: none;
}
&.sidebar__menu__item {
color: colour('mid_grey');
&:before,
&:after {
display: none;
}
@include theme('light') {
color: colour('soft_grey');
}
&.active {
background: transparent !important;
color: colour('turquoise') !important;
}
}
}
&--active {
background: colour('turquoise');
color: colour('white') !important;
.status {
.icon {
color: colour('white') !important;
}
}
&:before {
opacity: 0.15;
}
}
&[disabled] {
opacity: 0.3;
cursor: not-allowed;
}
&:hover:not([disabled]) {
&:before {
opacity: 0.15;
}
@include theme('light') {
color: lighten(colour('darkest_grey'), 20%);
}
}
&:focus:not([disabled]):not(.sidebar__menu__item--active) {
background: rgba(128, 128, 128, 0.25);
}
&:active {
@include noanimate();
transform: translate(1px, 1px);
}
}
&__section {
padding-bottom: 25px;
&__title {
display: block;
padding: 0 26px 5px 26px;
font-size: 12px;
text-transform: uppercase;
color: colour('middle_grey');
}
}
}
.close {
@include click_feedback();
display: none;
position: absolute;
bottom: 4px;
right: 4px;
padding: 10px;
display: inline-block;
z-index: 99;
font-size: 1.2rem;
@include theme('light') {
color: colour('darkest_grey');
}
}
@include responsive($bp_medium) {
@include animate(0.15s);
width: 250px;
left: auto;
right: -250px;
padding-bottom: 0;
position: fixed;
z-index: 100;
@include theme('light') {
background: lighten(colour('faint_grey'), 2%);
}
@include theme('dark') {
background: colour('darkest_grey');
}
.sidebar-open & {
-ms-transform: translate(-250px, 0);
-webkit-transform: translate(-250px, 0);
transform: translate(-250px, 0);
}
&__liner {
bottom: 0 !important;
padding-bottom: 30px;
}
.thumbnail {
display: none;
}
.close {
display: block;
}
&__menu {
&__section {
padding-bottom: 10px;
&__title {
padding-bottom: 2px;
}
}
}
}
}

View File

@ -32,7 +32,7 @@
body,
html {
font-family: "Overpass", Helvetica, Arial, sans-serif;
font-family: $font-family-alt;
font-size: 14px;
letter-spacing: -0.02rem;
touch-action: manipulation;

View File

@ -17,7 +17,7 @@ select {
padding: 10px 14px;
background: rgba(128, 128, 128, 0.05);
color: colour('white');
font-family: "Overpass", Helvetica, Arial, sans-serif;
font-family: $font-family-alt;
font-size: 14px;
border-radius: 3px;
letter-spacing: -0.01rem;

View File

@ -4,6 +4,7 @@ $bp_medium: 800px;
$bp_small: 500px;
$bp_narrow: 360px;
$bp_shallow: 650px;
$font-family-alt: "Overpass", Helvetica, Arial, sans-serif;
@mixin one_line_text {
white-space: nowrap;
@ -343,7 +344,7 @@ $bp_shallow: 650px;
}
@mixin feature_font() {
font-family: "Overpass";
font-family: $font-family-alt;
font-weight: 600;
}