diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 3d703863..6d5e39ff 100755 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -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: diff --git a/src/js/locale/br.yaml b/src/js/locale/br.yaml new file mode 100644 index 00000000..42463296 --- /dev/null +++ b/src/js/locale/br.yaml @@ -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 + diff --git a/src/js/locale/dictionaries.js b/src/js/locale/dictionaries.js index 8c4fab2c..7f384922 100755 --- a/src/js/locale/dictionaries.js +++ b/src/js/locale/dictionaries.js @@ -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, diff --git a/src/js/locale/ru.yaml b/src/js/locale/ru.yaml index 7be62a93..8dd5447b 100644 --- a/src/js/locale/ru.yaml +++ b/src/js/locale/ru.yaml @@ -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: Передайте свои авторизации и настройки интерфейса другому подключенному пользователю diff --git a/src/js/services/genius/actions.js b/src/js/services/genius/actions.js index e34a5bd0..b512b894 100755 --- a/src/js/services/genius/actions.js +++ b/src/js/services/genius/actions.js @@ -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)); }, diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 91fd4f00..ac186365 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -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); diff --git a/src/js/views/Album.js b/src/js/views/Album.js index ec7fd666..88608dbd 100755 --- a/src/js/views/Album.js +++ b/src/js/views/Album.js @@ -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); diff --git a/src/js/views/Library/BrowseDirectory.js b/src/js/views/Library/BrowseDirectory.js index d18bcef2..e416a417 100755 --- a/src/js/views/Library/BrowseDirectory.js +++ b/src/js/views/Library/BrowseDirectory.js @@ -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 ; - } - return ( - -

- -

-
- ); - } - - 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 = ( - <> - this.setState({ filter: value })} - onSubmit={() => uiActions.hideContextMenu()} - /> - { uiActions.set({ library_directory_view: value }); uiActions.hideContextMenu(); }} - /> - {tracks && ( - - )} - - - ); - - return ( -
-
- -
- {name || i18n('library.browse_directory.title')} - -
-
-
- - - - - - - -
-
- ); - } -} - -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 ; + } + return ( + +

+ +

+
+ ); + } + + 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 = ( + <> + dispatch(hideContextMenu())} + /> + + {tracks && ( + + )} + + + ); + + return ( +
+
+ +
+ {name || i18n('library.browse_directory.title')} + +
+
+
+ + + + + + + +
+
+ ); +} + +export default BrowseDirectory; diff --git a/src/js/views/Modals/KioskMode.js b/src/js/views/Modals/KioskMode.js index ed4d61c6..b9315047 100755 --- a/src/js/views/Modals/KioskMode.js +++ b/src/js/views/Modals/KioskMode.js @@ -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; } diff --git a/src/scss/components/_context-menu.scss b/src/scss/components/_context-menu.scss index 169051b5..6aea7835 100755 --- a/src/scss/components/_context-menu.scss +++ b/src/scss/components/_context-menu.scss @@ -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; + } +} diff --git a/src/scss/components/_dropdown-field.scss b/src/scss/components/_dropdown-field.scss index 8f568ed6..b24ae9c0 100755 --- a/src/scss/components/_dropdown-field.scss +++ b/src/scss/components/_dropdown-field.scss @@ -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; + } + } + } + } +} diff --git a/src/scss/components/_header.scss b/src/scss/components/_header.scss index 28832368..52ccefca 100755 --- a/src/scss/components/_header.scss +++ b/src/scss/components/_header.scss @@ -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; + } + } + } + } +} diff --git a/src/scss/components/_sidebar.scss b/src/scss/components/_sidebar.scss index 180b1279..e2e34a95 100755 --- a/src/scss/components/_sidebar.scss +++ b/src/scss/components/_sidebar.scss @@ -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; + } + } + } + } +} diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss index 20d9692d..58430cdd 100755 --- a/src/scss/global/_core.scss +++ b/src/scss/global/_core.scss @@ -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; diff --git a/src/scss/global/_forms.scss b/src/scss/global/_forms.scss index c623114a..1d01b908 100755 --- a/src/scss/global/_forms.scss +++ b/src/scss/global/_forms.scss @@ -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; diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index c27e089a..4a9ac367 100755 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -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; }