From c0ad5391c81b49b836a6b7f58b6ad72fad2f5c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Vassb=C3=B8?= Date: Tue, 12 Nov 2024 09:21:34 +0100 Subject: [PATCH] 1.3.1-beta.1 (#1010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✔ Fixed auto size timing issue - Fixed transition issue - UI tweaks * Updated issue templates * ✔ Fixed Quelea misspelling - Fixed scripture output freezing when template was deleted - Fixed some VideoPsalm songs not importing - Fixed audio loading issue on RemoteShow * ✨ Output slide clear will keep position - OpenSong background import - Slide progress one letter mode - Video audio fadeout on clear - Auto song zip uncompress - Correct overlay under slide preview - Improved text translation control - Custom translated language in text editor * ✨ Focus mode enhancements - Fixed text editor items sometimes swapping positions - Fixed text line background not copied to item clipboard - Quelea chord import - Controller draw thumbnail - UI improvements * ✔ Fixed RemoteShow pinch zoom - Fixed local scripture search not working always - Saved toast updating as soon as save is completed - Slide recorder updates * ✨ Scripture improvements - Jesus red words no longer removed by template - No auto scroll when manually clicking slides - Timer minutes input - ControlShow zoom - Chinese update - Freeze fixes - UI tweaks * ✔ Fixed capitalize words sometimes breaking text - Fixed text transform changing text casing - Fixed some Beblia files not working if missing testament key - Updated translation * 🔍 Stage editor zoom - Input paste context menu - Fixed invalid regular expression - UI updates * ✨ Slide recorder timestamps - Rearrange slide recorder items - Faster zoom open animation - Fixed Easyslides group importing - Version update * ✔ Version update --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- package.json | 4 +- .../import-logos/{quela.webp => quelea.webp} | Bin public/lang/en.json | 9 +- public/lang/pt_BR.json | 1057 +++++++++++------ public/lang/zh_CN.json | 2 +- src/electron/capture/CaptureHelper.ts | 4 + src/electron/data/import.ts | 35 +- src/electron/data/zip.ts | 25 + src/electron/ndi/NdiReceiver.ts | 3 + src/electron/servers.ts | 12 +- src/electron/utils/files.ts | 4 + src/frontend/MainOutput.svelte | 3 +- .../components/actions/CreateAction.svelte | 3 + .../components/context/ContextItem.svelte | 41 +- .../components/context/ContextMenu.svelte | 21 +- .../components/context/contextMenus.ts | 5 +- src/frontend/components/context/menuClick.ts | 22 +- src/frontend/components/drawer/Drawer.svelte | 2 +- .../components/drawer/audio/Audio.svelte | 2 +- .../components/drawer/audio/AudioMix.svelte | 6 +- .../components/drawer/audio/Metronome.svelte | 3 +- .../components/drawer/bible/Scripture.svelte | 67 +- .../components/drawer/bible/scripture.ts | 73 +- .../components/drawer/info/AudioInfo.svelte | 61 +- .../components/drawer/info/MediaInfo.svelte | 11 +- .../components/drawer/info/PlayerInfo.svelte | 15 +- .../drawer/info/ScriptureInfo.svelte | 59 +- .../components/drawer/info/ShowInfo.svelte | 5 +- .../components/drawer/live/Mic.svelte | 2 +- .../components/drawer/media/Folder.svelte | 7 +- .../components/drawer/media/Media.svelte | 7 +- .../components/drawer/media/pixabay.ts | 2 +- .../components/drawer/pages/Overlays.svelte | 12 +- .../components/drawer/pages/Templates.svelte | 12 +- .../components/edit/editbox/Editbox.svelte | 2 + .../edit/editbox/EditboxLines.svelte | 5 + .../edit/editbox/EditboxPlain.svelte | 15 +- .../edit/editors/OverlayEditor.svelte | 5 +- .../edit/editors/SlideEditor.svelte | 4 +- .../edit/editors/TemplateEditor.svelte | 2 +- .../components/edit/scripts/chords.ts | 27 + .../components/edit/scripts/itemClipboard.ts | 8 +- .../components/edit/scripts/itemHelpers.ts | 5 +- .../components/edit/tools/BoxStyle.svelte | 4 + .../components/edit/tools/EditValues.svelte | 5 + src/frontend/components/edit/values/boxes.ts | 2 + src/frontend/components/guide/guideSteps.ts | 3 +- src/frontend/components/helpers/clipboard.ts | 3 +- src/frontend/components/helpers/media.ts | 12 +- src/frontend/components/helpers/output.ts | 5 + src/frontend/components/helpers/show.ts | 2 +- .../components/helpers/showActions.ts | 9 + .../components/helpers/slideRecording.ts | 21 +- .../components/inputs/NumberInput.svelte | 6 +- .../components/inputs/ShowButton.svelte | 9 +- src/frontend/components/main/Tabs.svelte | 2 +- src/frontend/components/main/Toast.svelte | 12 + .../components/main/popups/EditList.svelte | 2 +- .../components/main/popups/Import.svelte | 8 +- .../components/main/popups/SelectShow.svelte | 2 +- .../components/main/popups/Timer.svelte | 23 +- .../components/main/popups/Transition.svelte | 3 +- .../main/popups/createShow/CreateShow.svelte | 1 + .../main/popups/localization/Translate.svelte | 51 +- .../main/popups/localization/translation.ts | 4 +- src/frontend/components/output/clear.ts | 50 +- .../output/layers/BackgroundMedia.svelte | 8 +- .../output/layers/SlideContent.svelte | 40 +- .../output/preview/AudioMeter.svelte | 2 +- .../transitions/OutputTransition.svelte | 2 +- .../transitions/SlideItemTransition.svelte | 4 +- .../components/settings/SettingsTabs.svelte | 16 +- src/frontend/components/show/Slides.svelte | 36 +- .../components/show/TextEditor.svelte | 6 +- .../components/show/focus/FocusMode.svelte | 21 +- .../components/show/formatTextEditor.ts | 27 +- .../components/show/tools/Media.svelte | 8 +- .../components/show/tools/Recording.svelte | 117 +- src/frontend/components/slide/Layouts.svelte | 2 +- src/frontend/components/slide/Slide.svelte | 42 +- .../slide/views/SlideProgress.svelte | 9 +- .../components/stage/StageShow.svelte | 145 ++- .../components/stage/StageSlide.svelte | 2 +- src/frontend/components/stage/Stagebox.svelte | 4 +- .../components/stage/tools/BoxStyle.svelte | 3 + .../components/system/Autoscroll.svelte | 3 +- .../components/system/Resizeable.svelte | 2 + src/frontend/converters/bebliaBible.ts | 6 +- src/frontend/converters/easyslides.ts | 16 +- src/frontend/converters/opensong.ts | 16 +- .../converters/{quela.ts => quelea.ts} | 64 +- src/frontend/converters/videopsalm.ts | 3 +- src/frontend/stores.ts | 1 + src/frontend/utils/controllerTalk.ts | 77 ++ src/frontend/utils/receivers.ts | 4 +- src/frontend/utils/remoteTalk.ts | 8 +- src/frontend/utils/save.ts | 2 +- src/frontend/utils/search.ts | 13 +- src/frontend/utils/sendData.ts | 58 +- src/frontend/utils/shortcuts.ts | 14 +- src/frontend/utils/stageTalk.ts | 14 +- src/frontend/utils/startup.ts | 41 +- src/server/controller/App.svelte | 72 +- .../remote/components/slide/ShowSlide.svelte | 2 +- .../remote/components/slide/Slides.svelte | 20 +- src/server/stage/App.svelte | 4 +- src/server/stage/items/SlideProgress.svelte | 6 +- src/types/Show.ts | 1 + 110 files changed, 2052 insertions(+), 801 deletions(-) rename public/import-logos/{quela.webp => quelea.webp} (100%) create mode 100644 src/electron/data/zip.ts rename src/frontend/converters/{quela.ts => quelea.ts} (59%) create mode 100644 src/frontend/utils/controllerTalk.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bf6d2886..ed2fceef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Help up improve the stability of FreeShow -title: "[Bug Report] {Add a short & descriptive title}" +title: "" labels: bug assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index af589313..324f66ff 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature request about: Suggest a new feature/improvement for FreeShow -title: "[Feature Request] {Add a descriptive title here}" +title: "" labels: enhancement assignees: '' diff --git a/package.json b/package.json index 8cfc5657..3ebfb96d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.3.0", + "version": "1.3.1-beta.1", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -141,6 +141,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@tsconfig/svelte": "^2.0.0", + "@types/adm-zip": "^0.5.6", "@types/exif": "^0.6.3", "@types/express": "^4.17.13", "@types/follow-redirects": "^1.14.2", @@ -168,6 +169,7 @@ "@mapbox/node-pre-gyp": "^1.0.11", "@sveltejs/svelte-virtual-list": "^3.0.1", "@vimeo/player": "^2.16.4", + "adm-zip": "^0.5.16", "axios": "^1.7.2", "chord-transposer": "^3.0.9", "cross-env": "^7.0.3", diff --git a/public/import-logos/quela.webp b/public/import-logos/quelea.webp similarity index 100% rename from public/import-logos/quela.webp rename to public/import-logos/quelea.webp diff --git a/public/lang/en.json b/public/lang/en.json index 317ead3a..207e0f3b 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -195,6 +195,7 @@ "custom_output": "Custom audio output", "mute_when_video_plays": "Mute when video plays", "pre_fader_volume_meter": "Pre fader volume meter", + "mixer": "Mixer", "metronome": "Metronome", "toggle_metronome": "Toggle metronome", "tempo": "Tempo", @@ -684,7 +685,9 @@ "tip": "Record and replay the timings of slides. Sync with an audio track on the first slide.", "layout_changed": "Layout has changed since last recording!", "audio_synced": "Synced with audio!", - "start": "Start slide recording" + "start": "Start slide recording", + "use_duration": "Use duration time", + "use_duration_tip": "Use duration time instead of timestamp time" }, "animate": { "change": "Change", @@ -801,6 +804,8 @@ "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", "media_fit": "Media fit", + "one_letter": "One letter mode", + "sub_indexes": "Sub indexes", "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", @@ -921,6 +926,7 @@ "to_event": "Time until event", "counter": "Countdown", "time": "Time", + "minutes": "Minutes", "seconds": "Seconds", "from": "From", "to": "To", @@ -1178,6 +1184,7 @@ "reference": "Show reference", "split_reference": "Split reference", "combine_with_text": "Combine with text", + "first_slide_reference": "Reference on first slide", "reference_at_bottom": "Move to bottom", "red_jesus": "Jesus words in red", "search": "Search in the Bible" diff --git a/public/lang/pt_BR.json b/public/lang/pt_BR.json index 1a412cdf..f64a1562 100644 --- a/public/lang/pt_BR.json +++ b/public/lang/pt_BR.json @@ -2,34 +2,66 @@ "main": { "welcome": "Seja bem-vindo", "quit": "Sair", - "docs": "Documentos", + "docs": "Documentação", "about": "Sobre", "unnamed": "Sem nome", "drop": "Soltar aqui", - "search": "Buscar", + "search": "Pesquisar", + "quick_search": "Pesquisa rápida", "none": "Nenhum", - "finished": "Finished", - "system_open": "Open in system" + "finished": "Concluído", + "open": "Abrir", + "system_open": "Abrir com aplicativo externo" + }, + "formats": { + "show": "Show", + "project": "Projeto", + "template": "Modelo", + "theme": "Tema", + "clipboard": "Área de transferência", + "text": "Arquivo de texto" + }, + "guide": { + "start": "Primeiros passos", + "skip": "Pular" + }, + "titlebar": { + "file": "Arquivo", + "edit": "Editar", + "view": "Visualizar", + "help": "Ajuda" + }, + "screen": { + "width": "Largura", + "height": "Altura", + "pixels": "pixels", + "top": "Topo", + "right": "Direita", + "bottom": "Base", + "left": "Esquerda" }, "about": { "check_updates": "Verificar por atualizações", "made": "Criado na Noruega por", - "report": "Gostaria de reportar um problema? Crie uma issue no GitHub", - "help": "Gostaria de ajudar na tradução ou solicitar uma funcionalidade? Envie um e-mail para", + "more": "Veja mais apps nossos em", + "report": "Para reportar um problema ou pedir um recurso novo, vá em", + "translate": "Quer ajudar a traduzir? Clique em", + "mail": "Contato via email", + "support": "Se você gosta do projeto, considere dar suporte", "assets": "Recursos utilizados", "libraries": "Bibliotecas utilizadas", "thanks": "Agradecimentos", "new_update": "Nova atualização disponível", - "download": "Vá até o freeshow.app para realizar o download", - "changes": "O que há de novo" + "download": "Reinicie o app para atualizar ou vá em freeshow.app para baixar manualmente", + "changes": "Novidades" }, "tooltip": { - "project": "Crie um novo projeto onde você pode adicionar e organizar apresentações.", - "show": "Crie uma nova visualização onde você pode adicionar letras de músicas, apresentações e mídia. Segure Ctrl/Cmd para criar uma visualização em branco.", - "groups": "Todos os grupos na visualização atual e todos os grupos globais. Clique ou arraste-os para adicioná-los ao layout atual.", - "layout": "Adicione transições e temporizadores aos slides do layout atual.", - "media": "Todas as mídias na apresentação atual. Mostre-os ou arraste-os para o layout atual.", - "metadata": "Edite os metadados da apresentação atual.", + "project": "Cria um novo projeto onde você pode adicionar e organizar Shows.", + "show": "Cria um novo Show onde você pode adicionar letras, slides e mídias.", + "groups": "Todos os grupos no Show atual e todos os grupos globais. Clique ou arraste-os para adicioná-los ao layout atual.", + "layout": "Adiciona transições e temporizadores aos slides do layout atual.", + "media": "Mostra todas as mídias no Show atual. Clique na thumb para aplicar na saída.", + "metadata": "Edite os metadados do Show atual.", "notes": "Escreva notas.", "text": "Edite o conteúdo de todos os itens selecionados.", "item": "Editar todos os itens selecionados.", @@ -39,8 +71,11 @@ "options": "Mais opções.", "scripture": "Segure Ctrl/Cmd ou Shift para selecionar vários versos." }, + "tips": { + "trigger": "Triggers são geralmente usados para enviar solicitações em HTTP para mudar a configuração prévia de câmeras." + }, "setup": { - "good_luck": "Espero que você ache este software útil. Boa sorte apresentando! :)", + "good_luck": "Espero que você ache este software útil. Boa sorte projetando! :)", "tips": "Encontre dicas e tutoriais úteis no site.", "change_later": "Você pode alterar essas configurações mais tarde", "get_started": "Vamos lá!" @@ -60,60 +95,57 @@ "big": "Grande", "default": "Padrão", "small": "Pequeno", - "bold": "Bold" - }, - "titlebar": { - "file": "Arquivo", - "edit": "Editar", - "view": "Visualizar", - "help": "Ajuda" - }, - "screen": { - "width": "Largura", - "height": "Altura", - "pixels": "pixels", - "top": "Top", - "right": "Right", - "bottom": "Bottom", - "left": "Left" + "bold": "Negrito" }, "create_show": { - "search_web": "Search for song on the web", + "web": "Pesquisa na Web", + "search_web": "Pesquisa a letra na web", + "search_results": "Resultados da pesquisa", "more_options": "Mais opções", + "auto_groups": "Organizar grupos automaticamente", "format_new_show": "Formatar texto", - "split_lines": "Números de linhas", - "quick_lyrics_example_text": "Line" + "format_new_show_tip": "Melhora a formatação adicionando maiúsculas, dividindo ou cortando o texto e mais.", + "split_lines": "Número de linhas por slide", + "split_lines_tip": "Número máximo de linhas permitida antes de serem divididas automaticamente.", + "quick_lyrics": "Criar letra de música", + "quick_lyrics_tip": "Cole o texto ou digite manualmente a letra", + "quick_lyrics_example_tip": "Escreva a letra ou qualquer texto aqui", + "quick_lyrics_example_text": "Linha", + "empty": "Show vazio", + "exists": "Há um Show com o mesmo nome" }, "preview": { - "_previous_show": "Apresentação anterior", + "_previous_show": "Show anterior", "_previous_slide": "Slide anterior", "_lock": "Bloquear saída", "_unlock": "Desbloquear saída", - "_start": "Iniciar apresentação", + "_start": "Iniciar Show", "_update": "Atualizar saída", "_next_slide": "Próximo slide", - "_next_show": "Próxima apresentação", - "_hide_preview": "Hide preview", - "show_preview": "Show preview", - "restore_output": "Restore output", - "enable_controls": "Show media preview controls", + "_next_show": "Próximo Show", + "_hide_preview": "Esconder preview", + "show_preview": "Mostrar preview", + "restore_output": "Restaurar saída", + "enable_controls": "Mostrar controles de mídia", "background": "Plano de fundo", - "foreground": "Foreground", + "foreground": "Primeiro plano", "slide": "Slide", "overlays": "Sobreposições", "audio": "Áudio", "to_start": "Ir para o início", "nextTimer": "Temporizador do próximo slide", "lock": "Bloquear", - "unlock": "Desbloquear" + "unlock": "Desbloquear", + "test_pattern": "Testar padrão" }, "clear": { "all": "Limpar tudo", "background": "Limpar plano de fundo", "slide": "Limpar slide", "overlays": "Limpar sobreposições", - "audio": "limpar áudio", - "nextTimer": "Limpar temporizador do próximo slide" + "audio": "Limpar áudio", + "nextTimer": "Limpar temporizador do próximo slide", + "drawing": "Limpar anotações" }, "remove": { "background": "Remover plano de fundo", @@ -127,26 +159,47 @@ "media": { "_loop": "Repetir", "play": "Reproduzir", + "play_multiple": "Reprodução mútipla", + "toggle_shuffle": "Aleatório", + "next": "Próximo", + "previous": "Anterior", "play_no_filters": "Reproduzir sem filtros", "favourite": "Favoritar", "pause": "Pausa", "stop": "Parar", - "back10": "Go back 10 seconds", - "forward10": "Go forward 10 seconds", + "back10": "Voltar 10 segundos", + "forward10": "Avançar 10 segundos", "volume": "Volume", - "gain": "Gain", - "speed": "Speed", + "gain": "Ganho", + "speed": "Velocidade", "show": "Mostrar", "flip": "Virar", + "flip_horizontally": "Virar horizontalmente", + "flip_vertically": "Virar verticalmente", "all": "Diretórios, imagens e vídeos", "folder": "Apenas diretórios", "image": "Apenas imagens", "video": "Apenas vídeos", - "fit": "Fit", - "contain": "Contain", - "fill": "Fill", - "cover": "Cover", - "online": "Online" + "fit": "Encaixe", + "contain": "Conter", + "fill": "Preencher", + "cover": "Cobrir", + "online": "Online", + "recommended": "Recomendado", + "bundle_media_files": "Agrupar arquivos de mídia", + "bundle_media_files_tip": "Copia todas as mídias de todos os Shows e os cola em uma pasta" + }, + "audio": { + "settings": "Configurações de áudio", + "playlist_settings": "Configurações de listas de reprodução", + "custom_output": "Saída de áudio personalizada", + "mute_when_video_plays": "Tornar mudo quando um vídeo reproduzir", + "pre_fader_volume_meter": "Exibir volume antes do fader", + "metronome": "Metrônomo", + "toggle_metronome": "Metrônomo", + "tempo": "Tempo", + "bpm": "BPM", + "beats": "Batidas" }, "menu": { "show": "Mostrar", @@ -154,7 +207,7 @@ "edit": "Editar", "_title_edit": "Editando", "stage": "Palco", - "_title_stage": "Visualização do palco", + "_title_stage": "Visão do palco", "draw": "Desenhar", "_title_draw": "Desenhando", "calendar": "Calendário", @@ -162,13 +215,14 @@ "settings": "Configurações", "_title_settings": "Configurações", "_title_display": "Apresentar", - "_title_display_stop": "Parar apresentação" + "_title_display_stop": "Parar apresentação", + "again_confirm": "Clique novamente para confirmar" }, "empty": { "general": "Nada aqui", "project_select": "Selecione um projeto", "show": "Sem visualizações selecionadas", - "shows": "Sem visualizações", + "shows": "Sem Shows", "slides": "Sem slides", "slide": "Sem slides selecionados", "items": "Sem itens selecionados", @@ -180,15 +234,15 @@ "groups": "Não há grupos", "events": "Não há eventos", "text": "Digite algo", - "timers": "No timers", - "input": "Missing value in input", - "recording": "Right click a screen or window to start a recording." + "timers": "Sem temporizadores", + "input": "Sem valor de entrada", + "recording": "Clique com o botão direito do mouse em uma tela ou janela para começar a gravar." }, "remote": { "projects": "Projetos", "project": "Projeto", - "shows": "Visualizações", - "show": "Visualização", + "shows": "Shows", + "show": "Show", "slide": "Slide", "lyrics": "Letra de músicas", "end": "Fim", @@ -197,7 +251,8 @@ "loading": "Carregando...", "submit": "Enviar", "password": "Senha", - "wrong_password": "Wrong password" + "wrong_password": "Senha incorreta", + "quick_play": "Reproduzir rapidamente" }, "error": { "no_show": "Não foi possível encontrar a apresentação", @@ -209,7 +264,9 @@ "display": "Não foi possível mostrar a janela de saída na tela atual", "keep_one_layout": "Você tem que ter pelo menos um layout", "video_unavailable": "Obtendo o vídeo indisponível? O criador desativou as incorporações do vídeo.", - "folder_exists": "Este diretório já existe" + "folder_exists": "Este diretório já existe", + "uri": "Não foi possível analisar o nome do áudio, por favor renomeie o arquivo", + "ip": "Não foi possível obter o endereço IP do seu dispositivo. Acesse as configurações de Wi-Fi do computador para encontrar seu endereço IPv4 local." }, "meta": { "title": "Título", @@ -220,20 +277,24 @@ "copyright": "Direitos autorais", "CCLI": "Licença (CCLI)", "year": "Ano", + "key": "Chave", "message": "Mensagem", - "message_tip": "Display something on all slides", - "auto_media": "Get meta from media content", - "override_output": "Override styling in output", + "message_tip": "Mostra algo em todos os slides", + "auto_media": "Copiar metadados da mídia", + "override_output": "Ignorar estilo de saída", "display_metadata": "Exibir metadados", - "meta_template": "Metadata template", - "text_divider": "Text separator", - "message_template": "Message template" + "meta_template": "Modelo de metadados", + "text_divider": "Separador de texto", + "message_template": "Modelo de mensagem", + "tags": "Tags", + "new_tag": "Nova tag", + "clear_tag_filter": "Limpar filtro de tags" }, "show_at": { "never": "Não há slides", "always": "Todos os slides", "first": "Primeiro slide", - "last": "último slide", + "last": "Último slide", "first_last": "Primeiro e último slide" }, "themes": { @@ -249,41 +310,47 @@ "primary-darker": "Primária Escura", "primary-darkest": "Primária Mais Escura", "text": "Texto", - "textInvert": "Text Invertido", - "secondary-text": "Texto Secundária", - "secondary": "Secundária", + "textInvert": "Texto Invertido", + "secondary-text": "Texto Secundário", + "secondary": "Secundário", "secondary-opacity": "Opacidade Secundária", - "hover": "Ao Passar o Mouse", + "hover": "Ao passar o mouse", "focus": "Foco" }, - "buttons": { - "changeTheme": "Alterar tema" - }, "inputs": { "name": "Nome", "url": "URL", + "method": "Método de entrada", + "contentType": "Tipo de conteúdo", + "payload": "Carga útil", "video_id": "Vídeo ID/URL", "close_ad": "Fechar anúncio na tela de saída", + "start": "Iniciar", + "end": "Finalizar", "change_folder": "Escolha outro local" }, "tabs": { - "shows": "Visualizações", + "shows": "Shows", "media": "Mídia", "overlays": "Sobreposições", "audio": "Áudio", - "effects": "Effects", + "effects": "Efeitos", "scripture": "Bíblia", "calendar": "Calendário", + "functions": "Funções", + "actions": "Ações", "player": "Reprodutor", "live": "Ao Vivo", "timers": "Temporizadores", - "variables": "Variables", + "variables": "Variáveis", + "triggers": "Triggers", "templates": "Modelos", - "web": "Internet" + "web": "Internet", + "search_tip": "Pesquisar conteúdo do Drawer" }, "category": { "all": "Tudo", - "unlabeled": "Sem rótulo", + "unlabeled": "Sem etiqueta", "favourites": "Favoritos", "song": "Canções", "presentation": "Apresentações", @@ -300,7 +367,9 @@ "groups": { "current": "Atual", "global": "Global", - "toggle_global_group": "Toggle global groups", + "toggle_global_group": "Grupos globais", + "group_shortcut": "Atalho de grupo", + "group_template": "Modelo de grupo", "intro": "Introdução", "verse": "Verso", "pre_chorus": "Pré Refrão", @@ -311,42 +380,49 @@ "outro": "Outro" }, "popup": { - "show": "Nova apresentação", - "select_show": "Select show", + "show": "Novo Show", + "select_show": "Selecione um show", "rename": "Renomear", "color": "Cor", - "find_replace": "Find and replace", - "edit_list": "Edit list", + "find_replace": "Localizar e substituir", + "edit_list": "Editar lista", "timer": "Temporizador", - "variable": "Variable", + "variable": "Variável", + "trigger": "Trigger", + "audio_stream": "Transmissão de áudio", "transition": "Transição", "delete_show": "Excluir apresentação", "delete_show_confirmation": "Tem certeza de que deseja excluir", + "delete_duplicated_shows": "Excluir Shows duplicados", "change_name": "Alterar nome", "choose_screen": "Selecionar tela", - "change_output_values": "Change output values", - "choose_style": "Choose style", - "set_time": "Set time", - "animate": "Animate", + "choose_output": "Escolha o tipo de saída", + "change_output_values": "Mudar valores de saída", + "choose_chord": "Escolha o acorde", + "set_time": "Definir tempo", + "animate": "Animar", + "translate": "Traduzir", "next_timer": "Temporizador do próximo slide", "import": "Importar", + "songbeamer_import": "Importar de Songbeamer", "export": "Exportar", "importing": "Importando", "import_scripture": "Importar bíblia", "player": "Reprodutor", "edit_event": "Editar evento", "about": "Sobre", - "history": "History", - "midi": "MIDI", - "connect": "Connect", - "cloud_update": "Syncing with cloud", - "cloud_method": "Data location", + "history": "Histórico", + "action": "Ação", + "connect": "Conectar", + "cloud_update": "Sincronizando com a nuvem", + "cloud_method": "Dados", "shortcuts": "Atalhos", - "icon": "Icones", - "manage_icons": "Manage icons", - "choose_camera": "Chooose camera", + "icon": "Ícones", + "manage_icons": "Gerenciar ícones", + "manage_colors": "Gerenciar cores", + "choose_camera": "Escolha a câmera", "initialize": "Seja bem-vindo ao FreeShow", - "unsaved": "Você ainda não salvou seu trabalho! Tem certeza que quer sair?", + "unsaved": "Tem certeza que deseja sair?", "cancel": "Cancelar", "continue": "Continuar", "reset_all": "Redefinir tudo", @@ -356,41 +432,47 @@ "save_quit": "Salvar e sair" }, "toast": { - "saving": "Saving...", - "saved": "Saved", - "error_media": "Could not get media.", - "empty_styles": "No styles", - "chapter_undefined": "Chapter {} does not exist in this book.", - "verse_undefined": "Verse {} does not exist in this chapter.", - "recording_started": "Recording started!", - "recording_stopped": "Recording stopped!", - "starting_show": "Starting show", - "less_than_minute": "in less than a minute.", - "less_than_seconds": "in less than {} seconds.", - "now": "now!", - "no_video_id": "No video ID", - "no_name": "No name", - "media_replaced": "Missing media file replaced with match.", - "lyrics_undefined": "Could not find any lyrics!", - "lyrics_copied": "Lyrics copied from ", - "no_pdf_linux": "Can't export as PDF on Linux.", - "one_output": "You have to have at least one active output!", - "empty_cache": "Cache is empty.", - "deleted_cache": "Deleted media thumbnail cache.", - "no_songswords_easyworship": "Missing SongsWords.db file.", - "delete_shows_empty": "No shows to delete.", - "midi_no_project": "Received MIDI to change project, but no project found at index:", - "midi_no_show": "Received MIDI to start slide, but no show active.", - "midi_no_slide": "Received MIDI to start slide, but no slide found at index:", + "saving": "Salvando...", + "saved": "Salvo", + "error_media": "Mídia indisponível", + "empty_styles": "Sem estilos", + "chapter_undefined": "Capítulo {} não existe neste livro.", + "verse_undefined": "Versículo {} não existe neste capítulo.", + "recording_started": "Gravação iniciada!", + "recording_stopped": "Gravação parada!", + "starting_action": "Iniciando ação", + "less_than_minute": "em menos de um minuto.", + "less_than_seconds": "em menos de {} segundos.", + "now": "agora!", + "unsupported_video": "Vídeo não suportado!", + "no_video_id": "Sem ID de vídeo", + "no_name": "Sem nome", + "reverting_setting": "Revertando esta mudança em {} segundos, ative novamente para mudar permanentemente!", + "reverted": "Configurações revertidas! Ative novamente apenas se você não teve problemas.", + "media_replaced": "Arquivo de mídia faltando substituído com semelhante.", + "lyrics_undefined": "Não foi possível encontrar letras!", + "lyrics_copied": "Letra copiada de ", + "one_output": "Você precisa ter ao menos uma saída ativa!", + "empty_cache": "Cache está vazio.", + "deleted_cache": "Cache de thumbnails foi excluído.", + "no_songswords_easyworship": "Arquivo SongsWords.db não encontrado.", + "delete_shows_empty": "Sem shows para deletar.", + "midi_no_project": "Trigger recebido para mudar de projeto, mas nenhum projeto foi encontrado no índice:", + "midi_no_show": "Trigger recebido para iniciar slide, mas nenhum Show está ativo.", + "midi_no_slide": "Trigger recebido para iniciar slide, mas nenhum slide foi encontrado no índice:", "midi_no_velocity": "Received MIDI signal, but no velocity, defaults to first index." }, "new": { - "show": "Nova apresentação", - "empty_show": "Nova apresentação em branco", + "create": "Criar novo", + "show": "Novo Show", + "empty_show": "Novo Show em branco", "project": "Novo projeto", - "section": "New section", + "section": "Nova seção", "timer": "Novo temporizador", - "variable": "New variable", + "variable": "Nova variável", + "trigger": "Novo trigger", + "audio_stream": "Nova transmissão de áudio", + "playlist": "Nova playlist", "category": "Nova categoria", "private": "Nova apresentação privada", "folder": "Novo diretório", @@ -398,153 +480,249 @@ "overlay": "Nova sobreposição", "template": "Novo modelo", "scripture": "Nova bíblia", - "collection": "New collection", + "collection": "Nova coleção", + "action": "Nova ação", "event": "Novo evento" }, "show": { "name": "Nome", "category": "Categoria", - "quick_lyrics": "Letra de música rápida", "new_layout": "Novo layout", "grid": "Visualização de grade", - "simple": "Simple view", + "simple": "Visualização simples", "list": "Visualização de lista", "lyrics": "Visualização das letras", - "text": "Text edit", - "update": "Update show" + "text": "Editar texto", + "update": "Atualizar Show", + "locked": "Este Show foi bloqueado!", + "locked_info": "Este Show foi bloqueado para edição. Desbloqueie o na aba Shows do Drawer", + "slide_template": "Modelo de slide", + "source": "Fonte", + "artist": "Artista", + "song": "Música", + "delete_manual": "Checagem manual", + "delete_match": "Excluir se encontrar idêntico", + "delete_keep_last_modified": "Excluir todos exceto o último modificado", + "delete_keep_first_created": "Excluir todos exceto o primeiro criado" }, "actions": { "rename": "Renomear", "recolor": "Alterar cor", "remove": "Remover", - "remove_group": "Remove group", - "choose_group": "Choose group", - "goto_group": "Go to group", - "change_output_style": "Change output style", - "active_outputs": "Active outputs", - "all_outputs": "All outputs", - "specific_outputs": "Specific outputs", + "remove_group": "Remover grupo", + "choose_group": "Escolher grupo", + "goto_group": "Ir até grupo", + "active_outputs": "Saídas ativas", + "all_outputs": "Todas as saídas", + "specific_outputs": "Saídas específicas", "toggle_private": "Privado", - "view_private": "Show private", + "view_private": "Mostrar Show privado", "import": "Importar", "export": "Exportar", "duplicate": "Duplicar", "delete": "Excluir", + "delete_slide": "Excluir slide", + "delete_group": "Excluir grupo", "delete_all": "Excluir tudo", "close": "Fechar", "save": "Salvar", - "done": "Done", + "done": "Feito", "disable": "Desabilitar", - "enable": "Enable", + "enable": "Habilitar", "undo": "Desfazer", - "redo": "refazer", + "redo": "Refazer", "cut": "Cortar", "copy": "Copiar", "paste": "Colar", "pasteAndMatchStyle": "Colar e combinar estilo", "selectAll": "Selecionar tudo", - "remove_selection": "Remove selection", + "remove_selection": "Remover seleção", "speech": "Fala", "startSpeaking": "Começar a falar", "stopSpeaking": "Parar de falar", - "fullscreen": "Alternar para o modo tela cheia", + "focus_mode": "Modo Foco", + "fullscreen": "Modo Tela Cheia", "resetZoom": "Redefinir zoom", "zoom": "Zoom", "zoomIn": "Mais Zoom", "zoomOut": "Menos Zoom", "reset": "Redefinir", - "reset_defaults": "Reset defaults", + "convert_to_images": "Converter para imagens", + "converting": "Convertendo...", + "remove_template_from_show": "Remover modelo da apresentação", + "reset_defaults": "Redefinir padrões", "to_all": "Aplicar a todos", - "to_following": "Apply to following", + "to_following": "Aplicar aos seguintes", "back": "Voltar", "home": "Início", "mute": "Silenciar", "unmute": "Parar de silenciar", - "toggle_time_marker": "Toggle time markers", - "add_time_marker": "Add time marker", - "bind_to": "Bind to", - "remove_binding": "Remove binding", - "show_timer": "Time until show", - "hide_timer": "Time until hide", - "choose_custom": "Choose custom", + "increase_volume": "Aumentar volume", + "decrease_volume": "Diminuir volume", + "toggle_time_marker": "Marcadores de tempo", + "add_time_marker": "Adicionar marcador de tempo", + "bind_to": "Saída específica", + "remove_binding": "Remover saída específica", + "dynamic_values": "Valores dinâmicos", + "rearrange": "Rearranjar", + "to_front": "Para primeiro plano", + "forward": "Para frente", + "backward": "Para último plano", + "to_back": "Para trás", + "show_timer": "Tempo até aparecer", + "hide_timer": "Tempo até esconder", + "choose_custom": "Escolher personalização", + "add_color": "Adicionar cor", "format": "Formatar", - "find_replace": "Find and replace text", - "cut_in_half": "Cut in half", - "find": "Find", - "replace": "Replace", - "case_sensitive": "Case sensitive", + "find_replace": "Localizar e substituir texto", + "cut_in_half": "Dividir em dois", + "merge": "Mesclar", + "find": "Localizar", + "replace": "Substituir", + "case_sensitive": "Diferenciar maiúsculas e minúsculas", "uppercase": "Transformar em maiúscula", "lowercase": "Transformar em minúscula", "capitalize": "Capitalizar", "trim": "Aparar", - "click_disable": "Click any to disable", - "svg_clipboard": "Import SVG from clipboard", - "fullscreen_preview": "Alternar pré-visualização em tela cheia", + "click_disable": "Clique em qualquer para desativar", + "svg_clipboard": "Importar SVG da área de transferência", + "fullscreen_preview": "Pré-visualização em tela cheia", "toggle_output": "Alternar tela de saída", + "toggle_panels": "Esconder/Mostrar painéis", "change_tab": "Alterar guia", "change_drawer_tab": "Alterar guia de desenho", - "toggle_drawer": "Toggle drawer", - "actions": "Ações", - "clear_history": "Clear history", - "set_key": "Set key", - "custom_key": "Set custom value", - "play_on_midi": "Play on MIDI in", - "send_midi": "Send MIDI out", - "delete_shows_not_indexed": "Delete shows in 'Shows' folder that are not indexed", - "delete_thumbnail_cache": "Delete thumbnail cache", - "open_log_file": "Open log file", - "refresh_all_shows": "Get all shows in 'Shows' folder", - "start_timer": "Start timer", - "stop_timers": "Stop active timers", - "next_after_media": "Next after media", + "change_slide": "Mudar slide", + "change_project_item": "Mudar item do projeto", + "change_drawer_item": "Mudar item do Drawer", + "change_drawer_category": "Mudar categoria do Drawer", + "toggle_drawer": "Drawer", + "slide_actions": "Ações para slide", + "item_actions": "Ações para item", + "clear_history": "Limpar histórico", + "chord_info": "Clique em qualquer letra para adicionar um acorde, por favor.", + "chord_key": "Chave", + "chord_type": "Tipo", + "chord_tension": "Tensão", + "chord_bass": "Baixo", + "roman_keys": "Chaves romanas", + "set_key": "Definir chave", + "custom_key": "Definir valor personalizado", + "select_chord": "Selecione esse acorde", + "play_on_midi": "Ativar ao sinal MIDI", + "play_on_midi_tip": "Ativar este específico slide quando receber sinal MIDI específico", + "send_midi": "Enviar sinal MIDI", + "delete_shows_not_indexed": "Excluir shows na pasta 'Shows' que não estão indexados", + "delete_empty_shows": "Excluir Shows vazios", + "delete_thumbnail_cache": "Excluir cache de thumbnails", + "export_usage_log": "Exportar registro de uso", + "reset_usage_log": "Reiniciar registro de uso", + "open_log_file": "Abrir arquivo de registro", + "open_cache_folder": "Abrir pasta de cache", + "refresh_all_shows": "Obter todos os Shows na pasta 'Shows'", + "start_timer": "Iniciar temporizador", + "stop_timers": "Parar temporizador ativo", + "next_after_media": "Depois da mídia concluir", "remove_media": "Remover mídia", - "remove_layers": "Remove layers", - "index_select_project": "Select project by index", - "index_select_project_show": "Select project item by index", - "index_select_slide": "Select slide by index", - "start_recording": "Start recording", - "stop_recording": "Stop recording" + "remove_layers": "Remover camadas", + "start_recording": "Iniciar gravação", + "stop_recording": "Parar gravação", + "export_recording": "Para de gravar e exportar", + "index_select_project": "Selecionar projeto pelo índice", + "next_project_item": "Próximo item do projeto", + "previous_project_item": "Item anterior do projeto", + "index_select_project_item": "Selecionar item do projeto pelo índice", + "name_select_show": "Selecionar Show pelo nome", + "random_slide": "Reproduzir slide aleatório", + "index_select_slide": "Selecionar slide pelo índice", + "name_select_slide": "Selecionar slide pelo nome", + "toggle_output_lock": "Travar saída", + "toggle_output_windows": "Janela de saída", + "id_select_group": "Selecionar grupo pelo ID", + "id_change_stage_layout": "Mudar o layout do palco pelo ID", + "start_camera": "Iniciar câmera", + "index_select_overlay": "Selecionar sobreposição pelo índice", + "name_select_overlay": "Selecionar sobreposição pelo nome", + "change_volume": "Mudar volume", + "start_audio_stream": "Iniciar transmissão de áudio", + "start_playlist": "Iniciar playlist", + "playlist_next": "Próxima faixa na playlist", + "start_metronome": "Iniciar metrônomo", + "name_start_timer": "Iniciar temporizador pelo nome", + "id_start_timer": "Iniciar temporizador pelo ID", + "start_slide_timers": "Iniciar temporizadores no slide ativo", + "id_select_output_style": "Selecionar estilo de saída pelo ID", + "change_output_style": "Mudar estilo da saída", + "change_stage_output_layout": "Mudar layout de saída do palco", + "change_transition": "Mudar transição", + "change_variable": "Mudar variável", + "start_trigger": "Iniciar trigger", + "run_action": "Executar ação", + "toggle_action": "Ação", + "send_rest_command": "Enviar HTTP-Request", + "custom_activation": "Ativação personalizada", + "activate_on_startup": "Ativar ao iniciar", + "activate_save": "Ativar ao salvar", + "activate_slide_clicked": "Ativar ao clicar no slide", + "activate_video_starting": "Ativar quando um vídeo iniciar", + "activate_video_ending": "Ativar quando um vídeo terminar", + "activate_audio_starting": "Ativar quando um áudio iniciar", + "activate_audio_ending": "Ativar quando um áudio terminar", + "activate_timer_ending": "Ativar quando um temporizador terminar", + "activate_scripture_start": "Ativar quando projetar um versículo", + "activate_slide_cleared": "Ativar quando limpar o slide", + "activate_background_cleared": "Ativar quando limpar o background", + "activate_show_created": "Ativar quando um Show é criado", + "activate_audio_playlist_ended": "Ativar quando uma playlist de áudio termina" + }, + "recording": { + "remove": "Remover gravação", + "tip": "Grave e reproduza os tempos de cada slide. Sincronize com uma faixa de áudio no primeiro slide.", + "layout_changed": "O layout mudou desde a última gravação!", + "audio_synced": "Sincronizado com áudio!", + "start": "Iniciar gravação de slide" }, "animate": { - "change": "Change", - "set": "Set", - "wait": "Wait", + "change": "Mudar", + "set": "Definir", + "wait": "Aguardar", "background": "background", - "text": "text", + "text": "texto", "item": "item", - "to": "to", - "for": "for", - "seconds": "seconds" + "to": "para", + "for": "por", + "seconds": "segundos" }, "cloud": { - "info": "Sync files with Google Drive for backups, or if you work on multiple computers.", - "tip_api": "You have to provide your own free Google API key so the program can automatically upload files to your Google Drive.", - "tip_how": "Don't know how to get one?", - "tip_guide": "Click here for a guide.", - "enable": "Automatic sync on startup and save", - "disable_upload": "Disable uploading data", - "media_id": "Media path ID", - "google_drive_api": "Google API service account key", - "select_key": "Import keys file", - "update_key": "Update keys file", - "main_folder": "Set main folder manually", - "media_folder": "Cloud media folder", - "reconnect": "Reconnect", - "sync": "Sync", - "choose_method_tip": "There is existing data in the cloud. Please choose from where you want to keep the data. And override the other location.", - "local": "Local" + "info": "Sincroniza os arquivos com o Google Drive para backups ou se você trabalha em vários computadores.", + "tip_api": "Você precisa fornecer sua própria chave de API gratuita do Google para que o programa possa carregar automaticamente os arquivos para seu Google Drive.", + "tip_how": "Não sabe como conseguir uma?", + "tip_guide": "Clique aqui para ver um passo a passo.", + "enable": "Sincronização automática ao salvar e iniciar", + "disable_upload": "Desativar upload de dados", + "media_id": "ID do caminho da mídia", + "google_drive_api": "Chave da conta Google API service", + "select_key": "Importar arquivos de chave", + "update_key": "Atualizar arquivos de chave", + "enable_custom_folder_id": "Usar pasta de ID personalizado", + "main_folder": "Definir pasta principal manualmente", + "media_folder": "Pasta de mídia da nuvem", + "reconnect": "Reconectar", + "sync": "Sincronizar agora", + "choose_method_tip": "Existem arquivos na nuvem. Por favor, escolha se fará upload para a nuvem ou se irá baixar da nuvem. Isto substituirá o destino da operação.", + "local": "Local", + "syncing": "Sincronizando com a nuvem", + "sync_complete": "Sincronização completa" }, "export": { "export": "Exportar", "export_as": "Exportar {} como", "exported": "Exportado!", - "oneFile": "One file", + "oneFile": "Um arquivo", "selected_shows": "Apresentações selecionadas", "current_project": "Projeto atual", "all_shows": "Todas as apresentações", "all_projects": "Todos os projetos", - "project": "Project", - "options": "Opções", + "project": "Projeto", "preview": "Pré-visualização", "title": "Título", "metadata": "Metadados", @@ -552,23 +730,29 @@ "groups": "Grupos", "numbers": "Slides enumerados", "invert": "Inverter slides", - "original_text_size": "Original text size", - "text": "Text puro", + "original_text_size": "Tamanho original do texto", + "text": "Texto puro", "slides": "Slides", "rows": "Linhas", "columns": "Colunas" }, "context": { "enabledTabs": "Alternar guias", + "filterByTags": "Filtrar por tags", "addToProject": "Adicionar ao projeto", + "add_to_show": "Adicionar ao Show", + "lockForChanges": "Bloquear para edição", + "use_as_archive": "Arquivar", "newCategory": "Nova categoria", "changeIcon": "Alterar ícone", "changeGroup": "Alterar grupo", "createNew": "Criar novo", "selectAll": "Selecionar tudo", "force_outputs": "Forçar saídas", + "align_with_screen": "Alinhar com a tela", "toggle_output": "Alterar saídas", "move_to_front": "Mover para frente", + "hide_from_preview": "Esconder do preview", "lock_to_output": "Travar na saída", "place_under_slide": "Place under slide", "toggle_clock": "Alternar relógio", @@ -588,51 +772,67 @@ "slide": "Slide" }, "edit": { + "text": "Texto", "font": "Fonte", - "family": "Família", + "family": "Família de fontes", + "font_size": "Tamanho da fonte", + "text_fit": "Encaixe do texto", + "shrink_to_fit": "Encolher", + "grow_to_fit": "Crescer", + "text_color": "Cor do texto", "style": "Estilo", + "lines": "Linhas", "options": "Opções", - "_title_bold": "Bold", + "_title_bold": "Negrito", "_title_italic": "Itálico", "_title_underline": "Sublinhado", "_title_strikethrough": "Tachado", "color": "Cor", - "background_color": "Cor de plano de fundo", - "background_opacity": "Background opacity", - "background_image": "Background image", - "media_fit": "Media fit", + "accent_color": "Cor de destaque", + "background_color": "Cor do plano de fundo", + "background_opacity": "Opacidade do plano de fundo", + "background_image": "Imagem de fundo", + "background_media": "Background", + "overlay_content": "Adicionar conteúdo de sobreposição", + "different_first_template": "Usar outro modelo no primeiro slide", + "media_fit": "Fazer mídia caber", "size": "Tamanho", + "max_lines": "Máximo de linhas", + "invert_items": "Inverter items", "chords": "Acordes", + "transpose": "Transpor", "auto_size": "Tamanho automático", + "no_wrap": "Texto em linha única", "line_spacing": "Espaçamento entre linhas", - "line_height": "Line height", + "line_height": "Altura da linha", "letter_spacing": "Espaçamento entre letras", "word_spacing": "Espaçamento entre palavras", - "transform": "Transform", - "uppercase": "Uppercase", - "lowercase": "Lowercase", - "capitalize": "Capitalize", + "transform": "Transformar", + "text_transform": "Transformar", + "uppercase": "Maiúsculas", + "lowercase": "Minúsculas", + "capitalize": "Iniciais maiúsculas", "align": "Alinhar", "_title_left": "Alinhar à esquerda", "_title_center": "Alinhar ao centro", "_title_right": "Alinhar à direita", "_title_justify": "Justificar", - "_title_top": "Alinhar ao top", - "_title_bottom": "Alinhar a parte inferior", + "_title_top": "Alinhar em cima", + "_title_bottom": "Alinhar em baixo", "outline": "Contorno", - "width": "Largura", "shadow": "Sombra", - "shadow_inset": "Inserção de sombra", - "offsetX": "Offset X", - "offsetY": "Offset Y", - "blur": "Blur", + "shadow_inset": "Sombra interna", + "offsetX": "Coordenada X", + "offsetY": "Coordenada Y", + "blur": "Desfoque", "item": "Item", + "width": "Largura", "height": "Altura", "rotation": "Rotação", - "tilt": "Tilt", - "perspective": "Perspective", + "tilt": "Inclinação", + "perspective": "Perspectiva", "opacity": "Opacidade", - "corner_radius": "Raio de canto", + "corner_radius": "Canto arredondado", "border": "Borda", "length": "Comprimento", "x": "X", @@ -641,68 +841,74 @@ "add_icons": "Adicionar ícones", "arrange_items": "Organizar itens", "filters": "Filtros", - "backdrop_filters": "Backdrop filters", - "interval": "Interval", - "video": "Video", - "choose_media": "Choose media", - "recent": "Recently edited", - "enable_stage": "Enable stage", - "select_stage": "Select stage", - "use_slide_index": "Use active index", - "slide_index": "Slide index", - "padding": "Padding", - "special": "Special", - "scrolling": "Scrolling", - "top_bottom": "Top to bottom", - "bottom_top": "Bottom to top", - "left_right": "Left to right", - "right_left": "Right to left", - "max_events": "Max events", - "start_days_from_today": "Start at days from today", - "just_one_day": "Just one day", - "enable_start_date": "Enable start date" + "backdrop_filters": "Filtros de fundo", + "interval": "Intervalo", + "video": "Vídeo", + "choose_media": "Escolher mídia", + "recent": "Editado recentemente", + "enable_stage": "Habilitar palco", + "select_stage": "Selecionar palco", + "next_slide": "Mostrar próximo slide", + "use_slide_index": "Usar índice ativo", + "slide_index": "Índice do slide", + "padding": "Preenchimento", + "special": "Especial", + "scrolling": "Rolagem", + "scrolling_speed": "Velocidade de rolagem", + "top_bottom": "Cima para baixo", + "bottom_top": "Baixo para cima", + "left_right": "Esquerda para direita", + "right_left": "Direita para esquerda", + "max_events": "Máximo de eventos", + "start_days_from_today": "Começa em dias a partir de hoje", + "just_one_day": "Dia único", + "enable_start_date": "Habilitar data de início", + "disable_navigation": "Desativar controles de navegação", + "progress_bar": "Barra de progresso" }, "items": { "text": "Caixa de texto", - "list": "List", + "list": "Lista", "media": "Mídia", - "camera": "Camera", - "image": "Imagen", + "image": "Imagem", + "camera": "Câmera", "video": "Vídeo", - "mirror": "Mirror", + "mirror": "Espelho", "clock": "Relógio", "events": "Eventos", "live": "Ao vivo", "audio": "Áudio", "timer": "Temporizador", - "variable": "Variable", + "variable": "Variável", "web": "Website", - "visualizer": "Visualizer", + "slide_tracker": "Progresso", + "visualizer": "Visualizador", + "captions": "Legendas", "icon": "Ícone" }, "borders": { - "solid": "Solida", + "solid": "Sólida", "dashed": "Tracejada", "dotted": "Pontilhada", "double": "Dupla", - "inset": "Inset", - "outset": "Outset", + "inset": "Interno", + "outset": "Externo", "groove": "Sulco", "ridge": "Cume" }, "list": { - "disc": "Disc", - "circle": "Circle", - "square": "Square", - "disclosure-closed": "Arrow", - "disclosure-open": "Triangle", - "decimal": "Number", - "decimal-leading-zero": "Number with zero", - "lower-alpha": "Letter", - "upper-alpha": "Upper letter", - "lower-roman": "Roman", - "upper-roman": "Upper roman", - "lower-greek": "Greek" + "disc": "Disco", + "circle": "Círculo", + "square": "Quadrado", + "disclosure-closed": "Seta", + "disclosure-open": "Triângulo", + "decimal": "Número", + "decimal-leading-zero": "Número com zero", + "lower-alpha": "Letra", + "upper-alpha": "Sobrescrito", + "lower-roman": "Romano", + "upper-roman": "Romano sobrescrito", + "lower-greek": "Grego" }, "timer": { "from_to": "Do ínico para o final", @@ -713,41 +919,58 @@ "seconds": "Segundos", "from": "A partir de", "to": "Até", - "overflow": "Overflow when reached end", - "overflow_color": "Overflow color", + "overflow": "Destacar quando chegar ao fim", + "overflow_color": "Cor de destaque", "preview": "Pré-visualizar", "clock": "Relógio", "event": "Evento", "no_events": "Não há eventos futuros", "edit": "Editar", "create": "Criar", - "line": "Line", - "mask": "Mask" + "line": "Linha", + "mask": "Máscara" }, "clock": { - "type": "Type", + "type": "Tipo", "digital": "Digital", - "analog": "Analog", - "seconds": "Seconds" + "analog": "Analógico", + "seconds": "Segundos" + }, + "captions": { + "info": "Por favor, clique no URL para abrir no seu navegador, caso ainda não tenha feito isso, ou abra-o em qualquer outro dispositivo! Certifique-se de dar acesso ao seu microfone e use o Google Chrome para melhor desempenho.", + "language": "Idioma da fala", + "translate": "Traduzir para", + "showtime": "Tempo de exibição", + "powered_by": "Powered by" + }, + "localization": { + "translate": "Traduzir", + "add": "Adicionar tradução", + "update": "Atualizar tradução", + "remove": "Remover items traduzidos" }, "midi": { - "name": "Name", - "input": "Input", - "output": "Output", - "type": "Type", - "note": "Note", - "velocity": "Velocity", - "channel": "Channel", - "start_action": "Start action", - "use_default_values": "Use default values", - "auto_values": "Update values when receiving a MIDI input", - "tip_velocity": "Set velocity to -1 to disable.", - "tip_action": "To activate spesific slides, right click any slide and choose the midi in action.", - "tip_index_by_velocity": "Index is determined by the received velocity, starting at 0." + "midi": "MIDI", + "activate": "Ativar pelo sinal MIDI", + "activate_keypress": "Ativar por uma tecla", + "name": "Nome", + "input": "Entrada", + "output": "Saída", + "type": "Tipo", + "note": "Nota", + "velocity": "Velocidade", + "channel": "Canal", + "start_action": "Iniciar ação", + "use_default_values": "Usar valores padrão", + "auto_values": "Atualize os valores quando recebendo dados via MIDI.", + "tip_velocity": "Configure a velocidade em -1 para desabilitar.", + "tip_action": "Para ativar slides específicos, clique com o botão direito em qualquer slide e selecione o MIDI atuante.", + "tip_index_by_velocity": "O índice é determinado pela velocidade recebida, começando em 0." }, "draw": { "focus": "Foco", "pointer": "Ponteiro", + "zoom": "Zoom", "fill": "Encher", "paint": "Pintar", "particles": "Partículas", @@ -764,90 +987,117 @@ }, "stage": { "slide": "Slide", + "stage_layout": "Layout do palco", "current_slide_text": "Texto do slide atual", "current_slide": "Slide atual", "current_slide_notes": "Notas do slide atual", "next_slide_text": "Texto do próximo slide", "next_slide": "Próximo slide", "next_slide_notes": "Notas do próximo slide", - "output": "Output", - "current_output": "Current output", + "output": "Saída", + "current_output": "Saída atual", + "slide_tracker": "Progresso", "time": "Tempo", "system_clock": "Relógio do sistema", "video_time": "Tempo do vídeo", - "video_countdown": "Contagem regressiva de vídeo", + "video_countdown": "Vídeo de contagem regressiva", + "first_active_timer": "Primeiro temporizador ativo", "other": "Outro", "message": "Mensagem", "color": "Cor", "font-size": "Tamanho da fonte", "zeros": "Zeros", "overrun": "Cor excedida", - "auto_stretch": "Auto stretch content" + "source_output": "Fonte de saída", + "auto_stretch": "Esticar conteúdo automaticamente", + "labels": "Mostrar etiquetas", + "label_color": "Cor da etiqueta" }, "settings": { "general": "Geral", "theme": "Tema", "groups": "Grupos", - "styles": "Styles", + "styles": "Estilos", "display_settings": "Saídas", - "actions": "Ações", "display": "Tela", "connection": "Conexão", - "cloud": "Cloud", + "cloud": "Nuvem", "calendar": "Calendário", + "text_import": "Texto", + "media_import": "Mídia", "other": "Outro", "language": "Idioma", - "autosave": "Autosave", - "never": "Never", - "minutes": "minutes", - "use24hClock": "Use 24-hour clock", - "styles_hint": "Create different styles that can be applied to the output to change the look.", - "hide_output_hint": "Clique duas vezes na janela de saída para ocultá-la. Hold CTRL to enable dragging.", - "hide_menubar_hint": "To hide the menu bar, enable kiosk mode, or enable \"Automatically hide or show the menu bar\" in the general MacOS settings.", + "autosave": "Salvamento automático", + "never": "Nunca", + "minutes": "minutos", + "use24hClock": "Usar relógio de 24 horas", + "styles_hint": "Crie estilos diferentes que podem ser aplicados à saída para alterar o estilo.", + "hide_output_hint": "Clique duas vezes na janela de saída para ocultá-la. Segure CTRL para permitir mover.", + "hide_menubar_hint": "Para esconder a barra de menu, habilite o modo quiosque ou habilite \"Esconder ou exibir automaticamente a barra de menu\" nas configurações gerais do MacOS.", "show_output_hint": "Segure ctrl/cmd enquanto clica no botão de exibição para forçar a exibição neste monitor.", - "move_output_hint": "Não encontra sua tela? Altere a posição até que a janela apareça na segunda tela.", - "select_display": "Click on the screen where you wan't to display the output window.", - "manual_input_hint": "Can't find the display? Click here to manually change the position.", - "manual_drag_hint": "You can also hold ctrl/cmd over an active output window to manually drag it around.", + "move_output_hint": "Não encontrou sua tela? Altere a posição até que a janela apareça na segunda tela.", + "select_display": "Clique na tela que você gostaria de exibir a janela de saída.", + "manual_input_hint": "Não consegue encontrar a janela de saída? Clique aqui para ajustar manualmente a posição.", + "manual_drag_hint": "Você pode segurar ctrl/cmd sobre uma janela de saída ativa para movê-la.", + "allow_main_screen": "Permitir posição e tamanho de saída personalizados", + "identify_screens": "Identificar telas", "new_output": "Nova saída", - "enable_key_output": "Enable alpha key output", - "always_on_top": "Always on top", - "kiosk_mode": "Kiosk mode", - "change_key_output_position": "Change key output position", + "normal": "Normal", + "enable_key_output": "Habilitar saída alfa", + "always_on_top": "Acima das outras", + "kiosk_mode": "Modo quiosque", + "change_key_output_position": "Alterar posição de saída chave", "position": "Posição", "enabled": "Habilitado", "color_when_active": "Cor quando ativo", "fixed": "Fixado", "lines": "Linhas", - "override_with_template": "Substituir estilo com modelo", + "override_with_template": "Substituir slide com modelo", + "override_scripture_with_template": "Substituir versículo com modelo", "active_layers": "Camadas ativas", "window": "Janela", - "active_style": "Use style", + "active_style": "Usar estilo", "alert_updates": "Alertar quando uma nova atualização estiver disponível", - "disable_labels": "Desativar rótulos", + "auto_updates": "Atualização automática", + "disable_labels": "Desativar etiquetas", "group_numbers": "Números do grupo", - "full_colors": "Cores completas do grupo de slides", + "full_colors": "Ativar alto contraste entre grupos", + "slide_number_keys": "Usar teclas de números para controlar slides", "auto_output": "Mostrar tela de saída na inicialização", + "hide_cursor_in_output": "Esconder cursor na saída", + "clear_media_when_finished": "Limpar mídia ao terminar", + "disable_presenter_controller_keys": "Desativar teclas do controle do apresentador", "default_project_name": "Nome do projeto padrão", + "audio_fade_duration": "Duração de fade", + "audio_crossfade": "Crossfade", + "clear_style_background_on_text": "Limpar estilo do fundo quando um slide estiver ativo", "resolution": "Resolução", - "cropping": "Cropping", - "frame_rate": "Frame rate", - "transparent": "Transparent", + "cropping": "Corte", + "frame_rate": "Taxa de atualização", + "device": "Dispositivo", + "display_mode": "Modo de exibição", + "pixel_format": "Formato de pixel", + "alpha_key": "Alpha Key", + "transparent": "Transparente", + "invisible_window": "Janela invisível", "video_extensions": "Extensões de vídeo", "image_extensions": "Extensões de imagem", "add": "Adicionar", "remove": "Remover", "change_name": "Alterar nome", - "show_location": "Mostrar localização", - "export_location": "Export location", - "scripture_location": "Scripture location", - "recording_location": "Recordings location", + "show_location": "Apresentações", + "data_location": "Local dos dados", + "user_data_location": "Salvar configurações do usuário em \"Dados\"", + "popup_before_close": "Mostrar janela de confirmação ao sair", + "disable_hardware_acceleration": "Desativar aceleração por hardware", + "restart_for_change": "Reinicie o app para que as mudanças tenham efeito.", "font": "Fonte", "font_family": "Família de fontes", "font_size": "Tamanho da fonte", + "border_radius": "Raio da borda", "colors": "Cores", "add_group": "Adicionar grupo", - "group_shortcut": "Shortcut to activate group", + "group_shortcut": "Atalho para ativar grupo", "output_screen": "Tela de saída", "device_name": "Nome do dispositivo", "password": "senha", @@ -856,34 +1106,38 @@ "max_connections": "Máx. de conexões", "allowed_connections": "Conexões permitidas", "connect": "Conecte-se digitando isso em um navegador da web", - "connect_qr": "Or scan this QR code", + "connect_qr": "Ou escaneie este código QR", "restart": "Reiniciar servidores", "reset_all": "Redefinir tudo", "reset_theme": "Redefinir tema", "reset_themes": "Redefinir temas", - "backup_all": "Backup everything", - "restore": "Restore", - "backup_started": "Backing up...", - "restore_started": "Restoring...", - "backup_finished": "Backup complete!", - "restore_finished": "Restore complete!", - "preview_frame_rate": "Preview frame rate", - "auto": "Auto", - "optimized": "Optimized", - "full": "Full" + "capitalize_words": "Iniciais em maiúsculas", + "comma_seperated": "Separado por vírgula", + "backup_all": "Backup de tudo", + "restore": "Restaurar", + "backup_started": "Fazendo backup...", + "restore_started": "Restaurando...", + "backup_finished": "Backup completo!", + "restore_finished": "Restauração completa!", + "auto_backup": "Backup automático", + "preview_frame_rate": "Pré-visualizar frame rate", + "auto": "Automático", + "optimized": "Otimizado", + "reduced": "Reduzido", + "full": "Completo" }, "sort": { - "sort_by": "Sort by", + "sort_by": "Ordenar por", "name": "Nome", - "date": "Date", + "date": "Data", "size": "Tamanho", "type": "Tipo", - "custom": "Custom" + "custom": "Customizado" }, "calendar": { "type": "Tipo", "event": "Evento", - "show": "Programar apresentação", + "schedule_action": "Ação de agenda", "name": "Nome", "color": "Cor", "time": "Tempo", @@ -904,18 +1158,23 @@ "year": "ano", "ending_the": "o", "ending_repeated": "repetida", - "ending_times": "tempos", + "ending_times": "vezes", "save_all": "Salve e atualize todos", - "add_slides_from_show": "Include slides from show" + "add_slides_from_show": "Incluir slides do show" }, "scripture": { "bibles": "Bíblias da API.Bible", "custom": "Ou importe a sua", - "max_verses": "Número máximo de versos por slide", + "max_verses": "Máximo de versículos por slide", "verse_numbers": "Números de versículos", + "verses_on_individual_lines": "Versículos em linhas individuais", "version": "Mostrar versão", "reference": "Mostrar referência", - "red_jesus": "Palavras de Jesus em vermelho" + "split_reference": "Referência dividida", + "combine_with_text": "Combinar com o texto", + "reference_at_bottom": "Mover para o rodapé", + "red_jesus": "Palavras de Jesus em vermelho", + "search": "Procurar na Bíblia" }, "filter": { "blur": "Borrão", @@ -932,21 +1191,39 @@ "screens": "Telas", "windows": "Janelas", "cameras": "Câmeras", - "microphones": "Microfones" + "microphones": "Microfones", + "audio_streams": "Streams de áudio" + }, + "presentation_control": { + "unsupported": "Controle de apresentação não suportado em", + "unsupported_tip": "Você pode convertê-los em imagens e criar um Show, ou desativar \"Acima das outras\" para apresentar de qualquer outro aplicativo.", + "opening": "Abrindo! Por favor, aguarde...", + "retry": "Por favor tente abrir manualmente, ou", + "try_again": "Tentar novamente", + "restart": "Reiniciar", + "start": "Iniciar", + "choose_window": "Por favor, selecione uma janela" }, "transition": { "current_slide": "para o slide atual", "text": "Transição de texto", "media": "Transição de mídia", - "duration": "Duration", - "easing": "Easing", + "slide_transition": "Transição de slide", + "background_transition": "Transição de fundo", + "specific": "Transições mais específicas", + "between": "Entre", + "in": "Entrando", + "out": "Saíndo", + "duration": "Duração", + "easing": "Transição", + "direction": "Direção", "type": "Tipo", "none": "Nenhum", "fade": "Desvanecer", - "blur": "Borar", + "blur": "Borrar", "scale": "Escala", "spin": "Rodar", - "slide": "Escorregar" + "slide": "Slide" }, "easings": { "linear": "Linear", @@ -954,18 +1231,18 @@ "ease-in": "Ease in", "ease-out": "Ease out", "ease-in-out": "Ease in & out", - "elastic": "Elastico", + "elastic": "Elástico", "bounce": "Quicar", "back": "Saltar", "circ": "Circular", - "cubic": "Cubico", + "cubic": "Cúbico", "sine": "Fluxo" }, "variables": { - "number": "Number", - "text": "Text", - "step": "Step", - "value": "Value" + "number": "Número", + "text": "Texto", + "step": "Passo", + "value": "Valor" }, "month": { "1": "Janeiro", @@ -990,16 +1267,70 @@ "6": "Sábado", "7": "Domingo" }, + "interval": { + "daily": "Diariamente", + "weekly": "Semanalmente", + "mothly": "Mensalmente" + }, "info": { "created": "Criado", "modified": "Modificado", "used": "Usado", "changed": "Alterado", + "recently_used": "Recentemente usados", "extension": "Extensão de arquivo", "size": "Tamanho", "slides": "Slides", "words": "Palavras", "template": "Modelo", - "category": "Categoria" + "category": "Categoria", + "codecs": "Codecs", + "mimeType": "MIME type", + "photoUrl": "Endereço da foto", + "likes": "Likes", + "artist": "Artista", + "artistUrl": "Página do artista" + }, + "guide_title": { + "projects": "Projetos", + "project": "Projeto", + "drawer": "Drawer", + "categories": "Categorias", + "show_create": "Criando um Show", + "show": "Show", + "output": "Saída", + "editing": "Editando", + "end": "Isso é o começo!" + }, + "guide_description": { + "start": "Esse é um pequeno guia de algumas funções básicas! (Você pode pular no canto direito.)", + "project_manage": "Gerencie todos os seus projetos em um só lugar.", + "project_create": "Crie pastas e mantenha seus novos projetos organizados.", + "project_list": "Após abrir um projeto você tem uma lista de todos os itens no lado esquerdo, arraste do Drawer para adicionar mais.", + "drawer": "Seus Shows, músicas, mídias, modelos, tudo em um só lugar.", + "drawer_search": "Pesquise conteúdo na guia ativa do Drawer (músicas, etc.). Use o atalho CTRL/CMD+F para acesso mais rápido.", + "drawer_shows": "Gerencie seus diferentes Shows separando cada um em uma categoria. Arraste qualquer Show selecionado para mover entre elas.", + "create_show_button": "Clique aqui para iniciar uma nova apresentação (música, slide, etc.)", + "create_show_popup": "Preencha os principais detalhes do Show, você também pode encontrar automaticamente as letras na web a partir do nome ou colar as letras manualmente.", + "show": "Um Show é a apresentação principal, clique em qualquer slide para apresentar e use as teclas de seta para navegar. Você também pode arrastar e soltar arquivos de mídia em um slide para definir como plano de fundo.", + "show_slide": "Depois de um slide ser clicado, ele será mostrado na saída e você verá uma prévia na direita. Aqui também vão aparecer ações extras como pausar um vídeo.", + "output_clear": "Limpe camadas específicas da saída ou todas de uma vez só.", + "edit": "Edite qualquer apresentação com um clique no topo.", + "end": "Você sempre pode procurar na web se precisar de ajuda com outros recursos." + }, + "songbeamer_import": { + "options": "Opções", + "encoding": "Encodificando", + "older_versions": "Para versões do Songbeamer anteriores a 6.02a", + "category": "Categoria", + "utf8": "UTF-8", + "latin1": "Latin 1", + "translations": "Translations", + "translation_multiline": "Multiline", + "translation_textboxes": "Textboxes", + "translation_layouts": "Layouts", + "translation_description_multiline": "Adiciona todos os idiomas em uma única caixa de texto em linhas alternadas. (Como no Songbeamer)", + "translation_description_textboxes": "Adiciona cada idioma como uma caixa de texto separada ao slide.", + "translation_description_layouts": "Cria um conjunto único de slides e um layout para cada idioma." } -} \ No newline at end of file +} diff --git a/public/lang/zh_CN.json b/public/lang/zh_CN.json index 6e2bcab7..ba39dfb7 100644 --- a/public/lang/zh_CN.json +++ b/public/lang/zh_CN.json @@ -1029,7 +1029,7 @@ "language": "语言", "autosave": "自动保存", "never": "从不", - "minutes": "分组", + "minutes": "分钟", "use24hClock": "显示24小时制时间", "styles_hint": "创建不同的样式,应用于输出以改变外观。", "hide_output_hint": "双击输出窗口可隐藏。按住 Ctrl 键可拖动。", diff --git a/src/electron/capture/CaptureHelper.ts b/src/electron/capture/CaptureHelper.ts index d5d034ae..ee178d8d 100644 --- a/src/electron/capture/CaptureHelper.ts +++ b/src/electron/capture/CaptureHelper.ts @@ -65,6 +65,10 @@ export class CaptureHelper { }) } + static async captureBase64Frame(window: BrowserWindow) { + return (await window.capturePage()).toDataURL({ scaleFactor: 0.5 }) + } + static resizeImage(image: NativeImage, initialSize: Size, newSize: Size) { if (initialSize.width / initialSize.height >= newSize.width / newSize.height) image = image.resize({ width: newSize.width }) else image = image.resize({ height: newSize.height }) diff --git a/src/electron/data/import.ts b/src/electron/data/import.ts index dbf23ed8..93a580b6 100644 --- a/src/electron/data/import.ts +++ b/src/electron/data/import.ts @@ -6,7 +6,8 @@ import sqlite3 from "sqlite3" import WordExtractor from "word-extractor" import { toApp } from ".." import { IMPORT } from "../../types/Channels" -import { readFileAsync, readFileBufferAsync } from "../utils/files" +import { getExtension, readFileAsync, readFileBufferAsync } from "../utils/files" +import { decompress } from "./zip" const specialImports: any = { powerpoint: async (files: string[]) => { @@ -77,11 +78,26 @@ export async function importShow(id: any, files: string[] | null, importSettings if (!files?.length) return let importId = id - let sqliteFile = id === "openlp" && files.find((a) => a.includes(".sqlite")) - if (sqliteFile) files = files.filter((a) => a.includes(".sqlite")) + let data: any[] = [] + + let sqliteFile = id === "openlp" && files.find((a) => a.endsWith(".sqlite")) + if (sqliteFile) files = files.filter((a) => a.endsWith(".sqlite")) if (id === "easyworship" || id === "softprojector" || sqliteFile) importId = "sqlite" - let data: any[] = [] + const zip = ["zip", "probundle", "vpc", "qsp"] + let zipFiles = files.filter((a) => zip.includes(a.slice(a.lastIndexOf(".") + 1).toLowerCase())) + if (zipFiles.length) { + data = decompress(zipFiles) + if (data.length) { + for (let i = 0; i < data.length; i++) { + const customContent = await checkSpecial(data[i]) + if (customContent) data[i].content = customContent + } + toApp(IMPORT, { channel: id, data }) + } + return + } + if (specialImports[importId]) data = await specialImports[importId](files, importSettings) else { // TXT | FreeShow | ProPresenter | VidoePsalm | OpenLP | OpenSong | XML Bible | Lessons.church @@ -97,7 +113,7 @@ async function readFile(filePath: string, encoding: BufferEncoding = "utf8") { let content: string = "" let name: string = getFileName(filePath) || "" - let extension: string = path.extname(filePath).substring(1).toLowerCase() + let extension: string = getExtension(filePath) try { if (extension === "pro") content = await decodeProto(filePath) @@ -116,14 +132,19 @@ const getFileName = (filePath: string) => path.basename(filePath).slice(0, path. // https://github.com/greyshirtguy/ProPresenter7-Proto // https://www.npmjs.com/package/protobufjs -async function decodeProto(filePath: string) { +async function decodeProto(filePath: string, fileContent: Buffer | null = null) { const dir = join(__dirname, "..", "..", "..", "public", "proto", "presentation.proto") const root = await protobufjs.load(dir) const Presentation = root.lookupType("Presentation") - const buffer = await readFileBufferAsync(filePath) + const buffer = fileContent || (await readFileBufferAsync(filePath)) const message = Presentation.decode(buffer) return JSON.stringify(message) } + +async function checkSpecial(file: any) { + if (file.extension === "pro") return await decodeProto("", file.content) + return +} diff --git a/src/electron/data/zip.ts b/src/electron/data/zip.ts new file mode 100644 index 00000000..02466af4 --- /dev/null +++ b/src/electron/data/zip.ts @@ -0,0 +1,25 @@ +import AdmZip from "adm-zip" +import { getExtension } from "../utils/files" + +// https://www.npmjs.com/package/adm-zip + +export function decompress(files: string[]) { + let data: any[] = [] + + files.forEach((file) => { + const zip = new AdmZip(file) + const zipEntries = zip.getEntries() + + zipEntries.forEach((zipEntry) => { + let content: Buffer | string = zipEntry.getData() + const name = zipEntry.entryName + const extension = getExtension(name) + + if (extension !== "pro") content = content.toString("utf8") + + data.push({ content, name, extension }) + }) + }) + + return data +} diff --git a/src/electron/ndi/NdiReceiver.ts b/src/electron/ndi/NdiReceiver.ts index 3850fed1..6ba97656 100644 --- a/src/electron/ndi/NdiReceiver.ts +++ b/src/electron/ndi/NdiReceiver.ts @@ -33,6 +33,7 @@ export class NdiReceiver { if (previousLength === currentLength) { clearInterval(findSourcesInterval) resolve(sources) + return } // finder.wait() previousLength = currentLength @@ -89,6 +90,7 @@ export class NdiReceiver { this.NDI_RECEIVERS[source.id].frameRate = Math.round(1000 / frameRate) let gettingFrame: boolean = false + if (this.NDI_RECEIVERS[source.id].interval) clearInterval(this.NDI_RECEIVERS[source.id].interval) this.NDI_RECEIVERS[source.id].interval = setInterval(async () => { if (gettingFrame) return gettingFrame = true @@ -113,6 +115,7 @@ export class NdiReceiver { if (!this.sendToOutputs.length) { clearInterval(this.NDI_RECEIVERS[data.id].interval) + // delete this.allActiveReceivers[data.id] delete this.NDI_RECEIVERS[data.id] } return diff --git a/src/electron/servers.ts b/src/electron/servers.ts index 18dc5dd9..2be78a5d 100644 --- a/src/electron/servers.ts +++ b/src/electron/servers.ts @@ -3,7 +3,9 @@ import express, { Response } from "express" import http from "http" import { join } from "path" import { Server } from "socket.io" +import { CaptureHelper } from "./capture/CaptureHelper" import { toApp } from "./index" +import { OutputHelper } from "./output/OutputHelper" type ServerName = "REMOTE" | "STAGE" | "CONTROLLER" | "OUTPUT_STREAM" let servers: { [key in ServerName]: any } = { @@ -97,7 +99,15 @@ function initialize(id: ServerName, socket: any) { servers[id].connections[socket.id] = { name } // SEND DATA FROM CLIENT TO APP - socket.on(id, (msg: any) => toApp(id, msg)) + socket.on(id, async (msg: any) => { + if (msg.channel === "OUTPUT_FRAME") { + const window = OutputHelper.getOutput(msg.data.outputId).window + const frame = await CaptureHelper.captureBase64Frame(window) + toServer(id, { channel: "OUTPUT_FRAME", data: { frame } }) + } else { + toApp(id, msg) + } + }) // DISCONNECT socket.on("disconnect", () => disconnect(id, socket)) diff --git a/src/electron/utils/files.ts b/src/electron/utils/files.ts index 4ba6aa7c..d161876d 100644 --- a/src/electron/utils/files.ts +++ b/src/electron/utils/files.ts @@ -198,6 +198,10 @@ export function getDataFolder(dataPath: string, name: string) { // HELPERS +export function getExtension(name: string) { + return path.extname(name).substring(1).toLowerCase() +} + export function createFolder(path: string) { if (doesPathExist(path)) return path return makeDir(path) diff --git a/src/frontend/MainOutput.svelte b/src/frontend/MainOutput.svelte index 6f746a64..17a5b1ae 100644 --- a/src/frontend/MainOutput.svelte +++ b/src/frontend/MainOutput.svelte @@ -8,6 +8,7 @@ import StageShow from "./components/stage/StageShow.svelte" import { currentWindow, outputs, special, styles } from "./stores" import { hideDisplay } from "./utils/common" + import { send } from "./utils/request" $: outputId = Object.keys($outputs)[0] @@ -23,7 +24,7 @@ if (e.ctrlKey || e.metaKey || e.target.closest(".dragger")) enableOutputMove = true else enableOutputMove = false } - $: if ($currentWindow === "output") window.api.send(OUTPUT, { channel: "MOVE", data: { enabled: enableOutputMove } }) + $: if ($currentWindow === "output") send(OUTPUT, ["MOVE"], { enabled: enableOutputMove }) // make sure it's loaded to prevent output not changing to stage output because of Svelte transition bug let loaded: boolean = false diff --git a/src/frontend/components/actions/CreateAction.svelte b/src/frontend/components/actions/CreateAction.svelte index e04a0d12..5e4f0d39 100644 --- a/src/frontend/components/actions/CreateAction.svelte +++ b/src/frontend/components/actions/CreateAction.svelte @@ -67,6 +67,9 @@ .filter(({ id }) => { // show if it is the currently selected if (id === actionId) return true + // don't display GET actions + if (id.includes("get_")) return false + // WIP MIDI multiple of the same (needs a new way of setting the id) // show if it has an input (because you probably want to have multiple) // if (actionData[actionId]?.input) return true diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index b7bb5e0b..ddc10277 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -22,6 +22,7 @@ topContextActive, undoHistory, } from "../../stores" + import { keysToID } from "../helpers/array" import Icon from "../helpers/Icon.svelte" import { _show } from "../helpers/shows" import T from "../helpers/T.svelte" @@ -136,6 +137,19 @@ let id = $selected.data[0] if ($overlays[id]?.locked) enabled = true }, + move_to_front: () => { + let previewOutputs = keysToID($outputs).filter((a) => a.enabled && !a.isKeyOutput) + // WIP check currently selected against the other outputs... + if (previewOutputs.length !== 2) { + disabled = false + return + } + + const alwaysOnTopState = [...new Set(previewOutputs.map((out) => out?.alwaysOnTop ?? true))] + + // disable if all outputs have different states! + disabled = alwaysOnTopState.length === previewOutputs.length + }, hide_from_preview: () => { let outputId = contextElem.id if ($outputs[outputId]?.hideFromPreview) enabled = true @@ -173,10 +187,10 @@ menuClick(id, enabled, menu, contextElem, actionItem, sel) // don't hide context menu - const format = ["uppercase", "lowercase", "capitalize", "trim"] - if (format.includes(id)) return - const keepOpen = ["enabled_drawer_tabs", "tags", "bind_to", "item_bind_to"] - if (keepOpen.includes(id)) { + const keepOpen = ["uppercase", "lowercase", "capitalize", "trim"] // "dynamic_values" (caret position is lost) + if (keepOpen.includes(id)) return + const keepOpenToggle = ["enabled_drawer_tabs", "tags", "bind_slide", "bind_item"] + if (keepOpenToggle.includes(id)) { enabled = !enabled return } @@ -202,13 +216,18 @@
{#if menu?.icon}{/if} - {#if menu?.translate === false} -

{menu?.label}

- {:else} - {#key menu} -

- {/key} - {/if} +

+ {#if menu?.translate === false} + {menu?.label} + {:else} + {#key menu} + + {/key} + {/if} + {#if menu.external} + + {/if} +

{#if shortcut} diff --git a/src/frontend/components/context/ContextMenu.svelte b/src/frontend/components/context/ContextMenu.svelte index d9515d78..a94d5fcc 100644 --- a/src/frontend/components/context/ContextMenu.svelte +++ b/src/frontend/components/context/ContextMenu.svelte @@ -1,5 +1,5 @@ @@ -72,7 +74,14 @@ gap: 5px; } main p:nth-child(even) { - background-color: var(--primary-darker); + background-color: rgb(0 0 20 / 0.15); + } + + main p span:not(.title) { + opacity: 0.8; + + overflow: hidden; + direction: rtl; } .scroll { diff --git a/src/frontend/components/drawer/info/ScriptureInfo.svelte b/src/frontend/components/drawer/info/ScriptureInfo.svelte index d1dedb0b..2381f380 100644 --- a/src/frontend/components/drawer/info/ScriptureInfo.svelte +++ b/src/frontend/components/drawer/info/ScriptureInfo.svelte @@ -7,7 +7,7 @@ import { customActionActivation } from "../../actions/actions" import Icon from "../../helpers/Icon.svelte" import T from "../../helpers/T.svelte" - import { removeDuplicates, sortByName } from "../../helpers/array" + import { clone, removeDuplicates, sortByName } from "../../helpers/array" import { history } from "../../helpers/history" import { getMediaStyle } from "../../helpers/media" import { getActiveOutputs, setOutput } from "../../helpers/output" @@ -69,6 +69,20 @@ let books = removeDuplicates(bibles.map((a) => a.book)).join(" / ") + // create first slide reference + if ($scriptureSettings.firstSlideReference) { + const slideClone = clone(slides[0]) + slides.forEach((a) => a.splice(a.length - 1, 1)) + // get verse text for correct styling + const metaStyle = slideClone.at(-2) + if (metaStyle) slides = [[metaStyle], ...slides] + // only keep one line/text item (not verse number) + slides[0][0].lines = [slides[0][0].lines![0]] + slides[0][0].lines![0].text = [slides[0][0].lines![0].text[1] || slides[0][0].lines![0].text[0]] + // set verse text to reference + slides[0][0].lines![0].text[0].value = slideClone.at(-1)?.lines?.[0].text?.[0].value || "" + } + let slides2: any = {} let layouts: any[] = [] const referenceDivider = $scriptureSettings.referenceDivider || ":" @@ -77,8 +91,10 @@ // get verse reference let v = $scriptureSettings.versesPerSlide + if ($scriptureSettings.firstSlideReference) i-- let range: any[] = sorted.slice((i + 1) * v - v, (i + 1) * v) let scriptureRef = books + " " + bibles[0].chapter + referenceDivider + joinRange(range) + if (i === -1) scriptureRef = "—" slides2[id] = { group: scriptureRef || "", color: null, settings: {}, notes: "", items } let l: any = { id } @@ -258,9 +274,9 @@
- +

- update("template", e.detail.id)} style="width: 30%;" /> + update("template", e.detail.id)} />
{#if $scriptureSettings.versesPerSlide != 3 || sorted.length > 1} @@ -318,7 +334,7 @@
- {#if $scriptureSettings.showVerse && sorted.length > 1} + {#if $scriptureSettings.showVerse && !$scriptureSettings.firstSlideReference && sorted.length > 1}

@@ -340,17 +356,28 @@ {/if} {#if $scriptureSettings.showVersion || $scriptureSettings.showVerse} - -

-
- -
-
- {#if $scriptureSettings.combineWithText} + {#if !$scriptureSettings.firstSlideReference} + +

+
+ +
+
+ {#if $scriptureSettings.combineWithText} + +

+
+ +
+
+ {/if} + {/if} + + {#if !$scriptureSettings.combineWithText} -

+

- +
{/if} @@ -386,8 +413,8 @@ } .settings :global(.dropdown) { - position: absolute; - width: 250% !important; - transform: translateX(-60%); + /* position: absolute; */ + width: 160%; + right: 0; } diff --git a/src/frontend/components/drawer/info/ShowInfo.svelte b/src/frontend/components/drawer/info/ShowInfo.svelte index f0cca7e1..9b7144a2 100644 --- a/src/frontend/components/drawer/info/ShowInfo.svelte +++ b/src/frontend/components/drawer/info/ShowInfo.svelte @@ -140,7 +140,7 @@ padding: 2px 10px; } .table p:nth-child(odd) { - background-color: var(--primary-darker); + background-color: rgb(0 0 20 / 0.15); } .title { @@ -148,5 +148,8 @@ } .table p span:not(.title) { opacity: 0.8; + + overflow: hidden; + direction: rtl; } diff --git a/src/frontend/components/drawer/live/Mic.svelte b/src/frontend/components/drawer/live/Mic.svelte index 75c1d469..34c1ed74 100644 --- a/src/frontend/components/drawer/live/Mic.svelte +++ b/src/frontend/components/drawer/live/Mic.svelte @@ -150,7 +150,7 @@ display: flex; } .main:nth-child(even) { - background-color: var(--primary-darkest); + background-color: rgb(0 0 20 / 0.08); } .meter { diff --git a/src/frontend/components/drawer/media/Folder.svelte b/src/frontend/components/drawer/media/Folder.svelte index bb158deb..b64d0fa0 100644 --- a/src/frontend/components/drawer/media/Folder.svelte +++ b/src/frontend/components/drawer/media/Folder.svelte @@ -2,7 +2,7 @@ import { onDestroy } from "svelte" import { uid } from "uid" import { MAIN, READ_FOLDER } from "../../../../types/Channels" - import { send } from "../../../utils/request" + import { destroy, send } from "../../../utils/request" import Icon from "../../helpers/Icon.svelte" import { getThumbnailPath, isMediaExtension, mediaSize } from "../../helpers/media" import Card from "../Card.svelte" @@ -17,10 +17,9 @@ let fileCount: number = 0 let listenerId = uid() + // WIP this creates one listener per individual folder... $: if (folderPreview && mode === "grid" && path) send(MAIN, ["READ_FOLDER"], { path, disableThumbnails: true }) - onDestroy(() => { - window.api.removeListener(READ_FOLDER, listenerId) - }) + onDestroy(() => destroy(READ_FOLDER, listenerId)) window.api.receive(READ_FOLDER, receiveContent, listenerId) function receiveContent(msg) { diff --git a/src/frontend/components/drawer/media/Media.svelte b/src/frontend/components/drawer/media/Media.svelte index 84dd0ed5..dd2116ed 100644 --- a/src/frontend/components/drawer/media/Media.svelte +++ b/src/frontend/components/drawer/media/Media.svelte @@ -5,7 +5,7 @@ import { uid } from "uid" import { MAIN, READ_FOLDER } from "../../../../types/Channels" import { activeDrawerOnlineTab, activeEdit, activeFocus, activePopup, activeShow, dictionary, focusMode, labelsDisabled, media, mediaFolders, mediaOptions, outLocked, outputs, popupData, selectAllMedia, selected } from "../../../stores" - import { send } from "../../../utils/request" + import { destroy, send } from "../../../utils/request" import Icon from "../../helpers/Icon.svelte" import T from "../../helpers/T.svelte" import { clone, sortByName, sortFilenames } from "../../helpers/array" @@ -65,6 +65,7 @@ if (prevActive === "online") activeView = "all" if (active === "online") { + // WIP this resets on zoom activeView = "image" prevActive = active @@ -104,7 +105,7 @@ let filesInFolders: string[] = [] let listenerId = uid() - onDestroy(() => window.api.removeListener(READ_FOLDER, listenerId)) + onDestroy(() => destroy(READ_FOLDER, listenerId)) // receive files window.api.receive(READ_FOLDER, receiveContent, listenerId) @@ -469,7 +470,7 @@ {#if zoomOpened} -
+
diff --git a/src/frontend/components/drawer/media/pixabay.ts b/src/frontend/components/drawer/media/pixabay.ts index 28292c00..06e9ac6e 100644 --- a/src/frontend/components/drawer/media/pixabay.ts +++ b/src/frontend/components/drawer/media/pixabay.ts @@ -22,7 +22,7 @@ export async function loadFromPixabay(query: string = "", video: boolean = false hits = data.hits.map((media) => { let path = media.largeImageURL if (video) path = media.videos.medium.url - return { path, previewUrl: media.previewURL, name: media.tags, extension: getExtension(path), credits: getPixabayCredits(media) } + return { path, previewUrl: video ? media.videos.small.thumbnail : media.previewURL, name: media.tags, extension: getExtension(path), credits: getPixabayCredits(media) } }) cache[query + video] = hits diff --git a/src/frontend/components/drawer/pages/Overlays.svelte b/src/frontend/components/drawer/pages/Overlays.svelte index b9ab72d2..9530b14c 100644 --- a/src/frontend/components/drawer/pages/Overlays.svelte +++ b/src/frontend/components/drawer/pages/Overlays.svelte @@ -1,4 +1,5 @@
- {#if fullFilteredOverlays.length} + {#if preloader && fullFilteredOverlays.length > 10} +
+ +
+ {:else if fullFilteredOverlays.length}
{#each fullFilteredOverlays as overlay} + import { onMount } from "svelte" import { activePopup, activeShow, alertMessage, dictionary, labelsDisabled, mediaOptions, outputs, showsCache, styles, templateCategories, templates } from "../../../stores" import { clone, keysToID, sortByName } from "../../helpers/array" import { history } from "../../helpers/history" @@ -12,6 +13,7 @@ import SelectElem from "../../system/SelectElem.svelte" import Card from "../Card.svelte" import TemplateSlide from "./TemplateSlide.svelte" + import Loader from "../../main/Loader.svelte" export let active: string | null export let searchValue: string = "" @@ -53,11 +55,19 @@ nextScrollTimeout = null }, 500) } + + // open drawer tab instantly before content has loaded + let preloader: boolean = true + onMount(() => setTimeout(() => (preloader = false), 20))
- {#if fullFilteredTemplates.length} + {#if preloader && fullFilteredTemplates.length > 10} +
+ +
+ {:else if fullFilteredTemplates.length}
{#each fullFilteredTemplates as template} chordUp({ showRef: ref, itemIndex: index, item })} --> + +
@@ -72,13 +76,22 @@
{#if item.language} -
a.code === item.language)?.name} class="actionButton" style="zoom: {1 / ratio};left: 0;right: unset;"> +
a.code === item.language)?.name || item.language} class="actionButton" style="zoom: {1 / ratio};left: 0;right: unset;">
{/if} + + {#if textTransform} +
+ + + +
+ {/if} + {#if item.bindings?.length}
diff --git a/src/frontend/components/edit/editors/OverlayEditor.svelte b/src/frontend/components/edit/editors/OverlayEditor.svelte index cfa6f468..607b31bc 100644 --- a/src/frontend/components/edit/editors/OverlayEditor.svelte +++ b/src/frontend/components/edit/editors/OverlayEditor.svelte @@ -15,6 +15,7 @@ import Editbox from "../editbox/Editbox.svelte" import { clone } from "../../helpers/array" import { onDestroy } from "svelte" + import { send } from "../../../utils/request" const update = () => (Slide = clone($overlays[currentId])) $: currentId = $activeEdit.id! @@ -58,7 +59,7 @@ let override = "overlay_items#" + $activeEdit.id + "indexes#" + active.join(",") history({ id: "UPDATE", newData: { key: "items", indexes: active, subkey: "style", data: values }, oldData: { id: $activeEdit.id }, location: { page: "edit", id: "overlay_items", override } }) - window.api.send(OUTPUT, { channel: "OVERLAY", data: $overlays }) + send(OUTPUT, ["OVERLAY"], $overlays) } // ZOOM @@ -113,7 +114,7 @@ {#if zoomOpened} -
+
diff --git a/src/frontend/components/edit/editors/SlideEditor.svelte b/src/frontend/components/edit/editors/SlideEditor.svelte index 4cb4001d..9778acac 100644 --- a/src/frontend/components/edit/editors/SlideEditor.svelte +++ b/src/frontend/components/edit/editors/SlideEditor.svelte @@ -299,7 +299,7 @@ {/if}
- {#if !$focusMode} + {#if !$focusMode && Slide}
{#if chordsMode} @@ -331,7 +331,7 @@ {#if zoomOpened} -
+
diff --git a/src/frontend/components/edit/editors/TemplateEditor.svelte b/src/frontend/components/edit/editors/TemplateEditor.svelte index 35aebbc3..fc9bcc5c 100644 --- a/src/frontend/components/edit/editors/TemplateEditor.svelte +++ b/src/frontend/components/edit/editors/TemplateEditor.svelte @@ -98,7 +98,7 @@ {#if zoomOpened} -
+
diff --git a/src/frontend/components/edit/scripts/chords.ts b/src/frontend/components/edit/scripts/chords.ts index 7b26f9a7..dabf4b25 100644 --- a/src/frontend/components/edit/scripts/chords.ts +++ b/src/frontend/components/edit/scripts/chords.ts @@ -160,6 +160,33 @@ export function loadChords(item: Item) { // get a list of unique chords used in a slide export function getUsedChords(slide: Slide) { + if (!slide?.items?.length) return [] let itemChords = slide.items.reduce((value: string[], item) => (value = [...value, ...loadChords(item)]), []) return [...new Set(itemChords)].sort((a, b) => a?.localeCompare(b)) } + +// IMPORT CHORD TEXT LINES + +export function isChordLine(text: string) { + const words = text.trim().split(/\s+/) + return words.every((word) => /^[A-G][#b]?m?\d?(7|9|13)?$/.test(word)) + // text.trim().match(/^[A-G][#b]?m?\d?7?\s+.*$/) +} + +export function parseChordLine(text: string) { + const chords: Chords[] = [] + let chordStart = -1 + + for (let i = 0; i <= text.length; i++) { + const char = text[i] + + if (char !== " " && chordStart === -1) { + chordStart = i + } else if ((char === " " || i === text.length) && chordStart !== -1) { + chords.push({ id: uid(5), key: text.slice(chordStart, i), pos: chordStart }) + chordStart = -1 + } + } + + return chords +} diff --git a/src/frontend/components/edit/scripts/itemClipboard.ts b/src/frontend/components/edit/scripts/itemClipboard.ts index f1d0ba15..4b80e54a 100644 --- a/src/frontend/components/edit/scripts/itemClipboard.ts +++ b/src/frontend/components/edit/scripts/itemClipboard.ts @@ -27,7 +27,7 @@ export function getBoxStyle(item: Item): StyleClipboard { const extraKeyValues: string[] = getSpecialBoxValues(item) - let itemKeys = getItemKeys() + let itemKeys = getItemKeys(true) let newStyles: any = getStyles(style) // remove any item keys (used for other items than textbox) @@ -80,7 +80,7 @@ export function getFilterStyle(): StyleClipboard { // PASTE // export function setBoxStyle(style: StyleClipboard, slides: any, type: ItemType) { - const itemKeys = getItemKeys() + const itemKeys = getItemKeys(true) slides.forEach(updateSlideStyle) @@ -247,7 +247,8 @@ export function setFilterStyle(style: StyleClipboard, indexes: number[]) { ///// -export function getItemKeys() { +const itemAndBoxKeys = ["background-color"] +export function getItemKeys(isBox: boolean = false) { // replace just item style or just box style if not textbox let itemKeys: string[] = [] @@ -256,6 +257,7 @@ export function getItemKeys() { // WIP transform not working with this }) + if (isBox) itemKeys = itemKeys.filter((a) => !itemAndBoxKeys.includes(a)) return itemKeys } diff --git a/src/frontend/components/edit/scripts/itemHelpers.ts b/src/frontend/components/edit/scripts/itemHelpers.ts index 55c9e0ef..96d6c604 100644 --- a/src/frontend/components/edit/scripts/itemHelpers.ts +++ b/src/frontend/components/edit/scripts/itemHelpers.ts @@ -77,7 +77,7 @@ export function getEditItems(onlyActive: boolean = false) { let selectedItems: number[] = active.items let editSlide = clone(getEditSlide()) - if (!editSlide?.items) return [] + if (!Array.isArray(editSlide?.items)) return [] let editItems = editSlide.items if (onlyActive) editItems = editItems.filter((_, i) => selectedItems.includes(i)) @@ -88,6 +88,8 @@ export function getEditItems(onlyActive: boolean = false) { // rearrange export function rearrangeItems(type: string, startIndex: number = get(activeEdit).items[0]) { let items = getEditItems() + if (!items?.length) return + let currentItem = items.splice(startIndex, 1)[0] if (type === "forward") startIndex = Math.min(startIndex + 1, items.length) @@ -96,6 +98,7 @@ export function rearrangeItems(type: string, startIndex: number = get(activeEdit else if (type === "to_back") startIndex = 0 items = [...items.slice(0, startIndex), currentItem, ...items.slice(startIndex)] + if (!items?.length) return if (!get(activeEdit).id) { let ref = _show().layouts("active").ref()[0] diff --git a/src/frontend/components/edit/tools/BoxStyle.svelte b/src/frontend/components/edit/tools/BoxStyle.svelte index 662a2423..da067c0f 100644 --- a/src/frontend/components/edit/tools/BoxStyle.svelte +++ b/src/frontend/components/edit/tools/BoxStyle.svelte @@ -146,6 +146,9 @@ box.edit.chords[1].hidden = !item?.chords?.enabled box.edit.chords[2].hidden = !item?.chords?.enabled } + $: if (id === "slide_tracker" && box?.edit?.default?.[2]) { + box.edit.default[2].hidden = item?.tracker?.type !== "group" + } $: if (id === "timer" && box?.edit?.font) box.edit.font[3].value = item?.auto ?? true @@ -522,6 +525,7 @@ {#if loaded} + {#key box} {/key} diff --git a/src/frontend/components/edit/tools/EditValues.svelte b/src/frontend/components/edit/tools/EditValues.svelte index 3e207ec7..4c1dbc9c 100644 --- a/src/frontend/components/edit/tools/EditValues.svelte +++ b/src/frontend/components/edit/tools/EditValues.svelte @@ -588,6 +588,11 @@ min-width: 50% !important; } + div :global(.dropdown) { + width: 160%; + right: 0; + } + p { width: 100%; /* width: 75%; */ diff --git a/src/frontend/components/edit/values/boxes.ts b/src/frontend/components/edit/values/boxes.ts index 99aaa478..69052154 100644 --- a/src/frontend/components/edit/values/boxes.ts +++ b/src/frontend/components/edit/values/boxes.ts @@ -55,6 +55,8 @@ export let trackerEdits = [ id: "tracker.accent", value: "#F0008C", }, + { name: "edit.sub_indexes", input: "checkbox", id: "tracker.childProgress", value: false }, + { name: "edit.one_letter", input: "checkbox", id: "tracker.oneLetter", value: false }, ] export const boxes: Box = { diff --git a/src/frontend/components/guide/guideSteps.ts b/src/frontend/components/guide/guideSteps.ts index 28de7aab..9ced6e28 100644 --- a/src/frontend/components/guide/guideSteps.ts +++ b/src/frontend/components/guide/guideSteps.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import { activeDrawerTab, activePage, activePopup, activeProject, activeShow, dictionary, drawer, outputCache, projects, projectView, showRecentlyUsedProjects, shows, showsCache } from "../../stores" +import { activeDrawerTab, activePage, activePopup, activeProject, activeShow, dictionary, drawer, focusMode, outputCache, projects, projectView, showRecentlyUsedProjects, shows, showsCache } from "../../stores" import { DEFAULT_DRAWER_HEIGHT } from "../../utils/common" import { createDefaultShow } from "../../utils/createData" import { loadShows } from "../helpers/setShow" @@ -15,6 +15,7 @@ export const guideSteps = [ pre: () => { activePage.set("show") activePopup.set(null) + focusMode.set(false) showRecentlyUsedProjects.set(false) if (get(shows).default) loadShows(["default"]) }, diff --git a/src/frontend/components/helpers/clipboard.ts b/src/frontend/components/helpers/clipboard.ts index f53e8dbd..7c17028f 100644 --- a/src/frontend/components/helpers/clipboard.ts +++ b/src/frontend/components/helpers/clipboard.ts @@ -79,7 +79,7 @@ export function copy({ id, data }: any = {}, getData: boolean = true) { } // pasting text in editbox is it's own function -export function paste(clip: any = null, extraData: any = {}) { +export function paste(clip: any = null, extraData: any = {}, customElem: any = null) { if (!clip) clip = get(clipboard) let activeElem: any = document.activeElement @@ -89,6 +89,7 @@ export function paste(clip: any = null, extraData: any = {}) { // edit item has its own paste function if (activeElem?.closest(".editItem")) return + if (customElem?.closest(".edit")) activeElem = customElem if (activeElem?.closest(".edit")) { pasteText(activeElem) return diff --git a/src/frontend/components/helpers/media.ts b/src/frontend/components/helpers/media.ts index 79fcba2a..1ed6d880 100644 --- a/src/frontend/components/helpers/media.ts +++ b/src/frontend/components/helpers/media.ts @@ -66,7 +66,7 @@ export function joinPath(path: string[]): string { // fix for media files with special characters in file name not playing export function encodeFilePath(path: string): string { // already encoded - if (path.match(/%\d+/g) || path.includes("http")) return path + if (path.match(/%\d+/g) || path.includes("http") || path.includes("data:")) return path let splittedPath = splitPath(path) let fileName = splittedPath.pop() || "" @@ -136,6 +136,8 @@ export function checkMedia(src: string): Promise { } export async function getMediaInfo(path: string) { + if (path.includes("http") || path.includes("data:")) return {} + const cachedInfo = get(media)[path]?.info if (cachedInfo?.codecs?.length) return cachedInfo @@ -207,7 +209,7 @@ export async function loadThumbnail(input: string, size: number) { if (!input) return "" // online media (e.g. Pixabay/Unsplash) - if (input.includes("http")) return input + if (input.includes("http") || input.includes("data:")) return input // already encoded (this could cause an infinite loop) if (input.includes("freeshow-cache")) return input @@ -226,7 +228,7 @@ export function getThumbnailPath(input: string, size: number) { if (!input) return "" // online media (e.g. Pixabay/Unsplash) - if (input.includes("http")) return input + if (input.includes("http") || input.includes("data:")) return input // already encoded if (input.includes("freeshow-cache")) return input @@ -270,10 +272,10 @@ function getThumbnailId(data: any) { // convert path to base64 export async function getBase64Path(path: string, size: number = mediaSize.big) { - if (!path) return "" + if (!path || !mediaExtensions.includes(getExtension(path))) return "" // online media (e.g. Pixabay/Unsplash) - if (path.includes("http")) return path + if (path.includes("http") || path.includes("data:")) return path let thumbnailPath = await loadThumbnail(path, size) if (!thumbnailPath) return "" diff --git a/src/frontend/components/helpers/output.ts b/src/frontend/components/helpers/output.ts index a2ef6633..f8f62d90 100644 --- a/src/frontend/components/helpers/output.ts +++ b/src/frontend/components/helpers/output.ts @@ -13,6 +13,7 @@ import { lockedOverlays, outputDisplay, outputs, + outputSlideCache, overlays, playingVideos, serverData, @@ -63,6 +64,8 @@ export function setOutput(key: string, data: any, toggle: boolean = false, outpu let firstOutputWithBackground = allOutputs.findIndex((id) => (get(styles)[get(outputs)[id]?.style || ""]?.layers || ["background"]).includes("background")) firstOutputWithBackground = Math.max(0, firstOutputWithBackground) + // reset slide cache (after update) + if (key === "slide" && data) setTimeout(() => outputSlideCache.set({}), 50) // append show usage if not already outputted if (key === "slide" && data?.id && get(outputs)[outs[0]]?.out?.slide?.id !== data?.id) appendShowUsage(data.id) @@ -128,6 +131,8 @@ function changeOutputBackground(data, { output, id, mute }) { setTimeout(() => { // update stage background if any sendBackgroundToStage(id) + // send thumbnail to controller + // sendBackgroundToController(id) }, 100) } diff --git a/src/frontend/components/helpers/show.ts b/src/frontend/components/helpers/show.ts index 1245dfe9..b98c9b10 100644 --- a/src/frontend/components/helpers/show.ts +++ b/src/frontend/components/helpers/show.ts @@ -67,7 +67,7 @@ export function getGroupName({ show, showId }: { show: Show; showId: string }, s let name = groupName if (name === null) return name // child slide - if (!name.length) name = "—" + if (!name?.length) name = "—" if (!get(groupNumbers)) return name // sort by order when just one layout diff --git a/src/frontend/components/helpers/showActions.ts b/src/frontend/components/helpers/showActions.ts index f343820f..9408cec4 100644 --- a/src/frontend/components/helpers/showActions.ts +++ b/src/frontend/components/helpers/showActions.ts @@ -20,6 +20,7 @@ import { media, outLocked, outputs, + outputSlideCache, overlays, projects, showsCache, @@ -157,6 +158,10 @@ export function nextSlide(e: any, start: boolean = false, end: boolean = false, let outputId = customOutputId || getActiveOutputs()[0] let currentOutput: any = get(outputs)[outputId] || {} let slide: null | OutSlide = currentOutput.out?.slide || null + if (!slide) { + let cachedSlide: null | OutSlide = get(outputSlideCache)[outputId] || null + if (cachedSlide && cachedSlide?.id === get(activeShow)?.id && cachedSlide?.layout === get(showsCache)[get(activeShow)?.id || ""]?.settings?.activeLayout) slide = cachedSlide + } // PPT if (slide?.type === "ppt") { @@ -336,6 +341,10 @@ export function previousSlide(e: any, customOutputId?: string) { let outputId = customOutputId || getActiveOutputs()[0] let currentOutput: any = get(outputs)[outputId] || {} let slide: null | OutSlide = currentOutput.out?.slide || null + if (!slide) { + let cachedSlide: null | OutSlide = get(outputSlideCache)[outputId] || null + if (cachedSlide && cachedSlide?.id === get(activeShow)?.id && cachedSlide?.layout === get(showsCache)[get(activeShow)?.id || ""]?.settings?.activeLayout) slide = cachedSlide + } // PPT if (slide?.type === "ppt") { diff --git a/src/frontend/components/helpers/slideRecording.ts b/src/frontend/components/helpers/slideRecording.ts index bb29a62e..6dfabfbb 100644 --- a/src/frontend/components/helpers/slideRecording.ts +++ b/src/frontend/components/helpers/slideRecording.ts @@ -1,9 +1,9 @@ import { get } from "svelte/store" -import { activeShow, activeSlideRecording, outLocked, outputs, playingAudio } from "../../stores" import type { Recording } from "../../../types/Show" -import { _show } from "./shows" -import { updateOut } from "./showActions" +import { activeShow, activeSlideRecording, outLocked, outputs, playingAudio } from "../../stores" import { getActiveOutputs, setOutput } from "./output" +import { updateOut } from "./showActions" +import { _show } from "./shows" ///// SLIDE RECORDING ///// @@ -20,6 +20,21 @@ export function playRecording(recording: Recording, { showId, layoutId }, startI if (audio || audioListener) { let showMedia = _show(showId).get("media") audio = showMedia[audio]?.path + + // WIP pause / change audio duration!! + + // reset playing audio + if (startIndex === 0) { + let playing = get(playingAudio)[audio]?.audio + if (playing) { + playingAudio.update((a) => { + a[audio].audio.currentTime = 0 + a[audio].paused = false + return a + }) + } + } + startAudioListener(audio) } diff --git a/src/frontend/components/inputs/NumberInput.svelte b/src/frontend/components/inputs/NumberInput.svelte index 9d77e817..43bf087a 100644 --- a/src/frontend/components/inputs/NumberInput.svelte +++ b/src/frontend/components/inputs/NumberInput.svelte @@ -87,15 +87,17 @@ {#if buttons} - {/if} + + {#if buttons} - {/if} diff --git a/src/frontend/components/inputs/ShowButton.svelte b/src/frontend/components/inputs/ShowButton.svelte index a2532aa5..2dbf037d 100644 --- a/src/frontend/components/inputs/ShowButton.svelte +++ b/src/frontend/components/inputs/ShowButton.svelte @@ -71,8 +71,13 @@ let newShow: any = { id, type } if ($focusMode) { - activeFocus.set({ id, index: pos ?? undefined }) - return + let inProject = $projects[$activeProject || ""]?.shows.find((p) => p.id === id) + if (inProject) { + activeFocus.set({ id, index: pos ?? undefined }) + return + } else { + focusMode.set(false) + } } if (pos !== null) { diff --git a/src/frontend/components/main/Tabs.svelte b/src/frontend/components/main/Tabs.svelte index 6acb7615..f54eb6d9 100644 --- a/src/frontend/components/main/Tabs.svelte +++ b/src/frontend/components/main/Tabs.svelte @@ -25,7 +25,7 @@
{#each Object.entries(tabs) as [id, tab]} {#if tab.remove !== true && (!tab.overflow || !overflowHidden)} - diff --git a/src/frontend/components/main/popups/localization/Translate.svelte b/src/frontend/components/main/popups/localization/Translate.svelte index df308136..67539c80 100644 --- a/src/frontend/components/main/popups/localization/Translate.svelte +++ b/src/frontend/components/main/popups/localization/Translate.svelte @@ -1,13 +1,14 @@ -
+

a.id === $special.translationLanguage)?.name || "—"} on:click={updateLanguage} /> @@ -76,8 +83,19 @@
{#if translatedLangs.length} +
+ {#each translatedLangs as lang} + +

{isoLanguages.find((a) => a.code === lang)?.name || lang}

+ +
+ {/each} +
+ - @@ -86,13 +104,20 @@
diff --git a/src/frontend/components/main/popups/localization/translation.ts b/src/frontend/components/main/popups/localization/translation.ts index a26f7005..25d1d3b1 100644 --- a/src/frontend/components/main/popups/localization/translation.ts +++ b/src/frontend/components/main/popups/localization/translation.ts @@ -96,14 +96,14 @@ export async function translateShow(showId: string, languageCode: string) { } } -export function removeAllTranslationsFromShow(showId: string) { +export function removeTranslationFromShow(showId: string, langId: string = "") { let show = get(showsCache)[showId] let slides = clone(show.slides) let changed = false Object.keys(slides).forEach((slideId) => { let previousSize = slides[slideId].items.length - slides[slideId].items = slides[slideId].items.filter((item) => !item.language) + slides[slideId].items = slides[slideId].items.filter((item) => !item.language || (langId ? item.language !== langId : false)) if (slides[slideId].items.length < previousSize) changed = true }) diff --git a/src/frontend/components/output/clear.ts b/src/frontend/components/output/clear.ts index 755f728f..bb6b0926 100644 --- a/src/frontend/components/output/clear.ts +++ b/src/frontend/components/output/clear.ts @@ -1,15 +1,37 @@ import { get } from "svelte/store" -import { activeEdit, activePopup, contextActive, customMessageCredits, lockedOverlays, outLocked, outputCache, outputs, overlays, playingAudio, playingMetronome, selected, slideTimers, topContextActive, videosData, videosTime } from "../../stores" +import { + activeEdit, + activePopup, + contextActive, + customMessageCredits, + lockedOverlays, + outLocked, + outputCache, + outputs, + outputSlideCache, + overlays, + playingAudio, + playingMetronome, + selected, + slideTimers, + topContextActive, + videosData, + videosTime, +} from "../../stores" import { clearPlayingVideo, getActiveOutputs, isOutCleared, setOutput } from "../helpers/output" import { clearAudio } from "../helpers/audio" import { clone } from "../helpers/array" import { customActionActivation } from "../actions/actions" import { stopSlideRecording } from "../helpers/slideRecording" +import { _show } from "../helpers/shows" export function clearAll(button: boolean = false) { if (get(outLocked)) return if (!button && (get(activePopup) || get(selected).id || get(activeEdit).items.length || get(contextActive) || get(topContextActive))) return + // reset slide cache on Escape + outputSlideCache.set({}) + let audioCleared = !Object.keys(get(playingAudio)).length && !get(playingMetronome) let allCleared = isOutCleared(null) && audioCleared if (allCleared) return @@ -17,7 +39,7 @@ export function clearAll(button: boolean = false) { storeCache() clearBackground() - clearSlide() + clearSlide(true) clearOverlays() clearAudio() clearTimers() @@ -79,7 +101,29 @@ export function clearBackground(outputId: string = "") { customActionActivation("background_cleared") } -export function clearSlide() { +export function clearSlide(clearAll: boolean = false) { + if (!clearAll) { + // store position + let slideCache: any = {} + let outputIds: string[] = getActiveOutputs() + outputIds.forEach((outputId) => { + let slide: any = get(outputs)[outputId]?.out?.slide || {} + if (!slide.id) return + + // only store if not last slide + let layoutRef = _show(slide.id).layouts([slide.layout]).ref()[0] || [] + if (slide.index >= layoutRef.length - 1) return + + slideCache[outputId] = slide + }) + if (Object.keys(slideCache).length) { + outputSlideCache.set(clone(slideCache)) + } + + // slide gets outlined if not blurred + ;(document.activeElement as any)?.blur() + } + setOutput("slide", null) stopSlideRecording() customActionActivation("slide_cleared") diff --git a/src/frontend/components/output/layers/BackgroundMedia.svelte b/src/frontend/components/output/layers/BackgroundMedia.svelte index 6ef0d4eb..f0673e8c 100644 --- a/src/frontend/components/output/layers/BackgroundMedia.svelte +++ b/src/frontend/components/output/layers/BackgroundMedia.svelte @@ -6,15 +6,15 @@ import type { OutBackground, Transition } from "../../../../types/Show" import { allOutputs, audioChannels, outputs, playingVideos, special, videosData, videosTime } from "../../../stores" import { destroy, receive, send } from "../../../utils/request" + import BmdStream from "../../drawer/live/BMDStream.svelte" + import NdiStream from "../../drawer/live/NDIStream.svelte" import { analyseAudio, getAnalyser } from "../../helpers/audio" import { getMediaStyle } from "../../helpers/media" import Player from "../../system/Player.svelte" import Camera from "../Camera.svelte" + import OutputTransition from "../transitions/OutputTransition.svelte" import Window from "../Window.svelte" import Media from "./Media.svelte" - import OutputTransition from "../transitions/OutputTransition.svelte" - import NdiStream from "../../drawer/live/NDIStream.svelte" - import BmdStream from "../../drawer/live/BMDStream.svelte" export let outputId: string = "" @@ -221,7 +221,7 @@ } - + (fadingOut = true)}> {#if type === "media"} {:else if type === "screen"} diff --git a/src/frontend/components/output/layers/SlideContent.svelte b/src/frontend/components/output/layers/SlideContent.svelte index 9e747ca8..df2b75fd 100644 --- a/src/frontend/components/output/layers/SlideContent.svelte +++ b/src/frontend/components/output/layers/SlideContent.svelte @@ -26,6 +26,8 @@ let current: any = {} let show: boolean = false + $: filteredItems = currentItems.filter((a) => !a.bindings?.length || a.bindings.includes(outputId)) + $: if (currentSlide.items !== undefined || outSlide) updateItems() let timeout: any = null @@ -86,7 +88,7 @@ {#key show} - {#each currentItems as item} + {#each filteredItems as item} {#if show} - {#if !customItem.bindings?.length || customItem.bindings.includes(outputId)} - - {/if} + {/if} {/each} diff --git a/src/frontend/components/output/preview/AudioMeter.svelte b/src/frontend/components/output/preview/AudioMeter.svelte index c84579e7..9d1d854a 100644 --- a/src/frontend/components/output/preview/AudioMeter.svelte +++ b/src/frontend/components/output/preview/AudioMeter.svelte @@ -53,7 +53,7 @@ // dB = max + min - dB let percentage = (dB - min) / (max - min) percentage = 1 - percentage - console.log(dB, percentage, transformRange(percentage)) + // console.log(dB, percentage, transformRange(percentage)) return transformRange(percentage) * 100 // const percentage = 1 - transformRange(percentage) diff --git a/src/frontend/components/output/transitions/OutputTransition.svelte b/src/frontend/components/output/transitions/OutputTransition.svelte index 628c438e..b32d9df6 100644 --- a/src/frontend/components/output/transitions/OutputTransition.svelte +++ b/src/frontend/components/output/transitions/OutputTransition.svelte @@ -15,7 +15,7 @@
{:else} -
+
{/if} diff --git a/src/frontend/components/output/transitions/SlideItemTransition.svelte b/src/frontend/components/output/transitions/SlideItemTransition.svelte index 9d09fc97..33cbd632 100644 --- a/src/frontend/components/output/transitions/SlideItemTransition.svelte +++ b/src/frontend/components/output/transitions/SlideItemTransition.svelte @@ -53,10 +53,10 @@ // auto size delay if (!outDelay) { let customTemplate = getStyleTemplate(outSlide, currentStyle) - if (!Object.keys(customTemplate).length && outSlide?.id === "temp") customTemplate = $templates[$scriptureSettings.template] + if (!Object.keys(customTemplate).length && outSlide?.id === "temp") customTemplate = $templates[$scriptureSettings.template] || {} // wait output style/scripture template auto size - if (Object.keys(customTemplate).length ? slideHasAutoSizeItem(customTemplate) : item.auto) outDelay = 200 + if (Object.keys(customTemplate).length ? slideHasAutoSizeItem(customTemplate) : item.auto) outDelay = 400 if (!inDelay) inDelay = outDelay * 0.98 } diff --git a/src/frontend/components/settings/SettingsTabs.svelte b/src/frontend/components/settings/SettingsTabs.svelte index 8982ca18..f1d0dcb5 100644 --- a/src/frontend/components/settings/SettingsTabs.svelte +++ b/src/frontend/components/settings/SettingsTabs.svelte @@ -1,6 +1,6 @@
- {#if recordingData} + {#if settingsOpened && recordingData} +
+ +

+
+ setRecordingKey("useDurationTime", isChecked(e))} /> +
+
+
+ {:else if recordingData} {#if recordingPlaying} + {#if recordingPlaying} + + {:else} + {#if settingsOpened} + + {/if} + + + {/if}
{/if} diff --git a/src/frontend/components/slide/Layouts.svelte b/src/frontend/components/slide/Layouts.svelte index c5a54e6b..a11f6fa0 100644 --- a/src/frontend/components/slide/Layouts.svelte +++ b/src/frontend/components/slide/Layouts.svelte @@ -205,7 +205,7 @@ {#if zoomOpened} -
+
diff --git a/src/frontend/components/slide/Slide.svelte b/src/frontend/components/slide/Slide.svelte index cf1abc06..edc92735 100644 --- a/src/frontend/components/slide/Slide.svelte +++ b/src/frontend/components/slide/Slide.svelte @@ -30,7 +30,7 @@ import Editbox from "../edit/editbox/Editbox.svelte" import { getItemText } from "../edit/scripts/textStyle" import { clone } from "../helpers/array" - import { getContrast } from "../helpers/color" + import { getContrast, hexToRgb, splitRgb } from "../helpers/color" import { checkMedia, getFileName, getMediaStyle, getThumbnailPath, loadThumbnail, mediaSize, splitPath } from "../helpers/media" import { getActiveOutputs, getResolution } from "../helpers/output" import { getGroupName } from "../helpers/show" @@ -64,8 +64,8 @@ let ghostBackground: Media | null = null let bgIndex: number = -1 let isFirstGhost: boolean = false - // don't show ghost backgrounds if more than 25 slides (because of loading!) - $: if (!background && layoutSlides.length < 25) { + // don't show ghost backgrounds if more than 50 slides (because of loading!) + $: if (!background && layoutSlides.length < 50) { ghostBackground = null layoutSlides.forEach((a, i) => { if (i > index) return @@ -285,6 +285,15 @@ if (layoutSlide["backdrop-filter"]) slideFilter += "backdrop-filter: " + layoutSlide["backdrop-filter"] + ";" } + function getOutputColor(color: string) { + if (output?.cached) { + let rgb = color.includes("rgb") ? splitRgb(color) : hexToRgb(color) + return "rgb(" + [rgb.r, rgb.g, rgb.b].join(" ") + " / 0.5);" + } + + return color + } + $: if ($refreshListBoxes >= 0) { setTimeout(() => { refreshListBoxes.set(-1) @@ -295,7 +304,7 @@ $: itemsList = clone(slide.items) || [] -
+
{#if $fullColors}
@@ -337,6 +346,7 @@ disableStyle={viewMode === "lyrics" && !noQuickEdit} relative={viewMode === "lyrics" && !noQuickEdit} > + {#if !altKeyPressed && bg && (viewMode !== "lyrics" || noQuickEdit)} {#key $refreshSlideThumbnails}
@@ -345,6 +355,19 @@
{/key} {/if} + + + {#if !altKeyPressed && layoutSlide.overlays?.length && (viewMode !== "lyrics" || noQuickEdit)} + {#each layoutSlide.overlays as id} + {#if $overlays[id]?.placeUnderSlide === true} + {#each $overlays[id].items as item} + + {/each} + {/if} + {/each} + {/if} + + {#if slide.items} {#each itemsList as item, i} {#if item && (viewMode !== "lyrics" || item.type === undefined || ["text", "events", "list"].includes(item.type))} @@ -367,9 +390,11 @@ {/if} {/each} {/if} + + {#if !altKeyPressed && layoutSlide.overlays?.length && (viewMode !== "lyrics" || noQuickEdit)} {#each layoutSlide.overlays as id} - {#if $overlays[id]} + {#if $overlays[id] && !$overlays[id]?.placeUnderSlide} {#each $overlays[id].items as item} {/each} @@ -382,7 +407,8 @@ {#if output?.maxLines}
-
+ +
{/if} @@ -396,7 +422,7 @@ {/if} {#if output?.maxLines}
-
+
{/if} {#if slide.notes && icons}

{slide.notes}

{/if} @@ -586,7 +612,7 @@ .label .text { width: 100%; - margin: 0 20px; + margin: 0 15px; text-align: center; overflow-x: hidden; text-overflow: ellipsis; diff --git a/src/frontend/components/slide/views/SlideProgress.svelte b/src/frontend/components/slide/views/SlideProgress.svelte index 5035acc7..1ebc436f 100644 --- a/src/frontend/components/slide/views/SlideProgress.svelte +++ b/src/frontend/components/slide/views/SlideProgress.svelte @@ -32,7 +32,9 @@ group = $groups[slide.globalGroup].default ? $dictionary.groups?.[$groups[slide.globalGroup].name] : $groups[slide.globalGroup].name } + if (tracker.oneLetter) group = group[0].toUpperCase() let name = getGroupName({ show: _show(currentShowId).get(), showId: currentShowId }, ref.id, group, ref.layoutIndex)?.replace(/ *\([^)]*\) */g, "") + if (tracker.oneLetter) name = name?.replace(" ", "") return { name: name || "—", index: ref.layoutIndex, child: a.type === "child" ? (currentLayoutRef[ref.layoutIndex]?.children || []).findIndex((id) => id === a.id) + 1 : 0 } }) @@ -51,12 +53,11 @@
{#each layoutGroups as group} {#if !group.child && !group.hide} - - + {@const activeGroup = layoutGroups.find((a, i) => a.index === group.index && i === currentShowSlide)} + {@const nextSlide = layoutGroups.find((a, i) => a.index === group.index && i === currentShowSlide + 1)}
i === currentShowSlide)?.index}> - {group.name} + {group.name}{#if tracker.childProgress && (activeGroup?.child || nextSlide?.child)}.{activeGroup.child + 1}{/if}
{/if} {/each} diff --git a/src/frontend/components/stage/StageShow.svelte b/src/frontend/components/stage/StageShow.svelte index 70941478..ca3b18e4 100644 --- a/src/frontend/components/stage/StageShow.svelte +++ b/src/frontend/components/stage/StageShow.svelte @@ -1,18 +1,21 @@ -
-
+ + +
+ +
= 1} bind:offsetWidth={width} bind:offsetHeight={height}> {#if stageShowId} - + = 1}> {#if edit} @@ -98,12 +137,49 @@ {/if}
+ + -
+ + {#if stageShowId} +
+
+ +
+ + + + {#if zoomOpened} +
+ + + +
+ {/if} +
+
+ {/if} +
diff --git a/src/frontend/components/stage/StageSlide.svelte b/src/frontend/components/stage/StageSlide.svelte index 40310648..c3e059b8 100644 --- a/src/frontend/components/stage/StageSlide.svelte +++ b/src/frontend/components/stage/StageSlide.svelte @@ -93,7 +93,7 @@ .label .text { width: 100%; - margin: 0 20px; + margin: 0 15px; text-align: center; overflow-x: hidden; text-overflow: ellipsis; diff --git a/src/frontend/components/stage/Stagebox.svelte b/src/frontend/components/stage/Stagebox.svelte index fcff2b57..476371d7 100644 --- a/src/frontend/components/stage/Stagebox.svelte +++ b/src/frontend/components/stage/Stagebox.svelte @@ -1,5 +1,5 @@