From 6301a520bf8015433cd7f94ca67e7b25d614eadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Vassb=C3=B8?= Date: Fri, 9 Aug 2024 15:22:48 +0200 Subject: [PATCH] v1.2.4 (#732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Option to not split scripture verse reference - Fixed freeze when renaming variable to nothing - Fixed error when closing stage outputs just after creating - Fixed stage output preview visual bug - Fixed dropdown click visual bug - Kiosk setting auto revert timer - Unsplash API changes * ✔ Fixed media ghost thumbnails not always loading - EasyWorship import CCLI - Improved screen recorder * 🚩 Updated Norwegian * 🔗 Updated links - Updated languages * 📺 Option to hide output from preview - Improvements to scripture loading * ✔ Special case fixes (like disabling kiosk mode) * ✔ Missing background search will search deeper in nested folders - Fixed media files with special characters not working - Fixed slide not being able to play if background path did not exist - Changed toggle panels from Q to T - Changed app closing * ✔ Version update --- README.md | 2 +- package.json | 2 +- public/lang/en.json | 5 + public/lang/hu.json | 32 +++++- public/lang/it.json | 48 ++++++++- public/lang/no.json | 32 +++++- .../capture/helpers/CaptureLifecycle.ts | 6 +- src/electron/data/export.ts | 2 +- src/electron/index.ts | 21 ++-- .../output/helpers/OutputLifecycle.ts | 7 +- src/electron/utils/files.ts | 98 ++++++++++++++----- src/electron/utils/responses.ts | 7 ++ .../components/context/ContextItem.svelte | 6 +- .../components/context/contextMenus.ts | 3 +- src/frontend/components/context/menuClick.ts | 26 ++++- .../components/drawer/audio/Audio.svelte | 12 +-- .../components/drawer/audio/AudioFile.svelte | 4 +- .../components/drawer/bible/Scripture.svelte | 13 ++- .../components/drawer/bible/scripture.ts | 14 +-- .../components/drawer/info/Info.svelte | 4 +- .../components/drawer/info/MediaInfo.svelte | 14 +-- .../components/drawer/info/PlayerInfo.svelte | 11 +++ .../drawer/info/ScriptureInfo.svelte | 8 ++ .../components/drawer/live/LiveInfo.svelte | 6 +- .../components/drawer/live/recorder.ts | 4 + .../components/drawer/media/Image.svelte | 3 +- .../components/drawer/media/Media.svelte | 4 +- .../components/drawer/media/MediaCard.svelte | 9 +- .../drawer/media/MediaLoader.svelte | 8 +- .../components/drawer/media/unsplash.ts | 5 +- .../components/drawer/pages/Variables.svelte | 4 +- .../edit/editbox/EditboxOther.svelte | 4 +- .../components/edit/tools/EditValues.svelte | 2 +- src/frontend/components/helpers/audio.ts | 32 +++--- .../components/helpers/dropActions.ts | 7 +- src/frontend/components/helpers/media.ts | 14 ++- src/frontend/components/helpers/output.ts | 2 +- .../components/helpers/showActions.ts | 18 +++- .../components/inputs/Dropdown.svelte | 5 +- .../components/inputs/FontDropdown.svelte | 5 +- src/frontend/components/main/Toast.svelte | 4 +- .../components/main/popups/About.svelte | 2 +- .../components/main/popups/Shortcuts.svelte | 4 +- .../components/main/popups/Variable.svelte | 4 +- src/frontend/components/media/Image.svelte | 3 +- src/frontend/components/media/Video.svelte | 3 +- src/frontend/components/output/Output.svelte | 6 +- src/frontend/components/output/clear.ts | 3 +- .../output/preview/MultiOutputs.svelte | 2 +- .../components/output/tools/Audio.svelte | 10 +- .../output/tools/MediaControls.svelte | 5 +- .../components/settings/tabs/Other.svelte | 17 ++-- .../components/settings/tabs/Outputs.svelte | 22 ++++- src/frontend/components/show/Slides.svelte | 4 +- .../components/show/tools/Media.svelte | 10 +- src/frontend/components/slide/Slide.svelte | 13 ++- src/frontend/components/slide/Textbox.svelte | 8 +- src/frontend/components/stage/Stagebox.svelte | 2 +- .../components/stage/tools/SlideStyle.svelte | 12 ++- src/frontend/converters/easyworship.ts | 2 +- src/frontend/stores.ts | 1 + src/frontend/utils/listeners.ts | 5 + src/frontend/utils/receivers.ts | 22 ++++- src/frontend/utils/shortcuts.ts | 2 +- src/types/Output.ts | 1 + 65 files changed, 490 insertions(+), 186 deletions(-) diff --git a/README.md b/README.md index 53b2a54c..7d9794ae 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ FreeShow exists because the creator found that other simular programs was either ## Support Us -The only reason this program is free is because of the generous support from users. If you want to support us to keep this free, please head over to [ChurchApps](https://churchapps/partner) or [sponsor us on GitHub](/~https://github.com/sponsors/ChurchApps/). Thank you so much! +The only reason this program is free is because of the generous support from users. If you want to support us to keep this free, please head over to [ChurchApps](https://churchapps.org/partner) or [sponsor us on GitHub](/~https://github.com/sponsors/ChurchApps/). Thank you so much! ## Join the Community diff --git a/package.json b/package.json index fc7bed4a..643d4eb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.2.3", + "version": "1.2.4", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", diff --git a/public/lang/en.json b/public/lang/en.json index 4cdaa0f2..00b4de3e 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -410,6 +410,8 @@ "now": "now!", "no_video_id": "No video ID", "no_name": "No name", + "reverting_setting": "Reverting this change in {} seconds, enable again to make the change stay!", + "reverted": "Setting reverted! Only enable again if you did not have any problems.", "media_replaced": "Missing media file replaced with match.", "lyrics_undefined": "Could not find any lyrics!", "lyrics_copied": "Lyrics copied from ", @@ -571,6 +573,7 @@ "remove_layers": "Remove layers", "start_recording": "Start recording", "stop_recording": "Stop recording", + "export_recording": "Stop recording and export", "index_select_project": "Select project by index", "next_project_item": "Next project item", "previous_project_item": "Previous project item", @@ -683,6 +686,7 @@ "align_with_screen": "Align with screen", "toggle_output": "Toggle output", "move_to_front": "Move to front", + "hide_from_preview": "Hide from preview", "lock_to_output": "Lock to output", "place_under_slide": "Place under slide", "toggle_clock": "Toggle clock", @@ -1080,6 +1084,7 @@ "verses_on_individual_lines": "Verses on individual lines", "version": "Show version", "reference": "Show reference", + "split_reference": "Split reference", "combine_with_text": "Combine with text", "reference_at_bottom": "Move to bottom", "red_jesus": "Jesus words in red", diff --git a/public/lang/hu.json b/public/lang/hu.json index 7592524a..17147091 100644 --- a/public/lang/hu.json +++ b/public/lang/hu.json @@ -165,6 +165,7 @@ }, "audio": { "settings": "Hangbeállítások", + "playlist_settings": "Lejátszási lista beállítások", "mute_when_video_plays": "Némítás videó lejátszása alatt", "metronome": "Metronóm", "toggle_metronome": "Metronóm átváltása", @@ -252,7 +253,10 @@ "display_metadata": "Metaadatok megjelenítése", "meta_template": "Metaadat sablon", "text_divider": "Szövegelválasztó", - "message_template": "Üzenet sablon" + "message_template": "Üzenet sablon", + "tags": "Címkék", + "new_tag": "Új címke", + "clear_tag_filter": "Címkeszűrő törlése" }, "show_at": { "never": "Egyetlen dián sem", @@ -406,6 +410,8 @@ "now": "most!", "no_video_id": "Nincs videóazonosító", "no_name": "Nincs név", + "reverting_setting": "A módosítás visszavonása {} másodperc múlva. Engedélyezze újra, hogy a módosítás megmaradjon!", + "reverted": "Beállítás visszavonva! Csak akkor engedélyezze újra, ha nem volt semmilyen probléma.", "media_replaced": "Hiányzó médiafájl helyettesítve a megfelelővel.", "lyrics_undefined": "Nem található dalszöveg!", "lyrics_copied": "Dalszöveg másolva erről:", @@ -520,6 +526,7 @@ "format": "Formázás", "find_replace": "Szöveg keresése és cseréje", "cut_in_half": "Kettéválasztás", + "merge": "Összevonás", "find": "Keresés", "replace": "Csere", "case_sensitive": "Kis- és nagybetű érzékeny", @@ -547,6 +554,7 @@ "chord_type": "Típus", "chord_tension": "Színezőhang", "chord_bass": "Basszus", + "roman_keys": "Római kulcsok", "set_key": "Billentyű beállítása", "custom_key": "Egyéni érték beállítása", "select_chord": "Akkord kijelölése", @@ -565,6 +573,7 @@ "remove_layers": "Rétegek eltávolítása", "start_recording": "Felvétel indítása", "stop_recording": "Felvétel leállítása", + "export_recording": "Felvétel és exportálás leállítása ", "index_select_project": "Projekt kijelölése index alapján", "next_project_item": "Következő projektelem", "previous_project_item": "Előző projektelem", @@ -584,6 +593,8 @@ "start_playlist": "Lejátszási lista indítása", "playlist_next": "Következő szám a lejátszási listában", "start_metronome": "Metronóm indítása", + "name_start_timer": "Időzítő indítása név alapján", + "id_start_timer": "Időzítő indítása azonosító alapján", "start_slide_timers": "Időzítők indítása az aktív dián", "id_select_output_style": "Kimeneti stílus kijelölése azonosító alapján", "change_output_style": "Kimeneti stílus módosítása", @@ -602,7 +613,9 @@ "activate_timer_ending": "Aktiválás időzítő befejezésekor", "activate_scripture_start": "Aktiválás szentírás indításakor", "activate_slide_cleared": "Aktiválás dia törlésekor", - "activate_show_created": "Aktiválás műsor létrehozásakor" + "activate_background_cleared": "Aktiválás háttér törlésekor", + "activate_show_created": "Aktiválás műsor létrehozásakor", + "activate_audio_playlist_ended": "Aktiválás hanglejátszási lista végén" }, "animate": { "change": "Módosítás", @@ -626,6 +639,7 @@ "google_drive_api": "Google API szolgáltatási fiók kulcsa", "select_key": "Kulcsfájl importálása", "update_key": "Kulcsfájl frissítése", + "enable_custom_folder_id": "Egyedi mappaazonosító alkalmazása", "main_folder": "Fő mappa kézi beállítása", "media_folder": "Felhő médiamappa", "reconnect": "Újracsatlakozás", @@ -661,6 +675,7 @@ }, "context": { "enabledTabs": "Fülek átváltása", + "filterByTags": "Szűrés címkékre", "addToProject": "Hozzáadás a projekthez", "newCategory": "Új kategória", "changeIcon": "Ikon módosítása", @@ -905,6 +920,7 @@ "system_clock": "Rendszeróra", "video_time": "Videó idő", "video_countdown": "Videó visszaszámláló", + "first_active_timer": "Első aktív időzítő", "other": "Egyéb", "message": "Üzenet", "color": "Szín", @@ -975,7 +991,12 @@ "resolution": "Felbontás", "cropping": "Vágás", "frame_rate": "Képkockasebesség", + "device": "Eszköz", + "display_mode": "Megjelenítése mód", + "pixel_format": "Pixel forma", + "alpha_key": "Alfakulcs", "transparent": "Átlátszó", + "invisible_window": "Láthatatlan ablak", "video_extensions": "Videó kiterjesztések", "image_extensions": "Kép kiterjesztések", "add": "Hozzáadás", @@ -1062,6 +1083,7 @@ "verses_on_individual_lines": "Versszakok az egyes sorokban", "version": "Verzió megjelenítése", "reference": "Hivatkozás megjelenítése", + "split_reference": "Hivatkozás szétválasztása", "combine_with_text": "Kombinálás a szöveggel", "reference_at_bottom": "Mozgatás alulra", "red_jesus": "Jézus szavai pirossal", @@ -1153,7 +1175,11 @@ "slides": "Dia", "words": "Szó", "template": "Sablon", - "category": "Kategória" + "category": "Kategória", + "photoUrl": "Fotó URL", + "likes": "Kedvelések", + "artist": "Művész", + "artistUrl": "Művész oldala" }, "songbeamer_import": { "options": "Beállítások", diff --git a/public/lang/it.json b/public/lang/it.json index 7570bfc9..12800146 100644 --- a/public/lang/it.json +++ b/public/lang/it.json @@ -165,6 +165,7 @@ }, "audio": { "settings": "Impostazioni audio", + "playlist_settings": "Playlist settings", "mute_when_video_plays": "Disattiva l'audio durante la riproduzione del video", "metronome": "Metronomo", "toggle_metronome": "Attiva metronomo", @@ -252,7 +253,10 @@ "display_metadata": "Mostra i metadati", "meta_template": "Modello di metadati", "text_divider": "Separatore di testo", - "message_template": "Modello di messaggio" + "message_template": "Modello di messaggio", + "tags": "Tags", + "new_tag": "New tag", + "clear_tag_filter": "Clear tag filter" }, "show_at": { "never": "Nessuna diapositiva", @@ -359,6 +363,7 @@ "change_name": "Cambia nome attivo", "choose_screen": "Scegli lo schermo", "change_output_values": "Modifica i valori di output", + "choose_chord": "Scegli accordo", "set_time": "Imposta orario", "animate": "Animate", "next_timer": "Timer diapositiva successiva", @@ -405,6 +410,8 @@ "now": "ora!", "no_video_id": "Nessun ID video", "no_name": "Nessun nome", + "reverting_setting": "Reverting this change in {} seconds, enable again to make the change stay!", + "reverted": "Setting reverted! Only enable again if you did not have any problems.", "media_replaced": "Missing media file replaced with match.", "lyrics_undefined": "Impossibile trovare alcun testo!", "lyrics_copied": "Testi copiati da", @@ -519,6 +526,7 @@ "format": "Formato", "find_replace": "Trova e sostituisci il testo", "cut_in_half": "Dividere in due", + "merge": "Merge", "find": "Trova", "replace": "Sostituisci", "case_sensitive": "Case sensitive", @@ -530,6 +538,7 @@ "svg_clipboard": "Importa SVG dagli appunti", "fullscreen_preview": "Attiva/disattiva l'anteprima a schermo intero", "toggle_output": "Attiva/disattiva la schermata di output", + "toggle_panels": "Attiva pannelli", "change_tab": "Cambia scheda", "change_drawer_tab": "Cambia scheda cassetto", "change_slide": "Cambia dispositiva", @@ -540,8 +549,15 @@ "slide_actions": "Azioni diapositiva", "item_actions": "Azioni degli elementi", "clear_history": "Pulisci cronologia", + "chord_info": "Clicca su una lettera qualsiasi per aggiungere un accordo.", + "chord_key": "Chiave", + "chord_type": "Tipo", + "chord_tension": "Tensione", + "chord_bass": "Basso", + "roman_keys": "Roman keys", "set_key": "Imposta chiave", "custom_key": "Imposta valore personalizzato", + "select_chord": "Selezione questo accordo", "play_on_midi": "Attiva sul segnale MIDI", "play_on_midi_tip": "Attiva questa diapositiva specifica quando ricevi il segnale MIDI scelto", "send_midi": "Invia segnale MIDI", @@ -557,6 +573,7 @@ "remove_layers": "Rimuovi livelli", "start_recording": "Inizia a registrare", "stop_recording": "Ferma registrazione", + "export_recording": "Stop recording and export", "index_select_project": "Seleziona progetto per indice", "next_project_item": "Elemento successivo del progetto", "previous_project_item": "Elemento del progetto precedente", @@ -576,6 +593,8 @@ "start_playlist": "Avvia la playlist", "playlist_next": "Traccia successiva nella playlist", "start_metronome": "Avvia il metronomo", + "name_start_timer": "Start timer by name", + "id_start_timer": "Start timer by ID", "start_slide_timers": "Avvia i timer sulla diapositiva attiva", "id_select_output_style": "Seleziona lo stile di output in base all'ID", "change_output_style": "Cambia stile di output", @@ -593,7 +612,10 @@ "activate_video_ending": "Attiva quando il video sta finendo", "activate_timer_ending": "Attiva quando il timer sta per scadere", "activate_scripture_start": "Attiva quando viene avviata la scrittura", - "activate_show_created": "Attiva quando viene creato lo spettacolo" + "activate_slide_cleared": "Si attiva quando la diapositiva viene cancellata", + "activate_background_cleared": "Activate when background is cleared", + "activate_show_created": "Attiva quando viene creato lo spettacolo", + "activate_audio_playlist_ended": "Activate when audio playlist has ended" }, "animate": { "change": "cambia", @@ -617,6 +639,7 @@ "google_drive_api": "Chiave dell'account di servizio dell'API di Google", "select_key": "Importa il file delle chiavi", "update_key": "Aggiorna il file delle chiavi", + "enable_custom_folder_id": "Use custom folder ID", "main_folder": "Imposta la cartella principale manualmente", "media_folder": "Cartella multimediale cloud", "reconnect": "Riconnetti", @@ -652,6 +675,7 @@ }, "context": { "enabledTabs": "Attiva/disattiva schede", + "filterByTags": "Filter by tags", "addToProject": "Aggiungi al progetto", "newCategory": "Nuova categoria", "changeIcon": "Cambia icona", @@ -694,6 +718,7 @@ "_title_underline": "Sottolineato", "_title_strikethrough": "Barrato", "color": "Colore", + "accent_color": "Colore di rifinitura", "background_color": "Colore di sfondo", "background_opacity": "Opacità dello sfondo", "background_image": "Immagine di sfondo", @@ -766,7 +791,8 @@ "start_days_from_today": "Inizia i giorni da oggi", "just_one_day": "Solo un giorno", "enable_start_date": "Abilita data di inizio", - "disable_navigation": "Disabilita i controlli di navigazione" + "disable_navigation": "Disabilita i controlli di navigazione", + "progress_bar": "Barra di avanzamento" }, "items": { "text": "Casella di testo", @@ -783,6 +809,7 @@ "timer": "Timer", "variable": "Variable", "web": "Sito web", + "slide_tracker": "Avanzamento", "visualizer": "Visualizzatore", "captions": "Didascalie", "icon": "Icona" @@ -888,10 +915,12 @@ "next_slide_notes": "Note sulla diapositiva successiva", "output": "Output", "current_output": "Output corrente", + "slide_tracker": "Avanzamento", "time": "Tempo", "system_clock": "Orologio di sistema", "video_time": "Tempo del video", "video_countdown": "Conto alla rovescia del video", + "first_active_timer": "First active timer", "other": "Altro", "message": "Messaggio", "color": "Colore", @@ -958,10 +987,16 @@ "audio_fade_duration": "Durata della dissolvenza audio", "audio_crossfade": "Dissolvenza incrociata dell'audio", "max_auto_font_size": "Dimensione massima del carattere automatico", + "clear_style_background_on_text": "Cancella lo stile dello sfondo quando la diapositiva è attiva", "resolution": "Risoluzione", "cropping": "Ritagliare", "frame_rate": "Frame rate", + "device": "Device", + "display_mode": "Display mode", + "pixel_format": "Pixel format", + "alpha_key": "Alpha key", "transparent": "Transparent", + "invisible_window": "Invisible window", "video_extensions": "Estensioni video", "image_extensions": "Estensioni immagine", "add": "Aggiungi", @@ -1048,6 +1083,7 @@ "verses_on_individual_lines": "Versetti su versi individuali", "version": "Mostra versione", "reference": "Mostra referenze", + "split_reference": "Split reference", "combine_with_text": "Combina con testo", "reference_at_bottom": "Sposta in basso", "red_jesus": "Parole di Gesù in rosso", @@ -1139,7 +1175,11 @@ "slides": "Diapositive", "words": "Parole", "template": "Modello", - "category": "Categoria" + "category": "Categoria", + "photoUrl": "Photo URL", + "likes": "Likes", + "artist": "Artist", + "artistUrl": "Artist page" }, "songbeamer_import": { "options": "Opzioni", diff --git a/public/lang/no.json b/public/lang/no.json index c84c2c44..4ea9fac8 100644 --- a/public/lang/no.json +++ b/public/lang/no.json @@ -165,6 +165,7 @@ }, "audio": { "settings": "Lydinnstillinger", + "playlist_settings": "Innstillinger for spilleliste", "mute_when_video_plays": "Demp når video spilles", "metronome": "Metronom", "toggle_metronome": "Veksle metronom", @@ -252,7 +253,10 @@ "display_metadata": "Vis metadata", "meta_template": "Mal for metadata", "text_divider": "Tekstskilletegn", - "message_template": "Mal for melding" + "message_template": "Mal for melding", + "tags": "Etiketter", + "new_tag": "Ny etikett", + "clear_tag_filter": "Fjern etikettfilter" }, "show_at": { "never": "Ingen lysbilder", @@ -406,6 +410,8 @@ "now": "nå!", "no_video_id": "Ingen video-ID", "no_name": "Ingen navn", + "reverting_setting": "Denne endringen vil bli reversert om {} sekunder, aktiver igjen for å bekrefte endringen!", + "reverted": "Innstilling tilbakestilt! Aktiver igjen på eget ansvar hvis du ikke hadde noen problemer.", "media_replaced": "Manglende mediefil erstattet med match.", "lyrics_undefined": "Kunne ikke finne noen sangtekster!", "lyrics_copied": "Sangtekst kopiert fra ", @@ -520,6 +526,7 @@ "format": "Formater", "find_replace": "Finn og erstatt tekst", "cut_in_half": "Del i to", + "merge": "Slå sammen", "find": "Finn", "replace": "Erstatt", "case_sensitive": "Skill mellom små og store bokstaver", @@ -547,6 +554,7 @@ "chord_type": "Type", "chord_tension": "Septim", "chord_bass": "Bass", + "roman_keys": "Romertall", "set_key": "Sett toneart", "custom_key": "Skriv inn verdi", "select_chord": "Velg denne akkorden", @@ -565,6 +573,7 @@ "remove_layers": "Fjern lag", "start_recording": "Start opptak", "stop_recording": "Stopp opptak", + "export_recording": "Stopp opptak og eksporter", "index_select_project": "Velg prosjekt fra indeks", "next_project_item": "Neste element i prosjekt", "previous_project_item": "Forrige element i prosjekt", @@ -584,6 +593,8 @@ "start_playlist": "Start spilleliste", "playlist_next": "Neste låt i spilleliste", "start_metronome": "Start metronom", + "name_start_timer": "Start tidtaker ved navn", + "id_start_timer": "Start tidtaker ved ID", "start_slide_timers": "Start tidtakere på aktivt lysbilde", "id_select_output_style": "Velg utgangsstil fra ID", "change_output_style": "Endre stil for utgangsskjerm", @@ -602,7 +613,9 @@ "activate_timer_ending": "Aktiver når tidtaker slutter", "activate_scripture_start": "Aktiver når bibeltekst vises", "activate_slide_cleared": "Aktiver når lysbilde fjernes", - "activate_show_created": "Aktiver når show blir laget" + "activate_background_cleared": "Aktiver når bakgrunn fjernes", + "activate_show_created": "Aktiver når show blir laget", + "activate_audio_playlist_ended": "Aktiver når spilleliste er ferdig" }, "animate": { "change": "Endre", @@ -626,6 +639,7 @@ "google_drive_api": "Google API tjenestekonto-nøkkel", "select_key": "Importer nøkkelfil", "update_key": "Oppdater nøkkelfil", + "enable_custom_folder_id": "Bruk egendefinert mappe-ID", "main_folder": "Angi hovedmappe manuelt", "media_folder": "Mediemappe i skyen", "reconnect": "Koble til på nytt", @@ -661,6 +675,7 @@ }, "context": { "enabledTabs": "Veksle faner", + "filterByTags": "Filtrer etter etikett", "addToProject": "Legg til i prosjekt", "newCategory": "Ny kategori", "changeIcon": "Endre ikon", @@ -905,6 +920,7 @@ "system_clock": "Systemklokke", "video_time": "Videotid", "video_countdown": "Video nedtelling", + "first_active_timer": "Første aktive tidtaker", "other": "Andre", "message": "Melding", "color": "Farge", @@ -975,7 +991,12 @@ "resolution": "Oppløsning", "cropping": "Beskjæring", "frame_rate": "Bildefrekvens", + "device": "Enhet", + "display_mode": "Visningsmodus", + "pixel_format": "Pikselformat", + "alpha_key": "Alpha key", "transparent": "Gjennomsiktig", + "invisible_window": "Usynlig vindu", "video_extensions": "Videoutvidelse", "image_extensions": "Bildeutvidelse", "add": "Legg til", @@ -1062,6 +1083,7 @@ "verses_on_individual_lines": "Vers på enkeltlinjer", "version": "Vis versjon", "reference": "Vis referanse", + "split_reference": "Del opp referanse", "combine_with_text": "Kombiner med tekst", "reference_at_bottom": "Flytt til bunn", "red_jesus": "Jesus ord i rødt", @@ -1153,7 +1175,11 @@ "slides": "Lysbilder", "words": "Ord", "template": "Mal", - "category": "Kategori" + "category": "Kategori", + "photoUrl": "URL for bilde", + "likes": "Likes", + "artist": "Fotograf", + "artistUrl": "Skaperside" }, "songbeamer_import": { "options": "Alternativer", diff --git a/src/electron/capture/helpers/CaptureLifecycle.ts b/src/electron/capture/helpers/CaptureLifecycle.ts index 727ccd65..f969c975 100644 --- a/src/electron/capture/helpers/CaptureLifecycle.ts +++ b/src/electron/capture/helpers/CaptureLifecycle.ts @@ -6,7 +6,9 @@ import { CaptureTransmitter } from "./CaptureTransmitter" export class CaptureLifecycle { static startCapture(id: string, toggle: any = {}) { const output = OutputHelper.getOutput(id) - let window = output?.window + if (!output) return + + let window = output.window let windowIsRemoved = !window || window.isDestroyed() if (windowIsRemoved) { delete output.captureOptions @@ -35,7 +37,7 @@ export class CaptureLifecycle { captureFrame() async function captureFrame() { - if (!output.captureOptions || output.captureOptions.window.isDestroyed()) return + if (!output?.captureOptions?.window || output.captureOptions.window.isDestroyed()) return let image = await output.captureOptions.window.webContents.capturePage() processFrame(image) diff --git a/src/electron/data/export.ts b/src/electron/data/export.ts index 046f6423..ae070150 100644 --- a/src/electron/data/export.ts +++ b/src/electron/data/export.ts @@ -88,8 +88,8 @@ export function generatePDF(path: string) { function exportMessage(message: string = "") { toApp(MAIN, { channel: "ALERT", data: message }) + exportWindow?.on("closed", () => (exportWindow = null)) exportWindow?.close() - exportWindow = null } let exportWindow: any = null diff --git a/src/electron/index.ts b/src/electron/index.ts index c62dfb01..8f9e41ce 100644 --- a/src/electron/index.ts +++ b/src/electron/index.ts @@ -90,6 +90,9 @@ function mainWindowLoaded() { if (config.get("maximized")) maximizeMain() mainWindow?.show() + // this has to be called to actually remove the process! + // WIP seems like like loading window process is still up after everything else is closed! + loadingWindow?.removeAllListeners("close") loadingWindow?.close() if (RECORD_STARTUP_TIME) console.timeEnd("Full startup") @@ -101,7 +104,7 @@ let loadingWindow: BrowserWindow | null = null function createLoading() { loadingWindow = new BrowserWindow(loadingOptions) loadingWindow.loadFile("public/loading.html") - loadingWindow.on("closed", () => (loadingWindow = null)) + loadingWindow.once("closed", () => (loadingWindow = null)) } // ----- MAIN WINDOW ----- @@ -115,13 +118,13 @@ function createMain() { let screenBounds: Rectangle = screen.getPrimaryDisplay().bounds let options: any = { - width: !bounds.width || bounds.width === 800 ? screenBounds.width : bounds.width, - height: !bounds.height || bounds.height === 600 ? screenBounds.height : bounds.height, + width: !bounds.width || bounds.width === 800 ? screenBounds.width || 800 : bounds.width, + height: !bounds.height || bounds.height === 600 ? screenBounds.height || 600 : bounds.height, frame: !isProd || !isWindows, autoHideMenuBar: isProd && isWindows, } - // should be centered to screen if x & y is not set + // should be centered to screen if x & y is not set (or bottom left on mac) if (bounds.x) options.x = bounds.x if (bounds.y) options.y = bounds.y @@ -217,7 +220,7 @@ function setMainListeners() { }) mainWindow.on("close", callClose) - mainWindow.on("closed", exitApp) + mainWindow.once("closed", exitApp) } function callClose(e: any) { @@ -230,7 +233,6 @@ function callClose(e: any) { export async function exitApp() { console.log("Closing app!") - mainWindow = null dialogClose = false await OutputHelper.Lifecycle.closeAllOutputs() @@ -244,8 +246,15 @@ export async function exitApp() { if (!isProd) { console.log("Dev mode active - Relaunching...") app.relaunch() + } else { + // this has to be called to actually remove the process! + // https://stackoverflow.com/a/43520274 + mainWindow?.removeAllListeners("close") + ipcMain.removeAllListeners() } + mainWindow = null + try { app.quit() diff --git a/src/electron/output/helpers/OutputLifecycle.ts b/src/electron/output/helpers/OutputLifecycle.ts index 147f20c1..b2592af6 100644 --- a/src/electron/output/helpers/OutputLifecycle.ts +++ b/src/electron/output/helpers/OutputLifecycle.ts @@ -108,15 +108,16 @@ export class OutputLifecycle { return } - OutputHelper.getOutput(id).window.on("closed", () => { + OutputHelper.getOutput(id).window.once("closed", () => { OutputHelper.deleteOutput(id) if (reopen) this.createOutput(reopen) }) try { const output = OutputHelper.getOutput(id) - output?.window?.destroy() - //output?.previewWindow?.destroy() + // this has to be called to actually remove the process! + output?.window?.removeAllListeners("close") + output?.window?.close() } catch (error) { console.log(error) } diff --git a/src/electron/utils/files.ts b/src/electron/utils/files.ts index 3c938552..7a987b05 100644 --- a/src/electron/utils/files.ts +++ b/src/electron/utils/files.ts @@ -7,13 +7,14 @@ import fs from "fs" import { Stats } from "original-fs" import path, { join, parse } from "path" import { uid } from "uid" -import { FILE_INFO, MAIN, OPEN_FOLDER, READ_FOLDER, SHOW, STORE } from "../../types/Channels" +import { FILE_INFO, MAIN, OPEN_FOLDER, OUTPUT, READ_FOLDER, SHOW, STORE } from "../../types/Channels" import { stores } from "../data/store" import { createThumbnail } from "../data/thumbnails" import { OPEN_FILE } from "./../../types/Channels" import { mainWindow, toApp } from "./../index" import { getAllShows, trimShow } from "./responses" import { defaultSettings } from "../data/defaults" +import { OutputHelper } from "../output/OutputHelper" function actionComplete(err: Error | null, actionFailedMessage: string) { if (err) console.error(actionFailedMessage + ":", err) @@ -356,58 +357,78 @@ export function readExifData({ id }: any, e: any) { } // SEARCH FOR MEDIA FILE (in drawer media folders & their following folders) +const NESTED_SEARCH = 8 // folder levels deep export function locateMediaFile({ fileName, splittedPath, folders, ref }: any) { let matches: string[] = [] - findMatches(true) - if (matches.length < 1) findMatches() - if (matches.length < 1) return + findMatches() + if (!matches.length) return toApp(MAIN, { channel: "LOCATE_MEDIA_FILE", data: { path: matches[0], ref } }) ///// - function findMatches(searchWithFolder: boolean = false) { + function findMatches() { for (const folderPath of folders) { - if (matches.length > 1) return - - checkFolderForMatches(folderPath, searchWithFolder) - + // if (matches.length > 1) return // this might be used if we want the user to choose if more than one match is found if (matches.length) return - - let files = readFolder(folderPath) - for (const name of files) { - if (matches.length) return - - let p: string = path.join(folderPath, name) - let fileStat = getFileStats(p) - if (fileStat?.folder) checkFolderForMatches(p, searchWithFolder) - } + searchInFolder(folderPath) } } - function checkFolderForMatches(folderPath: string, searchWithFolder: boolean = false) { + function searchInFolder(folderPath: string, level: number = 1) { + if (level > NESTED_SEARCH || matches.length) return + + let currentFolderFolders: string[] = [] let files = readFolder(folderPath) + for (const name of files) { + let currentFilePath: string = path.join(folderPath, name) + let fileStat = getFileStats(currentFilePath) + + if (fileStat?.folder) { + // search all files in current folder before searching in any nested folders + currentFolderFolders.push(currentFilePath) + } else { + checkFileForMatch(name, folderPath) + if (matches.length) return + } + } - let folderName = path.basename(folderPath) - let searchName = fileName - if (searchWithFolder && splittedPath?.length > 1) searchName = path.join(splittedPath[splittedPath.length - 2], fileName) + if (matches.length) return - for (let name of files) { - if (matches.length > 1) return + for (const folderName of currentFolderFolders) { + searchInFolder(folderName, level + 1) + if (matches.length) return + } + } - let pathName = searchWithFolder ? path.join(folderName, name) : name + function checkFileForMatch(currentFileName: string, folderPath: string) { + // include original parent folder name in search first (to limit it a bit if two files with same name are in two different folders!) + if (splittedPath?.length > 1) { + let currentParentFolder = path.basename(folderPath) + let pathName = path.join(currentParentFolder, currentFileName) + let searchName = path.join(splittedPath[splittedPath.length - 2], fileName) if (pathName === searchName) { - let p: string = path.join(folderPath, name) + let p: string = path.join(folderPath, currentFileName) matches.push(p) } } + + if (matches.length) return + + // check for file name exact match + if (currentFileName === fileName) { + let p: string = path.join(folderPath, currentFileName) + matches.push(p) + } } } // LOAD SHOWS export function loadShows({ showsPath }: any, returnShows: boolean = false) { + specialCaseFixer() + // list all shows in folder let filesInFolder: string[] = readFolder(showsPath) @@ -474,3 +495,28 @@ export function parseShow(jsonData: string) { return show } + +// some users might have got themselves in a situation they can't get out of +// example: enables "kiosk" mode on mac might have resulted in a black screen, and they can't find the app data location to revert it! +// how: Place any file in your Documents/FreeShow folder that has the FIXES key in it's name (e.g. DISABLE_KIOSK_MODE), when you now start your app the fix will be triggered! +const FIXES: any = { + DISABLE_KIOSK_MODE: () => { + // wait to ensure output settings have loaded in the app! + setTimeout(() => { + toApp(OUTPUT, { channel: "UPDATE_OUTPUTS_DATA", data: { key: "kioskMode", value: false, autoSave: true } }) + OutputHelper.getAllOutputs().forEach(([_id, output]) => output.window.setKiosk(false)) + }, 1000) + }, + OPEN_APPDATA_SETTINGS: () => { + // this will open the "settings.json" file located at the app data location (can also be used to find other setting files here) + openSystemFolder(stores.SETTINGS.path) + }, +} +function specialCaseFixer() { + let defaultDataFolder = getDocumentsFolder(null, "") + let files: string[] = readFolder(defaultDataFolder) + files.forEach((fileName) => { + let matchFound = Object.keys(FIXES).find((key) => fileName.includes(key)) + if (matchFound) FIXES[matchFound]() + }) +} diff --git a/src/electron/utils/responses.ts b/src/electron/utils/responses.ts index 2c686410..96305314 100644 --- a/src/electron/utils/responses.ts +++ b/src/electron/utils/responses.ts @@ -308,12 +308,19 @@ function getScreens(type: "window" | "screen" = "screen") { } // RECORDER +// only open once per session +let systemOpened: boolean = false export function saveRecording(_: any, msg: any) { let folder: string = getDataFolder(msg.path || "", dataFolderNames.recordings) let p: string = path.join(folder, msg.name) const buffer = Buffer.from(msg.blob) writeFile(p, buffer) + + if (!systemOpened) { + openSystemFolder(folder) + systemOpened = true + } } // ERROR LOGGER diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index 1719add2..1afd3512 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -1,5 +1,5 @@ -{#if subTab === "online"} - -{:else if subTab === "screens"} +{#if subTab === "screens" || $activeRecording} +{:else if subTab === "online"} + {:else}

diff --git a/src/frontend/components/drawer/info/PlayerInfo.svelte b/src/frontend/components/drawer/info/PlayerInfo.svelte index 19d8f5b1..2354ce3f 100644 --- a/src/frontend/components/drawer/info/PlayerInfo.svelte +++ b/src/frontend/components/drawer/info/PlayerInfo.svelte @@ -46,6 +46,10 @@ {$photoApiCredits.downloadUrl}

-->

+ +
+ Photo by {$photoApiCredits.artist} from {$photoApiCredits.type} +
{/if} {/if} @@ -83,4 +87,11 @@ margin: 20px 0; background-color: var(--primary-lighter); } */ + + .credits { + position: absolute; + bottom: 10px; + width: 100%; + text-align: center; + } diff --git a/src/frontend/components/drawer/info/ScriptureInfo.svelte b/src/frontend/components/drawer/info/ScriptureInfo.svelte index a989431f..01c93bb6 100644 --- a/src/frontend/components/drawer/info/ScriptureInfo.svelte +++ b/src/frontend/components/drawer/info/ScriptureInfo.svelte @@ -263,6 +263,14 @@ + {#if $scriptureSettings.showVerse && sorted.length > 1} + +

+
+ +
+
+ {/if}

diff --git a/src/frontend/components/drawer/live/LiveInfo.svelte b/src/frontend/components/drawer/live/LiveInfo.svelte index d09e1c4e..51448808 100644 --- a/src/frontend/components/drawer/live/LiveInfo.svelte +++ b/src/frontend/components/drawer/live/LiveInfo.svelte @@ -3,12 +3,12 @@ import Icon from "../../helpers/Icon.svelte" import T from "../../helpers/T.svelte" import Button from "../../inputs/Button.svelte" - import { stopMediaRecorder, toggleMediaRecorder } from "./recorder" + import { mediaRecorderIsPaused, stopMediaRecorder, toggleMediaRecorder } from "./recorder" let videoElem $: if ($currentRecordingStream && videoElem) { videoElem.srcObject = $currentRecordingStream - paused = false + paused = mediaRecorderIsPaused() || false } let paused = false @@ -39,7 +39,7 @@
{:else} diff --git a/src/frontend/components/drawer/live/recorder.ts b/src/frontend/components/drawer/live/recorder.ts index ea6d9c26..a4cd3b04 100644 --- a/src/frontend/components/drawer/live/recorder.ts +++ b/src/frontend/components/drawer/live/recorder.ts @@ -15,6 +15,10 @@ export function createMediaRecorder(stream) { mediaRecorder.onstop = handleStop } +export function mediaRecorderIsPaused() { + return mediaRecorder?.state === "paused" +} + export function toggleMediaRecorder() { if (mediaRecorder.state === "paused") { mediaRecorder.resume() diff --git a/src/frontend/components/drawer/media/Image.svelte b/src/frontend/components/drawer/media/Image.svelte index 2518f898..b511a11d 100644 --- a/src/frontend/components/drawer/media/Image.svelte +++ b/src/frontend/components/drawer/media/Image.svelte @@ -1,5 +1,6 @@ {#key retryCount} - + {/key}