diff --git a/README.md b/README.md index 5a4c2d97..af85ee0c 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,9 @@ You are welcome to contribute to the code! 2. Install [Node.js](https://nodejs.org/en/download/) 3. Install [Python 3.12](https://www.python.org/downloads/), and the [`setuptools`](https://pypi.org/project/setuptools/) package 4. On Windows, download [Visual Studio](https://visualstudio.microsoft.com/downloads/) and install "Desktop development with C++", also select the "Windows 10 SDK" -5. In the terminal, run: `npm install` -6. To start the app, run: `npm start` +5. On Linux, install the following library: `sudo apt-get install libfontconfig1-dev` +6. In the terminal, run: `npm install` +7. To start the app, run: `npm start` ## Join us on Slack diff --git a/package-lock.json b/package-lock.json index 61cbdb50..5994aaf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "freeshow", - "version": "1.3.3", + "version": "1.3.4-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freeshow", - "version": "1.3.3", + "version": "1.3.4-beta.1", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { @@ -18,14 +18,15 @@ "axios": "^1.7.8", "chord-transposer": "^3.0.9", "cross-env": "^7.0.3", - "css-fonts": "^1.0.8", + "css-fonts": "^1.0.9", + "dayjs": "^1.11.10", "electron-store": "^8.0.1", "electron-updater": "^6.3.1", "exif": "^0.6.0", "express": "^4.17.2", "follow-redirects": "^1.15.2", "genius-lyrics": "^4.4.7", - "grandiose": "vassbo/grandiose#53e7f98", + "grandiose": "github:vassbo/grandiose#f2021d2", "jzz": "^1.8.7", "mp4box": "^0.5.2", "node-machine-id": "^1.1.12", @@ -36,7 +37,7 @@ "protobufjs": "^7.2.3", "qrcode-generator": "^1.4.4", "sirv-cli": "^1.0.0", - "slideshow": "vassbo/slideshow#224d50f", + "slideshow": "github:vassbo/slideshow#224d50f", "socket.io": "^4.4.1", "socket.io-client": "^4.4.1", "sqlite-to-json": "^0.1.3", @@ -53,7 +54,7 @@ "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^12.1.1", + "@rollup/plugin-typescript": "^12.1.2", "@tsconfig/svelte": "^2.0.0", "@types/adm-zip": "^0.5.7", "@types/exif": "^0.6.3", @@ -74,7 +75,7 @@ "rollup-plugin-svelte": "^7.1.6", "svelte": "^3.59.2", "svelte-check": "^2.2.12", - "svelte-inspector": "vassbo/svelte-inspector#78307db", + "svelte-inspector": "github:vassbo/svelte-inspector#78307db", "svelte-preprocess": "^4.10.1", "tslib": "^2.0.0", "typescript": "^4.9.5" @@ -776,7 +777,9 @@ } }, "node_modules/@rollup/plugin-typescript": { - "version": "12.1.1", + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz", + "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==", "dev": true, "license": "MIT", "dependencies": { @@ -2656,9 +2659,9 @@ } }, "node_modules/css-fonts": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/css-fonts/-/css-fonts-1.0.8.tgz", - "integrity": "sha512-V42LQXa5Q+PU/4DNQzGWkkQEP6cm3f7c04MDWHuEfdf7PioDAKN3WS5r2jGcXIlYtb/3EhLGVztt87pkF6Qlng==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/css-fonts/-/css-fonts-1.0.9.tgz", + "integrity": "sha512-CVQWqqvhdjL7qPF0yFQ67PuCchYkAAJVMXB3YelSStmJZekHTZXyv2OkJXXMqsI7lZwNByb8y1Rb5K5x8COs6w==", "license": "ISC", "dependencies": { "font-scanner": "github:vassbo/font-scanner" @@ -2733,6 +2736,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debounce-fn": { "version": "4.0.0", "license": "MIT", @@ -4224,7 +4233,7 @@ }, "node_modules/grandiose": { "version": "0.0.6", - "resolved": "git+ssh://git@github.com/vassbo/grandiose.git#53e7f98c6a391781c221b40c85519e3f21f5cbdd", + "resolved": "git+ssh://git@github.com/vassbo/grandiose.git#f2021d21a5a207562ff1875cc811de03158005ee", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 4c9928d4..21075c66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.3.3", + "version": "1.3.4-beta.1", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -140,7 +140,7 @@ "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^12.1.1", + "@rollup/plugin-typescript": "^12.1.2", "@tsconfig/svelte": "^2.0.0", "@types/adm-zip": "^0.5.7", "@types/exif": "^0.6.3", @@ -161,7 +161,7 @@ "rollup-plugin-svelte": "^7.1.6", "svelte": "^3.59.2", "svelte-check": "^2.2.12", - "svelte-inspector": "vassbo/svelte-inspector#78307db", + "svelte-inspector": "github:vassbo/svelte-inspector#78307db", "svelte-preprocess": "^4.10.1", "tslib": "^2.0.0", "typescript": "^4.9.5" @@ -175,14 +175,15 @@ "axios": "^1.7.8", "chord-transposer": "^3.0.9", "cross-env": "^7.0.3", - "css-fonts": "^1.0.8", + "css-fonts": "^1.0.9", + "dayjs": "^1.11.10", "electron-store": "^8.0.1", "electron-updater": "^6.3.1", "exif": "^0.6.0", "express": "^4.17.2", "follow-redirects": "^1.15.2", "genius-lyrics": "^4.4.7", - "grandiose": "vassbo/grandiose#53e7f98", + "grandiose": "github:vassbo/grandiose#f2021d2", "jzz": "^1.8.7", "mp4box": "^0.5.2", "node-machine-id": "^1.1.12", @@ -193,7 +194,7 @@ "protobufjs": "^7.2.3", "qrcode-generator": "^1.4.4", "sirv-cli": "^1.0.0", - "slideshow": "vassbo/slideshow#224d50f", + "slideshow": "github:vassbo/slideshow#224d50f", "socket.io": "^4.4.1", "socket.io-client": "^4.4.1", "sqlite-to-json": "^0.1.3", diff --git a/public/lang/cs_CZ.json b/public/lang/cs_CZ.json index 1003f52a..6f627ccc 100644 --- a/public/lang/cs_CZ.json +++ b/public/lang/cs_CZ.json @@ -4,7 +4,7 @@ "quit": "Zavřít", "docs": "Dokumentace", "about": "O Freeshow", - "unnamed": "Nepojmenované", + "unnamed": "Nezařazené", "drop": "Pusťte zde", "search": "Vyhledat", "quick_search": "Rychlé vyhledávání", @@ -38,7 +38,10 @@ "top": "Nahoře", "right": "Vpravo", "bottom": "Dole", - "left": "Vlevo" + "left": "Vlevo", + "centered": "Vycentrované", + "edge_blending_tip": "Prolínat dohromady více výstupů/projektorů pro souvislé spoje", + "cropping_tip": "Přesně přesunout obsah okna oříznutím hran." }, "about": { "check_updates": "Zkontrolovat aktualizace", @@ -173,7 +176,6 @@ "volume": "Hlasitost", "gain": "Gain", "speed": "Rychlost", - "show": "Zobrazit", "flip": "Překlopit", "flip_horizontally": "Překlopit horizontálně", "flip_vertically": "Překlopit vertikálně", @@ -317,14 +319,14 @@ "primary-darkest": "Primární nejtmavší", "text": "Text", "textInvert": "Převrácený text", - "secondary-text": "Sekundární text", + "secondary-text": "Výběry", "secondary": "Sekundární", "secondary-opacity": "Sekundární neprůhlednost", "hover": "Vznášet", "focus": "Zaměřit" }, "inputs": { - "name": "Jméno", + "name": "Název", "url": "URL", "method": "Metoda", "contentType": "Typ obsahu", @@ -382,7 +384,7 @@ "pre_chorus": "Před-Refrén", "chorus": "Refrén", "break": "Výpustka", - "tag": "Štítek", + "tag": "Závěrečná fráze", "bridge": "Bridge", "outro": "Outro" }, @@ -414,7 +416,7 @@ "import": "Import", "songbeamer_import": "Import Songbeameru", "export": "Export", - "importing": "Importování", + "importing": "Importování...", "import_scripture": "Nahrát Bibli", "player": "Přehrávač", "edit_event": "Upravit událost", @@ -422,6 +424,7 @@ "history": "Historie", "action": "Akce", "category_action": "Akce kategorie", + "user_data_overwrite": "Existující data nalezeny", "connect": "Připojit", "cloud_update": "Synchonizovat s Cloudem", "cloud_method": "Umístění dat", @@ -430,6 +433,7 @@ "manage_icons": "Spravovat ikony", "manage_colors": "Spravovat barvy", "choose_camera": "Vyber kameru", + "manage_tags": "Spravovat štítky", "initialize": "Vítejte ve FreeShow", "unsaved": "Určitě chceš opustit FreeShow?", "cancel": "Ukončit", @@ -441,7 +445,7 @@ "save_quit": "Uložit a ukončit" }, "toast": { - "saving": "Ukládám...", + "saving": "Ukládání...", "saved": "Uloženo", "error_media": "Nelze získat média.", "empty_styles": "Žádné styly", @@ -466,6 +470,7 @@ "deleted_cache": "Mezipamět náhledu média odstaněna.", "no_songswords_easyworship": "Soubor SongsWords.db nenalezen.", "delete_shows_empty": "Žadné prezentace k odstranění", + "output_capture_enabled": "Nahrávání obrazovky výstupu spuštěno, toto může způsobit potíže s výkonem. Používat pouze v případě potřeby!", "midi_no_project": "Byl spuštěn spouštěč pro změnu projektu, ale nebyl nalezen žádný projekt v rejstříku:", "midi_no_show": "Byl přijat signál pro zahájení snímku, ale žádnou aktivní prezentaci.", "midi_no_slide": "Byl spuštěn spouštěč pro zahájení snímku, ale nebyl nalezen žádný snímek v rejstříku:", @@ -494,7 +499,7 @@ "event": "Nová událost" }, "show": { - "name": "Jméno", + "name": "Název", "category": "Kategorie", "new_layout": "Nové rozložení", "grid": "Mřížkové zobrazení", @@ -528,6 +533,7 @@ "view_private": "Zobrazit soukromé", "import": "Importovat", "export": "Exportovat", + "imported": "Importováno!", "duplicate": "Duplikovat", "delete": "Smazat", "delete_slide": "Smazat snímek", @@ -558,8 +564,10 @@ "reset": "Resetovat", "create_template": "Vytvořit šablonu", "project_template_tip": "Vytvořit nový projekt z této šablony", + "pdf_single_page": "Renderovat jako jeden dokument", "convert_to_images": "Převést na obrázky", "converting": "Převádění...", + "closing": "Zavírání aplikace...", "remove_template_from_show": "Odstranit šablonu z prezentace", "reset_defaults": "Obnovit výchozí nastavení", "to_all": "Aplikovat na vše", @@ -734,6 +742,7 @@ "export": { "export": "Export", "export_as": "Exportovat {} jako", + "exporting": "Exportování...", "exported": "Exportováno!", "oneFile": "Jeden soubor", "selected_shows": "Vybrané prezentace", @@ -741,6 +750,7 @@ "all_shows": "Všechny prezentace", "all_projects": "Všechny projekty", "project": "Projekt", + "include_media": "Zahrnout soubory médií", "preview": "Náhled", "title": "Název", "metadata": "Metadata", @@ -767,11 +777,12 @@ "changeGroup": "Změnit skupinu", "createNew": "Vytvořit nový", "selectAll": "Vybrat vše", - "force_outputs": "Vynutit výstupy", + "force_outputs": "Zobrazit výstupy", "align_with_screen": "Srovnat s obrazovkou", "toggle_output": "Přepnout výstup", "move_to_front": "Přesunout vpřed", "hide_from_preview": "Skrýt z náhledu", + "enable_preview": "Zapnout náhled", "lock_to_output": "Zamknout k výstupu", "place_under_slide": "Umístit pod snímek", "toggle_clock": "Přepnout hodiny", @@ -809,17 +820,19 @@ "color": "Barva", "accent_color": "Nádech barvy", "background_color": "Barva pozadí", - "background_opacity": "Opacita pozadí", + "background_opacity": "Průhlednost pozadí", "background_image": "Obrázek pozadí", "background_media": "Média pozadí", "overlay_content": "Přidat obsah překrytí", "different_first_template": "Vlastní šablona na prvním snímku", + "max_lines_per_slide": "Max řádků za snímek", "media_fit": "Vmístit média", "one_letter": "Režim jednoho písmene", "sub_indexes": "Pod-rejstříky", "size": "Velikost", "max_lines": "Maximum řádků", "invert_items": "Invertovat předměty", + "list": "Seznam", "chords": "Akordy", "transpose": "Transponovat", "auto_size": "Automatická velikost", @@ -843,6 +856,7 @@ "outline": "Obrys", "shadow": "Stín", "shadow_inset": "Vnitřní stín", + "offset": "Odsazení", "offsetX": "Posun X", "offsetY": "Posun Y", "blur": "Rozmazání", @@ -889,7 +903,6 @@ }, "items": { "text": "Textové pole", - "list": "Seznam", "media": "Média", "image": "Obrázek", "camera": "Kamera", @@ -1062,6 +1075,7 @@ "manual_input_hint": "Nelze najít obraz? Klikni zde pro manuální změnu pozice.", "manual_drag_hint": "Můžeš také držet ctrl/cmd nad aktivním výstupovým oknem pro manuální přetáhnutí.", "allow_main_screen": "Povolit vlastní pozici a velikost výstupu", + "edge_blending": "Prolnutí okrajů", "identify_screens": "Identifikovat obrazovky", "new_output": "Nový výstup", "normal": "Normální", @@ -1088,7 +1102,7 @@ "auto_shortcut_first_letter": "Automatická zkratka k prvnímu písmenu v textu", "auto_output": "Aktivovat výstupní obraz na spuštění", "hide_cursor_in_output": "Skrýt kursor ve výstupu", - "clear_media_when_finished": "Odstranit po přehrátí média", + "clear_media_when_finished": "Odstranit média po přehrátí", "disable_presenter_controller_keys": "Vypnout klávesy ovladače prezentace", "default_project_name": "Výchozí název projektu", "audio_fade_duration": "Trvání zeslábnutí audia", @@ -1111,11 +1125,15 @@ "show_location": "Zobrazit lokaci", "data_location": "Lokace dat", "user_data_location": "Uložit nastavení uživatele do 'Lokace dat'", + "user_data_exists": "Nalezeny existující data ve vlastní lokaci, přejete si přepsat?", + "user_data_yes": "Ano, zachovat aktuální data", + "user_data_no": "Ne, importovat aktuální data", "popup_before_close": "Aktivovat zavírání vyskakovacích oken pro potvrzení", "disable_hardware_acceleration": "Vypnout hardwarovou akceleraci", "restart_for_change": "Musíte restartovat program, aby se změna projevila!", "font": "Font", "font_family": "Rodina fontu", + "font_style": "Styl fontu", "font_size": "Velikost fontu", "border_radius": "Dosah okraje", "colors": "Barvy", @@ -1152,7 +1170,8 @@ }, "sort": { "sort_by": "Třídit podle", - "name": "Jméno", + "name": "Název", + "name_des": "Název Sestupně", "date": "Datum", "size": "Velikost", "type": "Typ", @@ -1162,7 +1181,7 @@ "type": "Typ", "event": "Událost", "schedule_action": "Naplánovat akci", - "name": "Jméno", + "name": "Název", "color": "Barva", "time": "Čas", "from_date": "Od data", diff --git a/public/lang/en.json b/public/lang/en.json index 257c6a67..8321c641 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -148,6 +148,7 @@ "overlays": "Clear overlays", "audio": "Clear audio", "nextTimer": "Clear next slide timer", + "timer": "Clear timer", "drawing": "Clear drawing" }, "remove": { @@ -400,6 +401,7 @@ "trigger": "Trigger", "audio_stream": "Audio stream", "transition": "Transition", + "media_fit": "Change media fit", "delete_show": "Delete show", "delete_show_confirmation": "Are you sure you want to delete", "delete_duplicated_shows": "Delete duplicated shows", @@ -413,6 +415,7 @@ "animate": "Animate", "translate": "Localization", "next_timer": "Next slide timer", + "display_duration": "Display duration", "import": "Import", "songbeamer_import": "Songbeamer Import", "export": "Export", @@ -422,6 +425,7 @@ "edit_event": "Edit event", "about": "About", "history": "History", + "manage_emitters": "Manage emitters", "action": "Action", "category_action": "Category action", "user_data_overwrite": "Found existing data", @@ -578,6 +582,7 @@ "unmute": "Unmute", "increase_volume": "Increase volume", "decrease_volume": "Decrease volume", + "manage_subtitles": "Manage subtitles", "toggle_time_marker": "Toggle time markers", "add_time_marker": "Add time marker", "bind_to": "Specific outputs", @@ -681,8 +686,10 @@ "start_trigger": "Start trigger", "run_action": "Run action", "toggle_action": "Toggle action", + "emit_data": "Emit data", "send_rest_command": "Send HTTP-Request", "custom_activation": "Custom activation", + "any": "Any", "activate_on_startup": "Activate on startup", "activate_save": "Activate on save", "activate_slide_clicked": "Activate on slide click", @@ -952,6 +959,7 @@ "to_event": "Time until event", "counter": "Countdown", "time": "Time", + "hours": "Hours", "minutes": "Minutes", "seconds": "Seconds", "from": "From", @@ -971,7 +979,9 @@ "type": "Type", "digital": "Digital", "analog": "Analog", - "seconds": "Seconds" + "seconds": "Seconds", + "custom": "Custom", + "show_time": "Display time" }, "captions": { "info": "Please click the URL to open in your browser if you haven't already, or open it on any other device! Make sure to give access to your microphone, and use Google Chrome for the best performance.", @@ -1004,6 +1014,14 @@ "tip_action": "To activate specific 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." }, + "emitters": { + "emitter": "Emitter", + "tip": "Create emit templates to easily send specific messages.", + "signal": "Signal", + "message_template": "Message template", + "message_templates": "Message templates", + "inputs": "Inputs" + }, "draw": { "focus": "Focus", "pointer": "Pointer", diff --git a/public/lang/en_GB.json b/public/lang/en_GB.json index 67a77a1a..adc95543 100644 --- a/public/lang/en_GB.json +++ b/public/lang/en_GB.json @@ -38,7 +38,10 @@ "top": "Top", "right": "Right", "bottom": "Bottom", - "left": "Left" + "left": "Left", + "centered": "Centered", + "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition", + "cropping_tip": "Fine adjust the window content position by cropping the sides" }, "about": { "check_updates": "Look for updates", @@ -173,7 +176,6 @@ "volume": "Volume", "gain": "Gain", "speed": "Speed", - "show": "Show", "flip": "Flip", "flip_horizontally": "Flip horizontally", "flip_vertically": "Flip vertically", @@ -317,7 +319,7 @@ "primary-darkest": "Primary darkest", "text": "Text", "textInvert": "Inverted text", - "secondary-text": "Secondary text", + "secondary-text": "Selections", "secondary": "Secondary", "secondary-opacity": "Secondary opacity", "hover": "Hover", @@ -414,7 +416,7 @@ "import": "Import", "songbeamer_import": "Songbeamer Import", "export": "Export", - "importing": "Importing", + "importing": "Importing...", "import_scripture": "Import scripture", "player": "Player", "edit_event": "Edit event", @@ -422,6 +424,7 @@ "history": "History", "action": "Action", "category_action": "Category action", + "user_data_overwrite": "Found existing data", "connect": "Connect", "cloud_update": "Syncing with cloud", "cloud_method": "Data location", @@ -430,6 +433,7 @@ "manage_icons": "Manage icons", "manage_colors": "Manage colours", "choose_camera": "Choose camera", + "manage_tags": "Manage tags", "initialize": "Welcome to FreeShow", "unsaved": "Are you sure you want to quit?", "cancel": "Cancel", @@ -466,6 +470,7 @@ "deleted_cache": "Deleted media thumbnail cache.", "no_songswords_easyworship": "Missing SongsWords.db file.", "delete_shows_empty": "No shows to delete.", + "output_capture_enabled": "Output screen capture enabled, this may cause performance issues. Only use if needed!", "midi_no_project": "Received trigger to change project, but no project found at index:", "midi_no_show": "Received trigger to start slide, but no show active.", "midi_no_slide": "Received trigger to start slide, but no slide found at index:", @@ -528,6 +533,7 @@ "view_private": "Show private", "import": "Import", "export": "Export", + "imported": "Imported!", "duplicate": "Duplicate", "delete": "Delete", "delete_slide": "Delete slide", @@ -558,8 +564,10 @@ "reset": "Reset", "create_template": "Create template", "project_template_tip": "Create a new project from this template", + "pdf_single_page": "Render as one document", "convert_to_images": "Convert to images", "converting": "Converting...", + "closing": "Closing app...", "remove_template_from_show": "Remove template from show", "reset_defaults": "Reset defaults", "to_all": "Apply to all", @@ -734,6 +742,7 @@ "export": { "export": "Export", "export_as": "Export {} as", + "exporting": "Exporting...", "exported": "Exported!", "oneFile": "One file", "selected_shows": "Selected shows", @@ -741,6 +750,7 @@ "all_shows": "All shows", "all_projects": "All projects", "project": "Project", + "include_media": "Include media files", "preview": "Preview", "title": "Title", "metadata": "Metadata", @@ -772,6 +782,7 @@ "toggle_output": "Toggle output", "move_to_front": "Move to front", "hide_from_preview": "Hide from preview", + "enable_preview": "Enable preview", "lock_to_output": "Lock to output", "place_under_slide": "Place under slide", "toggle_clock": "Toggle clock", @@ -814,12 +825,14 @@ "background_media": "Background media", "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", + "max_lines_per_slide": "Max lines per slide", "media_fit": "Media fit", "one_letter": "One letter mode", "sub_indexes": "Sub indexes", "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", + "list": "List", "chords": "Chords", "transpose": "Transpose", "auto_size": "Auto size", @@ -843,6 +856,7 @@ "outline": "Outline", "shadow": "Shadow", "shadow_inset": "Shadow inset", + "offset": "Offset", "offsetX": "Offset X", "offsetY": "Offset Y", "blur": "Blur", @@ -889,7 +903,6 @@ }, "items": { "text": "Textbox", - "list": "List", "media": "Media", "image": "Image", "camera": "Camera", @@ -1062,6 +1075,7 @@ "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.", "allow_main_screen": "Allow custom output position & size", + "edge_blending": "Edge blending", "identify_screens": "Identify screens", "new_output": "New output", "normal": "Normal", @@ -1111,11 +1125,15 @@ "show_location": "Show location", "data_location": "Data location", "user_data_location": "Save user settings at 'Data location'", + "user_data_exists": "Found existing data at custom location, would you like to overwrite it?", + "user_data_yes": "Yes, keep current data", + "user_data_no": "No, import existing data", "popup_before_close": "Enable close confirmation popup", "disable_hardware_acceleration": "Disable hardware acceleration", "restart_for_change": "You have to restart the program for the change to take effect!", "font": "Font", "font_family": "Font family", + "font_style": "Font style", "font_size": "Font size", "border_radius": "Border radius", "colors": "Colours", @@ -1153,6 +1171,7 @@ "sort": { "sort_by": "Sort by", "name": "Name", + "name_des": "Name Descending", "date": "Date", "size": "Size", "type": "Type", diff --git a/public/lang/en_ZM.json b/public/lang/en_ZM.json index a1d2d2cc..cc38a511 100644 --- a/public/lang/en_ZM.json +++ b/public/lang/en_ZM.json @@ -38,7 +38,10 @@ "top": "Top", "right": "Right", "bottom": "Bottom", - "left": "Left" + "left": "Left", + "centered": "Centered", + "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition", + "cropping_tip": "Fine adjust the window content position by cropping the sides" }, "about": { "check_updates": "Look for updates", @@ -173,7 +176,6 @@ "volume": "Volume", "gain": "Gain", "speed": "Speed", - "show": "Show", "flip": "Flip", "flip_horizontally": "Flip horizontally", "flip_vertically": "Flip vertically", @@ -317,7 +319,7 @@ "primary-darkest": "Primary darkest", "text": "Text", "textInvert": "Inverted text", - "secondary-text": "Secondary text", + "secondary-text": "Selections", "secondary": "Secondary", "secondary-opacity": "Secondary opacity", "hover": "Hover", @@ -414,7 +416,7 @@ "import": "Import", "songbeamer_import": "Songbeamer Import", "export": "Export", - "importing": "Importing", + "importing": "Importing...", "import_scripture": "Import scripture", "player": "Player", "edit_event": "Edit event", @@ -422,6 +424,7 @@ "history": "History", "action": "Action", "category_action": "Category action", + "user_data_overwrite": "Found existing data", "connect": "Connect", "cloud_update": "Syncing with cloud", "cloud_method": "Data location", @@ -430,6 +433,7 @@ "manage_icons": "Manage icons", "manage_colors": "Manage colours", "choose_camera": "Choose camera", + "manage_tags": "Manage tags", "initialize": "Welcome to FreeShow", "unsaved": "Are you sure you want to quit?", "cancel": "Cancel", @@ -466,6 +470,7 @@ "deleted_cache": "Deleted media thumbnail cache.", "no_songswords_easyworship": "Missing SongsWords.db file.", "delete_shows_empty": "No shows to delete.", + "output_capture_enabled": "Output screen capture enabled, this may cause performance issues. Only use if needed!", "midi_no_project": "Received trigger to change project, but no project found at index:", "midi_no_show": "Received trigger to start slide, but no show active.", "midi_no_slide": "Received trigger to start slide, but no slide found at index:", @@ -528,6 +533,7 @@ "view_private": "Show private", "import": "Import", "export": "Export", + "imported": "Imported!", "duplicate": "Duplicate", "delete": "Delete", "delete_slide": "Delete slide", @@ -558,8 +564,10 @@ "reset": "Reset", "create_template": "Create template", "project_template_tip": "Create a new project from this template", + "pdf_single_page": "Render as one document", "convert_to_images": "Convert to images", "converting": "Converting...", + "closing": "Closing app...", "remove_template_from_show": "Remove template from show", "reset_defaults": "Reset defaults", "to_all": "Apply to all", @@ -734,6 +742,7 @@ "export": { "export": "Export", "export_as": "Export {} as", + "exporting": "Exporting...", "exported": "Exported!", "oneFile": "One file", "selected_shows": "Selected shows", @@ -741,6 +750,7 @@ "all_shows": "All shows", "all_projects": "All projects", "project": "Project", + "include_media": "Include media files", "preview": "Preview", "title": "Title", "metadata": "Metadata", @@ -772,6 +782,7 @@ "toggle_output": "Toggle output", "move_to_front": "Move to front", "hide_from_preview": "Hide from preview", + "enable_preview": "Enable preview", "lock_to_output": "Lock to output", "place_under_slide": "Place under slide", "toggle_clock": "Toggle clock", @@ -814,12 +825,14 @@ "background_media": "Background media", "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", + "max_lines_per_slide": "Max lines per slide", "media_fit": "Media fit", "one_letter": "One letter mode", "sub_indexes": "Sub indexes", "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", + "list": "List", "chords": "Chords", "transpose": "Transpose", "auto_size": "Auto size", @@ -843,6 +856,7 @@ "outline": "Outline", "shadow": "Shadow", "shadow_inset": "Shadow inset", + "offset": "Offset", "offsetX": "Offset X", "offsetY": "Offset Y", "blur": "Blur", @@ -889,7 +903,6 @@ }, "items": { "text": "Textbox", - "list": "List", "media": "Media", "image": "Image", "camera": "Camera", @@ -1062,6 +1075,7 @@ "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.", "allow_main_screen": "Allow custom output position & size", + "edge_blending": "Edge blending", "identify_screens": "Identify screens", "new_output": "New output", "normal": "Normal", @@ -1111,11 +1125,15 @@ "show_location": "Show location", "data_location": "Data location", "user_data_location": "Save user settings at 'Data location'", + "user_data_exists": "Found existing data at custom location, would you like to overwrite it?", + "user_data_yes": "Yes, keep current data", + "user_data_no": "No, import existing data", "popup_before_close": "Enable close confirmation popup", "disable_hardware_acceleration": "Disable hardware acceleration", "restart_for_change": "You have to restart the program for the change to take effect!", "font": "Font", "font_family": "Font family", + "font_style": "Font style", "font_size": "Font size", "border_radius": "Border radius", "colors": "Colours", @@ -1153,6 +1171,7 @@ "sort": { "sort_by": "Sort by", "name": "Name", + "name_des": "Name Descending", "date": "Date", "size": "Size", "type": "Type", diff --git a/public/lang/es.json b/public/lang/es.json index 26345ddb..b82f9950 100644 --- a/public/lang/es.json +++ b/public/lang/es.json @@ -38,7 +38,10 @@ "top": "Arriba", "right": "Derecha", "bottom": "Abajo", - "left": "Izquierda" + "left": "Izquierda", + "centered": "Centrado", + "edge_blending_tip": "Combine múltiples salidas/proyectores para lograr una transición más fluida", + "cropping_tip": "Ajuste con precisión la posición del contenido de la ventana recortando los lados" }, "about": { "check_updates": "Buscar actualizaciones", @@ -163,6 +166,7 @@ "toggle_shuffle": "Activar reproducción aleatoria", "next": "Siguiente", "previous": "Anterior", + "play_no_audio": "Reproducir sin audio", "play_no_filters": "Reproducir sin filtros", "favourite": "Favoritos", "pause": "Pausar", @@ -172,7 +176,6 @@ "volume": "Volumen", "gain": "Ganancia", "speed": "Velocidad", - "show": "Mostrar", "flip": "Voltear", "flip_horizontally": "Voltear horizontalmente", "flip_vertically": "Voltear verticalmente", @@ -194,7 +197,10 @@ "playlist_settings": "Ajuste de Lista de Reproducción", "custom_output": "Salida de audio personalizada", "mute_when_video_plays": "Silenciar cuando un vídeo se reproduce", + "allow_gaining": "Permitir ganancia", + "allow_gaining_tip": "Permitir configurar el volumen por encima del 100% (Puede causar distorsión)", "pre_fader_volume_meter": "Medidor de volumen pre-fader", + "mixer": "Mezclador", "metronome": "Metrónomo", "toggle_metronome": "Activar metrónomo", "tempo": "Tempo", @@ -269,15 +275,17 @@ "ip": "No se pudo obtener la dirección IP de tu dispositivo, vaya a la configuración Wi-Fi de la computadora para encontrar tu dirección IPv4 local." }, "meta": { + "number": "Número", "title": "Título", "artist": "Artista", "author": "Autor", "composer": "Compositor", "publisher": "Editor", "copyright": "Derechos de autor", - "CCLI": "Licencia (CCLI)", + "CCLI": "ID de Canción (CCLI)", "year": "Año", "key": "Clave", + "autofill": "Autocompletar", "message": "Mensaje", "message_tip": "Mostrar algo en todas las diapositivas", "auto_media": "Obtener metadatos del contenido multimedia", @@ -311,7 +319,7 @@ "primary-darkest": "Primario más oscuro", "text": "Texto", "textInvert": "Texto invertido", - "secondary-text": "Texto secundario", + "secondary-text": "Selecciones", "secondary": "Secundario", "secondary-opacity": "Opacidad de Secundario", "hover": "Flotar", @@ -362,7 +370,8 @@ "music": "Música", "offers": "Ofertas", "notice": "Noticia", - "visuals": "Ilustraciones" + "visuals": "Ilustraciones", + "action_tip": "Una acción que se activa cada vez que se presenta un programa con esta categoría." }, "groups": { "current": "Actual", @@ -400,19 +409,22 @@ "change_output_values": "Cambiar valores de salida", "choose_chord": "Elige acorde", "set_time": "Establecer tiempo", + "slide_shortcut": "Atajo de Diapositiva", "animate": "Animar", "translate": "Localización", "next_timer": "Temporizador de siguiente diapositiva", "import": "Importar", "songbeamer_import": "Importación de Songbeamer", "export": "Exportar", - "importing": "Importando", + "importing": "Importando...", "import_scripture": "Importar Escritura", "player": "Reproductor", "edit_event": "Editar evento", "about": "Acerca de", "history": "Historial", "action": "Acción", + "category_action": "Acción de Categoría", + "user_data_overwrite": "Se encontraron datos existentes", "connect": "Conectar", "cloud_update": "Sincronizando con la nube", "cloud_method": "Ubicación de Datos", @@ -421,6 +433,7 @@ "manage_icons": "Gestionar iconos", "manage_colors": "Gestionar colores", "choose_camera": "Elegir cámara", + "manage_tags": "Gestionar etiquetas", "initialize": "Bienvenido a FreeShow", "unsaved": "¿Estás seguro de que quieres salir?", "cancel": "Cancelar", @@ -457,6 +470,7 @@ "deleted_cache": "Se eliminó el caché de miniaturas de medios.", "no_songswords_easyworship": "Falta el archivo SongsWords.db.", "delete_shows_empty": "No hay programas para eliminar.", + "output_capture_enabled": "Captura de la pantalla de salida habilitada, ésto puede causar problemas de rendimiento. ¡Solo úselo si es necesario!", "midi_no_project": "Se recibió un disparador para cambiar el proyecto, pero no se encontró ningún proyecto en el índice:", "midi_no_show": "Se recibió el disparador para iniciar la diapositiva, pero no el programa activo.", "midi_no_slide": "Se recibió el disparador para iniciar la diapositiva, pero no se encontró ninguna diapositiva en el índice:", @@ -519,6 +533,7 @@ "view_private": "Mostrar privado", "import": "Importar", "export": "Exportar", + "imported": "¡Importado!", "duplicate": "Duplicar", "delete": "Eliminar", "delete_slide": "Eliminar diapositiva", @@ -547,8 +562,12 @@ "zoomIn": "Acercarse", "zoomOut": "Alejarse", "reset": "Reiniciar", + "create_template": "Crear Plantilla", + "project_template_tip": "Crear un nuevo proyecto a partir esta plantilla", + "pdf_single_page": "Renderizar como un solo documento", "convert_to_images": "Convertir a imágenes", "converting": "Convirtiendo...", + "closing": "Cerrando aplicación...", "remove_template_from_show": "Quitar plantilla del programa", "reset_defaults": "Restablecer valores predeterminados", "to_all": "Aplicar a todo", @@ -608,6 +627,8 @@ "set_key": "Establecer clave", "custom_key": "Establecer valor personalizado", "select_chord": "Seleccionar este acorde", + "play_with_shortcut": "Activar con atajo", + "press_to_assign": "Presione cualquier tecla de letra para asignar", "play_on_midi": "Activar en la señal MIDI", "play_on_midi_tip": "Activar esta diapositiva específica al recibir la señal MIDI elegida", "send_midi": "Enviar señal MIDI", @@ -624,6 +645,7 @@ "next_after_media": "Próximo en los medios terminados", "remove_media": "Quitar medios", "remove_layers": "Quitar capas", + "toggle_checkbox_tip": "La acción se alternará si la casilla de verificación no cambia", "start_recording": "Iniciar grabación", "stop_recording": "Parar grabación", "export_recording": "Detener la grabación y exportar", @@ -632,6 +654,7 @@ "previous_project_item": "Anterior elemento del proyecto", "index_select_project_item": "Seleccionar elemento del proyecto por índice", "name_select_show": "Seleccionar programa por nombre", + "set_template_active": "Establecer plantilla en el programa activo", "random_slide": "Reproducir diapositiva aleatoria", "index_select_slide": "Seleccionar diapositiva por índice", "name_select_slide": "Seleccionar diapositiva por nombre", @@ -672,6 +695,7 @@ "activate_slide_cleared": "Activar cuando se borra la diapositiva", "activate_background_cleared": "Activar cuando se borra el fondo", "activate_show_created": "Activar cuando se crea un programa", + "activate_show_opened": "Activar al abrir el programa", "activate_audio_playlist_ended": "Activar cuando la lista de reproducción de audio haya finalizado" }, "recording": { @@ -679,7 +703,9 @@ "tip": "Graba y reproduce la duración de las diapositivas. Sincroniza con una pista de audio en la primera diapositiva.", "layout_changed": "¡El diseño cambió desde la última grabación!", "audio_synced": "¡Sincronizado con el audio!", - "start": "Iniciar grabación de diapositiva" + "start": "Iniciar grabación de diapositiva", + "use_duration": "Usar tiempo de duración", + "use_duration_tip": "Utilice el tiempo de duración en lugar de la hora de la marca de tiempo" }, "animate": { "change": "Cambiar", @@ -716,6 +742,7 @@ "export": { "export": "Exportar", "export_as": "Exportar {} como", + "exporting": "Exportando...", "exported": "Exportado", "oneFile": "Un archivo", "selected_shows": "Programas seleccionados", @@ -723,6 +750,7 @@ "all_shows": "Todos los programas", "all_projects": "Todos los proyectos", "project": "Proyecto", + "include_media": "Incluir archivos de medios", "preview": "Vista previa", "title": "Título", "metadata": "Metadato", @@ -738,6 +766,7 @@ }, "context": { "enabledTabs": "Mostrar/ocultar pestañas", + "setTag": "Establecer etiqueta", "filterByTags": "Filtrar por etiquetas", "addToProject": "Agregar al proyecto", "add_to_show": "Agregar al programa", @@ -753,6 +782,7 @@ "toggle_output": "Alternar salida", "move_to_front": "Mover al frente", "hide_from_preview": "Ocultar de la vista previa", + "enable_preview": "Habilitar vista previa", "lock_to_output": "Bloquear a la salida", "place_under_slide": "Colocar debajo de la diapositiva", "toggle_clock": "Alternar reloj", @@ -795,10 +825,14 @@ "background_media": "Medios de fondo", "overlay_content": "Añadir contenido superpuesto", "different_first_template": "Plantilla personalizada en la primera diapositiva", + "max_lines_per_slide": "Máximo de líneas por diapositiva", "media_fit": "Ajuste de medios", + "one_letter": "Modo de una letra", + "sub_indexes": "Subíndices", "size": "Tamaño", "max_lines": "Máximo de líneas", "invert_items": "Invertir elementos", + "list": "Lista", "chords": "Acordes", "transpose": "Transponer", "auto_size": "Tamaño automático", @@ -822,6 +856,7 @@ "outline": "Contorno", "shadow": "Sombra", "shadow_inset": "Añadir sombra", + "offset": "Desplazamiento", "offsetX": "Desplazamiento horizontal", "offsetY": "Desplazamiento vertical", "blur": "Desenfocar", @@ -868,7 +903,6 @@ }, "items": { "text": "Cuadro de texto", - "list": "Lista", "media": "Medios", "image": "Imagen", "camera": "Cámara", @@ -916,6 +950,7 @@ "to_event": "Tiempo hasta el evento", "counter": "Cuenta regresiva", "time": "Hora", + "minutes": "Minutos", "seconds": "Segundos", "from": "Desde", "to": "Hacia", @@ -1040,6 +1075,7 @@ "manual_input_hint": "¿No puede encontrar la pantalla? Haga clic aquí para cambiar la posición manualmente.", "manual_drag_hint": "También puedes mantener presionada la tecla Ctrl/Cmd sobre una ventana de salida activa para arrastrarla manualmente.", "allow_main_screen": "Permitir posición y tamaño de salida personalizados", + "edge_blending": "Combinación de bordes", "identify_screens": "Identificar pantallas", "new_output": "Nueva salida", "normal": "Normal", @@ -1063,6 +1099,7 @@ "group_numbers": "Números de grupo", "full_colors": "Grupo de colores de alto contraste", "slide_number_keys": "Reproducir diapositivas con las teclas numéricas", + "auto_shortcut_first_letter": "Atajo automático a la primera letra en el texto", "auto_output": "Muestra la ventana de salida en la pantalla de inicio", "hide_cursor_in_output": "Ocultar el cursor en la salida", "clear_media_when_finished": "Borrar los medios cuando termine", @@ -1088,11 +1125,15 @@ "show_location": "Mostrar ubicación", "data_location": "Ubicación de los datos", "user_data_location": "Guardar la configuración del usuario en 'Ubicación de datos'", + "user_data_exists": "Se encontraron datos existentes en una ubicación personalizada, ¿quieres sobrescribirlos?", + "user_data_yes": "Si, mantener los datos actuales", + "user_data_no": "No, importar datos existentes", "popup_before_close": "Habilitar ventana de confirmación de cierre", "disable_hardware_acceleration": "Deshabilitar aceleración por hardware", "restart_for_change": "¡Tienes que reiniciar el programa para que el cambio surta efecto!", "font": "Fuente", "font_family": "Familia de fuentes", + "font_style": "Estilo de fuente", "font_size": "Tamaño de fuente", "border_radius": "Radio del borde", "colors": "Colores", @@ -1124,11 +1165,13 @@ "auto": "Automático", "optimized": "Optimizado", "reduced": "Reducido", - "full": "Lleno" + "full": "Lleno", + "section_trigger_action": "Activar acción al navegar la presentación hacia una sección" }, "sort": { "sort_by": "Ordenar por", "name": "Nombre", + "name_des": "Nombre descendente", "date": "Fecha", "size": "Tamaño", "type": "Tipo", @@ -1172,6 +1215,7 @@ "reference": "Mostrar referencia", "split_reference": "Referencia dividida", "combine_with_text": "Combinar con el texto", + "first_slide_reference": "Referencia en la primera diapositiva", "reference_at_bottom": "Mover hacia abajo", "red_jesus": "Palabra Jesús en letras rojas", "search": "Buscar en la Biblia" diff --git a/public/lang/pt_BR.json b/public/lang/pt_BR.json index 6731eded..db77afa5 100644 --- a/public/lang/pt_BR.json +++ b/public/lang/pt_BR.json @@ -38,7 +38,10 @@ "top": "Topo", "right": "Direita", "bottom": "Base", - "left": "Esquerda" + "left": "Esquerda", + "centered": "Centralizado", + "edge_blending_tip": "Combina várias saídas/projetores para uma transição mais suave", + "cropping_tip": "Ajuste fino da posição do conteúdo da janela cortando as laterais" }, "about": { "check_updates": "Verificar por atualizações", @@ -173,7 +176,6 @@ "volume": "Volume", "gain": "Ganho", "speed": "Velocidade", - "show": "Mostrar", "flip": "Virar", "flip_horizontally": "Virar horizontalmente", "flip_vertically": "Virar verticalmente", @@ -317,7 +319,7 @@ "primary-darkest": "Primária Mais Escura", "text": "Texto", "textInvert": "Texto Invertido", - "secondary-text": "Texto Secundário", + "secondary-text": "Seleções", "secondary": "Secundário", "secondary-opacity": "Opacidade Secundária", "hover": "Ao passar o mouse", @@ -414,7 +416,7 @@ "import": "Importar", "songbeamer_import": "Importar de Songbeamer", "export": "Exportar", - "importing": "Importando", + "importing": "Importando...", "import_scripture": "Importar bíblia", "player": "Reprodutor", "edit_event": "Editar evento", @@ -422,6 +424,7 @@ "history": "Histórico", "action": "Ação", "category_action": "Ação de categoria", + "user_data_overwrite": "Dados existentes encontrados", "connect": "Conectar", "cloud_update": "Sincronizando com a nuvem", "cloud_method": "Dados", @@ -430,6 +433,7 @@ "manage_icons": "Gerenciar ícones", "manage_colors": "Gerenciar cores", "choose_camera": "Escolha a câmera", + "manage_tags": "Gerenciar tags", "initialize": "Seja bem-vindo ao FreeShow", "unsaved": "Tem certeza que deseja sair?", "cancel": "Cancelar", @@ -466,6 +470,7 @@ "deleted_cache": "Cache de thumbnails foi excluído.", "no_songswords_easyworship": "Arquivo SongsWords.db não encontrado.", "delete_shows_empty": "Sem shows para deletar.", + "output_capture_enabled": "Captura de tela de saída habilitada, isso pode causar problemas de desempenho. Use somente se necessário!", "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:", @@ -528,6 +533,7 @@ "view_private": "Mostrar Show privado", "import": "Importar", "export": "Exportar", + "imported": "Importado!", "duplicate": "Duplicar", "delete": "Excluir", "delete_slide": "Excluir slide", @@ -558,8 +564,10 @@ "reset": "Redefinir", "create_template": "Criar modelo de projeto", "project_template_tip": "Cria um novo projeto a partir deste modelo", + "pdf_single_page": "Renderizar como um documento", "convert_to_images": "Converter para imagens", "converting": "Convertendo...", + "closing": "Fechando app...", "remove_template_from_show": "Remover modelo da apresentação", "reset_defaults": "Redefinir padrões", "to_all": "Aplicar a todos", @@ -734,6 +742,7 @@ "export": { "export": "Exportar", "export_as": "Exportar {} como", + "exporting": "Exportando...", "exported": "Exportado!", "oneFile": "Um arquivo", "selected_shows": "Apresentações selecionadas", @@ -741,6 +750,7 @@ "all_shows": "Todas as apresentações", "all_projects": "Todos os projetos", "project": "Projeto", + "include_media": "Incluir arquivos de mídia", "preview": "Pré-visualização", "title": "Título", "metadata": "Metadados", @@ -772,6 +782,7 @@ "toggle_output": "Alterar saídas", "move_to_front": "Mover para frente", "hide_from_preview": "Esconder do preview", + "enable_preview": "Ativar preview", "lock_to_output": "Travar na saída", "place_under_slide": "Place under slide", "toggle_clock": "Alternar relógio", @@ -814,12 +825,14 @@ "background_media": "Background", "overlay_content": "Adicionar conteúdo de sobreposição", "different_first_template": "Usar outro modelo no primeiro slide", + "max_lines_per_slide": "Número de linhas por slide", "media_fit": "Fazer mídia caber", "one_letter": "Modo de letra única", "sub_indexes": "Sub índices ", "size": "Tamanho", "max_lines": "Máximo de linhas", "invert_items": "Inverter items", + "list": "Lista", "chords": "Acordes", "transpose": "Transpor", "auto_size": "Tamanho automático", @@ -843,6 +856,7 @@ "outline": "Contorno", "shadow": "Sombra", "shadow_inset": "Sombra interna", + "offset": "Ajuste", "offsetX": "Coordenada X", "offsetY": "Coordenada Y", "blur": "Desfoque", @@ -889,7 +903,6 @@ }, "items": { "text": "Caixa de texto", - "list": "Lista", "media": "Mídia", "image": "Imagem", "camera": "Câmera", @@ -1062,6 +1075,7 @@ "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", + "edge_blending": "Edge blending", "identify_screens": "Identificar telas", "new_output": "Nova saída", "normal": "Normal", @@ -1111,11 +1125,15 @@ "show_location": "Apresentações", "data_location": "Local dos dados", "user_data_location": "Salvar configurações do usuário em \"Dados\"", + "user_data_exists": "Há dados existentes em um local personalizado. Gostaria de substituí-los?", + "user_data_yes": "Sim, mantenha os arquivos atuais", + "user_data_no": "Não, importe os arquivos existentes", "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_style": "Estilo da fonte", "font_size": "Tamanho da fonte", "border_radius": "Raio da borda", "colors": "Cores", @@ -1153,6 +1171,7 @@ "sort": { "sort_by": "Ordenar por", "name": "Nome", + "name_des": "Nome Decrescente", "date": "Data", "size": "Tamanho", "type": "Tipo", diff --git a/public/lang/tr.json b/public/lang/tr.json index 3d4c6cb1..3942d891 100644 --- a/public/lang/tr.json +++ b/public/lang/tr.json @@ -38,7 +38,10 @@ "top": "Üst", "right": "Sağ", "bottom": "Alt", - "left": "Sol" + "left": "Sol", + "centered": "Ortalanmış", + "edge_blending_tip": "Daha sorunsuz bir geçiş için birden fazla çıkışı/projektörü bir araya getirin", + "cropping_tip": "Kenarları kırparak pencere içeriğinin konumunu hassas bir şekilde ayarlayın" }, "about": { "check_updates": "Güncellemeleri ara", @@ -163,6 +166,7 @@ "toggle_shuffle": "Karıştırmayı değiştir", "next": "Sonraki", "previous": "Önceki", + "play_no_audio": "Sessiz oynat", "play_no_filters": "Filtresiz oyna", "favourite": "Favori", "pause": "Duraklat", @@ -172,7 +176,6 @@ "volume": "Volume", "gain": "Kazanç", "speed": "Hız", - "show": "Göster", "flip": "Çevir", "flip_horizontally": "Yatay olarak çevir", "flip_vertically": "Dikey olarak çevir", @@ -194,7 +197,10 @@ "playlist_settings": "Çalma listesi ayarları", "custom_output": "Özel ses çıkışı", "mute_when_video_plays": "Video oynatılırken sesi kapat", + "allow_gaining": "Kazanmaya izin ver", + "allow_gaining_tip": "Sesi %100'ün üzerine ayarlamaya izin verin (Bozulmaya neden olabilir)", "pre_fader_volume_meter": "Ön fader ses seviyesi ölçer", + "mixer": "Mikser", "metronome": "Metronom", "toggle_metronome": "Metronomu değiştir", "tempo": "Tempo", @@ -269,15 +275,17 @@ "ip": "Cihazınızın IP adresine ulaşılamıyor, yerel IPv4 adresinizi bulmak için bilgisayarınızın Wi-Fi ayarlarına gidin." }, "meta": { + "number": "Sayı", "title": "Başlık", "artist": "Sanatçı", "author": "Yazar", "composer": "Besteci", "publisher": "Yayımcı", "copyright": "Telif hakkı", - "CCLI": "Lisans (CCLI)", + "CCLI": "Şarkı Kimliği (CCLI)", "year": "Yıl", "key": "Anahtar", + "autofill": "Otomatik doldurma", "message": "Mesaj", "message_tip": "Tüm slaytlarda bir şey göster", "auto_media": "Medya içeriğinden meta alın", @@ -311,7 +319,7 @@ "primary-darkest": "Birincil en koyu", "text": "Metin", "textInvert": "Ters metin", - "secondary-text": "ikincil metin", + "secondary-text": "Seçimler", "secondary": "İkincil", "secondary-opacity": "ikincil opaklık", "hover": "Fareyle üzerine gelin", @@ -362,7 +370,8 @@ "music": "Müzik", "offers": "Teklifler", "notice": "Bildiri", - "visuals": "Görseller" + "visuals": "Görseller", + "action_tip": "Bu kategoriye ait bir gösteri her sunulduğunda tetiklenen bir eylem." }, "groups": { "current": "Akım", @@ -400,19 +409,22 @@ "change_output_values": "Çıkış değerlerini değiştir", "choose_chord": "Akoru seçin", "set_time": "Zamanı ayarla", + "slide_shortcut": "Slayt kısayolu", "animate": "Hareketlendir", "translate": "Lokalizasyon", "next_timer": "Sonraki slayt zamanlayıcı", "import": "İçeri aktar", "songbeamer_import": "Songbeamer İçe aktar", "export": "Dışarı aktar", - "importing": "İçe Aktarma", + "importing": "İçe aktarma...", "import_scripture": "Kutsal kitabı içe aktar", "player": "Player", "edit_event": "Etkinliği düzenle", "about": "Hakkında", "history": "Tarih", "action": "Eylem", + "category_action": "Kategori eylemi", + "user_data_overwrite": "Mevcut veriler bulundu", "connect": "Bağlan", "cloud_update": "Bulut ile senkronizasyon", "cloud_method": "Veri konumu", @@ -421,6 +433,7 @@ "manage_icons": "Simgeleri yönet", "manage_colors": "Renkleri yönet", "choose_camera": "Kamera seç", + "manage_tags": "Etiketleri yönet", "initialize": "FreeShow'a Hoşgeldiniz", "unsaved": "Bırakmak istediğine emin misin?", "cancel": "İptal", @@ -457,6 +470,7 @@ "deleted_cache": "Medya küçük resim önbelleği silindi.", "no_songswords_easyworship": "SongsWords.db dosyası eksik.", "delete_shows_empty": "Silinecek gösteri yok.", + "output_capture_enabled": "Çıkış ekran görüntüsü etkinleştirildi, bu performans sorunlarına neden olabilir. Sadece gerektiğinde kullanın!", "midi_no_project": "Projeyi değiştirmek için tetikleyici alındı, ancak dizinde proje bulunamadı:", "midi_no_show": "Slaytı başlatmak için tetik alındı, ancak gösteri etkin değil.", "midi_no_slide": "Slaytı başlatmak için tetik alındı, ancak dizinde slayt bulunamadı:", @@ -519,6 +533,7 @@ "view_private": "Özel göster", "import": "İçe aktar", "export": "Dışa aktar", + "imported": "İçe aktarıldı!", "duplicate": "Çoğalt", "delete": "Sil", "delete_slide": "Slaydı sil", @@ -547,8 +562,12 @@ "zoomIn": "Yakınlaştır", "zoomOut": "Uzaklaştır", "reset": "Sıfırla", + "create_template": "Şablon oluştur", + "project_template_tip": "Bu şablondan yeni bir proje oluşturun", + "pdf_single_page": "Tek bir belge olarak oluşturun", "convert_to_images": "Resimlere dönüştür", "converting": "Dönüştürülüyor...", + "closing": "Uygulama kapatılıyor...", "remove_template_from_show": "Şablonu gösteriden kaldır", "reset_defaults": "Varsayılanları sıfırla", "to_all": "Hepsine uygula", @@ -608,6 +627,8 @@ "set_key": "Ayar tuşu", "custom_key": "Özel değer ayarla", "select_chord": "Bu akoru seçin", + "play_with_shortcut": "Kısayolla etkinleştir", + "press_to_assign": "Atama yapmak için herhangi bir harf tuşuna basın", "play_on_midi": "MIDI sinyalinde etkinleştir", "play_on_midi_tip": "Seçilen MIDI sinyalini alırken bu özel slaydı etkinleştirin", "send_midi": "MIDI sinyali gönderme", @@ -624,6 +645,7 @@ "next_after_media": "Sonraki medya bitti", "remove_media": "Medyayı kaldır", "remove_layers": "Katmanları kaldır", + "toggle_checkbox_tip": "Onay kutusu değiştirilmemişse eylem geçiş yapacaktır", "start_recording": "Kayda başla", "stop_recording": "Kaydı durdur", "export_recording": "Kaydı durdur ve dışa aktar", @@ -632,6 +654,7 @@ "previous_project_item": "Önceki proje öğesi", "index_select_project_item": "Proje öğesini dizine göre seçme", "name_select_show": "Gösteriyi isme göre seçin", + "set_template_active": "Şablonu aktif gösteride ayarla", "random_slide": "Rastgele slayt oynat", "index_select_slide": "Dizine göre slayt seç", "name_select_slide": "Slaytı ada göre seçme", @@ -672,6 +695,7 @@ "activate_slide_cleared": "Slayt temizlendiğinde etkinleştir", "activate_background_cleared": "Arka plan temizlendiğinde etkinleştir", "activate_show_created": "Gösteri oluşturulduğunda etkinleştir", + "activate_show_opened": "Gösteri açıldığında etkinleştir", "activate_audio_playlist_ended": "Ses çalma listesi sona erdiğinde etkinleştir" }, "recording": { @@ -679,7 +703,9 @@ "tip": "Slaytların zamanlamalarını kaydedin ve tekrar oynatın. İlk slayttaki bir ses parçasıyla senkronize edin.", "layout_changed": "Son kayıttan bu yana düzen değişti!", "audio_synced": "Sesle senkronize!", - "start": "Slayt kaydını başlat" + "start": "Slayt kaydını başlat", + "use_duration": "Süreyi kullan", + "use_duration_tip": "Zaman damgası zamanı yerine süre zamanını kullanın" }, "animate": { "change": "Değiştir", @@ -716,6 +742,7 @@ "export": { "export": "Dışa aktar", "export_as": "Dışa aktar {} olarak dışa aktar", + "exporting": "Dışa aktarma...", "exported": "Dışa aktarıldı!", "oneFile": "Bir dosya", "selected_shows": "Seçilmiş şovlar", @@ -723,6 +750,7 @@ "all_shows": "Tüm gösteriler", "all_projects": "Tüm projeler", "project": "Proje", + "include_media": "Medya dosyalarını ekle", "preview": "Ön izleme", "title": "Başlık", "metadata": "Meta veri", @@ -738,6 +766,7 @@ }, "context": { "enabledTabs": "Sekmeleri değiştir", + "setTag": "Etiket ayarla", "filterByTags": "Etiketlere göre filtrele", "addToProject": "Projeye ekle", "add_to_show": "Gösteriye ekle", @@ -753,6 +782,7 @@ "toggle_output": "Çıkışı değiştir", "move_to_front": "Öne taşı", "hide_from_preview": "Önizlemeden gizle", + "enable_preview": "Önizlemeyi etkinleştir", "lock_to_output": "Çıktıya kilitle", "place_under_slide": "Slayt altına yerleştir", "toggle_clock": "Saati değiştir", @@ -795,10 +825,14 @@ "background_media": "Arkaplan medyası", "overlay_content": "Bindirme içeriği ekleme", "different_first_template": "İlk slaytta özel şablon", + "max_lines_per_slide": "Slayt başına maksimum satır sayısı", "media_fit": "Medya sığdır", + "one_letter": "Tek harf modu", + "sub_indexes": "Alt dizinler", "size": "Boyut", "max_lines": "Maksimum çizgiler", "invert_items": "Öğeleri ters çevir", + "list": "Liste", "chords": "Akorlar", "transpose": "Transpoze", "auto_size": "Otomatik boyut", @@ -822,6 +856,7 @@ "outline": "Anahat", "shadow": "Gölge", "shadow_inset": "Gölge eki", + "offset": "Ofset", "offsetX": "Offset X", "offsetY": "Offset Y", "blur": "Blur", @@ -868,7 +903,6 @@ }, "items": { "text": "Metin kutusu", - "list": "Liste", "media": "Medya", "image": "Resim", "camera": "Kamera", @@ -916,6 +950,7 @@ "to_event": "Olaya kadar geçen süre", "counter": "Geri sayım", "time": "Zaman", + "minutes": "Dakikalar", "seconds": "Saniye", "from": "İtibaren", "to": "İle", @@ -1040,6 +1075,7 @@ "manual_input_hint": "Ekranı bulamıyor musunuz? Konumu manuel olarak değiştirmek için buraya tıklayın.", "manual_drag_hint": "Etkin bir çıktı penceresi üzerinde elle sürüklemek için ctrl/cmd'yi de basılı tutabilirsiniz.", "allow_main_screen": "Özel çıktı pozisyonuna ve boyutuna izin ver", + "edge_blending": "Kenar karıştırma", "identify_screens": "Ekranları tanımlayın", "new_output": "Yeni çıkış", "normal": "Normal", @@ -1063,6 +1099,7 @@ "group_numbers": "Grup numaraları", "full_colors": "Yüksek kontrastlı grup renkleri", "slide_number_keys": "Slaytları sayı tuşlarıyla oynatın", + "auto_shortcut_first_letter": "Metindeki ilk harfe otomatik kısayol", "auto_output": "Başlangıçta çıktı ekranını etkinleştir", "hide_cursor_in_output": "İmleci çıktıda gizle", "clear_media_when_finished": "Bittiğinde medyayı temizleyin", @@ -1088,11 +1125,15 @@ "show_location": "Konumu göster", "data_location": "Veri konumu", "user_data_location": "Kullanıcı ayarlarını 'Veri konumu'na kaydedin", + "user_data_exists": "Özel konumda mevcut veriler bulundu, üzerine yazmak ister misiniz?", + "user_data_yes": "Evet, güncel verileri koru", + "user_data_no": "Hayır, mevcut verileri içe aktar", "popup_before_close": "Kapatma onayı açılır penceresini etkinleştir", "disable_hardware_acceleration": "Donanım hızlandırmayı devre dışı bırak", "restart_for_change": "Değişikliğin etkili olması için programı yeniden başlatmanız gerekiyor!", "font": "Yazı tipi", "font_family": "Font ailesi", + "font_style": "Yazı tipi stili", "font_size": "Yazı Boyutu", "border_radius": "Kenarlık yarıçapı", "colors": "Renkler", @@ -1124,11 +1165,13 @@ "auto": "Otomatik", "optimized": "Optimize et", "reduced": "Azaltılmış", - "full": "Tam dolu" + "full": "Tam dolu", + "section_trigger_action": "Sunumu bir bölüme yönlendirirken tetikleyici eylem" }, "sort": { "sort_by": "Göre sırala", "name": "İsim", + "name_des": "İsim Azalan", "date": "Tarih", "size": "Boyut", "type": "Tip", @@ -1172,6 +1215,7 @@ "reference": "Show reference", "split_reference": "Bölünmüş referans", "combine_with_text": "Metinle birleştir", + "first_slide_reference": "İlk slayttaki referans", "reference_at_bottom": "En alta taşı", "red_jesus": "Jesus words in red", "search": "Kutsal Kitap'ta Ara" diff --git a/public/lang/zh_CN.json b/public/lang/zh_CN.json index 56cf43d8..8109c3f9 100644 --- a/public/lang/zh_CN.json +++ b/public/lang/zh_CN.json @@ -39,9 +39,9 @@ "right": "右", "bottom": "下", "left": "左", - "centered": "Centered", - "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition", - "cropping_tip": "Fine adjust the window content position by cropping the sides" + "centered": "居中", + "edge_blending_tip": "混合多个输出/投影仪以实现更无缝过渡", + "cropping_tip": "通过裁剪边缘微调窗口内容位置" }, "about": { "check_updates": "检查更新...", @@ -319,7 +319,7 @@ "primary-darkest": "主要最暗色", "text": "文本", "textInvert": "反转文字", - "secondary-text": "Selections", + "secondary-text": "选项", "secondary": "辅助色", "secondary-opacity": "辅助透明度", "hover": "悬浮", @@ -416,7 +416,7 @@ "import": "导入", "songbeamer_import": "Songbeamer 导入", "export": "导出", - "importing": "Importing...", + "importing": "正在导入…", "import_scripture": "导入圣经", "player": "播放器", "edit_event": "编辑事件", @@ -424,7 +424,7 @@ "history": "历史记录", "action": "动作", "category_action": "分类动作", - "user_data_overwrite": "Found existing data", + "user_data_overwrite": "发现已有数据", "connect": "连接", "cloud_update": "与云端同步", "cloud_method": "数据位置", @@ -433,7 +433,7 @@ "manage_icons": "管理图标", "manage_colors": "管理颜色", "choose_camera": "选择相机", - "manage_tags": "Manage tags", + "manage_tags": "管理标签", "initialize": "欢迎使用 FreeShow", "unsaved": "你确定要退出吗?", "cancel": "取消", @@ -470,7 +470,7 @@ "deleted_cache": "已删除媒体缩略图缓存。", "no_songswords_easyworship": "缺少 SongsWords.db 文件。", "delete_shows_empty": "没有节目可删除。", - "output_capture_enabled": "Output screen capture enabled, this may cause performance issues. Only use if needed!", + "output_capture_enabled": "已启用输出屏幕捕获,这可能会导致性能问题。仅在需要时使用!", "midi_no_project": "收到更改项目的触发信号,但未在指定索引处未找到项目:", "midi_no_show": "接收到开始播放幻灯片的指令,但当前没有正在进行的节目。", "midi_no_slide": "收到开始幻灯片的触发信号,但未在以下序号处找到幻灯片:", @@ -533,7 +533,7 @@ "view_private": "节目私有", "import": "导入", "export": "导出", - "imported": "Imported!", + "imported": "已导入!", "duplicate": "创建副本", "delete": "删除", "delete_slide": "删除幻灯片", @@ -564,10 +564,10 @@ "reset": "重置", "create_template": "创建模板", "project_template_tip": "使用此模板创建新项目", - "pdf_single_page": "Render as one document", + "pdf_single_page": "渲染为一个文档", "convert_to_images": "转换成图片", "converting": "正在转换…", - "closing": "Closing app...", + "closing": "正在关闭应用程序…", "remove_template_from_show": "从节目中移除模板", "reset_defaults": "重置默认设置", "to_all": "全部应用", @@ -742,7 +742,7 @@ "export": { "export": "导出", "export_as": "将 {} 导出为", - "exporting": "Exporting...", + "exporting": "正在导出…", "exported": "已导出!", "oneFile": "单文件", "selected_shows": "已选的节目", @@ -750,7 +750,7 @@ "all_shows": "全部节目", "all_projects": "全部项目", "project": "项目", - "include_media": "Include media files", + "include_media": "包含媒体文件", "preview": "预览", "title": "标题", "metadata": "元数据", @@ -782,7 +782,7 @@ "toggle_output": "切换输出", "move_to_front": "移到最前", "hide_from_preview": "在预览中隐藏", - "enable_preview": "Enable preview", + "enable_preview": "启用预览", "lock_to_output": "锁定输出", "place_under_slide": "置于幻灯片下方", "toggle_clock": "切换时间", @@ -825,14 +825,14 @@ "background_media": "背景媒体", "overlay_content": "添加叠加内容", "different_first_template": "在第一张幻灯片上使用自定义模板", - "max_lines_per_slide": "Max lines per slide", + "max_lines_per_slide": "每张幻灯片的最大行数", "media_fit": "媒体适应", "one_letter": "单字母模式", "sub_indexes": "子索引", "size": "大小", "max_lines": "最大行数", "invert_items": "反转元素", - "list": "List", + "list": "列表", "chords": "合弦", "transpose": "转置", "auto_size": "自动调整大小", @@ -856,7 +856,7 @@ "outline": "描边", "shadow": "阴影", "shadow_inset": "内阴影", - "offset": "Offset", + "offset": "偏移", "offsetX": "X 轴偏移", "offsetY": "Y 轴偏移", "blur": "模糊", @@ -1075,7 +1075,7 @@ "manual_input_hint": "找不到显示屏?点击这里手动改变位置。", "manual_drag_hint": "可通过按住 Ctrl/Cmd 键并拖动激活的输出窗口来手动更改其位置。", "allow_main_screen": "允许自定义输出位置和大小", - "edge_blending": "Edge blending", + "edge_blending": "边缘融合", "identify_screens": "识别屏幕", "new_output": "新建输出", "normal": "正常", @@ -1125,15 +1125,15 @@ "show_location": "节目位置", "data_location": "数据位置", "user_data_location": "将个人设置存储在“数据位置”", - "user_data_exists": "Found existing data at custom location, would you like to overwrite it?", - "user_data_yes": "Yes, keep current data", - "user_data_no": "No, import existing data", + "user_data_exists": "在自定义位置发现已有数据,是否要覆盖它?", + "user_data_yes": "是,保留当前数据", + "user_data_no": "否,导入已有数据", "popup_before_close": "关闭时显示确认弹窗", "disable_hardware_acceleration": "禁用硬件加速", "restart_for_change": "你必须重启程序才能使修改生效!", "font": "字体", "font_family": "字体", - "font_style": "Font style", + "font_style": "字体样式", "font_size": "字体大小", "border_radius": "边框圆角", "colors": "颜色", @@ -1171,7 +1171,7 @@ "sort": { "sort_by": "排序", "name": "名称", - "name_des": "Name Descending", + "name_des": "名称降序", "date": "日期", "size": "大小", "type": "类型", diff --git a/rollup.config.mjs b/rollup.config.mjs index fa8cef04..426f8f1e 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -98,6 +98,11 @@ function mainApp() { mangle: true, }), ], + onwarn: (warning, warn) => { + // many false Svelte "Circular dependencies" warnings + if (warning.code === "CIRCULAR_DEPENDENCY") return + warn(warning) + }, watch: { clearScreen: false, }, diff --git a/src/electron/capture/CaptureHelper.ts b/src/electron/capture/CaptureHelper.ts index 23ecd03d..cd54119b 100644 --- a/src/electron/capture/CaptureHelper.ts +++ b/src/electron/capture/CaptureHelper.ts @@ -1,10 +1,10 @@ import type { BrowserWindow, Display, NativeImage, Size } from "electron" import electron from "electron" import { NdiSender } from "../ndi/NdiSender" -import { CaptureTransmitter } from "./helpers/CaptureTransmitter" -import { CaptureOptions } from "./CaptureOptions" -import { CaptureLifecycle } from "./helpers/CaptureLifecycle" import { OutputHelper } from "../output/OutputHelper" +import type { CaptureOptions } from "./CaptureOptions" +import { CaptureLifecycle } from "./helpers/CaptureLifecycle" +import { CaptureTransmitter } from "./helpers/CaptureTransmitter" export class CaptureHelper { static Lifecycle = CaptureLifecycle diff --git a/src/electron/capture/CaptureOptions.ts b/src/electron/capture/CaptureOptions.ts index b10f590a..837389a0 100644 --- a/src/electron/capture/CaptureOptions.ts +++ b/src/electron/capture/CaptureOptions.ts @@ -1,4 +1,4 @@ -import { BrowserWindow } from "electron" +import type { BrowserWindow } from "electron" export type CaptureOptions = { id: string diff --git a/src/electron/capture/helpers/CaptureLifecycle.ts b/src/electron/capture/helpers/CaptureLifecycle.ts index 96a4771d..6f81a432 100644 --- a/src/electron/capture/helpers/CaptureLifecycle.ts +++ b/src/electron/capture/helpers/CaptureLifecycle.ts @@ -1,6 +1,6 @@ -import { NativeImage } from "electron" -import { CaptureHelper } from "../CaptureHelper" +import type { NativeImage } from "electron" import { OutputHelper } from "../../output/OutputHelper" +import { CaptureHelper } from "../CaptureHelper" import { CaptureTransmitter } from "./CaptureTransmitter" export class CaptureLifecycle { diff --git a/src/electron/data/defaults.ts b/src/electron/data/defaults.ts index 858187d7..cd71b1e6 100644 --- a/src/electron/data/defaults.ts +++ b/src/electron/data/defaults.ts @@ -43,10 +43,6 @@ export const defaultSettings: { [key in SaveListSettings]: any } = { maxConnections: 10, mediaFolders: {}, audioFolders: {}, - playerVideos: { - chosen: { name: "The Chosen Trailer", type: "youtube", id: "X-AJdKty74M" }, - jesus: { name: "Jesus, Only Jesus", type: "vimeo", id: "426363743" }, - }, resized: { leftPanel: 290, rightPanel: 290, @@ -122,6 +118,11 @@ export const defaultSyncedSettings: { [key in SaveListSyncedSettings]: any } = { }, groups: defaultGroups, midiIn: {}, + emitters: {}, + playerVideos: { + chosen: { name: "The Chosen Trailer", type: "youtube", id: "X-AJdKty74M" }, + jesus: { name: "Jesus, Only Jesus", type: "vimeo", id: "426363743" }, + }, videoMarkers: {}, mediaTags: {}, customizedIcons: { disabled: [], svg: [] }, diff --git a/src/electron/data/export.ts b/src/electron/data/export.ts index d858683e..f8c871a2 100644 --- a/src/electron/data/export.ts +++ b/src/electron/data/export.ts @@ -2,16 +2,16 @@ // Export as TXT or PDF // When exporting as PDF we create a new window and capture its content +import AdmZip from "adm-zip" import { BrowserWindow, ipcMain } from "electron" import fs from "fs" import { join } from "path" import { EXPORT, MAIN, STARTUP } from "../../types/Channels" +import type { Message } from "../../types/Socket" import { isProd, toApp } from "../index" import { createFolder, dataFolderNames, doesPathExist, getDataFolder, getShowsFromIds, getTimePointString, makeDir, openSystemFolder, parseShow, readFile, selectFolderDialog } from "../utils/files" -import { exportOptions } from "../utils/windowOptions" -import { Message } from "../../types/Socket" import { getAllShows } from "../utils/shows" -import AdmZip from "adm-zip" +import { exportOptions } from "../utils/windowOptions" // SHOW: .show, PROJECT: .project, BIBLE: .fsb const customJSONExtensions: any = { diff --git a/src/electron/index.ts b/src/electron/index.ts index 1cee8645..dc92bd69 100644 --- a/src/electron/index.ts +++ b/src/electron/index.ts @@ -8,6 +8,7 @@ import { BIBLE, IMPORT } from "./../types/Channels" import { cloudConnect } from "./cloud/cloud" import { currentlyDeletedShows } from "./cloud/drive" import { startBackup } from "./data/backup" +import { defaultSettings, defaultSyncedSettings } from "./data/defaults" import { startExport } from "./data/export" import { config, getStore, stores, updateDataPath, userDataPath } from "./data/store" import { NdiReceiver } from "./ndi/NdiReceiver" @@ -15,13 +16,12 @@ import { receiveNDI } from "./ndi/talk" import { OutputHelper } from "./output/OutputHelper" import { closeServers } from "./servers" import { stopApiListener } from "./utils/api" -import { checkShowsFolder, dataFolderNames, deleteFile, getDataFolder, loadShows, writeFile } from "./utils/files" +import { checkShowsFolder, dataFolderNames, deleteFile, doesPathExist, getDataFolder, loadShows, writeFile } from "./utils/files" import { template } from "./utils/menuTemplate" import { stopMidi } from "./utils/midi" import { catchErrors, loadScripture, loadShow, receiveMain, saveRecording, startImport } from "./utils/responses" -import { loadingOptions, mainOptions } from "./utils/windowOptions" import { renameShows } from "./utils/shows" -import { defaultSettings, defaultSyncedSettings } from "./data/defaults" +import { loadingOptions, mainOptions } from "./utils/windowOptions" // ----- STARTUP ----- @@ -46,8 +46,7 @@ if (!config.get("loaded")) console.error("Could not get stored data!") // info console.log("Starting FreeShow...") -if (!isProd) console.log("Building app! This may take 20-90 seconds") -if (isLinux) console.log("libva error on Linux can be ignored") +if (!isProd) console.log("Building app! (This may take 20-90 seconds)") // set application menu setGlobalMenu() @@ -147,28 +146,37 @@ function createMain() { mainWindow.setMinimumSize(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE) // this is to debug any weird positioning - console.log("Main Window Bounds:", mainWindow.getBounds()) + // console.log("Main Window Bounds:", mainWindow.getBounds()) loadWindowContent(mainWindow) setMainListeners() - // open devtools - if (!isProd) mainWindow.webContents.openDevTools() - if (RECORD_STARTUP_TIME) console.timeEnd("Main window") } -export function loadWindowContent(window: BrowserWindow, type: null | "output" = null) { +function openDevTools(window: BrowserWindow) { + console.log('Opening DevTools... ("[ERROR:CONSOLE] Request Autofill" can be ignored)') + window.webContents.openDevTools() + // ERROR:CONSOLE(1)] "Request Autofill.enable failed. - can be ignored: + // https://github.com/electron/electron/issues/41614 +} + +export async function loadWindowContent(window: BrowserWindow, type: null | "output" = null) { let mainOutput = type === null if (mainOutput && RECORD_STARTUP_TIME) console.time("Main window content") - if (mainOutput) console.log("Loading main window content") + if (isProd) window.loadFile("public/index.html").catch(error) - else window.loadURL("http://localhost:3000").catch(error) + else { + // load development environment + if (mainOutput) { + await waitForBundle() + openDevTools(window) + } + window.loadURL("http://localhost:3000").catch(error) + } window.webContents.on("did-finish-load", () => { - // if (window === mainWindow) type = null // make sure type is correct window.webContents.send(STARTUP, { channel: "TYPE", data: type }) - if (mainOutput) retryLoadingContent() }) function error(err: any) { @@ -177,24 +185,29 @@ export function loadWindowContent(window: BrowserWindow, type: null | "output" = } } -// retry loading until content has finshed building -const retryInterval = 10 -let tries = 0 -function retryLoadingContent() { - if (isProd) return - - setTimeout(() => { - if (isLoaded) return - - if (tries < 1) console.log("Loading content again. App is probably not finished building yet") - else if (tries > 15) { - console.log("Could not load app content. Please check console for any errors!") - return app.quit() - } else console.log("Trying to load content again") - tries++ - - mainWindow!.webContents.reload() - }, retryInterval * 1000) +// wait until the main Rollup bundle exists before loading +function waitForBundle() { + const BUNDLE_PATH = path.resolve(__dirname, "..", "..", "public/build/bundle.js") + const CHECK_INTERVAL = 2 // every 2 seconds + const MAX_SECONDS = 120 + let tries = 0 + + return new Promise((resolve) => { + const interval = setInterval(() => { + if (doesPathExist(BUNDLE_PATH)) { + console.log("Main bundle created! Loading interface...") + clearInterval(interval) + resolve(true) + } + + tries += CHECK_INTERVAL / MAX_SECONDS + if (tries >= 1) { + clearInterval(interval) + app.quit() + throw new Error("Could not load app content. Please check console for any errors!") + } + }, CHECK_INTERVAL * 1000) + }) } function setMainListeners() { diff --git a/src/electron/ndi/talk.ts b/src/electron/ndi/talk.ts index d04a18f1..504c3d91 100644 --- a/src/electron/ndi/talk.ts +++ b/src/electron/ndi/talk.ts @@ -1,5 +1,5 @@ import { NDI } from "../../types/Channels" -import { Message } from "../../types/Socket" +import type { Message } from "../../types/Socket" import { CaptureHelper } from "../capture/CaptureHelper" import { NdiReceiver } from "./NdiReceiver" diff --git a/src/electron/output/Output.ts b/src/electron/output/Output.ts index 093f89fd..4baed47d 100644 --- a/src/electron/output/Output.ts +++ b/src/electron/output/Output.ts @@ -1,5 +1,5 @@ -import { BrowserWindow } from "electron" -import { CaptureOptions } from "../capture/CaptureOptions" +import type { BrowserWindow } from "electron" +import type { CaptureOptions } from "../capture/CaptureOptions" export class Output { window: BrowserWindow diff --git a/src/electron/output/OutputHelper.ts b/src/electron/output/OutputHelper.ts index dccfc69f..0898fba1 100644 --- a/src/electron/output/OutputHelper.ts +++ b/src/electron/output/OutputHelper.ts @@ -1,14 +1,14 @@ +import { toApp } from ".." +import { OUTPUT } from "../../types/Channels" +import type { Message } from "../../types/Socket" +import { CaptureHelper } from "../capture/CaptureHelper" import { OutputBounds } from "./helpers/OutputBounds" import { OutputIdentify } from "./helpers/OutputIdentify" +import { OutputLifecycle } from "./helpers/OutputLifecycle" import { OutputSend } from "./helpers/OutputSend" import { OutputValues } from "./helpers/OutputValues" import { OutputVisibility } from "./helpers/OutputVisibility" -import { OutputLifecycle } from "./helpers/OutputLifecycle" -import { Message } from "../../types/Socket" -import { toApp } from ".." -import { OUTPUT } from "../../types/Channels" -import { Output } from "./Output" -import { CaptureHelper } from "../capture/CaptureHelper" +import type { Output } from "./Output" export class OutputHelper { static receiveOutput(_e: any, msg: Message) { diff --git a/src/electron/output/helpers/OutputBounds.ts b/src/electron/output/helpers/OutputBounds.ts index 353579fb..bc08b5cf 100644 --- a/src/electron/output/helpers/OutputBounds.ts +++ b/src/electron/output/helpers/OutputBounds.ts @@ -1,7 +1,7 @@ -import { BrowserWindow, screen } from "electron" -import { OutputHelper } from "../OutputHelper" +import { screen, type BrowserWindow } from "electron" import { toApp } from "../.." import { OUTPUT } from "../../../types/Channels" +import { OutputHelper } from "../OutputHelper" export class OutputBounds { // BOUNDS diff --git a/src/electron/output/helpers/OutputLifecycle.ts b/src/electron/output/helpers/OutputLifecycle.ts index 7d492e7d..429848ac 100644 --- a/src/electron/output/helpers/OutputLifecycle.ts +++ b/src/electron/output/helpers/OutputLifecycle.ts @@ -1,13 +1,13 @@ import { BrowserWindow } from "electron" import { OUTPUT_CONSOLE, isMac, loadWindowContent, mainWindow, toApp } from "../.." -import { Output } from "../../../types/Output" +import { OUTPUT } from "../../../types/Channels" +import type { Output } from "../../../types/Output" +import { CaptureHelper } from "../../capture/CaptureHelper" import { NdiSender } from "../../ndi/NdiSender" import { setDataNDI } from "../../ndi/talk" +import { wait } from "../../utils/helpers" import { outputOptions } from "../../utils/windowOptions" import { OutputHelper } from "../OutputHelper" -import { OUTPUT } from "../../../types/Channels" -import { CaptureHelper } from "../../capture/CaptureHelper" -import { wait } from "../../utils/helpers" export class OutputLifecycle { static async createOutput(output: Output) { diff --git a/src/electron/output/helpers/OutputValues.ts b/src/electron/output/helpers/OutputValues.ts index 22904b2d..21b36965 100644 --- a/src/electron/output/helpers/OutputValues.ts +++ b/src/electron/output/helpers/OutputValues.ts @@ -1,4 +1,4 @@ -import { BrowserWindow } from "electron" +import type { BrowserWindow } from "electron" import { CaptureHelper } from "../../capture/CaptureHelper" import { NdiSender } from "../../ndi/NdiSender" import { OutputHelper } from "../OutputHelper" diff --git a/src/electron/utils/LyricSearch.ts b/src/electron/utils/LyricSearch.ts index 88ab3443..469d7108 100644 --- a/src/electron/utils/LyricSearch.ts +++ b/src/electron/utils/LyricSearch.ts @@ -1,7 +1,7 @@ import axios from "axios" export type LyricSearchResult = { - source: string + source: "Genius" | "Hymnary" | "Letras" key: string artist: string title: string @@ -10,13 +10,18 @@ export type LyricSearchResult = { export class LyricSearch { static search = async (artist: string, title: string) => { - const results = await Promise.all([LyricSearch.searchGenius(artist, title), LyricSearch.searchHymnary(title)]) + const results = await Promise.all([ + LyricSearch.searchGenius(artist, title), + LyricSearch.searchHymnary(title), + LyricSearch.searchLetras(title), + ]) return results.flat() } static get(song: LyricSearchResult) { if (song.source === "Genius") return LyricSearch.getGenius(song) else if (song.source === "Hymnary") return LyricSearch.getHymnary(song) + else if (song.source === "Letras") return LyricSearch.getLetras(song) return Promise.resolve("") } @@ -83,10 +88,53 @@ export class LyricSearch { const url = `https://hymnary.org/text/${song.key}` const response = await axios.get(url) const html = await response.data - const regex = /
(.*?)<\/div>/gs - const match = regex.exec(html) + return this.getLyricFromHtml(html, /
(.*?)<\/div>/gs); + } + + private static convertHymnaryToResult = (hymnaryResult: any, originalQuery: string) => { + return { + source: "Hymnary", + key: hymnaryResult[4], + artist: hymnaryResult[6], + title: hymnaryResult[0], + originalQuery: originalQuery, + } as LyricSearchResult + } + + //Letras + private static searchLetras = async (title: string) => { + try { + const url = `https://solr.sscdn.co/letras/m1/?q=${encodeURIComponent(title)}` + const response = await axios.get(url) + const json = JSON.parse(response.data.replace('LetrasSug(', '').slice(0, -2)) + const songs = json.response.docs.filter((d: any) => d.id.startsWith("mus")) + return songs.map((s: any) => LyricSearch.convertLetrasToResult(s, title)) + } catch (ex) { + console.log(ex) + return [] + } + } + + private static convertLetrasToResult = (letrasResult: any, originalQuery: string) => { + return { + source: "Letras", + key: `${letrasResult.dns}/${letrasResult.url}`, + artist: letrasResult.art, + title: letrasResult.txt, + originalQuery: originalQuery, + } as LyricSearchResult + } + + private static getLetras = async (song: LyricSearchResult) => { + const url = `https://www.letras.mus.br/${song.key}` + const response = await axios.get(url) + const html = await response.data + return this.getLyricFromHtml(html, /
(.*?)<\/div>/gs); + } + private static getLyricFromHtml = (songHtml: string, regex: RegExp) => { let result = "" + const match = regex.exec(songHtml) if (match) { result = match[0] result = result.replace(//gi, "\n") @@ -104,18 +152,7 @@ export class LyricSearch { }) result = newLines.join("\n").trim() } - - return result - } - - private static convertHymnaryToResult = (hymnaryResult: any, originalQuery: string) => { - return { - source: "Hymnary", - key: hymnaryResult[4], - artist: hymnaryResult[6], - title: hymnaryResult[0], - originalQuery: originalQuery, - } as LyricSearchResult + return result; } // ref: http://stackoverflow.com/a/1293163/2343 diff --git a/src/electron/utils/api.ts b/src/electron/utils/api.ts index 461b746e..1f6cce52 100644 --- a/src/electron/utils/api.ts +++ b/src/electron/utils/api.ts @@ -130,6 +130,42 @@ function startOSC(PORT: number | undefined) { osc.open({ port: PORT }) } +// let OSC_SENDER: null | OSC = null // must work with different ip:port +export function emitOSC(msg: any) { + if (typeof msg.data !== "string") return + + // if (!OSC_SENDER) { + const OSC_SENDER = new OSC({ plugin: new OSC.DatagramPlugin() }) // UDP + + const options: any = { port: msg.signal?.port || 8080, host: msg.signal?.host || "localhost" } + + OSC_SENDER.on("open", sendMessage) + OSC_SENDER.open(options) + // return + // } + + // const IS_OPEN = 1 + // if (OSC_SENDER?.status() === IS_OPEN) { + // sendMessage() + // } + + OSC_SENDER.on("error", (err: any) => { + console.error("OSC EMIT ERROR:", err) + OSC_SENDER.close() + }) + + function sendMessage() { + if (!OSC_SENDER) return + + const message = new OSC.Message(msg.data) + console.log(`Emitting OSC at ${options.host}:${options.port}:`, message.address) + + OSC_SENDER.send(message) + // ensure message is sent + setTimeout(() => OSC_SENDER.close(), 100) + } +} + // DATA async function receivedData(data: any = {}, log: any) { diff --git a/src/electron/utils/files.ts b/src/electron/utils/files.ts index d161876d..357ccb36 100644 --- a/src/electron/utils/files.ts +++ b/src/electron/utils/files.ts @@ -7,7 +7,8 @@ import fs, { type Stats } from "fs" import path, { join, parse } from "path" import { uid } from "uid" import { FILE_INFO, MAIN, OPEN_FOLDER, OUTPUT, READ_FOLDER, SHOW, STORE } from "../../types/Channels" -import { Show } from "../../types/Show" +import type { Subtitle } from "../../types/Main" +import type { Show } from "../../types/Show" import { imageExtensions, mimeTypes, videoExtensions } from "../data/media" import { stores } from "../data/store" import { createThumbnail } from "../data/thumbnails" @@ -453,6 +454,82 @@ function getMimeType(path: string) { return mimeTypes[ext] || "" } +// get embedded subtitles/captions +export function getMediaTracks(data: any) { + extractSubtitles(data) +} + +async function extractSubtitles(data: any) { + const MP4Box = require("mp4box") + let arrayBuffer: any + + try { + arrayBuffer = new Uint8Array(fs.readFileSync(data.path)).buffer + } catch (err) { + console.error(err) + toApp(MAIN, { channel: "MEDIA_TRACKS", data: { ...data, tracks: [] } }) + return + } + + const mp4boxfile = MP4Box.createFile() + mp4boxfile.onError = (e: Error) => console.error("MP4Box error:", e) + mp4boxfile.onReady = (info: any) => { + let tracks: Subtitle[] = [] + let trackCount: number = 0 + let completed: number = 0 + info.tracks.forEach((track: any) => { + if (track.type !== "subtitles" && track.type !== "text") return + trackCount++ + + // console.log(`Found subtitle track ID ${track.id}, language: ${track.language}`) + const vttLines = ["WEBVTT\n"] + const timescale = track.timescale + + mp4boxfile.setExtractionOptions(track.id, null, { nbSamples: track.nb_samples }) + mp4boxfile.start() + + mp4boxfile.onSamples = (_id: number, _user: any, samples: any[]) => { + let index = 1 + samples.forEach((sample) => { + const utf8Decoder = new TextDecoder("utf-8") + let subtitleText = utf8Decoder.decode(sample.data).trim() + // remove any non-printable characters (excluding line breaks) + subtitleText = subtitleText.replace(/[^\x20-\x7E\n\r]+/g, "") + if (!subtitleText) return + + const startTime = formatTimestamp((sample.cts / timescale) * 1000) + const endTime = formatTimestamp(((sample.cts + sample.duration) / timescale) * 1000) + + vttLines.push(`${index}`) + vttLines.push(`${startTime} --> ${endTime}`) + vttLines.push(`${subtitleText}\n`) + + index++ + }) + + completed++ + if (vttLines.length > 1) tracks.push({ lang: track.language?.slice(0, 2), name: track.language || "", vtt: vttLines.join("\n"), embedded: true }) + if (completed === trackCount) toApp(MAIN, { channel: "MEDIA_TRACKS", data: { ...data, tracks } }) + } + }) + } + + arrayBuffer.fileStart = 0 + mp4boxfile.appendBuffer(arrayBuffer) + mp4boxfile.flush() +} + +// format timestamp in WebVTT format (HH:MM:SS.mmm) +function formatTimestamp(timestamp: number) { + const hours = Math.floor(timestamp / 3600000) + const minutes = Math.floor((timestamp % 3600000) / 60000) + const seconds = Math.floor((timestamp % 60000) / 1000) + const milliseconds = Math.floor(timestamp % 1000) + + const formatted = [hours.toString().padStart(2, "0"), minutes.toString().padStart(2, "0"), seconds.toString().padStart(2, "0") + "." + milliseconds.toString().padStart(3, "0")].join(":") + return formatted +} + ////// // SEARCH FOR MEDIA FILE (in drawer media folders & their following folders) diff --git a/src/electron/utils/responses.ts b/src/electron/utils/responses.ts index 17393fcd..86f53cf4 100644 --- a/src/electron/utils/responses.ts +++ b/src/electron/utils/responses.ts @@ -17,8 +17,8 @@ import { captureSlide, getThumbnail, getThumbnailFolderPath, saveImage } from ". import { OutputHelper } from "../output/OutputHelper" import { getPresentationApplications, presentationControl, startSlideshow } from "../output/ppt/presentation" import { closeServers, startServers } from "../servers" -import { Message } from "./../../types/Socket" -import { apiReturnData, startWebSocketAndRest, stopApiListener } from "./api" +import type { Message } from "./../../types/Socket" +import { apiReturnData, emitOSC, startWebSocketAndRest, stopApiListener } from "./api" import { bundleMediaFiles, checkShowsFolder, @@ -29,6 +29,7 @@ import { getFileInfo, getFolderContent, getMediaCodec, + getMediaTracks, getPaths, getSimularPaths, getTempPaths, @@ -126,6 +127,7 @@ const mainResponses: any = { SAVE_IMAGE: (data: any): any => saveImage(data), READ_EXIF: (data: any, e: any) => readExifData(data, e), MEDIA_CODEC: (data: any) => getMediaCodec(data), + MEDIA_TRACKS: (data: any) => getMediaTracks(data), DOWNLOAD_MEDIA: (data: any) => downloadMedia(data), MEDIA_BASE64: (data: any) => storeMedia(data), CAPTURE_SLIDE: (data: any) => captureSlide(data), @@ -140,10 +142,11 @@ const mainResponses: any = { // SERVERS START: (data: any): void => startServers(data), STOP: (): void => closeServers(), - // WebSocket / REST + // WebSocket / REST / OSC WEBSOCKET_START: (port: number | undefined) => startWebSocketAndRest(port), WEBSOCKET_STOP: () => stopApiListener(), API_TRIGGER: (data: any) => apiReturnData(data), + EMIT_OSC: (data: any) => emitOSC(data), // MIDI GET_MIDI_OUTPUTS: (): string[] => getMidiOutputs(), GET_MIDI_INPUTS: (): string[] => getMidiInputs(), @@ -182,6 +185,7 @@ const mainResponses: any = { BUNDLE_MEDIA_FILES: (data: any) => bundleMediaFiles(data), FILE_INFO: (data: any, e: any) => getFileInfo(data, e), READ_FOLDER: (data: any) => getFolderContent(data), + READ_FILE: (data: any) => ({ ...data, content: readFile(data.path) }), OPEN_FOLDER: (data: any, e: any) => selectFolder(data, e), OPEN_FILE: (data: any, e: any) => selectFiles(data, e), } diff --git a/src/electron/utils/shows.ts b/src/electron/utils/shows.ts index e6982869..567f562e 100644 --- a/src/electron/utils/shows.ts +++ b/src/electron/utils/shows.ts @@ -1,7 +1,7 @@ import path from "path" import { toApp } from ".." import { MAIN } from "../../types/Channels" -import { Show } from "../../types/Show" +import type { Show } from "../../types/Show" import { deleteFile, doesPathExist, parseShow, readFile, readFileAsync, readFolder, readFolderAsync, renameFile } from "./files" export function getAllShows(data: any) { diff --git a/src/electron/utils/updater.ts b/src/electron/utils/updater.ts index 68590183..bc34db61 100644 --- a/src/electron/utils/updater.ts +++ b/src/electron/utils/updater.ts @@ -1,9 +1,12 @@ // import { Notification, app } from "electron" import { autoUpdater } from "electron-updater" +import { isProd } from ".." // let notification: Notification | null export default function checkForUpdates() { + if (!isProd) return + autoUpdater.checkForUpdatesAndNotify() // { // title: app.getName(), diff --git a/src/electron/utils/windowOptions.ts b/src/electron/utils/windowOptions.ts index 8635f376..999f75cf 100644 --- a/src/electron/utils/windowOptions.ts +++ b/src/electron/utils/windowOptions.ts @@ -2,9 +2,9 @@ // Options for electron windows // https://www.electronjs.org/docs/latest/api/browser-window +import type { BrowserWindowConstructorOptions } from "electron" import { join } from "path" import { isMac, isProd } from ".." -import { BrowserWindowConstructorOptions } from "electron" export const loadingOptions: BrowserWindowConstructorOptions = { width: 500, diff --git a/src/frontend/App.svelte b/src/frontend/App.svelte index d0240b2e..7d55bdb1 100644 --- a/src/frontend/App.svelte +++ b/src/frontend/App.svelte @@ -14,7 +14,7 @@ import QuickSearch from "./components/quicksearch/QuickSearch.svelte" import Center from "./components/system/Center.svelte" import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay, outputs } from "./stores" - import { focusArea, logerror, startAutosave, toggleRemoteStream } from "./utils/common" + import { focusArea, logerror, mainClick, startAutosave, toggleRemoteStream } from "./utils/common" import { keydown } from "./utils/shortcuts" import { startup } from "./utils/startup" @@ -44,7 +44,7 @@ $: if ($currentWindow === "output" && Object.values($outputs)[0]?.blending) blending = getBlending() - + {#if $currentWindow === "pdf"} diff --git a/src/frontend/components/actions/ChooseEmitter.svelte b/src/frontend/components/actions/ChooseEmitter.svelte new file mode 100644 index 00000000..0106caa5 --- /dev/null +++ b/src/frontend/components/actions/ChooseEmitter.svelte @@ -0,0 +1,93 @@ + + + +

+ updateValue("emitter", e.detail.id)} /> +
+ +{#if value.emitter} + +

+ updateValue("template", e.detail.id)} /> +
+ + {#if templateInputs.length} + {#each templateInputs as input, i} + + + setTemplateValue(i, e)} /> + + {/each} + {:else} + !a.name && !a.value)} + items={customTemplateInputs} + let:item={input} + on:add={createTemplateValue} + on:delete={(e) => removeTemplateValue(e.detail)} + allowOpen={false} + nothingText={false} + > +
+ setTemplateValue(input.id, e, "name")} style="width: 50%;" /> + setTemplateValue(input.id, e, "value")} /> +
+
+ {/if} +{/if} diff --git a/src/frontend/components/actions/CreateAction.svelte b/src/frontend/components/actions/CreateAction.svelte index 8477b08a..3adebbb6 100644 --- a/src/frontend/components/actions/CreateAction.svelte +++ b/src/frontend/components/actions/CreateAction.svelte @@ -25,38 +25,25 @@ pickAction = false } - const removeFromSlideAction = [ - "next_project_item", - "previous_project_item", - "name_select_show", - "next_slide", - "previous_slide", - "random_slide", - "index_select_slide", - "name_select_slide", - "id_select_group", - "lock_output", - "toggle_output_windows", - "restore_output", - "clear_all", - "clear_slide", - "clear_next_timer", - "name_select_overlay", - "name_start_timer", - "id_start_timer", - "playlist_next", - "id_select_output_style", - "id_select_stage_layout", - "change_stage_output_layout", - "change_transition", - "change_variable", - "start_camera", - "toggle_action", - "send_rest_command", + const slideActions = [ + "start_show", + "set_template", + "clear_background", + "clear_overlays", + "clear_audio", + "change_volume", + "start_audio_stream", + "start_playlist", + "start_metronome", + "start_slide_timers", + "stop_timers", + "start_slide_recording", + "change_output_style", + "start_trigger", + "send_midi", ] // remove actions that are not fully implemented to CustomInput yet const removeActions = ["change_transition"] - if (list) removeActions.push(...removeFromSlideAction) if ($popupData.mode !== "template") removeActions.push("run_action") $: ACTIONS = [ @@ -82,7 +69,9 @@ // if (actionData[actionId]?.input) return true // remove already added or custom ones if (removeActions.includes(id) || existingActions.includes(id)) return false - if (list && id.includes("index_select")) return false + // custom slide actions list + if (list && !slideActions.includes(id)) return false + // if (list && id.includes("index_select")) return false return true }), // custom special @@ -137,7 +126,7 @@
{/if} {:else} - + 1 || !actionId ? "border-top: 2px solid var(--primary-lighter);" : ""}>

diff --git a/src/frontend/components/actions/CustomInput.svelte b/src/frontend/components/actions/CustomInput.svelte index f9487951..2e5cb771 100644 --- a/src/frontend/components/actions/CustomInput.svelte +++ b/src/frontend/components/actions/CustomInput.svelte @@ -1,10 +1,9 @@ -{#if type !== "output"} - {#if playSlide} -

- {:else} -

- {/if} -{/if} - {#if type === "input"}

@@ -173,13 +166,3 @@

setValues("channel", Number(e.detail))} disabled={noActionOrDefaultValues && type !== "output" && !playSlide} />
- - diff --git a/src/frontend/components/actions/RestValues.svelte b/src/frontend/components/actions/RestValues.svelte index f05c92e5..bbb29b15 100644 --- a/src/frontend/components/actions/RestValues.svelte +++ b/src/frontend/components/actions/RestValues.svelte @@ -8,7 +8,16 @@ import type { API_rest_command } from "./api" - export let rest: API_rest_command + export let value: API_rest_command + export let emitter: boolean = false + $: rest = value + + // const REST_MESSAGE_INPUTS: Input[] = [ + // {name: "inputs.url", id: "url", type: "string", value: ""}, + // {name: "inputs.method", id: "method", type: "dropdown", value: "GET", options: [{ name: "GET" }, { name: "POST" }, { name: "PUT" }, { name: "DELETE" }]}, + // {name: "inputs.contentType", id: "contentType", type: "string", value: "application/json"}, + // {name: "inputs.payload", id: "payload", type: "string", value: "{}"}, + // ] /** * onChange Handler for URL component @@ -77,14 +86,16 @@

- updateContentType(e)} /> + updateContentType(e)} />
- -

- updatePayload(e)} /> -
+{#if !emitter} + +

+ updatePayload(e)} /> +
+{/if} +
+ + {:else if type === "timer"} {#if $activeTimers.length} diff --git a/src/frontend/components/drawer/pages/Actions.svelte b/src/frontend/components/drawer/pages/Actions.svelte index 5c2efaf0..dcb27406 100644 --- a/src/frontend/components/drawer/pages/Actions.svelte +++ b/src/frontend/components/drawer/pages/Actions.svelte @@ -1,6 +1,6 @@ -
+
{#if actions.length}
{#each actions as action} diff --git a/src/frontend/components/drawer/pages/OverlayActions.svelte b/src/frontend/components/drawer/pages/OverlayActions.svelte new file mode 100644 index 00000000..482318e2 --- /dev/null +++ b/src/frontend/components/drawer/pages/OverlayActions.svelte @@ -0,0 +1,80 @@ + + +
+ {#each actionsList as action} + {#if overlay[action.id]} +
+
+ +
+ {#if !isNaN(overlay[action.id])} +

{overlay[action.id]}s

+ {/if} +
+ {/if} + {/each} +
+ + diff --git a/src/frontend/components/drawer/pages/Overlays.svelte b/src/frontend/components/drawer/pages/Overlays.svelte index 288cf2fe..ceaa7416 100644 --- a/src/frontend/components/drawer/pages/Overlays.svelte +++ b/src/frontend/components/drawer/pages/Overlays.svelte @@ -14,6 +14,7 @@ import DropArea from "../../system/DropArea.svelte" import SelectElem from "../../system/SelectElem.svelte" import Card from "../Card.svelte" + import OverlayActions from "./OverlayActions.svelte" export let active: string | null export let searchValue: string = "" @@ -74,19 +75,22 @@ showPlayOnHover on:click={(e) => { if ($outLocked || e.ctrlKey || e.metaKey) return - if (e.target?.closest(".edit")) return + if (e.target?.closest(".edit") || e.target?.closest(".icons")) return setOutput("overlays", overlay.id, true) }} on:dblclick={(e) => { if (e.ctrlKey || e.metaKey) return - if (e.target?.closest(".edit")) return + if (e.target?.closest(".edit") || e.target?.closest(".icons")) return activeShow.set({ id: overlay.id, type: "overlay" }) activePage.set("show") if ($focusMode) focusMode.set(false) }} > + + + {#each overlay.items as item} diff --git a/src/frontend/components/drawer/timers/Timers.svelte b/src/frontend/components/drawer/timers/Timers.svelte index c6ce998f..abd315eb 100644 --- a/src/frontend/components/drawer/timers/Timers.svelte +++ b/src/frontend/components/drawer/timers/Timers.svelte @@ -12,7 +12,7 @@ export let searchValue - $: sortedTimers = sortByName(sortByName(keysToID(clone($timers))), "type") + $: sortedTimers = sortByName(sortByName(keysToID(clone($timers)), "name", true), "type") $: sortedTimersWithProject = sortedTimers.sort((a, b) => (list.includes(a.id) && !list.includes(b.id) ? -1 : 1)) $: filteredTimers = searchValue.length > 1 ? sortedTimersWithProject.filter((a) => a.name.toLowerCase().includes(searchValue.toLowerCase())) : sortedTimersWithProject diff --git a/src/frontend/components/edit/EditTools.svelte b/src/frontend/components/edit/EditTools.svelte index 8a2487fa..d3cbc629 100644 --- a/src/frontend/components/edit/EditTools.svelte +++ b/src/frontend/components/edit/EditTools.svelte @@ -1,7 +1,7 @@ + +{#if items.length} + {#each items as item} + + {#if allowOpen} + + {:else} + + {/if} + + + {/each} +{:else if nothingText} +
+ +
+ +
+{/if} + + + + diff --git a/src/frontend/components/input/HRule.svelte b/src/frontend/components/input/HRule.svelte new file mode 100644 index 00000000..50b76790 --- /dev/null +++ b/src/frontend/components/input/HRule.svelte @@ -0,0 +1,37 @@ + + +
+ {#if title} + + {/if} +
+ + diff --git a/src/frontend/components/input/Input.svelte b/src/frontend/components/input/Input.svelte new file mode 100644 index 00000000..dad06f34 --- /dev/null +++ b/src/frontend/components/input/Input.svelte @@ -0,0 +1,36 @@ + + +{#if input.type === "dropdown"} + +

+ changed(e)} /> +
+{:else if input.type === "checkbox"} + +

+ changed(e)} /> +
+{:else if customInputs[input.type]} + changed(e)} /> +{:else} + +

+ changed(e)} /> +
+{/if} diff --git a/src/frontend/components/input/Inputs.svelte b/src/frontend/components/input/Inputs.svelte new file mode 100644 index 00000000..c3598f45 --- /dev/null +++ b/src/frontend/components/input/Inputs.svelte @@ -0,0 +1,26 @@ + + +{#if title} + +{/if} + +{#each inputs as input} + {#if input?.type} + changed(e, input)} /> + {/if} +{/each} diff --git a/src/frontend/components/input/inputs.ts b/src/frontend/components/input/inputs.ts new file mode 100644 index 00000000..79e3c985 --- /dev/null +++ b/src/frontend/components/input/inputs.ts @@ -0,0 +1,76 @@ +import type { Option } from "../../../types/Main" +import type { Input, InputType } from "../../../types/Input" +import MidiValues from "../actions/MidiValues.svelte" +import RestValues from "../actions/RestValues.svelte" +import { clone, sortByName } from "../helpers/array" +import Checkbox from "../inputs/Checkbox.svelte" +import Dropdown from "../inputs/Dropdown.svelte" +import NumberInput from "../inputs/NumberInput.svelte" +import TextInput from "../inputs/TextInput.svelte" + +////// + +export const INPUT_MIDI: Input = { name: "", id: "", type: "midi", value: { type: "noteon", values: { note: 0, velocity: -1, channel: 1 } } } +export const INPUT_REST: Input = { name: "", id: "", type: "rest", value: { url: "", method: "", contentType: "", payload: "" } } + +////// + +const getInputValue = { + string: (e: any) => e.target.value, + number: (e: any) => Number(e.detail), + checkbox: (e: any) => e.target.checked, +} +export function getValue(e: any, type: InputType) { + const input = getInputValue[type] + if (input) return input(e) + return e.detail +} + +// multiple preset inputs +export const customInputs = { + midi: MidiValues, + rest: RestValues, +} + +// [DEFAULT] IN: value= OUT=on:change +export const commonInputs = { + string: TextInput, + number: NumberInput, + dropdown: Dropdown, // OUT=click + checkbox: Checkbox, // IN=checked +} + +// init values + +// WIP similar to convertToOptions() +export function initDropdownOptions(object: Option[] | { [key: string]: { name: string; [key: string]: any } }, addEmpty: boolean = false) { + let options: Option[] = [] + if (Array.isArray(object)) options = object + else options = sortByName(Object.keys(object).map((id) => ({ id, name: object[id].name }))) + + if (addEmpty) options = [{ name: "—", id: "" }, ...options] + return clone(options) +} + +export function getDropdownValue(options: Option[], id: string | undefined) { + return options.find((a) => a.id === id)?.name || "—" +} + +// get values + +export function getValues(inputs: Input[], data: Object) { + if (!Array.isArray(inputs)) return [] + + inputs = inputs.map((input) => { + if (!input.id) return { ...input, value: data || input.value } + + const keys = input.id.includes(".") ? input.id.split(".") : [input.id] + let value = data?.[keys[0]] + if (keys.length > 1) value = value?.[keys[1]] + + if (value === undefined) return input + return { ...input, value } + }) + + return inputs +} diff --git a/src/frontend/components/inputs/MediaPicker.svelte b/src/frontend/components/inputs/MediaPicker.svelte index bb1704e4..c21050ff 100644 --- a/src/frontend/components/inputs/MediaPicker.svelte +++ b/src/frontend/components/inputs/MediaPicker.svelte @@ -11,6 +11,7 @@ export let multiple: boolean = false export let clearOnClick: boolean = false export let center: boolean = true + export let dark: boolean = true function pick() { if (clearOnClick) { @@ -34,6 +35,6 @@ } - diff --git a/src/frontend/components/inputs/ProjectButton.svelte b/src/frontend/components/inputs/ProjectButton.svelte index 33bc0ace..faa53079 100644 --- a/src/frontend/components/inputs/ProjectButton.svelte +++ b/src/frontend/components/inputs/ProjectButton.svelte @@ -53,7 +53,7 @@ activeProject.set(id) // select first if ALT key is NOT held down - if (e.altKey || !$projects[id].shows.length) return + if (e.altKey || !$projects[id]?.shows?.length) return activeShow.set({ ...$projects[id].shows[0], index: 0 }) } diff --git a/src/frontend/components/inputs/ShowButton.svelte b/src/frontend/components/inputs/ShowButton.svelte index 4ff222ca..9628a561 100644 --- a/src/frontend/components/inputs/ShowButton.svelte +++ b/src/frontend/components/inputs/ShowButton.svelte @@ -108,7 +108,7 @@ let currentOutput: any = getActiveOutputs()[0] || {} let slide: any = currentOutput.out?.slide || null - if (type === "show" && $showsCache[id] && $showsCache[id].layouts[$showsCache[id].settings.activeLayout].slides.length) { + if (type === "show" && $showsCache[id] && $showsCache[id].layouts[$showsCache[id].settings.activeLayout]?.slides?.length) { updateOut("active", 0, _show("active").layouts("active").ref()[0], !e.altKey) if (slide?.id === id && slide?.index === 0 && slide?.layout === $showsCache[id].settings.activeLayout) return setOutput("slide", { id, layout: $showsCache[id].settings.activeLayout, index: 0 }) diff --git a/src/frontend/components/main/Tabs.svelte b/src/frontend/components/main/Tabs.svelte index f54eb6d9..34d33387 100644 --- a/src/frontend/components/main/Tabs.svelte +++ b/src/frontend/components/main/Tabs.svelte @@ -1,4 +1,5 @@
diff --git a/src/frontend/components/main/popups/Action.svelte b/src/frontend/components/main/popups/Action.svelte index 74395479..314ef6a8 100644 --- a/src/frontend/components/main/popups/Action.svelte +++ b/src/frontend/components/main/popups/Action.svelte @@ -1,25 +1,25 @@ + + + + diff --git a/src/frontend/components/main/popups/Emitters.svelte b/src/frontend/components/main/popups/Emitters.svelte new file mode 100644 index 00000000..c0065be6 --- /dev/null +++ b/src/frontend/components/main/popups/Emitters.svelte @@ -0,0 +1,194 @@ + + +{#if editTemplate && template} + + + +

+ updateTemplate("name", e)} autofocus={!template.name} /> +
+ + + + !a.name && !a.value)} items={templateInputs} let:item={input} on:add={createTemplateValue} on:delete={(e) => removeTemplateValue(e.detail)} allowOpen={false}> +
+ updateTemplateValue(input.id, "name", e)} style="width: 50%;" /> + updateTemplateValue(input.id, "value", e)} /> +
+
+ + {#if dataPreview} +
+ {dataPreview} +
+ {/if} +{:else if editEmitter && emitter} + + + +

+ updateValue("name", e)} autofocus={!emitter.name} /> +
+ + +

+ a.id === emitter.type)?.name || "—"} on:click={(e) => updateValue("type", e.detail.id)} /> +
+ + changed(e, "signal")} /> + + + + + + (editTemplate = e.detail)} on:delete={(e) => deleteTemplate(e.detail)} on:add={createTemplate}> +

{template.name || "—"}

+
+{:else} + {#if !emittersList.length} +

+ {/if} + + (editEmitter = e.detail)} on:delete={(e) => deleteEmitter(e.detail)} on:add={createEmitter}> +

[{emitter.type}]{emitter.name || "—"}

+
+{/if} + + diff --git a/src/frontend/components/main/popups/Initialize.svelte b/src/frontend/components/main/popups/Initialize.svelte index 8fc174d7..98683719 100644 --- a/src/frontend/components/main/popups/Initialize.svelte +++ b/src/frontend/components/main/popups/Initialize.svelte @@ -1,7 +1,7 @@
@@ -35,11 +40,19 @@

:

+

+ +

+
+ +
+
+

diff --git a/src/frontend/components/main/popups/ManageTags.svelte b/src/frontend/components/main/popups/ManageTags.svelte index b8dc9a63..8568fd1a 100644 --- a/src/frontend/components/main/popups/ManageTags.svelte +++ b/src/frontend/components/main/popups/ManageTags.svelte @@ -1,6 +1,6 @@ + +
+ {#each mediaFitOptions as fit} + {@const isActive = fit.id === styleFit} + + {/each} +
+ + diff --git a/src/frontend/components/main/popups/createShow/CreateShow.svelte b/src/frontend/components/main/popups/createShow/CreateShow.svelte index fb3ffaf4..af87492e 100644 --- a/src/frontend/components/main/popups/createShow/CreateShow.svelte +++ b/src/frontend/components/main/popups/createShow/CreateShow.svelte @@ -164,7 +164,7 @@
{#each createOptions as type, i} - @@ -220,7 +220,6 @@ {/if} -