diff --git a/package-lock.json b/package-lock.json index 3bf04072..60cdefe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "freeshow", - "version": "1.3.2", + "version": "1.3.3-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freeshow", - "version": "1.3.2", + "version": "1.3.3-beta.1", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { @@ -18,18 +18,19 @@ "axios": "^1.7.8", "chord-transposer": "^3.0.9", "cross-env": "^7.0.3", + "css-fonts": "^1.0.8", "electron-store": "^8.0.1", "electron-updater": "^6.3.1", "exif": "^0.6.0", "express": "^4.17.2", "follow-redirects": "^1.15.2", - "font-list": "^1.4.5", "genius-lyrics": "^4.4.7", - "grandiose": "vassbo/grandiose#9857c8e", + "grandiose": "vassbo/grandiose#53e7f98", "jzz": "^1.8.7", "mp4box": "^0.5.2", "node-machine-id": "^1.1.12", "npm-run-all": "^4.1.5", + "osc-js": "^2.4.1", "pdf2img-electron": "^1.2.3", "pptx2json": "^0.0.10", "protobufjs": "^7.2.3", @@ -2654,6 +2655,15 @@ "node": ">=12.10" } }, + "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==", + "license": "ISC", + "dependencies": { + "font-scanner": "github:vassbo/font-scanner" + } + }, "node_modules/css-select": { "version": "5.1.0", "license": "BSD-2-Clause", @@ -3772,9 +3782,14 @@ } } }, - "node_modules/font-list": { - "version": "1.5.1", - "license": "MIT" + "node_modules/font-scanner": { + "version": "0.2.2", + "resolved": "git+ssh://git@github.com/vassbo/font-scanner.git#76b1e872605eb3581f66825651c486045d4a7b93", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^5.0.0" + } }, "node_modules/for-each": { "version": "0.3.3", @@ -4208,8 +4223,8 @@ "license": "ISC" }, "node_modules/grandiose": { - "version": "0.0.5", - "resolved": "git+ssh://git@github.com/vassbo/grandiose.git#9857c8e6fca307491f655736bf16a6fb3c0a0c90", + "version": "0.0.6", + "resolved": "git+ssh://git@github.com/vassbo/grandiose.git#53e7f98c6a391781c221b40c85519e3f21f5cbdd", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -5727,6 +5742,12 @@ "version": "1.0.5", "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "license": "MIT", @@ -6179,6 +6200,36 @@ "node": ">=0.10.0" } }, + "node_modules/osc-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.4.1.tgz", + "integrity": "sha512-QlSeRKJclL47FNvO1MUCAAp9frmCF9zcYbnf6R9HpcklAst8ZyX3ISsk1v/Vghr/5GmXn0bhVjFXF9h+hfnl4Q==", + "license": "MIT", + "dependencies": { + "ws": "^8.16.0" + } + }, + "node_modules/osc-js/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "dev": true, diff --git a/package.json b/package.json index fae747fc..22d63a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.3.3-beta.1", + "version": "1.3.3-beta.2", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -182,11 +182,12 @@ "express": "^4.17.2", "follow-redirects": "^1.15.2", "genius-lyrics": "^4.4.7", - "grandiose": "vassbo/grandiose#9857c8e", + "grandiose": "vassbo/grandiose#53e7f98", "jzz": "^1.8.7", "mp4box": "^0.5.2", "node-machine-id": "^1.1.12", "npm-run-all": "^4.1.5", + "osc-js": "^2.4.1", "pdf2img-electron": "^1.2.3", "pptx2json": "^0.0.10", "protobufjs": "^7.2.3", diff --git a/public/global.css b/public/global.css index 9f97f71b..ddfa4260 100644 --- a/public/global.css +++ b/public/global.css @@ -1,3 +1,4 @@ +/* custom CMG Sans */ @font-face { font-family: "CMGSans"; src: url("./fonts/CMGSans-Regular.ttf"); @@ -39,20 +40,26 @@ --hover: rgb(255 255 255 / 0.05); --focus: rgb(255 255 255 / 0.1); - /* --active: rgb(230 52 156 / .8); */ - /* --font-family: sans-serif; */ /* https://css-tricks.com/snippets/css/system-font-stack/ */ - /* --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; */ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; --font-size: 1em; --border-radius: 0; - /* --navigation-width: 18vw; */ --navigation-width: 290px; } +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + + outline-offset: -4px; + outline-color: var(--secondary); +} + html, body { position: relative; @@ -63,9 +70,6 @@ body { body { display: flex; flex-direction: column; - /* background-color: #292c36; - background-color: var(--primary); */ - color: #f0f0ff; color: var(--text); box-sizing: border-box; transition: background-color 0.5s; @@ -122,76 +126,3 @@ p { overflow: hidden; white-space: nowrap; } - -* { - margin: 0; - padding: 0; - box-sizing: border-box; - user-select: none; - - outline-offset: -4px; - outline-color: var(--secondary); -} - -/* button { - font-family: sans-serif; - font-family: system-ui; -} */ - -/* body { - color: #333; - margin: 0; - padding: 8px; - box-sizing: border-box; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; -} - -a { - color: rgb(0,100,200); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a:visited { - color: rgb(0,80,160); -} - -label { - display: block; -} - -input, button, select, textarea { - font-family: inherit; - font-size: inherit; - -webkit-padding: 0.4em 0; - padding: 0.4em; - margin: 0 0 0.5em 0; - box-sizing: border-box; - border: 1px solid #ccc; - border-radius: 2px; -} - -input:disabled { - color: #ccc; -} - -button { - color: #333; - background-color: #f4f4f4; - outline: none; -} - -button:disabled { - color: #999; -} - -button:not(:disabled):active { - background-color: #ddd; -} - -button:focus { - border-color: #666; -} */ diff --git a/public/import-logos/verseview.webp b/public/import-logos/verseview.webp index 8d50fd06..f9020de5 100644 Binary files a/public/import-logos/verseview.webp and b/public/import-logos/verseview.webp differ diff --git a/public/lang/en.json b/public/lang/en.json index 606bf3bf..715ff5ca 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -38,7 +38,9 @@ "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" }, "about": { "check_updates": "Look for updates", @@ -316,7 +318,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", @@ -413,7 +415,7 @@ "import": "Import", "songbeamer_import": "Songbeamer Import", "export": "Export", - "importing": "Importing", + "importing": "Importing...", "import_scripture": "Import scripture", "player": "Player", "edit_event": "Edit event", @@ -429,6 +431,7 @@ "manage_icons": "Manage icons", "manage_colors": "Manage colors", "choose_camera": "Choose camera", + "manage_tags": "Manage tags", "initialize": "Welcome to FreeShow", "unsaved": "Are you sure you want to quit?", "cancel": "Cancel", @@ -465,6 +468,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:", @@ -735,7 +739,7 @@ "export": { "export": "Export", "export_as": "Export {} as", - "exporting": "Exporting", + "exporting": "Exporting...", "exported": "Exported!", "oneFile": "One file", "selected_shows": "Selected shows", @@ -823,6 +827,7 @@ "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", + "list": "List", "chords": "Chords", "transpose": "Transpose", "auto_size": "Auto size", @@ -846,6 +851,7 @@ "outline": "Outline", "shadow": "Shadow", "shadow_inset": "Shadow inset", + "offset": "Offset", "offsetX": "Offset X", "offsetY": "Offset Y", "blur": "Blur", @@ -892,7 +898,6 @@ }, "items": { "text": "Textbox", - "list": "List", "media": "Media", "image": "Image", "camera": "Camera", @@ -1065,6 +1070,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", @@ -1157,6 +1163,7 @@ "sort": { "sort_by": "Sort by", "name": "Name", + "name_des": "Name Descending", "date": "Date", "size": "Size", "type": "Type", diff --git a/public/lang/en_GB.json b/public/lang/en_GB.json index c7d6c827..67a77a1a 100644 --- a/public/lang/en_GB.json +++ b/public/lang/en_GB.json @@ -163,6 +163,7 @@ "toggle_shuffle": "Toggle shuffle", "next": "Next", "previous": "Previous", + "play_no_audio": "Play without audio", "play_no_filters": "Play without filters", "favourite": "Favourite", "pause": "Pause", @@ -194,7 +195,10 @@ "playlist_settings": "Playlist settings", "custom_output": "Custom audio output", "mute_when_video_plays": "Mute when video plays", + "allow_gaining": "Allow gaining", + "allow_gaining_tip": "Allow setting volume above 100% (May cause distortion)", "pre_fader_volume_meter": "Pre fader volume meter", + "mixer": "Mixer", "metronome": "Metronome", "toggle_metronome": "Toggle metronome", "tempo": "Tempo", @@ -269,15 +273,17 @@ "ip": "Could not get the IP-address of your device, go to the computer Wi-Fi settings to find your local IPv4 address." }, "meta": { + "number": "Number", "title": "Title", "artist": "Artist", "author": "Author", "composer": "Composer", "publisher": "Publisher", "copyright": "Copyright", - "CCLI": "License (CCLI)", + "CCLI": "Song ID (CCLI)", "year": "Year", "key": "Key", + "autofill": "Autofill", "message": "Message", "message_tip": "Display something on all slides", "auto_media": "Get meta from media content", @@ -362,7 +368,8 @@ "music": "Music", "offers": "Offers", "notice": "Notice", - "visuals": "Visuals" + "visuals": "Visuals", + "action_tip": "An action that triggers each time a show with this category is presented." }, "groups": { "current": "Current", @@ -400,6 +407,7 @@ "change_output_values": "Change output values", "choose_chord": "Choose chord", "set_time": "Set time", + "slide_shortcut": "Slide shortcut", "animate": "Animate", "translate": "Localisation ", "next_timer": "Next slide timer", @@ -413,6 +421,7 @@ "about": "About", "history": "History", "action": "Action", + "category_action": "Category action", "connect": "Connect", "cloud_update": "Syncing with cloud", "cloud_method": "Data location", @@ -547,6 +556,8 @@ "zoomIn": "Zoom In", "zoomOut": "Zoom Out", "reset": "Reset", + "create_template": "Create template", + "project_template_tip": "Create a new project from this template", "convert_to_images": "Convert to images", "converting": "Converting...", "remove_template_from_show": "Remove template from show", @@ -608,6 +619,8 @@ "set_key": "Set key", "custom_key": "Set custom value", "select_chord": "Select this chord", + "play_with_shortcut": "Activate with shortcut", + "press_to_assign": "Press any letter key to assign", "play_on_midi": "Activate on MIDI signal", "play_on_midi_tip": "Activate this specific slide when receiving chosen MIDI signal", "send_midi": "Send MIDI signal", @@ -624,6 +637,7 @@ "next_after_media": "Next on media finished", "remove_media": "Remove media", "remove_layers": "Remove layers", + "toggle_checkbox_tip": "Action will toggle if checkbox is unchanged", "start_recording": "Start recording", "stop_recording": "Stop recording", "export_recording": "Stop recording and export", @@ -632,6 +646,7 @@ "previous_project_item": "Previous project item", "index_select_project_item": "Select project item by index", "name_select_show": "Select show by name", + "set_template_active": "Set template on active show", "random_slide": "Play random slide", "index_select_slide": "Select slide by index", "name_select_slide": "Select slide by name", @@ -672,6 +687,7 @@ "activate_slide_cleared": "Activate when slide is cleared", "activate_background_cleared": "Activate when background is cleared", "activate_show_created": "Activate when show is created", + "activate_show_opened": "Activate when show is opened", "activate_audio_playlist_ended": "Activate when audio playlist has ended" }, "recording": { @@ -679,7 +695,9 @@ "tip": "Record and replay the timings of slides. Sync with an audio track on the first slide.", "layout_changed": "Layout has changed since last recording!", "audio_synced": "Synced with audio!", - "start": "Start slide recording" + "start": "Start slide recording", + "use_duration": "Use duration time", + "use_duration_tip": "Use duration time instead of timestamp time" }, "animate": { "change": "Change", @@ -738,6 +756,7 @@ }, "context": { "enabledTabs": "Toggle tabs", + "setTag": "Set tag", "filterByTags": "Filter by tags", "addToProject": "Add to project", "add_to_show": "Add to show", @@ -796,6 +815,8 @@ "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", "media_fit": "Media fit", + "one_letter": "One letter mode", + "sub_indexes": "Sub indexes", "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", @@ -916,6 +937,7 @@ "to_event": "Time until event", "counter": "Countdown", "time": "Time", + "minutes": "Minutes", "seconds": "Seconds", "from": "From", "to": "To", @@ -1063,6 +1085,7 @@ "group_numbers": "Group numbers", "full_colors": "High contrast group colours", "slide_number_keys": "Play slides with number keys", + "auto_shortcut_first_letter": "Auto shortcut to first letter in text", "auto_output": "Activate output screen on startup", "hide_cursor_in_output": "Hide cursor in output", "clear_media_when_finished": "Clear media when finished", @@ -1124,7 +1147,8 @@ "auto": "Auto", "optimized": "Optimised", "reduced": "Reduced", - "full": "Full" + "full": "Full", + "section_trigger_action": "Trigger action when navigating presentation to a section" }, "sort": { "sort_by": "Sort by", @@ -1172,6 +1196,7 @@ "reference": "Show reference", "split_reference": "Split reference", "combine_with_text": "Combine with text", + "first_slide_reference": "Reference on first slide", "reference_at_bottom": "Move to bottom", "red_jesus": "Jesus words in red", "search": "Search in the Bible" diff --git a/public/lang/en_ZM.json b/public/lang/en_ZM.json index b2fedcde..a1d2d2cc 100644 --- a/public/lang/en_ZM.json +++ b/public/lang/en_ZM.json @@ -163,6 +163,7 @@ "toggle_shuffle": "Toggle shuffle", "next": "Next", "previous": "Previous", + "play_no_audio": "Play without audio", "play_no_filters": "Play without filters", "favourite": "Favourite", "pause": "Pause", @@ -194,7 +195,10 @@ "playlist_settings": "Playlist settings", "custom_output": "Custom audio output", "mute_when_video_plays": "Mute when video plays", + "allow_gaining": "Allow gaining", + "allow_gaining_tip": "Allow setting volume above 100% (May cause distortion)", "pre_fader_volume_meter": "Pre fader volume meter", + "mixer": "Mixer", "metronome": "Metronome", "toggle_metronome": "Toggle metronome", "tempo": "Tempo", @@ -269,15 +273,17 @@ "ip": "Could not get the IP-address of your device, go to the computer Wi-Fi settings to find your local IPv4 address." }, "meta": { + "number": "Number", "title": "Title", "artist": "Artist", "author": "Author", "composer": "Composer", "publisher": "Publisher", "copyright": "Copyright", - "CCLI": "License (CCLI)", + "CCLI": "Song ID (CCLI)", "year": "Year", "key": "Key", + "autofill": "Autofill", "message": "Message", "message_tip": "Display something on all slides", "auto_media": "Get meta from media content", @@ -362,7 +368,8 @@ "music": "Music", "offers": "Offers", "notice": "Notice", - "visuals": "Visuals" + "visuals": "Visuals", + "action_tip": "An action that triggers each time a show with this category is presented." }, "groups": { "current": "Current", @@ -400,6 +407,7 @@ "change_output_values": "Change output values", "choose_chord": "Choose chord", "set_time": "Set time", + "slide_shortcut": "Slide shortcut", "animate": "Animate", "translate": "Localisation", "next_timer": "Next slide timer", @@ -413,6 +421,7 @@ "about": "About", "history": "History", "action": "Action", + "category_action": "Category action", "connect": "Connect", "cloud_update": "Syncing with cloud", "cloud_method": "Data location", @@ -547,6 +556,8 @@ "zoomIn": "Zoom In", "zoomOut": "Zoom Out", "reset": "Reset", + "create_template": "Create template", + "project_template_tip": "Create a new project from this template", "convert_to_images": "Convert to images", "converting": "Converting...", "remove_template_from_show": "Remove template from show", @@ -608,6 +619,8 @@ "set_key": "Set key", "custom_key": "Set custom value", "select_chord": "Select this chord", + "play_with_shortcut": "Activate with shortcut", + "press_to_assign": "Press any letter key to assign", "play_on_midi": "Activate on MIDI signal", "play_on_midi_tip": "Activate this specific slide when receiving chosen MIDI signal", "send_midi": "Send MIDI signal", @@ -624,6 +637,7 @@ "next_after_media": "Next on media finished", "remove_media": "Remove media", "remove_layers": "Remove layers", + "toggle_checkbox_tip": "Action will toggle if checkbox is unchanged", "start_recording": "Start recording", "stop_recording": "Stop recording", "export_recording": "Stop recording and export", @@ -632,6 +646,7 @@ "previous_project_item": "Previous project item", "index_select_project_item": "Select project item by index", "name_select_show": "Select show by name", + "set_template_active": "Set template on active show", "random_slide": "Play random slide", "index_select_slide": "Select slide by index", "name_select_slide": "Select slide by name", @@ -672,6 +687,7 @@ "activate_slide_cleared": "Activate when slide is cleared", "activate_background_cleared": "Activate when background is cleared", "activate_show_created": "Activate when show is created", + "activate_show_opened": "Activate when show is opened", "activate_audio_playlist_ended": "Activate when audio playlist has ended" }, "recording": { @@ -679,7 +695,9 @@ "tip": "Record and replay the timings of slides. Sync with an audio track on the first slide.", "layout_changed": "Layout has changed since last recording!", "audio_synced": "Synced with audio!", - "start": "Start slide recording" + "start": "Start slide recording", + "use_duration": "Use duration time", + "use_duration_tip": "Use duration time instead of timestamp time" }, "animate": { "change": "Change", @@ -738,6 +756,7 @@ }, "context": { "enabledTabs": "Toggle tabs", + "setTag": "Set tag", "filterByTags": "Filter by tags", "addToProject": "Add to project", "add_to_show": "Add to show", @@ -796,6 +815,8 @@ "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", "media_fit": "Media fit", + "one_letter": "One letter mode", + "sub_indexes": "Sub indexes", "size": "Size", "max_lines": "Max lines", "invert_items": "Invert items", @@ -916,6 +937,7 @@ "to_event": "Time until event", "counter": "Countdown", "time": "Time", + "minutes": "Minutes", "seconds": "Seconds", "from": "From", "to": "To", @@ -1063,6 +1085,7 @@ "group_numbers": "Group numbers", "full_colors": "High contrast group colours", "slide_number_keys": "Play slides with number keys", + "auto_shortcut_first_letter": "Auto shortcut to first letter in text", "auto_output": "Activate output screen on startup", "hide_cursor_in_output": "Hide cursor in output", "clear_media_when_finished": "Clear media when finished", @@ -1124,7 +1147,8 @@ "auto": "Auto", "optimized": "Optimised", "reduced": "Reduced", - "full": "Full" + "full": "Full", + "section_trigger_action": "Trigger action when navigating presentation to a section" }, "sort": { "sort_by": "Sort by", @@ -1172,6 +1196,7 @@ "reference": "Show reference", "split_reference": "Split reference", "combine_with_text": "Combine with text", + "first_slide_reference": "Reference on first slide", "reference_at_bottom": "Move to bottom", "red_jesus": "Jesus words in red", "search": "Search in the Bible" diff --git a/public/lang/pt_BR.json b/public/lang/pt_BR.json index f64a1562..6731eded 100644 --- a/public/lang/pt_BR.json +++ b/public/lang/pt_BR.json @@ -163,6 +163,7 @@ "toggle_shuffle": "Aleatório", "next": "Próximo", "previous": "Anterior", + "play_no_audio": "Executar sem áudio", "play_no_filters": "Reproduzir sem filtros", "favourite": "Favoritar", "pause": "Pausa", @@ -194,7 +195,10 @@ "playlist_settings": "Configurações de listas de reprodução", "custom_output": "Saída de áudio personalizada", "mute_when_video_plays": "Tornar mudo quando um vídeo reproduzir", + "allow_gaining": "Permitir ganho", + "allow_gaining_tip": "Permitir configurar volume acima de 100% (Pode causar distorção)", "pre_fader_volume_meter": "Exibir volume antes do fader", + "mixer": "Mixer", "metronome": "Metrônomo", "toggle_metronome": "Metrônomo", "tempo": "Tempo", @@ -269,15 +273,17 @@ "ip": "Não foi possível obter o endereço IP do seu dispositivo. Acesse as configurações de Wi-Fi do computador para encontrar seu endereço IPv4 local." }, "meta": { + "number": "Número", "title": "Título", "artist": "Artista", "author": "Autor", "composer": "Compositor", "publisher": "Editor", "copyright": "Direitos autorais", - "CCLI": "Licença (CCLI)", + "CCLI": "ID da música (CCLI)", "year": "Ano", "key": "Chave", + "autofill": "Preencher automaticamente", "message": "Mensagem", "message_tip": "Mostra algo em todos os slides", "auto_media": "Copiar metadados da mídia", @@ -362,7 +368,8 @@ "music": "Musica", "offers": "Ofertas", "notice": "Avisos", - "visuals": "Visuais" + "visuals": "Visuais", + "action_tip": "Uma ação que será ativada toda vez que um Show desta categoria for apreesentado." }, "groups": { "current": "Atual", @@ -400,6 +407,7 @@ "change_output_values": "Mudar valores de saída", "choose_chord": "Escolha o acorde", "set_time": "Definir tempo", + "slide_shortcut": "Atalho para slide", "animate": "Animar", "translate": "Traduzir", "next_timer": "Temporizador do próximo slide", @@ -413,6 +421,7 @@ "about": "Sobre", "history": "Histórico", "action": "Ação", + "category_action": "Ação de categoria", "connect": "Conectar", "cloud_update": "Sincronizando com a nuvem", "cloud_method": "Dados", @@ -547,6 +556,8 @@ "zoomIn": "Mais Zoom", "zoomOut": "Menos Zoom", "reset": "Redefinir", + "create_template": "Criar modelo de projeto", + "project_template_tip": "Cria um novo projeto a partir deste modelo", "convert_to_images": "Converter para imagens", "converting": "Convertendo...", "remove_template_from_show": "Remover modelo da apresentação", @@ -587,10 +598,10 @@ "click_disable": "Clique em qualquer para desativar", "svg_clipboard": "Importar SVG da área de transferência", "fullscreen_preview": "Pré-visualização em tela cheia", - "toggle_output": "Alternar tela de saída", + "toggle_output": "Ativar tela de saída", "toggle_panels": "Esconder/Mostrar painéis", "change_tab": "Alterar guia", - "change_drawer_tab": "Alterar guia de desenho", + "change_drawer_tab": "Alterar guia do Drawer", "change_slide": "Mudar slide", "change_project_item": "Mudar item do projeto", "change_drawer_item": "Mudar item do Drawer", @@ -608,6 +619,8 @@ "set_key": "Definir chave", "custom_key": "Definir valor personalizado", "select_chord": "Selecione esse acorde", + "play_with_shortcut": "Ativar com atalho", + "press_to_assign": "Aperte qualquer letra para assinalar", "play_on_midi": "Ativar ao sinal MIDI", "play_on_midi_tip": "Ativar este específico slide quando receber sinal MIDI específico", "send_midi": "Enviar sinal MIDI", @@ -624,6 +637,7 @@ "next_after_media": "Depois da mídia concluir", "remove_media": "Remover mídia", "remove_layers": "Remover camadas", + "toggle_checkbox_tip": "A ação irá ativar se uma caixa de marcação não for alterada", "start_recording": "Iniciar gravação", "stop_recording": "Parar gravação", "export_recording": "Para de gravar e exportar", @@ -632,6 +646,7 @@ "previous_project_item": "Item anterior do projeto", "index_select_project_item": "Selecionar item do projeto pelo índice", "name_select_show": "Selecionar Show pelo nome", + "set_template_active": "Aplicar modelo no Show ativo", "random_slide": "Reproduzir slide aleatório", "index_select_slide": "Selecionar slide pelo índice", "name_select_slide": "Selecionar slide pelo nome", @@ -672,6 +687,7 @@ "activate_slide_cleared": "Ativar quando limpar o slide", "activate_background_cleared": "Ativar quando limpar o background", "activate_show_created": "Ativar quando um Show é criado", + "activate_show_opened": "Ativar quando um Show for aberto", "activate_audio_playlist_ended": "Ativar quando uma playlist de áudio termina" }, "recording": { @@ -679,7 +695,9 @@ "tip": "Grave e reproduza os tempos de cada slide. Sincronize com uma faixa de áudio no primeiro slide.", "layout_changed": "O layout mudou desde a última gravação!", "audio_synced": "Sincronizado com áudio!", - "start": "Iniciar gravação de slide" + "start": "Iniciar gravação de slide", + "use_duration": "Usar tempo de duração", + "use_duration_tip": "Usar tempo de duração ao invés de tempo marcado" }, "animate": { "change": "Mudar", @@ -738,6 +756,7 @@ }, "context": { "enabledTabs": "Alternar guias", + "setTag": "Adicionar tag", "filterByTags": "Filtrar por tags", "addToProject": "Adicionar ao projeto", "add_to_show": "Adicionar ao Show", @@ -796,6 +815,8 @@ "overlay_content": "Adicionar conteúdo de sobreposição", "different_first_template": "Usar outro modelo no primeiro 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", @@ -916,6 +937,7 @@ "to_event": "Tempo até o evento", "counter": "Contagem regressiva", "time": "Tempo", + "minutes": "Minutos", "seconds": "Segundos", "from": "A partir de", "to": "Até", @@ -1063,6 +1085,7 @@ "group_numbers": "Números do grupo", "full_colors": "Ativar alto contraste entre grupos", "slide_number_keys": "Usar teclas de números para controlar slides", + "auto_shortcut_first_letter": "Usar a primeira letra como atalho", "auto_output": "Mostrar tela de saída na inicialização", "hide_cursor_in_output": "Esconder cursor na saída", "clear_media_when_finished": "Limpar mídia ao terminar", @@ -1124,7 +1147,8 @@ "auto": "Automático", "optimized": "Otimizado", "reduced": "Reduzido", - "full": "Completo" + "full": "Completo", + "section_trigger_action": "Ativar ação quando navegar de uma apresentação para uma seção" }, "sort": { "sort_by": "Ordenar por", @@ -1172,6 +1196,7 @@ "reference": "Mostrar referência", "split_reference": "Referência dividida", "combine_with_text": "Combinar com o texto", + "first_slide_reference": "Referência no primeiro slide", "reference_at_bottom": "Mover para o rodapé", "red_jesus": "Palavras de Jesus em vermelho", "search": "Procurar na Bíblia" diff --git a/public/lang/sk.json b/public/lang/sk.json index 41fd4942..c25487ee 100644 --- a/public/lang/sk.json +++ b/public/lang/sk.json @@ -7,25 +7,57 @@ "unnamed": "Bez názvu", "drop": "Pusti sem", "search": "Hľadať", + "quick_search": "Rýchle hľadanie", "none": "Nič", "finished": "Dokončené", + "open": "Otvoriť", "system_open": "Otvoriť v systéme" }, + "formats": { + "show": "Zobraziť", + "project": "Projekt", + "template": "Šablóna", + "theme": "Téma", + "clipboard": "Schránka", + "text": "Textový súbor" + }, + "guide": { + "start": "Príručka rýchleho štartu", + "skip": "Preskočiť" + }, + "titlebar": { + "file": "Súbor", + "edit": "Upraviť", + "view": "Zobrazenie", + "help": "Pomoc" + }, + "screen": { + "width": "Šírka", + "height": "Výška", + "pixels": "pixelov", + "top": "Hore", + "right": "Vpravo", + "bottom": "Dole", + "left": "Vľavo" + }, "about": { "check_updates": "Skontrolovať aktualizácie", "made": "Vyrobil v Nórsku", - "report": "Chcete nahlásiť problém? Vytvorte issue na GitHube", - "help": "Chcete pomôcť s prekladom alebo implementovať funkciu? Napíšte email na", + "more": "Zistite viac o našej aplikácii na", + "report": "Nahlásiť problém alebo požiadať o funkciu môžete na", + "translate": "Chcete pomôcť prekladať? Prosím prejdite na", + "mail": "Kontaktovať emailom", + "support": "Ak ste vďační za tento projekt, prosím zvážte jeho podporu", "assets": "Použité materiály", "libraries": "Použité knižnice", "thanks": "Ďakujeme", "new_update": "Aktualizácia je k dispozícii", - "download": "Stiahnite z freeshow.app", + "download": "Aplikáciu aktualizujete reštartovaní, alebo prejdite na freeshow.app a stiahnite ručne", "changes": "Čo je nové" }, "tooltip": { "project": "Vytvorí projekt, kde môžete pridávať a upravovať prezentácie.", - "show": "Vytvorí nové prezentácie, kde môžete pridávať texty piestí, prezentácie a médiá. Podržaním Ctrl/Cmd sa vytvorí prázdne.", + "show": "Vytvorí novú prezentáciu kde môžete pridávať texty piesní, snímky a médiá.", "groups": "Všetky skupiny v aktuálnej prezentácii a všetkých globálnych skupinách. Kliknutím alebo presunutím ich pridáte do aktuálneho rozloženia.", "layout": "Pridá prechody a časovače do snímkov v aktuálnom rozložení.", "media": "Všetky médiá v aktuálnej prezentácii. Môžete ich zobraziť alebo presunúť do aktuálneho rozloženia.", @@ -39,6 +71,9 @@ "options": "Viac možností.", "scripture": "Držaním Ctrl/Cmd alebo Shift označíte viac veršov." }, + "tips": { + "trigger": "Spúšťače sa zvyknú používať na odoslanie HTTP požiadavky na zmenu nastavení kamier." + }, "setup": { "good_luck": "Dúfam, že vám tento softvér bude užitočný. Veľa šťastia pri prezentácii! :)", "tips": "Na webstránke nájdete tipy a návody.", @@ -62,27 +97,22 @@ "small": "Malé", "bold": "Tučné" }, - "titlebar": { - "file": "Súbor", - "edit": "Upraviť", - "view": "Zobrazenie", - "help": "Pomoc" - }, - "screen": { - "width": "Šírka", - "height": "Výška", - "pixels": "pixelov", - "top": "Hore", - "right": "Vpravo", - "bottom": "Dole", - "left": "Vľavo" - }, "create_show": { + "web": "Hľadať na webe", "search_web": "Hľadať pieseň na webe", + "search_results": "Výsledky hľadania", "more_options": "Viac možností", + "auto_groups": "Automaticky priradené skupiny", "format_new_show": "Formátovať text", - "split_lines": "Počty riadkov", - "quick_lyrics_example_text": "Riadok" + "format_new_show_tip": "Vylepší formát textu automatickými kapitálkami, oreže/spojí text a iné.", + "split_lines": "Max počet riadkov na snímok", + "split_lines_tip": "Počet riadkov povolený na snímok pre automatickým rozdelením", + "quick_lyrics": "Rýchle texty", + "quick_lyrics_tip": "Vložte alebo napíšte text", + "quick_lyrics_example_tip": "Sem zadajte text", + "quick_lyrics_example_text": "Riadok", + "empty": "Prázdna prezentácia", + "exists": "Našla sa existujúca prezentácia s rovnakým názvom" }, "preview": { "_previous_show": "Predchádzajúca prezentácia", @@ -105,7 +135,8 @@ "to_start": "Prejsť a začiatok", "nextTimer": "Časovač ďalšieho snímku", "lock": "Zamknúť", - "unlock": "Odomknúť" + "unlock": "Odomknúť", + "test_pattern": "Testovací vzor" }, "clear": { "all": "Vyčistiť všetko", @@ -113,7 +144,8 @@ "slide": "Vyčistit snímok", "overlays": "Vyčistiť prvky", "audio": "Vyčistiť audio", - "nextTimer": "Vyčistit časovač ďalšieho snímku" + "nextTimer": "Vyčistit časovač ďalšieho snímku", + "drawing": "Vyčistiť kresbu" }, "remove": { "background": "Odstrániť pozadie", @@ -127,6 +159,11 @@ "media": { "_loop": "Slučka", "play": "Prehrať", + "play_multiple": "Prehrať viaceré", + "toggle_shuffle": "Prepnúť náhodné", + "next": "Ďalšie", + "previous": "Predošlé", + "play_no_audio": "Prehrať bez zvuku", "play_no_filters": "Prehrať bez filtrov", "favourite": "Obľúbené", "pause": "Pauza", @@ -138,6 +175,8 @@ "speed": "Rýchlosť", "show": "Zobraziť", "flip": "Preklopiť", + "flip_horizontally": "Prevrátiť horizontálne", + "flip_vertically": "Prevrátiť vertikálne", "all": "Priečinky, obrázky a videá", "folder": "Len priečinky", "image": "Len obrázky", @@ -146,7 +185,25 @@ "contain": "Obsiahnuť", "fill": "Vyplniť", "cover": "Pokryť", - "online": "Online" + "online": "Online", + "recommended": "Odporúčané", + "bundle_media_files": "Pripojiť všetky media súbory", + "bundle_media_files_tip": "Skopíruje media súbory zo všetkých prezentácií do jedného priečinka" + }, + "audio": { + "settings": "Audio nastavenia", + "playlist_settings": "Playlist nastavenia", + "custom_output": "Vlastný audio výstup", + "mute_when_video_plays": "Stíšiť keď hrá video", + "allow_gaining": "Povoliť hlasitosť", + "allow_gaining_tip": "Povolí zvýšenie hlasitosti na 100% (Môže skresliť zvuk)", + "pre_fader_volume_meter": "Pre fader ukazovateľ hlasitosti", + "mixer": "Mix", + "metronome": "Metronóm", + "toggle_metronome": "Prepnúť metronóm", + "tempo": "Tempo", + "bpm": "BPM", + "beats": "Takty" }, "menu": { "show": "Prezentácia", @@ -162,7 +219,8 @@ "settings": "Nastavenia", "_title_settings": "Nastavenia", "_title_display": "Prezentovať", - "_title_display_stop": "Zastaví prezentáciu" + "_title_display_stop": "Zastaví prezentáciu", + "again_confirm": "Kliknutím znova potvrdíte" }, "empty": { "general": "Prázdne", @@ -197,7 +255,8 @@ "loading": "Načítanie...", "submit": "Poslať", "password": "Heslo", - "wrong_password": "Nesprávne heslo" + "wrong_password": "Nesprávne heslo", + "quick_play": "Rýchle prehratie" }, "error": { "no_show": "Nenašla sa prezentácia", @@ -209,25 +268,33 @@ "display": "Nepodarilo sa zobraziť výstupné okno na aktuálnej obrazovke", "keep_one_layout": "Musíte mať aspoň jedno rozloženie", "video_unavailable": "Video nie je dostupné? Tvorca zakázal vkladanie videa.", - "folder_exists": "Tento priečinok už existuje" + "folder_exists": "Tento priečinok už existuje", + "uri": "Nepodarilo sa rozparsovať názov audia, prosím premenujte súbor", + "ip": "Nepodarilo sa zistiť IP adresu vášho zariadenia, prosím prejdite do nastavení Wi-Fi a nájdite vašu IPv4 adresu." }, "meta": { + "number": "Číslo", "title": "Názov", "artist": "Umelec", "author": "Autor", "composer": "Skladateľ", "publisher": "Vydavateľ", "copyright": "Copyright", - "CCLI": "Licencia (CCLI)", + "CCLI": "ID piesne (CCLI)", "year": "Rok", + "key": "Kľúč", + "autofill": "Autovyplnenie", "message": "Správa", "message_tip": "Zobrazí niečo na všetkých snímkoch", "auto_media": "Meta z mediálneho obsahu", - "override_output": "Prepísať štýl vo výstupe", + "override_output": "Prepíše výstupný štýl", "display_metadata": "Zobrazovať metadata", "meta_template": "Šablóna metadát", "text_divider": "Oddeľovač textu", - "message_template": "Šablóna správy" + "message_template": "Šablóna správy", + "tags": "Značky", + "new_tag": "Nová značka", + "clear_tag_filter": "Vymazať filter značiek" }, "show_at": { "never": "Žiadne snímky", @@ -256,14 +323,16 @@ "hover": "Hover", "focus": "Focus" }, - "buttons": { - "changeTheme": "Zmeniť tému" - }, "inputs": { "name": "Názov", "url": "URL", + "method": "Metóda", + "contentType": "Typ obsahu", + "payload": "Obsah", "video_id": "Video ID/URL", "close_ad": "Zavrieť reklamu na výstupnej obrazovke", + "start": "Začiatok", + "end": "Koniec", "change_folder": "Zvoliť iné umiestnenie" }, "tabs": { @@ -274,12 +343,16 @@ "effects": "Efekty", "scripture": "Biblia", "calendar": "Kalendár", + "functions": "Funkcie", + "actions": "Akcie", "player": "Prehrávač", "live": "Naživo", "timers": "Časovače", "variables": "Variables", + "triggers": "Spúšťače", "templates": "Šablóny", - "web": "Web" + "web": "Web", + "search_tip": "Vyhľadávanie v zásuvke" }, "category": { "all": "Všetko", @@ -295,12 +368,15 @@ "music": "Hudba", "offers": "Ponuky", "notice": "Oznamy", - "visuals": "Vizuály" + "visuals": "Vizuály", + "action_tip": "Akcia, ktorá sa spustí pri každej prezentácii s touto kategóriou." }, "groups": { "current": "Aktuálne", "global": "Globálne", "toggle_global_group": "Toggle global groups", + "group_shortcut": "Skupinové skratky", + "group_template": "Skupinová šablóna", "intro": "Úvod", "verse": "Verš", "pre_chorus": "Pre Chorus", @@ -319,17 +395,24 @@ "edit_list": "Upraviť zoznam", "timer": "Časovač", "variable": "Variable", + "trigger": "Spúšťač", + "audio_stream": "Audio stream", "transition": "Prechod", "delete_show": "Odstrániť prezentáciu", "delete_show_confirmation": "Naozaj ju chcete odstrániť?", + "delete_duplicated_shows": "Odstrániť duplicitné prezentácie", "change_name": "Premenovať", "choose_screen": "Vyberte obrazovku", + "choose_output": "Zvoľte typ výstupu", "change_output_values": "Zmeniť výstupné hodnoty", - "choose_style": "Vybrať štýl", + "choose_chord": "Zvoľte akord", "set_time": "Nastaviť čas", + "slide_shortcut": "Skratka snímku", "animate": "Animovať", + "translate": "Lokalizácia", "next_timer": "Časovač ďalšieho snímku", "import": "Import", + "songbeamer_import": "Songbeamer import", "export": "Export", "importing": "Importovanie", "import_scripture": "Import biblie", @@ -337,16 +420,18 @@ "edit_event": "Upraviť udalosť", "about": "Viac info", "history": "História", - "midi": "MIDI", + "action": "Akcia", + "category_action": "Akcia kategórie", "connect": "Pripojiť", "cloud_update": "Synchronizácia s cloudom", "cloud_method": "Miestp dát", "shortcuts": "Skratky", "icon": "Ikony", "manage_icons": "Spravovať ikony", + "manage_colors": "Spravovať farby", "choose_camera": "Zvoľte kameru", "initialize": "Vitajte vo FreeShow", - "unsaved": "Máte neuložené zmeny! Naozaj chcete ukončiť?", + "unsaved": "Naozaj si prajete skončiť?", "cancel": "Zrušiť", "continue": "Pokračovať", "reset_all": "Všetko resetovať", @@ -364,33 +449,39 @@ "verse_undefined": "Verš {} neexistuje v tejto kapitole.", "recording_started": "Nahrávanie začalo!", "recording_stopped": "Nahrávanie skončilo!", - "starting_show": "Začína prezentácia", + "starting_action": "Akcia pri štarte", "less_than_minute": "o menej než minútu.", "less_than_seconds": "o menej než {} sekúnd.", "now": "teraz!", + "unsupported_video": "Nepodporovan video!", "no_video_id": "Žiadne video ID", "no_name": "Žiadny názov", + "reverting_setting": "Táto zmena sa vráti o {} sekúnd, zapnite znova aby zmena ostala!", + "reverted": "Nastavenie vrátené! Zapnite len ak ste nemali žiadne problémy.", "media_replaced": "Chýbajúci media súbor nahradený iným.", "lyrics_undefined": "Nenašli sa žiadne texty!", - "lyrics_copied": "Texty skopírované z ", - "no_pdf_linux": "Nedá sa exportovať PDF na Linuxe.", + "lyrics_copied": "Texty skopírované z", "one_output": "Musíte mať aspoň jeden aktívny výstup!", "empty_cache": "Cache je prázdna.", "deleted_cache": "Cache náhľadov zmazaných médií.", "no_songswords_easyworship": "Chýba súbor SongsWords.db.", "delete_shows_empty": "Žiadne prezentácie na zmazanie.", - "midi_no_project": "Prijatý MIDI na zmenu projektu, ale nenašiel sa žiadny projekt na pozícii:", - "midi_no_show": "Prijatý MIDI na spustenie snímku, ale nie je aktívna prezentácia.", - "midi_no_slide": "Prijatý MIDI na spustenie snímku, ale nenašiel sa snímok na pozícii:", + "midi_no_project": "Prijatý spúšťač na zmenu projektu, ale žiadny projekt sa nenašiel na danom indexe:", + "midi_no_show": "Prijatý spúšťač na zmenu snímku, ale žiadna prezentácia nie je aktívna.", + "midi_no_slide": "Prijatý spúšťač na zmenu snímku, ale snímok sa nenašiel na danom indexe:", "midi_no_velocity": "Prijatý MIDI signál, ale bez velocity, predvolene ide na prvú pozíciu." }, "new": { + "create": "Vytvoriť nové", "show": "Nová prezentácia", "empty_show": "Nová prázdna prezentácia", "project": "Nový projekt", "section": "Nová sekcia", "timer": "Nový časovač", "variable": "Nová premenná", + "trigger": "Nový spúšťač", + "audio_stream": "Nový audio stream", + "playlist": "Nový playlist", "category": "Nová kategória", "private": "Nová súkromná prezentácia", "folder": "Nový priečinok", @@ -399,19 +490,29 @@ "template": "Nová šablóna", "scripture": "Nové písmo", "collection": "Nová kolekcia", + "action": "Nová akcia", "event": "Nová udalosť" }, "show": { "name": "Názov", "category": "Kategória", - "quick_lyrics": "Rýchle texty", "new_layout": "Nové rozloženie", "grid": "Zobrazenie v mriežke", "simple": "Jednoduchý pohľad", "list": "Zobrazenie v zozname", "lyrics": "Zobrazenie textov", "text": "Text edit", - "update": "Aktualizovať prezentáciu" + "update": "Aktualizovať prezentáciu", + "locked": "Táto prezentácia je zamknutá!", + "locked_info": "Táto prezentácia je zamknutá na úpravy. Odomknete ju v zásuvke prezentácií.", + "slide_template": "Šablóna snímku", + "source": "Zdroj", + "artist": "Umelec", + "song": "Pieseň", + "delete_manual": "Ručná kontrola", + "delete_match": "Odstrániť pri presnej zhode", + "delete_keep_last_modified": "Odstrániť všetky okrem naposledy upravenej", + "delete_keep_first_created": "Odstrániť všetky okrem prvej vytvorenej" }, "actions": { "rename": "Premenovať", @@ -420,7 +521,6 @@ "remove_group": "Odstrániť skupinu", "choose_group": "Vybrať skupiny", "goto_group": "Prejsť na skupinu", - "change_output_style": "Zmeniť výstupný štýl", "active_outputs": "Aktívne výstupy", "all_outputs": "Všetky výstupy", "specific_outputs": "Špecifické vystupy", @@ -430,6 +530,8 @@ "export": "Export", "duplicate": "Duplikovať", "delete": "Zmazať", + "delete_slide": "Odstrániť snímok", + "delete_group": "Odstrániť skupinu", "delete_all": "Zmazať všetko", "close": "Zavrieť", "save": "Uložiť", @@ -447,12 +549,18 @@ "speech": "Reč", "startSpeaking": "Začať hovorenie", "stopSpeaking": "Zastaviť hovorenie", + "focus_mode": "Prepne režim zamerania", "fullscreen": "Na celú obrazovku", "resetZoom": "Resetovať priblíženie", "zoom": "Zoom", "zoomIn": "Priblížiť", "zoomOut": "Oddialiť", "reset": "Resetovať", + "create_template": "Vytvoriť šablónu", + "project_template_tip": "Vytvorí nový projekt zo šablóny", + "convert_to_images": "Konvertovať obrázky", + "converting": "Konvertovanie...", + "remove_template_from_show": "Odstrániť šablónu z prezentácie", "reset_defaults": "Obnoviť predvolené", "to_all": "Použiť na všetky", "to_following": "Použiť na nasledovné", @@ -460,16 +568,26 @@ "home": "Domov", "mute": "Stíšiť", "unmute": "Zapnúť zvuk", + "increase_volume": "Zvýšiť hlasitosť", + "decrease_volume": "Znížiť hlasitosť", "toggle_time_marker": "Prepnúť časové značky", "add_time_marker": "Pridať časovú značku", - "bind_to": "Pripojiť na", - "remove_binding": "Odstrániť pripojenie", + "bind_to": "Špecifické výstupy", + "remove_binding": "Odstrániť špecifické výstupy", + "dynamic_values": "Dynamické hodnoty", + "rearrange": "Preusporiadať", + "to_front": "Presunúť dopredu", + "forward": "Presunúť dozadu", + "backward": "Presunúť dozadu", + "to_back": "Presunúť úplne dozadu", "show_timer": "Čas do prezentácie", "hide_timer": "Čas do skrytia", "choose_custom": "Vybrať vlastné", + "add_color": "Pridať farbu", "format": "Formát", "find_replace": "Nájsť a nahradiť text", - "cut_in_half": "Odrezať v polovici", + "cut_in_half": "Rozdeliť na dve", + "merge": "Zlúčiť", "find": "Nájsť", "replace": "Nahradiť", "case_sensitive": "Case sensitive", @@ -481,29 +599,105 @@ "svg_clipboard": "Importovať SVG zo schránky", "fullscreen_preview": "Prepnúť náhľad na celú obrazovku", "toggle_output": "Prepnúť výstupnú obrazovku", + "toggle_panels": "Prepnúť panely", "change_tab": "Zmeniť tab", "change_drawer_tab": "Zmeniť tab zásuviek", + "change_slide": "Zmeniť snímok", + "change_project_item": "Zmeniť položku projektu", + "change_drawer_item": "Zmeniť položku zásuvky", + "change_drawer_category": "Zmeniť kategóriu zásuvky", "toggle_drawer": "Prepnúť zásuvku", - "actions": "Akcie", + "slide_actions": "Akcie snímku", + "item_actions": "Akcie položky", "clear_history": "Vyčistiť históriu", + "chord_info": "Prosím kliknite na písmeno na pridanie akordu", + "chord_key": "Kľúč", + "chord_type": "Typ", + "chord_tension": "Napätie", + "chord_bass": "Basové", + "roman_keys": "Rímske kľúče", "set_key": "Nastaviť kľúč", "custom_key": "Nastaviť vlastnú hodnotu", - "play_on_midi": "Prehrať na MIDI vstup", - "send_midi": "Poslať MIDI", + "select_chord": "Vyberte akord", + "play_with_shortcut": "Aktivovať skratku", + "press_to_assign": "Stlačte písmenovú klávesu na priradenie", + "play_on_midi": "Aktivovať na MIDI signál", + "play_on_midi_tip": "Aktivuje tento konkrétny snímok pri prijatí zvoleného MIDI signálu", + "send_midi": "Poslať MIDI signál", "delete_shows_not_indexed": "Odstrániť nezaindexované prezentácie z priečinka 'Shows'", + "delete_empty_shows": "Odstrániť prázdne prezentácie", "delete_thumbnail_cache": "Delete thumbnail cache", + "export_usage_log": "Export záznamok o používaní", + "reset_usage_log": "Resetovať záznamy o používaní", "open_log_file": "Otvoriť log súbor", + "open_cache_folder": "Otvoriť priečinok cache", "refresh_all_shows": "Získať všetky prezentácie v priečinku 'Shows'", "start_timer": "Spustiť časovať", "stop_timers": "Zastaviť aktívne časovače", - "next_after_media": "Ďalšie po media", + "next_after_media": "Ďalší po ukončení média", "remove_media": "Odstrániť médium", "remove_layers": "Odstrániť vrstvy", + "toggle_checkbox_tip": "Akcia sa prepne ak je zaškrtávacia políčko nezmenené", + "start_recording": "Spustiť nahrávanie", + "stop_recording": "Zastaviť nahrávanie", + "export_recording": "Ukončiť nahrávanie a export", "index_select_project": "Vybrať projekt podľa pozície", - "index_select_project_show": "Vybrať položku projektu podľa pozície", + "next_project_item": "Ďalšia položka projektu", + "previous_project_item": "Predošlá položka projektu", + "index_select_project_item": "Vybrať položku projektu podľa indexu", + "name_select_show": "Vybrať prezentáciu podľa názvu", + "set_template_active": "Nastaviť šablónu na aktívnu prezentáciu", + "random_slide": "Prehrať náhodný snímok", "index_select_slide": "Vybrať snímok podľa pozície", - "start_recording": "Spustiť nahrávanie", - "stop_recording": "Zastaviť nahrávanie" + "name_select_slide": "Vybrať snímok podľa názvu", + "toggle_output_lock": "Prepnúť zámok výstupu", + "toggle_output_windows": "Prepnúť výstupné okno", + "id_select_group": "Vybrať skupinu podľa ID", + "id_change_stage_layout": "Zmeniť rozloženie stage podľa ID", + "start_camera": "Spustiť kameru", + "index_select_overlay": "Vybrať overlay podľa indexu", + "name_select_overlay": "Vybrať overlay podľa názvu", + "change_volume": "Zmeniť hlasitosť", + "start_audio_stream": "Spustiť audio stream", + "start_playlist": "Spustiť playlist", + "playlist_next": "Ďalšia položka v playliste", + "start_metronome": "Spustiť metronóm", + "name_start_timer": "Spustiť časovač podľa názvu", + "id_start_timer": "Spustiť časovač podľa ID", + "start_slide_timers": "Spustiť časovače na aktívnom snímku", + "id_select_output_style": "Vybrať štýl výstupu podľa ID", + "change_output_style": "Zmeniť výstupný štýl", + "change_stage_output_layout": "Zmeniť rozloženie stage výstupu", + "change_transition": "Zmeniť prechod", + "change_variable": "Zmeniť premennú", + "start_trigger": "Spustiť spúšťač", + "run_action": "Spustiť akciu", + "toggle_action": "Prepnúť akciu", + "send_rest_command": "Poslať HTTP požiadavku", + "custom_activation": "Vlastná aktivácia", + "activate_on_startup": "Aktivovať pri štarte", + "activate_save": "Aktivovať pri uložení", + "activate_slide_clicked": "Aktivovať pri kliku na snímok", + "activate_video_starting": "Aktivovať pri spustení videa", + "activate_video_ending": "Aktivovať pri ukončení videa", + "activate_audio_starting": "Aktivovať pri spustení audia", + "activate_audio_ending": "Aktivovať pri ukončení audia", + "activate_timer_ending": "Aktivovať pri ukončení časovača", + "activate_scripture_start": "Aktivovať pri spustení veršu", + "activate_slide_cleared": "Aktivovať pri vyčistení snímku", + "activate_background_cleared": "Aktivovať pri vyčistení pozadia", + "activate_show_created": "Aktivovať pri vytvorení prezentácie", + "activate_show_opened": "Aktivovať pri otvorení prezentácie", + "activate_audio_playlist_ended": "Aktivovať pri ukončení audio playlistu" + }, + "recording": { + "remove": "Odstrániť nahrávanie", + "tip": "Nahráva a prehráva časovania snímkov. Synchronizuje s audio stopou na prvom snímku.", + "layout_changed": "Rozloženie sa zmenilo od prvého nahrávania!", + "audio_synced": "Synchronizované s audiom!", + "start": "Spustiť nahrávanie snímku", + "use_duration": "Použiť čas trvania", + "use_duration_tip": "Použije čas trvania namiesto času timestamp" }, "animate": { "change": "Zmeniť", @@ -521,18 +715,21 @@ "tip_api": "Musíte nastaviť svoj Google API kľúč, aby program mohol automaticky nahrávať súbory na váš Google Drive.", "tip_how": "Neviete ako ho získať?", "tip_guide": "Tu nájdete návod.", - "enable": "Automaticky synchronizovať po spustení alebo uložení", + "enable": "Automaticky synchronizuje spustenie a zavretie", "disable_upload": "Vypnuť nahrávanie dát", "media_id": "Media path ID", "google_drive_api": "Google API service account key", "select_key": "Import keys file", "update_key": "Update keys file", + "enable_custom_folder_id": "Použiť vlastné ID priečinka", "main_folder": "Nastaviť ručne hlavný priečinok", "media_folder": "Cloud media priečinok", "reconnect": "Pripojiť znova", - "sync": "Synchronizovať", - "choose_method_tip": "Na cloude sa už nachádzajú dáta. Prosím vyberte odkiaľ chcete údaje zachovať. A prepísať na opačnom mieste.", - "local": "Lokálne" + "sync": "Sync teraz", + "choose_method_tip": "Na cloude sú existujúce údaje. Prosím zvoľte buď nahratie z lokálu alebo stiahnutie z cloudu. Druhá strana sa prepíše.", + "local": "Lokálne", + "syncing": "Synchronizácia na cloud", + "sync_complete": "Synchronizácia hotová" }, "export": { "export": "Export", @@ -544,7 +741,6 @@ "all_shows": "Všetky prezentácie", "all_projects": "Všetky projekty", "project": "Projekt", - "options": "Voľby", "preview": "Náhľad", "title": "Názov", "metadata": "Metadata", @@ -560,15 +756,22 @@ }, "context": { "enabledTabs": "Zobraziť/skryť taby", + "setTag": "Nastaviť značku", + "filterByTags": "Filter značiek", "addToProject": "Pridať do projektu", + "add_to_show": "Pridať do prezentácie", + "lockForChanges": "Zamknúť pre zmeny", + "use_as_archive": "Označiť ako archivované", "newCategory": "Nová kategória", "changeIcon": "Zmeniť ikonu", "changeGroup": "Zmeniť skupiny", "createNew": "Vytvoriť nové", "selectAll": "Vybrať všetky", "force_outputs": "Vynútiť výstupy", + "align_with_screen": "Zarovnať s obrazovkou", "toggle_output": "Prepnúť výstup", "move_to_front": "Presunúť dopredu", + "hide_from_preview": "Skryť z náhľadu", "lock_to_output": "Zamknúť na výstup", "place_under_slide": "Umiesniť pod snímok", "toggle_clock": "Prepnúť hodiny", @@ -588,27 +791,45 @@ "slide": "Snímok" }, "edit": { + "text": "Text", "font": "Písmo", - "family": "Rodina", + "family": "Rodina písma", + "font_size": "Veľkosť písma", + "text_fit": "Zmestenie textu", + "shrink_to_fit": "Stlač aby sa zmestil", + "grow_to_fit": "Natiahni aby sa zmestil", + "text_color": "Farba textu", "style": "Štýl", + "lines": "Riadky", "options": "Voľby", "_title_bold": "Tučné", "_title_italic": "Naklonené", "_title_underline": "Podčiarknuté", "_title_strikethrough": "Prečiarknuté", "color": "Farba", + "accent_color": "Odtieň farby", "background_color": "Farba pozadia", "background_opacity": "Priehľadnosť pozadia", "background_image": "Obrázok na pozadí", + "background_media": "Médium na pozadí", + "overlay_content": "Pridať obsah overlay", + "different_first_template": "Vlastná šablóna na prvom snímku", "media_fit": "Prispôsobenie Media", + "one_letter": "Režim jedného písmena", + "sub_indexes": "Pod indexy", "size": "Veľkosť", + "max_lines": "Max riadkov", + "invert_items": "Prevrátiť položky", "chords": "Akordy", + "transpose": "Transponovať", "auto_size": "Automatická veľkosť", + "no_wrap": "Text na jeden riadok", "line_spacing": "Riadkovania", "line_height": "Výška riadka", "letter_spacing": "Medzi písmenami", "word_spacing": "Medzi slovami", "transform": "Transformácia", + "text_transform": "Transformácia textu", "uppercase": "VEĽKÉ", "lowercase": "malé", "capitalize": "Kapitálky", @@ -620,13 +841,13 @@ "_title_top": "Zarovnať na vrch", "_title_bottom": "Zarovnať na spodok", "outline": "Obrys", - "width": "Šírka", "shadow": "Tieň", "shadow_inset": "Tieň vnútorný", "offsetX": "Odstup X", "offsetY": "Odstup Y", "blur": "Rozostrenie", "item": "Položka", + "width": "Šírka", "height": "Výška", "rotation": "Otočenie", "tilt": "Naklonenie", @@ -648,11 +869,13 @@ "recent": "Recently edited", "enable_stage": "Zapnúť pódium", "select_stage": "Vybrať pódium", + "next_slide": "Ukázať ďalší snímok", "use_slide_index": "Použiť aktívnu pozíciu", "slide_index": "Pozícia snímku", "padding": "Padding", "special": "Special", "scrolling": "Skrolovanie", + "scrolling_speed": "Rýchlosť skrolovania", "top_bottom": "Zhora nadol", "bottom_top": "Zdola nahor", "left_right": "Zľava doprava", @@ -660,14 +883,16 @@ "max_events": "Maximum udalostí", "start_days_from_today": "Začať dni od dnes", "just_one_day": "Len jeden deň", - "enable_start_date": "Zapnúť počiatočný dátum" + "enable_start_date": "Zapnúť počiatočný dátum", + "disable_navigation": "Vypnúť ovládanie navigácie", + "progress_bar": "Ukazovateľ priebehu" }, "items": { "text": "Textové pole", "list": "Zoznam", "media": "Mediá", - "camera": "Kamera", "image": "Obrázok", + "camera": "Kamera", "video": "Video", "mirror": "Zrkadlo", "clock": "Hodiny", @@ -677,7 +902,9 @@ "timer": "Časovač", "variable": "Premenná", "web": "Webstránka", + "slide_tracker": "Priebeh", "visualizer": "Visualizer", + "captions": "Titulky", "icon": "Ikona" }, "borders": { @@ -710,6 +937,7 @@ "to_event": "Čas do udalosti", "counter": "Odpočet", "time": "Čas", + "minutes": "Minúty", "seconds": "Sekundy", "from": "Od", "to": "Do", @@ -730,7 +958,23 @@ "analog": "Ručičkové", "seconds": "Sekundy" }, + "captions": { + "info": "Prosím kliknitím na URL ju otvorte v prehliadači ak ste tak ešte nespravili, alebo ju otvorte na inom zariadení! Prosím uistite sa, že ste povolili prístup k mikrofónu, a použite Google Chrome pre najlepší výkon.", + "language": "Jazyk prepisu", + "translate": "Preložiť do", + "showtime": "Zobraziť trvanie", + "powered_by": "Poháňa" + }, + "localization": { + "translate": "Preložiť", + "add": "Pridať preklad", + "update": "Upraviť preklad", + "remove": "Odstrániť preložené položky" + }, "midi": { + "midi": "MIDI", + "activate": "Aktivovať MIDI signálom", + "activate_keypress": "Aktivovať klávesou", "name": "Názov", "input": "Vstup", "output": "Výstup", @@ -748,6 +992,7 @@ "draw": { "focus": "Focus", "pointer": "Kurzor", + "zoom": "Priblíženie", "fill": "Výplň", "paint": "Maľba", "particles": "Častice", @@ -764,6 +1009,7 @@ }, "stage": { "slide": "Snímok", + "stage_layout": "Rozloženie stage", "current_slide_text": "Text aktuálneho snímku", "current_slide": "Aktuálny snímok", "current_slide_notes": "Poznámky aktuálneho snímku", @@ -772,17 +1018,22 @@ "next_slide_notes": "Poznámky ďalšieho snímku", "output": "Výstup", "current_output": "Aktuálny výstyp", + "slide_tracker": "Priebeh", "time": "Čas", "system_clock": "Systémové hodiny", "video_time": "Video čas", "video_countdown": "Video odpočet", + "first_active_timer": "Prvý aktívny časovač", "other": "Iné", "message": "Správa", "color": "Farba", "font-size": "Veľkosť písma", "zeros": "Nuly", "overrun": "Farba po ukončení", - "auto_stretch": "Automaticky roztiahnuť obsah" + "source_output": "Výstup zdroja", + "auto_stretch": "Automaticky roztiahnuť obsah", + "labels": "Zobraziť popisky", + "label_color": "Farba popisku" }, "settings": { "general": "Všeobecné", @@ -790,11 +1041,12 @@ "groups": "Skupiny", "styles": "Štýly", "display_settings": "Výstupy", - "actions": "Akcie", "display": "Obrazovka", "connection": "Pripojenia", "cloud": "Cloud", "calendar": "Kalendár", + "text_import": "Text", + "media_import": "Media", "other": "Iné", "language": "Jazyk", "autosave": "Automatické ukladanie", @@ -809,7 +1061,10 @@ "select_display": "Kliknite na obrazovku, na ktorú chcete zobrazovať výstupné okno.", "manual_input_hint": "Nenašli ste displej? Kliknite sem na zmenu pozície.", "manual_drag_hint": "Môžete tiež držať ctrl/cmd nad aktívnym výstupným oknom a ručne ho posúvať.", + "allow_main_screen": "Povoliť vlastnú výstupnú pozíciu a veľkosť", + "identify_screens": "Identifikovať obrazovky", "new_output": "Nový výstup", + "normal": "Normálne", "enable_key_output": "Zapnúť výstup alpha kľúčovania", "always_on_top": "Vždy na vrchu", "kiosk_mode": "Kiosk režim", @@ -819,32 +1074,50 @@ "color_when_active": "Farba aktívneho", "fixed": "Pevné", "lines": "Čiary", - "override_with_template": "Prepísať šablónou", + "override_with_template": "Prepísať snímok šablónou", + "override_scripture_with_template": "Prepísať verš šablónou", "active_layers": "Aktívne vrstvy", "window": "Okno", "active_style": "Použiť štýl", "alert_updates": "Upozorniť na dostupné aktualizácie", + "auto_updates": "Auto aktualizácie", "disable_labels": "Vypnúť popisky", "group_numbers": "Čísla skupín", - "full_colors": "Plné farby skupín snímok", + "full_colors": "Farby skupín s vysokým kontrastom", + "slide_number_keys": "Spúštať snímky čiselnými klávesmi", + "auto_shortcut_first_letter": "Automatická skratka na prvé písmeno textu", "auto_output": "Zobraziť výstupné okno pri spustení", + "hide_cursor_in_output": "Skryť kurzor na výstupe", + "clear_media_when_finished": "Vyčistiť médium pri ukončení", + "disable_presenter_controller_keys": "Vypnúť prezentačné ovládacie klávesy", "default_project_name": "Predvolený názov projektu", + "audio_fade_duration": "Trvanie auto fade", + "audio_crossfade": "Audio crossfade", + "clear_style_background_on_text": "Vyčistiť štýl pozadia pri aktivácii snímku", "resolution": "Rozlíšenie", "cropping": "Orezanie", "frame_rate": "Snímková rýchlosť", + "device": "Zariadenie", + "display_mode": "Režim zobrazenia", + "pixel_format": "Formát pixelov", + "alpha_key": "Alfa kľúč", "transparent": "Priehľadné", + "invisible_window": "Neviditeľné okno", "video_extensions": "Prípony videí", "image_extensions": "Prípony obrázkov", "add": "Pridať", "remove": "Odstrániť", "change_name": "Zmeniť názov", "show_location": "Zobraziť umiestnenie", - "export_location": "Miesto Exportov", - "scripture_location": "Miesto biblií", - "recording_location": "Miesto nahrávok", + "data_location": "Umiestnenie údajov", + "user_data_location": "Uložiť používateľské nastavenia na 'Umiestnenie dát'", + "popup_before_close": "Zapnúť potvrdzovanie ukončenia", + "disable_hardware_acceleration": "Vypnúť hardvérovú akceleráciu", + "restart_for_change": "Na prejavenie zmeny treba reštartovať program!", "font": "Písmo", "font_family": "Rodina písma", "font_size": "Veľkosť písma", + "border_radius": "Polomer rámiku", "colors": "Farby", "add_group": "Pridať skupinu", "group_shortcut": "Shortcut to activate group", @@ -861,16 +1134,21 @@ "reset_all": "Reset všetkého", "reset_theme": "Reset témy", "reset_themes": "Reset tém", + "capitalize_words": "Kapitalizovať slová", + "comma_seperated": "Oddelené čiarkou", "backup_all": "Zálohovať všetko", "restore": "Obnoviť", "backup_started": "Zálohuje sa ...", "restore_started": "Obnovuje sa...", "backup_finished": "Záloha hotová!", "restore_finished": "Obnova hotová!", + "auto_backup": "Auto zálohy", "preview_frame_rate": "Rýchlosť snímkov v náhľade", "auto": "Auto", "optimized": "Optimalizované", - "full": "Plné" + "reduced": "Znížené", + "full": "Plné", + "section_trigger_action": "Spustí akciu pri nevigovaní prezentácie do sekcie" }, "sort": { "sort_by": "Zoradiť podľa", @@ -883,7 +1161,7 @@ "calendar": { "type": "Typ", "event": "Nová udalosť", - "show": "Naplánovať prezentáciu", + "schedule_action": "Naplánovať akciu", "name": "Názov", "color": "Farba", "time": "Čas", @@ -902,7 +1180,7 @@ "week": "týždeň", "month": "mesiac", "year": "rok", - "ending_the": "", + "ending_the": "dátumu", "ending_repeated": "opakovať", "ending_times": "krát", "save_all": "Uložiť a aktualizovať všetko", @@ -913,9 +1191,15 @@ "custom": "Alebo importujete svoju vlastnú", "max_verses": "Maximum veršov na snímok", "verse_numbers": "Čísla veršov", + "verses_on_individual_lines": "Verše na jednotlivých riadkoch", "version": "Zobraziť preklad", "reference": "Zobraziť odkazy", - "red_jesus": "Ježišove slová červeným" + "split_reference": "Rozdeliť adresy", + "combine_with_text": "Kombinovať s textom", + "first_slide_reference": "Adresy na prvom snímku", + "reference_at_bottom": "Presunúť na spodok", + "red_jesus": "Ježišove slová červeným", + "search": "Hľadať v Biblii" }, "filter": { "blur": "Rozostrenie", @@ -932,14 +1216,32 @@ "screens": "Obrazovky", "windows": "Okná", "cameras": "Kamery", - "microphones": "Mikrofóny" + "microphones": "Mikrofóny", + "audio_streams": "Audio streamy" + }, + "presentation_control": { + "unsupported": "Prezentačný ovládač nie je podporovaný na", + "unsupported_tip": "Môžete ich konvertovať na obrázky a vytvoriť prezentáciu, alebo vypnúť 'Vždy na vrchu' a prezentovať z akejkoľvek aplikácie.", + "opening": "Otvára sa! Prosím čakajte...", + "retry": "Prosím skúste otvoriť ručne alebo", + "try_again": "Skúste znova", + "restart": "Reštart", + "start": "Štart", + "choose_window": "Prosím vyberte okno" }, "transition": { "current_slide": "pre aktuálny snímok", "text": "Prechod textu", "media": "Prechod média", + "slide_transition": "Prechod snímku", + "background_transition": "Prechod pozadia", + "specific": "Zapnúť konkrétnejšie prechody", + "between": "Medzi", + "in": "Do", + "out": "Z", "duration": "Trvanie", "easing": "Spomalenie", + "direction": "Smer", "type": "Typ", "none": "Žiadne", "fade": "Prelínanie", @@ -990,16 +1292,70 @@ "6": "Sobota", "7": "Nedeľa" }, + "interval": { + "daily": "Denne", + "weekly": "Týždenne", + "mothly": "Mesačne" + }, "info": { "created": "Vytvorené", "modified": "Upravené", "used": "Použité", "changed": "Zmenené", + "recently_used": "Naposledy použité", "extension": "Prípona", "size": "Veľkosť", "slides": "Snímky", "words": "Slová", "template": "Šablóna", - "category": "Kategória" + "category": "Kategória", + "codecs": "Kódeky", + "mimeType": "MIME typ", + "photoUrl": "Foto URL", + "likes": "Páči sa", + "artist": "Umelec", + "artistUrl": "Stránka umelca" + }, + "guide_title": { + "projects": "Projekty", + "project": "Projekt", + "drawer": "Zásuvka", + "categories": "Kategórie", + "show_create": "Vytvorenie prezentácie", + "show": "Prezentácia", + "output": "Výstup", + "editing": "Upravovanie", + "end": "To sú základy!" + }, + "guide_description": { + "start": "Toto je krátka prehliadka niektorých základných funkcií! (Preskakovať môžete v pravom dolnom rohu.)", + "project_manage": "Spravujte všetky svoje projekty na jednom mieste.", + "project_create": "Vytvorte priečinky pre lepšiu organizáciu a vytvárajte nové projekty.", + "project_list": "Po otvorení projektu dostanete zoznam všetkých položiek tu na ľavej strane, presúvajte ich myšou zo zásuvky ak potrebujete viac.", + "drawer": "Nájdite všetky vaše prezentácie, médiá, overlay, atď. na jednom mieste.", + "drawer_search": "Hľadajte obsah v aktívnej zásuvke (piesne, atď.). Použite skratku Ctrl/Cmd+F na rýchlejší prístup.", + "drawer_shows": "Spravujte všetky rôzne prezentácie zaraďovaním do rôznych kategórií. Presúvajte vybraté prezentácie medzi nimi.", + "create_show_button": "Kliknite sem na vytvorenie novej prezentácie (pieseň, snímky, atď.", + "create_show_popup": "Vyplňte najdôležitejšie údaje o prezentácii, môžete tiež nájsť texty piesní na webe podľa názvu alebo ich zadať ručne.", + "show": "Prezentácia na hlavný typ obsahu, kliknite na akýkoľvek snímok na prezentovanie a používajte klávesy šípok na navigáciu. Taktiež môžete presúvač media súbory do snímku ako pozadie", + "show_slide": "Po kliknutí na snímok sa zobrazí vo výstupe a náhľad vidíte napravo, tu nájdete aj ďalšie akcie ako zastavenia videa.", + "output_clear": "Vyčistite jednotlivé vrstvy výstupu, alebo vyčistite všetky vrstvy naraz.", + "edit": "Úpravy snímku otvoríte jedným klikom hore", + "end": "Vždy môžete pozrieť na web ak potrebujete pomoc s ďalšími funkciami." + }, + "songbeamer_import": { + "options": "Voľby", + "encoding": "Enkódovanie", + "older_versions": "Pre Songbeamer verzie staršie ako 6.02a", + "category": "Kategória", + "utf8": "UTF-8", + "latin1": "Latin 1", + "translations": "Preklady", + "translation_multiline": "Viacriadkové", + "translation_textboxes": "Textové polia", + "translation_layouts": "Rozloženia", + "translation_description_multiline": "Pridá všetky jazyky v jednom textovom poli ako alternatívne riadky. (Ako v Songbeameri)", + "translation_description_textboxes": "Pridá každý jazyk ako oddelené pole v snímku.", + "translation_description_layouts": "Vytvorí unikátnu množinu snímkov rozloženia pre každý jazyk." } -} \ No newline at end of file +} diff --git a/src/electron/capture/helpers/CaptureTransmitter.ts b/src/electron/capture/helpers/CaptureTransmitter.ts index dce2df12..28e65f1c 100644 --- a/src/electron/capture/helpers/CaptureTransmitter.ts +++ b/src/electron/capture/helpers/CaptureTransmitter.ts @@ -48,7 +48,7 @@ export class CaptureTransmitter { // WIP one global capture (on the highest frame rate) instead of multiple per frame rate - but using multipe at once is probably an edge case static startChannel(captureId: string, key: string) { const combinedKey = `${captureId}-${key}` - const interval = 1000 / OutputHelper.getOutput(captureId)?.captureOptions?.framerates?.[key] || 30 + const interval = 1000 / (OutputHelper.getOutput(captureId)?.captureOptions?.framerates?.[key] || 30) // console.log("START CHANNEL:", key, interval) if (this.channels[combinedKey]?.timer) { diff --git a/src/electron/data/defaults.ts b/src/electron/data/defaults.ts index 99ad7dee..858187d7 100644 --- a/src/electron/data/defaults.ts +++ b/src/electron/data/defaults.ts @@ -123,6 +123,7 @@ export const defaultSyncedSettings: { [key in SaveListSyncedSettings]: any } = { groups: defaultGroups, midiIn: {}, videoMarkers: {}, + mediaTags: {}, customizedIcons: { disabled: [], svg: [] }, companion: {}, globalTags: {}, diff --git a/src/electron/ndi/NdiSender.ts b/src/electron/ndi/NdiSender.ts index 43374cb5..ee4a3cba 100644 --- a/src/electron/ndi/NdiSender.ts +++ b/src/electron/ndi/NdiSender.ts @@ -3,8 +3,7 @@ import os from "os" import { toApp } from ".." import { CaptureHelper } from "../capture/CaptureHelper" import util from "./vingester-util" - -// WIP - NDI issue on Linux: libndi.so.5: No such file or dialog +import { CaptureTransmitter } from "../capture/helpers/CaptureTransmitter" // Resources: // https://www.npmjs.com/package/grandiose-mac @@ -63,6 +62,15 @@ export class NdiSender { CaptureHelper.updateFramerate(id) this.NDI[id].previousStatus = newStatus + + if (this.NDI[id].status === "connected") { + Object.keys(CaptureTransmitter.channels).forEach((key) => { + if (key.includes("ndi")) { + // force an instant check / output refresh when reconnected + CaptureTransmitter.channels[key].lastCheck = 999 + } + }) + } } }, 1000) } diff --git a/src/electron/utils/api.ts b/src/electron/utils/api.ts index c83552e8..461b746e 100644 --- a/src/electron/utils/api.ts +++ b/src/electron/utils/api.ts @@ -1,6 +1,7 @@ import { ipcMain } from "electron" import express from "express" import http from "http" +import OSC from "osc-js" import { Server } from "socket.io" import { uid } from "uid" import { toApp } from ".." @@ -15,6 +16,7 @@ const DEFAULT_PORTS = { WebSocket: 5505, REST: 5506 } export function startWebSocketAndRest(port: number | undefined) { startRestListener(port ? port + 1 : 0) startWebSocket(port) + startOSC(port) } // WEBSOCKET @@ -24,7 +26,7 @@ export function startWebSocket(PORT: number | undefined) { if (!PORT) PORT = DEFAULT_PORTS.WebSocket server.listen(PORT, () => { - console.log(`WebSocket: Starting server at port ${PORT}.`) + console.log(`WebSocket: Starting server at port ${PORT}`) }) server.once("error", (err: any) => { @@ -93,6 +95,41 @@ export function startRestListener(PORT: number | undefined) { }) } +// Open Sound Control + +function startOSC(PORT: number | undefined) { + if (!PORT) PORT = DEFAULT_PORTS.WebSocket + + // const osc = new OSC({ plugin: new OSC.WebsocketServerPlugin() }) // ws://ip:port + const osc = (servers.OSC = new OSC({ plugin: new OSC.DatagramPlugin() })) // UDP + + osc.on("/freeshow/*", async (msg: any) => { + // const active = msg.args[1] || 0 + let args: any = {} + try { + args = JSON.parse(msg.args[0] || "{}") + } catch (err) { + console.log("OSC: Could not parse JSON!\n", err) + } + + const action = msg.address.replace("/freeshow", "") + const returnData = await receivedData({ action, ...args }, (msg: string) => console.log(`OSC: ${msg}`)) + if (!returnData) return + + var message = new OSC.Message(msg.address, returnData) + osc.send(message) + }) + + osc.on("open", () => { + console.log(`OSC: Listening for data at port ${PORT}`) + }) + osc.on("error", (err: any) => { + console.log(`OSC: Error. ${JSON.stringify(err)}`) + }) + + osc.open({ port: PORT }) +} + // DATA async function receivedData(data: any = {}, log: any) { diff --git a/src/frontend/App.svelte b/src/frontend/App.svelte index 674d89b5..d0240b2e 100644 --- a/src/frontend/App.svelte +++ b/src/frontend/App.svelte @@ -4,6 +4,7 @@ import ContextMenu from "./components/context/ContextMenu.svelte" import Pdf from "./components/export/Pdf.svelte" import Guide from "./components/guide/Guide.svelte" + import { getBlending } from "./components/helpers/output" import { startEventTimer, startTimer } from "./components/helpers/timerTick" import Loader from "./components/main/Loader.svelte" import MenuBar from "./components/main/MenuBar.svelte" @@ -12,7 +13,7 @@ import Toast from "./components/main/Toast.svelte" import QuickSearch from "./components/quicksearch/QuickSearch.svelte" import Center from "./components/system/Center.svelte" - import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay } from "./stores" + import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay, outputs } from "./stores" import { focusArea, logerror, startAutosave, toggleRemoteStream } from "./utils/common" import { keydown } from "./utils/shortcuts" import { startup } from "./utils/startup" @@ -37,6 +38,10 @@ // close youtube ad $: if ($closeAd) setTimeout(() => closeAd.set(false), 10) + + // edge blending + let blending = "" + $: if ($currentWindow === "output" && Object.values($outputs)[0]?.blending) blending = getBlending() @@ -49,7 +54,7 @@ {/if} -
+
{#if $currentWindow === "output"} diff --git a/src/frontend/MainLayout.svelte b/src/frontend/MainLayout.svelte index ae9b7ec2..3e93acd1 100644 --- a/src/frontend/MainLayout.svelte +++ b/src/frontend/MainLayout.svelte @@ -21,7 +21,7 @@ import StageShow from "./components/stage/StageShow.svelte" import StageTools from "./components/stage/StageTools.svelte" import Resizeable from "./components/system/Resizeable.svelte" - import { activeEdit, activePage, activeShow, activeStage, currentWindow, focusMode, loaded, os } from "./stores" + import { activeEdit, activePage, activeShow, activeStage, currentWindow, focusMode, loaded, os, showsCache, textEditActive } from "./stores" import { DEFAULT_WIDTH } from "./utils/common" $: page = $activePage @@ -87,10 +87,14 @@ {:else if page === "edit"} {#if $activeEdit.type === "media"} + {:else if $activeEdit.type === "audio"} + {:else if $activeEdit.type === "effect"} - {:else if !$focusMode} - + {:else if $activeEdit.type === "overlay" || $activeEdit.type === "template" || $showsCache[$activeShow?.id || ""]} + {#if !$focusMode && !$textEditActive} + + {/if} {/if} {:else if page === "draw"} @@ -125,6 +129,8 @@ } .center { + position: relative; + flex: 1; background-color: var(--primary-darker); overflow: auto; diff --git a/src/frontend/components/actions/CreateAction.svelte b/src/frontend/components/actions/CreateAction.svelte index e464dcd0..8477b08a 100644 --- a/src/frontend/components/actions/CreateAction.svelte +++ b/src/frontend/components/actions/CreateAction.svelte @@ -51,13 +51,13 @@ "change_transition", "change_variable", "start_camera", - "run_action", "toggle_action", "send_rest_command", ] // 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 = [ ...Object.keys(API_ACTIONS) @@ -86,7 +86,7 @@ return true }), // custom special - ...(list ? [] : [{ id: "wait", name: $dictionary.animate?.wait, icon: "time_in", common: false }]), + ...(list ? [] : [{ id: "wait", name: $dictionary.animate?.wait || "", icon: "time_in", common: false }]), ] let pickAction: boolean = false diff --git a/src/frontend/components/actions/api.ts b/src/frontend/components/actions/api.ts index 82708f6c..31263187 100644 --- a/src/frontend/components/actions/api.ts +++ b/src/frontend/components/actions/api.ts @@ -34,6 +34,7 @@ import { startScripture, toggleLock, } from "./apiHelper" +import { oscToAPI } from "./apiOSC" import { sendRestCommandSync } from "./rest" /// STEPS TO CREATE A CUSTOM API ACTION /// @@ -180,7 +181,7 @@ export const API_ACTIONS = { // start specific folder (playlist) // folder_select_audio: () => , change_volume: (data: API_volume) => updateVolume(data.volume ?? data.gain, data.gain !== undefined), // BC - start_audio_stream: (data: API_id) => startAudioStream(data.id), + start_audio_stream: (data: API_id) => startAudioStream(data), start_playlist: (data: API_id) => startPlaylist(data.id), playlist_next: () => audioPlaylistNext(), start_metronome: (data: API_metronome) => startMetronome(data), @@ -224,6 +225,9 @@ export const API_ACTIONS = { /// RECEIVER / SENDER /// export async function triggerAction(data: API) { + // Open Sound Control format + if (data.action.startsWith("/")) data = oscToAPI(data) + let id = data.action // API start at 1, code start at 0 diff --git a/src/frontend/components/actions/apiOSC.ts b/src/frontend/components/actions/apiOSC.ts new file mode 100644 index 00000000..62a39994 --- /dev/null +++ b/src/frontend/components/actions/apiOSC.ts @@ -0,0 +1,80 @@ +// Examples: /show//start | /slide/next | /clear/all +const oscActions = { + // project: { + // _id: (id: string) => ({ + // open: () => ({ action: "id_select_project", id }), + // // open: () => ({ action: "index_select_project", index: Number(id) }), + // }), + // }, + slide: { + next: () => ({ action: "next_slide" }), + previous: () => ({ action: "previous_slide" }), + }, + show: { + _id: (id: string) => ({ + // open: () => ({ action: "id_select_show", id }), + start: () => ({ action: "start_show", id }), + // slide: () => ({ + // next: () => ({ action: "next_slide", id }), + // previous: () => ({ action: "previous_slide", id }), + // // _id: (slideId: string) => ({ + // // start: () => ({ action: "id_select_slide", id, slideId }), + // // }), + // }), + }), + }, + clear: { + all: () => ({ action: "clear_all" }), + background: () => ({ action: "clear_background" }), + slide: () => ({ action: "clear_slide" }), + overlays: () => ({ action: "clear_overlays" }), + audio: () => ({ action: "clear_audio" }), + next_timer: () => ({ action: "clear_next_timer" }), + }, + timer: { + _id: (id: string) => ({ + start: () => ({ action: "id_start_timer", id }), + }), + stop: () => ({ action: "stop_timers" }), + }, +} + +// data: { action: string, ... } +export function oscToAPI(data: any) { + try { + data = { ...data, ...parsePath(data.action) } + } catch (err) { + // use path value as api action id + let action = data.action.slice(1) + if (!action.includes("/")) return { ...data, action } + + console.log(err) + return data + } + + console.log("OSC API DATA:", data) + return data +} + +function parsePath(path) { + const parts = path.split("/").filter(Boolean) + console.log("OSC API PATH:", path) + + let currentPath: any = oscActions + + for (let part of parts) { + if (typeof currentPath[part] === "function") { + currentPath = currentPath[part]() + } else if (currentPath[part]) { + currentPath = currentPath[part] + } else if (currentPath._id) { + currentPath = currentPath._id(part) + } else { + throw new Error(`Invalid OSC API path: ${path}`) + } + } + + if (typeof currentPath !== "object") return {} + + return currentPath +} diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index b39b9ad9..fdec729c 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -47,7 +47,6 @@ view_simple: () => ($slidesOptions.mode === "simple" ? (enabled = true) : ""), view_list: () => ($slidesOptions.mode === "list" ? (enabled = true) : ""), view_lyrics: () => ($slidesOptions.mode === "lyrics" ? (enabled = true) : ""), - view_text: () => ($slidesOptions.mode === "text" ? (enabled = true) : ""), rename: () => { hide = $shows[$selected.data[0]?.id]?.locked }, @@ -218,7 +217,7 @@ // don't hide context menu const keepOpen = ["uppercase", "lowercase", "capitalize", "trim"] // "dynamic_values" (caret position is lost) if (keepOpen.includes(id)) return - const keepOpenToggle = ["enabled_drawer_tabs", "tag_set", "tag_filter", "bind_slide", "bind_item"] + const keepOpenToggle = ["enabled_drawer_tabs", "tag_set", "tag_filter", "media_tag_set", "media_tag_filter", "bind_slide", "bind_item"] if (keepOpenToggle.includes(id)) { enabled = !enabled return diff --git a/src/frontend/components/context/ContextMenu.svelte b/src/frontend/components/context/ContextMenu.svelte index f48e0d04..5741a386 100644 --- a/src/frontend/components/context/ContextMenu.svelte +++ b/src/frontend/components/context/ContextMenu.svelte @@ -95,6 +95,7 @@ if (id === "format") return $contextData.textContent || $activePage !== "show" if (id === "remove_layers") return $contextData.layers if (id === "tag_set" || id === "tag_filter") return $contextData.tags + if (id === "media_tag_filter") return $contextData.media_tags return true } diff --git a/src/frontend/components/context/contextMenus.ts b/src/frontend/components/context/contextMenus.ts index 5e61c4e5..f6e5b972 100644 --- a/src/frontend/components/context/contextMenus.ts +++ b/src/frontend/components/context/contextMenus.ts @@ -42,6 +42,8 @@ export const contextMenuItems: { [key: string]: ContextMenuItem } = { enabledTabs: { label: "context.enabledTabs", items: ["LOAD_enabled_drawer_tabs"] }, tag_set: { label: "context.setTag", icon: "tag", items: ["LOAD_tag_set"] }, tag_filter: { label: "context.filterByTags", icon: "tag", items: ["LOAD_tag_filter"] }, + media_tag_set: { label: "context.setTag", icon: "tag", items: ["LOAD_media_tag_set"] }, + media_tag_filter: { label: "context.filterByTags", icon: "tag", items: ["LOAD_media_tag_filter"] }, newCategory: { label: "context.newCategory", icon: "add" }, newScripture: { label: "new.scripture", icon: "add" }, createCollection: { label: "new.collection", icon: "collection" }, @@ -89,7 +91,6 @@ export const contextMenuItems: { [key: string]: ContextMenuItem } = { view_simple: { label: "show.simple", icon: "simple" }, view_list: { label: "show.list", icon: "list" }, view_lyrics: { label: "show.lyrics", icon: "lyrics" }, - view_text: { label: "show.text", icon: "text" }, // SLIDE slide_transition: { label: "popup.transition", icon: "transition" }, disable: { label: "actions.disable", icon: "disable" }, @@ -106,7 +107,7 @@ export const contextMenuItems: { [key: string]: ContextMenuItem } = { transition: { label: "popup.transition", icon: "transition" }, dynamic_values: { label: "actions.dynamic_values", icon: "star", items: ["LOAD_dynamic_values"] }, item_bind_to: { label: "actions.bind_to", icon: "bind", items: ["LOAD_bind_item"] }, - format: { label: "actions.format", icon: "format", items: ["find_replace", "cut_in_half", "merge", "SEPERATOR", "uppercase", "lowercase", "capitalize", "trim"] }, + format: { label: "actions.format", icon: "format", items: ["find_replace", "SEPERATOR", "cut_in_half", "merge", "SEPERATOR", "uppercase", "lowercase", "capitalize", "trim"] }, rearrange: { label: "actions.rearrange", icon: "rearrange", items: ["to_front", "forward", "backward", "to_back"] }, // stage stage: { label: "menu.stage", id: "stage" }, @@ -198,7 +199,8 @@ export const contextMenuLayouts: { [key: string]: string[] } = { midi: ["play", "SEPERATOR", "edit", "delete"], // , "addToShow" // show_in_explorer!! - media_card: ["addToProject", "SEPERATOR", "edit", "preview", "favourite", "SEPERATOR", "play_no_audio", "play_no_filters", "SEPERATOR", "system_open"], + media: ["media_tag_filter"], + media_card: ["addToProject", "SEPERATOR", "edit", "preview", "favourite", "SEPERATOR", "play_no_audio", "play_no_filters", "SEPERATOR", "media_tag_set", "media_tag_filter", "SEPERATOR", "system_open"], // "addToFirstSlide", overlay_card: ["edit", "preview", "SEPERATOR", "lock_to_output", "place_under_slide", "SEPERATOR", "rename", "recolor", "duplicate", "delete"], // "addToShow", @@ -242,14 +244,14 @@ export const contextMenuLayouts: { [key: string]: string[] } = { // SHOWS // , "copy", "paste" - slide: ["slideGroups", "actions", "bind_to", "format", "remove_layers", "slide_transition", "disable", "edit", "SEPERATOR", "duplicate", "delete_slide", "remove_slide"], + slide: ["edit", "SEPERATOR", "slideGroups", "actions", "bind_to", "format", "remove_layers", "slide_transition", "disable", "SEPERATOR", "duplicate", "delete_slide", "remove_slide"], slideChild: ["slideGroups", "actions", "bind_to", "format", "remove_layers", "slide_transition", "disable", "edit", "SEPERATOR", "duplicate", "delete_slide", "remove_slide"], slideFocus: ["editSlideText"], group: ["rename", "recolor", "SEPERATOR", "selectAll", "SEPERATOR", "duplicate", "delete_group"], global_group: ["edit"], // global_group: ["rename"], layout: ["rename", "duplicate", "remove"], - slideViews: ["view_grid", "view_simple", "view_list", "view_lyrics", "view_text"], + slideViews: ["view_grid", "view_simple", "view_list", "view_lyrics"], tag: ["rename", "recolor", "SEPERATOR", "delete"], chord: ["set_key", "chord_list", "custom_key", "SEPERATOR", "delete"], meta_message: ["dynamic_values"], diff --git a/src/frontend/components/context/loadItems.ts b/src/frontend/components/context/loadItems.ts index 0adf6ea6..97868ae2 100644 --- a/src/frontend/components/context/loadItems.ts +++ b/src/frontend/components/context/loadItems.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import { activeEdit, activeTagFilter, contextData, drawerTabsData, globalTags, groups, outputs, overlays, selected, shows, sorted } from "../../stores" +import { activeEdit, activeMediaTagFilter, activeTagFilter, contextData, drawerTabsData, globalTags, groups, media, mediaTags, outputs, overlays, selected, shows, sorted } from "../../stores" import { translate } from "../../utils/language" import { drawerTabs } from "../../values/tabs" import { actionData } from "../actions/actionData" @@ -33,18 +33,33 @@ const loadActions = { setContextData("tags", sortedTags.length) return sortedTags }, + media_tag_set: () => { + let selectedTags = get(media)[get(selected).data[0]?.path]?.tags || [] + let sortedTags = sortObject(sortByName(keysToID(get(mediaTags))), "color").map((a) => ({ ...a, label: a.name, enabled: selectedTags.includes(a.id), translate: false })) + const create = { label: "popup.manage_tags", icon: "edit", id: "create" } + if (sortedTags.length) sortedTags.push("SEPERATOR") + sortedTags.push(create) + return sortedTags + }, + media_tag_filter: () => { + let sortedTags = sortObject(sortByName(keysToID(get(mediaTags))), "color").map((a) => ({ ...a, label: a.name, enabled: get(activeMediaTagFilter).includes(a.id), translate: false })) + setContextData("media_tags", sortedTags.length) + return sortedTags + }, sort_shows: (items: ContextMenuItem[]) => sortItems(items, "shows"), sort_projects: (items: ContextMenuItem[]) => sortItems(items, "projects"), slide_groups: (items: ContextMenuItem[]) => { let selectedIndex = get(selected).data[0]?.index - let currentSlide = _show().layouts("active").ref()[0]?.[selectedIndex] + let slideRef = _show().layouts("active").ref()[0]?.[selectedIndex] || {} + let currentSlide = _show().get("slides")[slideRef.id] if (!currentSlide) return [] - let currentGroup = currentSlide.data?.globalGroup || "" + let currentGroup: string = currentSlide.globalGroup || "" items = Object.entries(get(groups)).map(([id, a]: any) => { return { id, color: a.color, label: a.default ? "groups." + a.name : a.name, translate: !!a.default, enabled: id === currentGroup } }) + if (!items.length) return [{ label: "empty.general", disabled: true }] return sortItemsByLabel(items) }, actions: () => { @@ -53,6 +68,7 @@ const loadActions = { let slideActions = [ { id: "action", label: "midi.start_action", icon: "actions" }, + "SEPERATOR", { id: "slide_shortcut", label: "actions.play_with_shortcut", icon: "play", enabled: currentActions?.slide_shortcut || false }, { id: "receiveMidi", label: "actions.play_on_midi", icon: "play", enabled: currentActions?.receiveMidi || false }, "SEPERATOR", @@ -217,7 +233,19 @@ const loadActions = { let firstShowIndex = values.findIndex((a) => a.id.includes("show_")) let firstVideoIndex = values.findIndex((a) => a.id.includes("video_")) let firstMetaIndex = values.findIndex((a) => a.id.includes("meta_")) - values = [...values.slice(0, firstShowIndex), "SEPERATOR", ...values.slice(firstShowIndex, firstVideoIndex), "SEPERATOR", ...values.slice(firstVideoIndex, firstMetaIndex), "SEPERATOR", ...values.slice(firstMetaIndex)] + let firstVarIndex = values.findIndex((a) => a.id.includes("variable_")) + + values = [ + ...values.slice(0, firstShowIndex), + "SEPERATOR", + ...values.slice(firstShowIndex, firstVideoIndex), + "SEPERATOR", + ...values.slice(firstVideoIndex, firstMetaIndex), + "SEPERATOR", + ...values.slice(firstMetaIndex, firstVarIndex), + "SEPERATOR", + ...values.slice(firstVarIndex), + ] return values }, @@ -235,10 +263,11 @@ function sortItems(items: ContextMenuItem[], id: "projects" | "shows") { items = [ { id: "name", label: "sort.name", icon: "text", enabled: type === "name" }, + { id: "name_des", label: "sort.name_des", icon: "text", enabled: type === "name_des" }, { id: "created", label: "info.created", icon: "calendar", enabled: type === "created" }, + { id: "modified", label: "info.modified", icon: "calendar", enabled: type === "modified" }, ] if (id === "shows") { - items.push({ id: "modified", label: "info.modified", icon: "calendar", enabled: type === "modified" }) items.push({ id: "used", label: "info.used", icon: "calendar", enabled: type === "used" }) // WIP load used metadata values... diff --git a/src/frontend/components/context/menuClick.ts b/src/frontend/components/context/menuClick.ts index 6001ecab..1fa357ac 100644 --- a/src/frontend/components/context/menuClick.ts +++ b/src/frontend/components/context/menuClick.ts @@ -9,6 +9,7 @@ import { activeDrawerTab, activeEdit, activeFocus, + activeMediaTagFilter, activePage, activePopup, activeRecording, @@ -17,6 +18,7 @@ import { activeTagFilter, audioFolders, categories, + contextActive, currentOutputSettings, currentWindow, dataPath, @@ -286,6 +288,44 @@ const actions: any = { activeTagFilter.set(activeTags || []) }, + media_tag_set: (obj: any) => { + let tagId = obj.menu.id + if (tagId === "create") { + contextActive.set(false) + popupData.set({ type: "media" }) + activePopup.set("manage_tags") + return + } + + let disable = get(media)[get(selected).data[0]?.path]?.tags?.includes(tagId) + + obj.sel.data?.forEach(({ path }) => { + let tags = get(media)[path]?.tags || [] + + let existingIndex = tags.indexOf(tagId) + if (disable) { + if (existingIndex > -1) tags.splice(existingIndex, 1) + } else { + if (existingIndex < 0) tags.push(tagId) + } + + media.update((a) => { + if (!a[path]) a[path] = {} + a[path].tags = tags + return a + }) + }) + }, + media_tag_filter: (obj: any) => { + let tagId = obj.menu.id + + let activeTags = get(activeMediaTagFilter) + let currentIndex = activeTags.indexOf(tagId) + if (currentIndex < 0) activeTags.push(tagId) + else activeTags.splice(currentIndex, 1) + + activeMediaTagFilter.set(activeTags || []) + }, addToProject: (obj: any) => { if ((obj.sel.id !== "show" && obj.sel.id !== "show_drawer" && obj.sel.id !== "player" && obj.sel.id !== "media" && obj.sel.id !== "audio") || !get(activeProject)) return @@ -584,9 +624,6 @@ const actions: any = { view_lyrics: () => { slidesOptions.set({ ...get(slidesOptions), mode: "lyrics" }) }, - view_text: () => { - slidesOptions.set({ ...get(slidesOptions), mode: "text" }) - }, // show slide_transition: (obj: any) => { diff --git a/src/frontend/components/draw/DrawSettings.svelte b/src/frontend/components/draw/DrawSettings.svelte index 0823a7bd..b354ca86 100644 --- a/src/frontend/components/draw/DrawSettings.svelte +++ b/src/frontend/components/draw/DrawSettings.svelte @@ -78,35 +78,41 @@
{#key $drawTool} -
- {#key $drawSettings} - {#if $drawSettings[$drawTool]} - {#each Object.entries($drawSettings[$drawTool]) as [key, value]} - {#if key !== "clear" && (key !== "hold" || $drawTool !== "paint")} - - {#if key !== "clear" && (key !== "hold" || $drawTool !== "paint")} -

- {/if} - {#if key === "color"} - change(e, key)} style="width: 100%;" /> - {:else if ["glow", "hold", "rainbow", "hollow", "dots", "threed"].includes(key)} -
- check(e, key)} /> -
- {:else if key === "opacity"} - change(e, key)} /> - {:else if key === "radius"} - change(e, key)} /> - {:else if key !== "clear" && key !== "hold"} - change(e, key)} /> - {:else} -
- {/if} -
- {/if} - {/each} - {/if} - {/key} +
+ + +
+ +
+ {#key $drawSettings} + {#if $drawSettings[$drawTool]} + {#each Object.entries($drawSettings[$drawTool]) as [key, value]} + {#if key !== "clear" && (key !== "hold" || $drawTool !== "paint")} + + {#if key !== "clear" && (key !== "hold" || $drawTool !== "paint")} +

+ {/if} + {#if key === "color"} + change(e, key)} style="width: 100%;" /> + {:else if ["glow", "hold", "rainbow", "hollow", "dots", "threed"].includes(key)} +
+ check(e, key)} /> +
+ {:else if key === "opacity"} + change(e, key)} /> + {:else if key === "radius"} + change(e, key)} /> + {:else if key !== "clear" && key !== "hold"} + change(e, key)} /> + {:else} +
+ {/if} +
+ {/if} + {/each} + {/if} + {/key} +
{/key}
@@ -134,15 +140,35 @@ height: 100%; } + h6 { + display: flex; + align-items: center; + justify-content: center; + + font-weight: 600; + letter-spacing: 0.5px; + + padding: 0.3em 0.5em; + background-color: var(--primary-darkest); + border-radius: var(--border-radius); + + /* font-size: 0.9em; */ + text-transform: none !important; + margin: 0 !important; + } + .padding { display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; - padding: 10px; height: 100%; } + .options { + padding: 10px; + } + .bottom { display: flex; flex-direction: column; diff --git a/src/frontend/components/draw/Paint.svelte b/src/frontend/components/draw/Paint.svelte index b70b1142..aff2dcb5 100644 --- a/src/frontend/components/draw/Paint.svelte +++ b/src/frontend/components/draw/Paint.svelte @@ -96,7 +96,7 @@ x: x, y: y, size: settings.size || 10, - color: settings.color || "white", + color: settings.color || "#ffffff", } lines.push(line) paintCache.set(lines) @@ -114,7 +114,7 @@ // ctx.moveTo(previousPos.x, previousPos.y) ctx.lineWidth = settings.size || 10 ctx.lineCap = "round" - ctx.strokeStyle = settings.color || "white" + ctx.strokeStyle = settings.color || "#ffffff" } else { previousPos = null if (lines.length && lines[lines.length - 1] !== "mouseup") lines.push("mouseup") diff --git a/src/frontend/components/drawer/Drawer.svelte b/src/frontend/components/drawer/Drawer.svelte index 8959c663..efcf7028 100644 --- a/src/frontend/components/drawer/Drawer.svelte +++ b/src/frontend/components/drawer/Drawer.svelte @@ -169,11 +169,12 @@ {/if} {/each} + {#if !searchActive && !searchValue.length} {/if} diff --git a/src/frontend/components/drawer/audio/Audio.svelte b/src/frontend/components/drawer/audio/Audio.svelte index c8b5a86e..b256b6e3 100644 --- a/src/frontend/components/drawer/audio/Audio.svelte +++ b/src/frontend/components/drawer/audio/Audio.svelte @@ -2,7 +2,7 @@ import { onDestroy } from "svelte" import { uid } from "uid" import { MAIN, READ_FOLDER } from "../../../../types/Channels" - import { activePlaylist, activeRename, audioFolders, audioPlaylists, dictionary, drawerTabsData, media } from "../../../stores" + import { activePlaylist, activeRename, audioFolders, audioPlaylists, dictionary, drawerTabsData, media, outLocked } from "../../../stores" import { destroy, send } from "../../../utils/request" import Icon from "../../helpers/Icon.svelte" import T from "../../helpers/T.svelte" @@ -259,7 +259,14 @@ {#if isDefault} . {:else if playlist} - + + +

{tags.length === 1 ? $mediaTags[tags[0]]?.name || "—" : tags.length}

+
+ + {/if} + + {#if thumbnail} {:else} @@ -149,4 +177,38 @@ .icon :global(svg) { height: 100%; } + + /* icons */ + + .icons { + pointer-events: none; + display: flex; + flex-direction: column; + position: absolute; + left: 0; + z-index: 1; + font-size: 0.9em; + + height: 80%; + flex-wrap: wrap; + + max-width: calc(100% - 21px); + } + .icons div { + opacity: 0.9; + display: flex; + } + .icons .button { + background-color: rgb(0 0 0 / 0.6); + pointer-events: all; + } + .icons span { + pointer-events: all; + background-color: rgb(0 0 0 / 0.6); + padding: 3px; + font-size: 0.75em; + font-weight: bold; + display: flex; + align-items: center; + } diff --git a/src/frontend/components/drawer/pages/Overlays.svelte b/src/frontend/components/drawer/pages/Overlays.svelte index 9530b14c..288cf2fe 100644 --- a/src/frontend/components/drawer/pages/Overlays.svelte +++ b/src/frontend/components/drawer/pages/Overlays.svelte @@ -1,6 +1,6 @@ {#if $focusMode} @@ -91,7 +91,7 @@ {#if $focusMode && currentShowId} -{:else if $activeEdit.id || ((!currentShowId || !$shows[currentShowId]) && $editHistory.length)} +{:else if $activeEdit.id || ((!currentShowId || !$shows[currentShowId]) && $editHistory.length) || $textEditActive}

@@ -109,7 +109,7 @@ else activeShow.set(edited.show) } }} - active={$activeEdit.id === edited.id} + active={$activeEdit.id ? $activeEdit.id === edited.id : currentShowId === edited.id} bold={false} border > diff --git a/src/frontend/components/edit/editbox/EditboxHelper.ts b/src/frontend/components/edit/editbox/EditboxHelper.ts index d1427b8f..cdef57c7 100644 --- a/src/frontend/components/edit/editbox/EditboxHelper.ts +++ b/src/frontend/components/edit/editbox/EditboxHelper.ts @@ -118,10 +118,12 @@ export class EditboxHelper { let html = "" let firstTextStyleArchive: string = "" let lineBg = item.specialStyle?.lineBg ? `background-color: ${item.specialStyle.lineBg};` : "" + let listStyle = "" // item.list?.enabled ? `;list-style${item.list?.style?.includes("disclosure") ? "-type:" : ": inside"} ${item.list?.style || "disc"};` : "" + item?.lines?.forEach((line, i) => { let align = line.align.replaceAll(lineBg, "") currentStyle += align + lineBg // + line.chords?.map((a) => a.key) - let style = align || lineBg ? 'style="' + align + ";" + lineBg + '"' : "" + let style = align || lineBg || listStyle ? 'style="' + align + ";" + lineBg + listStyle + '"' : "" html += `
` // fix removing all text in a line @@ -139,7 +141,8 @@ export class EditboxHelper { let textChords = currentChords.filter((a) => a.pos >= textIndex && (a.pos <= textEnd || line.text.length - 1 >= tIndex)) textIndex = textEnd - let style = a.style ? 'style="' + a.style + '"' : "" + let listStyle = "" // item.list?.enabled ? ";display: list-item;" : "" + let style = a.style || listStyle ? 'style="' + a.style + listStyle + '"' : "" let value = a.value.replaceAll("\n", "
") || "
" if (value === " ") value = " " diff --git a/src/frontend/components/edit/editbox/EditboxOther.svelte b/src/frontend/components/edit/editbox/EditboxOther.svelte index 3b22d45e..fb27a665 100644 --- a/src/frontend/components/edit/editbox/EditboxOther.svelte +++ b/src/frontend/components/edit/editbox/EditboxOther.svelte @@ -56,6 +56,7 @@ {#if item?.type === "list"} + {:else if item?.type === "media"} {#if thumbnailPath} @@ -72,6 +73,7 @@ {:else if item?.type === "events"} {:else if item?.type === "variable"} + {:else if item?.type === "web"} diff --git a/src/frontend/components/edit/editbox/EditboxPlain.svelte b/src/frontend/components/edit/editbox/EditboxPlain.svelte index 9bbbf4f5..feb65d6f 100644 --- a/src/frontend/components/edit/editbox/EditboxPlain.svelte +++ b/src/frontend/components/edit/editbox/EditboxPlain.svelte @@ -92,6 +92,15 @@
{/if} + + {#if item.list?.enabled} +
+ + + +
+ {/if} + {#if item.bindings?.length}
diff --git a/src/frontend/components/edit/editors/SlideEditor.svelte b/src/frontend/components/edit/editors/SlideEditor.svelte index 81562733..235b559f 100644 --- a/src/frontend/components/edit/editors/SlideEditor.svelte +++ b/src/frontend/components/edit/editors/SlideEditor.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte" import { slide } from "svelte/transition" import type { MediaStyle } from "../../../../types/Main" - import { activeEdit, activePopup, activeShow, alertMessage, dictionary, driveData, focusMode, labelsDisabled, media, outputs, overlays, refreshEditSlide, showsCache, styles } from "../../../stores" + import { activeEdit, activePopup, activeShow, alertMessage, dictionary, driveData, focusMode, labelsDisabled, media, outputs, overlays, refreshEditSlide, showsCache, styles, textEditActive } from "../../../stores" import { slideHasAction } from "../../actions/actions" import MediaLoader from "../../drawer/media/MediaLoader.svelte" import Icon from "../../helpers/Icon.svelte" @@ -336,6 +336,15 @@ {#if !$labelsDisabled}{/if} + {#if !$focusMode} +
+ + + {/if} +
{:else if input.input === "media"} - valueChange(e, input)}> - - {#if input.value} -

{getFileName(input.value)}

- {:else} -

- {/if} -
+ + valueChange(e, input)}> + + + {#if input.value} +

{getFileName(input.value)}

+ {:else} +

+ {/if} +
+
+
{:else if input.input === "multiselect"}
{#each input.values as option} diff --git a/src/frontend/components/edit/tools/Items.svelte b/src/frontend/components/edit/tools/Items.svelte index de17fc06..83a16cd6 100644 --- a/src/frontend/components/edit/tools/Items.svelte +++ b/src/frontend/components/edit/tools/Items.svelte @@ -26,10 +26,8 @@ ] const specialItems: ItemRef[] = [ - { id: "list" }, // { id: "table" }, { id: "camera" }, - { id: "variable" }, { id: "slide_tracker", icon: "percentage" }, { id: "events", icon: "calendar" }, { id: "mirror" }, diff --git a/src/frontend/components/edit/values/boxes.ts b/src/frontend/components/edit/values/boxes.ts index 69052154..2e1c28a0 100644 --- a/src/frontend/components/edit/values/boxes.ts +++ b/src/frontend/components/edit/values/boxes.ts @@ -111,6 +111,42 @@ export const boxes: Box = { { name: "background_color", id: "specialStyle.lineBg", input: "color", value: "", enableNoColor: true }, { name: "background_opacity", id: "specialStyle.opacity", input: "number", value: 1, values: { step: 0.1, decimals: 1, max: 1, inputMultiplier: 10 } }, ], + list: [ + { name: "list", id: "list.enabled", input: "checkbox", value: false }, + { + name: "style", + input: "dropdown", + id: "list.style", + value: "disc", + values: { + options: [ + // common + { id: "disc", name: "$:list.disc:$" }, + { id: "circle", name: "$:list.circle:$" }, + { id: "square", name: "$:list.square:$" }, + { id: "disclosure-closed", name: "$:list.disclosure-closed:$" }, + { id: "disclosure-open", name: "$:list.disclosure-open:$" }, + // numbers + { id: "decimal", name: "$:list.decimal:$" }, + { id: "decimal-leading-zero", name: "$:list.decimal-leading-zero:$" }, + // alpha + { id: "lower-alpha", name: "$:list.lower-alpha:$" }, // same as latin + { id: "upper-alpha", name: "$:list.upper-alpha:$" }, // same as latin + { id: "lower-roman", name: "$:list.lower-roman:$" }, + { id: "upper-roman", name: "$:list.upper-roman:$" }, + { id: "lower-greek", name: "$:list.lower-greek:$" }, + // special + // {id: "bengali", name: "$:list.bengali:$" }, + // {id: "cambodian", name: "$:list.cambodian:$" }, + // {id: "devanagari", name: "$:list.devanagari:$" }, + ], + }, + // disabled: "list.interval", // WIP still disabled when set back to 0 + hidden: true, + }, + // { name: "one_at_a_time", id: "one_at_a_time", input: "checkbox", value: false }, + // { name: "interval", id: "list.interval", input: "number", value: 0, hidden: true }, // slide timers can be user for this + ], outline: [ { name: "color", id: "style", key: "-webkit-text-stroke-color", input: "color", value: "#000000" }, { name: "width", id: "style", key: "-webkit-text-stroke-width", input: "number", value: 0, values: { max: 100 }, extension: "px" }, diff --git a/src/frontend/components/edit/values/media.ts b/src/frontend/components/edit/values/media.ts index 544d4347..b75f73c0 100644 --- a/src/frontend/components/edit/values/media.ts +++ b/src/frontend/components/edit/values/media.ts @@ -46,6 +46,13 @@ export const videoEdit = [ ], }, }, + { + name: "media.volume", + id: "volume", + input: "number", + value: 100, + values: { max: 100 }, + }, { name: "inputs.start", id: "fromTime", diff --git a/src/frontend/components/helpers/audio.ts b/src/frontend/components/helpers/audio.ts index bbec8dcd..aa06d5c0 100644 --- a/src/frontend/components/helpers/audio.ts +++ b/src/frontend/components/helpers/audio.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store" import { uid } from "uid" import { MAIN, OUTPUT } from "../../../types/Channels" -import { activePlaylist, audioChannels, audioPlaylists, gain, media, outLocked, playingAudio, playingVideos, special } from "../../stores" +import { activePlaylist, audioChannels, audioPlaylists, gain, isFadingOut, media, outLocked, playingAudio, playingVideos, special } from "../../stores" import { send } from "../../utils/request" import { customActionActivation } from "../actions/actions" import { stopMetronome } from "../drawer/audio/metronome" @@ -13,7 +13,7 @@ import { checkNextAfterMedia } from "./showActions" // WIP use get(special).audio_fade_duration ?? 1.5 to fade in when starting song ?? (currently just when fading out) -export async function playAudio({ path, name = "", audio = null, stream = null }: any, pauseIfPlaying: boolean = true, startAt: number = 0, playMultiple: boolean = false, crossfade: number = 0) { +export async function playAudio({ path, name = "", audio = null, stream = null }: any, pauseIfPlaying: boolean = true, startAt: number = 0, playMultiple: boolean = false, crossfade: number = 0, playlistCrossfade: boolean = false) { let existing: any = get(playingAudio)[path] if (existing) { if (!pauseIfPlaying) { @@ -36,7 +36,7 @@ export async function playAudio({ path, name = "", audio = null, stream = null } let audioPlaying = Object.keys(get(playingAudio)).length if (crossfade) crossfadeAudio(crossfade) - else if (!playMultiple) clearAudio("", false) + else if (!playMultiple) clearAudio("", false, playlistCrossfade) let encodedPath = encodeFilePath(path) audio = audio || new Audio(encodedPath) @@ -80,8 +80,11 @@ export async function playAudio({ path, name = "", audio = null, stream = null } }) let localVolume: number = get(volume) * (get(media)[path]?.volume || 1) - if (analyser.gainNode) analyser.gainNode.gain.value = localVolume * (get(gain) || 1) - else audio.volume = localVolume + if (analyser.gainNode) { + analyser.gainNode.gain.value = localVolume * (get(gain) || 1) + if (get(special).preFaderVolumeMeter) audio.volume = 1 + else audio.volume = localVolume + } else audio.volume = localVolume let waitToPlay = 0 if (audioPlaying && crossfade) { @@ -109,15 +112,20 @@ let currentlyCrossfading: string[] = [] // if no "path" is provided it will fade out/clear all audio async function crossfadeAudio(crossfade: number = 0, path: string = "", waitToPlay: boolean = false) { if (currentlyCrossfading[path]) return - if (path) currentlyCrossfading.push(path) // fade in if (path) { let playing = get(playingAudio)[path]?.audio if (!playing) return - setTimeout(() => fadeAudio(playing, waitToPlay ? crossfade * 0.4 : crossfade, true), waitToPlay ? crossfade * 0.6 * 1000 : 0) - currentlyCrossfading.splice(currentlyCrossfading.indexOf(path), 1) + currentlyCrossfading.push(path) + setTimeout( + async () => { + await fadeAudio(playing, waitToPlay ? crossfade * 0.4 : crossfade, true) + currentlyCrossfading.splice(currentlyCrossfading.indexOf(path), 1) + }, + waitToPlay ? crossfade * 0.6 * 1000 : 0 + ) return } @@ -127,6 +135,7 @@ async function crossfadeAudio(crossfade: number = 0, path: string = "", waitToPl }) async function fadeoutAudio(audio, path) { + currentlyCrossfading.push(path) let faded = await fadeAudio(audio, crossfade) if (faded) deleteAudio(path) } @@ -165,6 +174,9 @@ export function updateVolume(value: number | undefined | "local", changeGain: bo if (a[id].analyser.gainNode) { let gainedValue = localVolume * (get(gain) || 1) a[id].analyser.gainNode.gain.value = gainedValue + + if (get(special).preFaderVolumeMeter) a[id].audio.volume = 1 + else a[id].audio.volume = localVolume } else a[id].audio.volume = localVolume }) @@ -252,7 +264,7 @@ export function playlistNext(previous: string = "", specificSong: string = "", c }) // if (crossfade) isCrossfading = true - playAudio({ path: nextSong }, false, 0, false, crossfade) + playAudio({ path: nextSong }, false, 0, false, crossfade, true) function getSongs(): string[] { if (previous && get(activePlaylist)?.songs) return get(activePlaylist).songs @@ -467,7 +479,8 @@ function getPlayingAudio() { get(playingAudio)[audioPath].audio.currentTime = 0 get(playingAudio)[audioPath].audio.play() } else if (get(activePlaylist)?.active === audioPath) { - let playlist = get(audioPlaylists)[audioPath] || {} + let playlistId = get(activePlaylist).id || "" + let playlist = get(audioPlaylists)[playlistId] || {} playingAudio.update((a: any) => { a[audioPath]?.audio?.pause() @@ -509,7 +522,7 @@ function getPlayingVideos() { videos.map((a) => { // set volume (video in output window) - let newVolume = get(volume) + let newVolume = get(volume) * ((get(media)[a.id]?.volume ?? 100) / 100) if (a.analyser.gainNode) { let gainedValue = newVolume * (get(gain) || 1) a.analyser.gainNode.gain.value = gainedValue @@ -567,9 +580,9 @@ function getHighestNumber(numbers: number[]): number { return Math.max(...numbers) } -let clearing = false +let clearing: string[] = [] let forceClear: boolean = false -export function clearAudio(path: string = "", clearPlaylist: boolean = true, playlistCrossfade: boolean = false) { +export function clearAudio(path: string = "", clearPlaylist: boolean = true, playlistCrossfade: boolean = false, commonClear: boolean = false) { // turn off any playlist if (clearPlaylist && (!path || get(activePlaylist)?.active === path)) activePlaylist.set(null) @@ -578,14 +591,14 @@ export function clearAudio(path: string = "", clearPlaylist: boolean = true, pla const clearTime = playlistCrossfade ? 0 : (get(special).audio_fade_duration ?? 1.5) - if (clearing) { + if (clearing.includes(path)) { + if (!commonClear) return // force stop audio files (bypass timeout if already active) forceClear = true setTimeout(() => (forceClear = false), 100) return } if (!Object.keys(get(playingAudio)).length) return - clearing = true let newPlaying: any = get(playingAudio) playingAudio.update((a) => { @@ -595,6 +608,7 @@ export function clearAudio(path: string = "", clearPlaylist: boolean = true, pla return a async function clearAudio(path) { + clearing.push(path) if (!a[path]?.audio) return deleteAudio(path) let faded = await fadeAudio(a[path].audio, clearTime) @@ -625,7 +639,7 @@ export function clearAudio(path: string = "", clearPlaylist: boolean = true, pla setTimeout(() => { playingAudio.set(newPlaying) clearAudioStreams() - clearing = false + clearing.splice(clearing.indexOf(path), 1) }, 200) } } @@ -684,7 +698,11 @@ async function fadeAudio(audio, duration = 1, increment: boolean = false): Promi // WIP non linear easing - let fadeId = uid() + if (!increment) { + isFadingOut.set(true) + } + + let fadeId = (increment ? "in_" : "out_") + uid() return new Promise((resolve) => { currentlyFading[fadeId] = setInterval(() => { if (forceClear) return finished() @@ -709,6 +727,10 @@ async function fadeAudio(audio, duration = 1, increment: boolean = false): Promi delete currentlyFading[fadeId] clearTimeout(timedout) setTimeout(() => resolve(true), 50) + + if (!increment && !Object.keys(currentlyFading).filter((a) => a.includes("out")).length) { + isFadingOut.set(false) + } } }) } diff --git a/src/frontend/components/helpers/historyHelpers.ts b/src/frontend/components/helpers/historyHelpers.ts index 16941c91..4a6c79a4 100644 --- a/src/frontend/components/helpers/historyHelpers.ts +++ b/src/frontend/components/helpers/historyHelpers.ts @@ -7,6 +7,7 @@ import { activeRename, activeShow, activeStage, + activeTagFilter, audioPlaylists, currentOutputSettings, dictionary, @@ -440,6 +441,9 @@ export const _updaters = { activeRename.set("tag_" + id) return data }, + deselect: () => { + activeTagFilter.set([]) + } }, tag_key: { store: globalTags }, diff --git a/src/frontend/components/helpers/media.ts b/src/frontend/components/helpers/media.ts index ca2d2854..6994bd50 100644 --- a/src/frontend/components/helpers/media.ts +++ b/src/frontend/components/helpers/media.ts @@ -60,6 +60,8 @@ export function joinPath(path: string[]): string { // fix for media files with special characters in file name not playing export function encodeFilePath(path: string): string { + if (!path) return "" + // already encoded if (path.match(/%\d+/g) || path.includes("http") || path.includes("data:")) return path diff --git a/src/frontend/components/helpers/output.ts b/src/frontend/components/helpers/output.ts index 38f96dfe..e622e052 100644 --- a/src/frontend/components/helpers/output.ts +++ b/src/frontend/components/helpers/output.ts @@ -41,6 +41,8 @@ import { fadeinAllPlayingAudio, fadeoutAllPlayingAudio } from "./audio" import { getExtension, getFileName, removeExtension } from "./media" import { replaceDynamicValues } from "./showActions" import { _show } from "./shows" +import { newToast } from "../../utils/common" +import { getStyles } from "./style" export function displayOutputs(e: any = {}, auto: boolean = false) { // sort so display order can be changed! (needs app restart) @@ -64,7 +66,7 @@ export function setOutput(key: string, data: any, toggle: boolean = false, outpu let outs = outputId ? [outputId] : allOutputs let inputData = clone(data) - let firstOutputWithBackground = allOutputs.findIndex((id) => (get(styles)[get(outputs)[id]?.style || ""]?.layers || ["background"]).includes("background")) + let firstOutputWithBackground = allOutputs.findIndex((id) => !a[id]?.isKeyOutput && !a[id]?.stageOutput && (get(styles)[get(outputs)[id]?.style || ""]?.layers || ["background"]).includes("background")) firstOutputWithBackground = Math.max(0, firstOutputWithBackground) // reset slide cache (after update) @@ -318,12 +320,12 @@ export function getOutputResolution(outputId: string, _updater = get(outputs)) { return style || { width: 1920, height: 1080 } } -export function checkWindowCapture() { - getActiveOutputs(get(outputs), false, true, true).forEach(shouldBeCaptured) +export function checkWindowCapture(startup: boolean = false) { + getActiveOutputs(get(outputs), false, true, true).forEach((a) => shouldBeCaptured(a, startup)) } // NDI | OutputShow | Stage CurrentOutput -export function shouldBeCaptured(outputId: string) { +export function shouldBeCaptured(outputId: string, startup: boolean = false) { let output = get(outputs)[outputId] let captures: any = { ndi: !!output.ndi, @@ -331,6 +333,9 @@ export function shouldBeCaptured(outputId: string) { stage: stageHasOutput(outputId), } + // alert user that screen recording starts + if (!startup && Object.values(captures).filter(Boolean).length) newToast("$toast.output_capture_enabled") + send(OUTPUT, ["CAPTURE"], { id: outputId, captures }) } function stageHasOutput(outputId: string) { @@ -522,7 +527,7 @@ export function getCurrentMediaTransition() { // TEMPLATE export function mergeWithTemplate(slideItems: Item[], templateItems: Item[], addOverflowTemplateItems: boolean = false, resetAutoSize: boolean = true) { - if (!slideItems) return [] + if (!slideItems?.length) return [] slideItems = clone(slideItems) if (!templateItems.length) return slideItems @@ -566,7 +571,16 @@ export function mergeWithTemplate(slideItems: Item[], templateItems: Item[], add line.align = templateLine?.align || "" line.text?.forEach((text: any, k: number) => { let templateText = templateLine?.text[k] || templateLine?.text[0] - if (!text.customType?.includes("disableTemplate")) text.style = templateText?.style || "" + if (!text.customType?.includes("disableTemplate")) { + let style = templateText?.style || "" + + // add original text color + let textColor = getStyles(text.style)["color"] || "#FFFFFF" + // use template color if text is white (default) + if (textColor !== "#FFFFFF") style += `color: ${textColor};` + + text.style = style + } let firstChar = templateText?.value?.[0] || "" @@ -926,3 +940,15 @@ export function getSlideFilter(slideData: any) { return slideFilter } + +export function getBlending() { + let blending = Object.values(get(outputs))[0]?.blending + if (!blending) return "" + + if (!blending.left && !blending.right) return "" + + const opacity = (blending.opacity ?? 50) / 100 + const center = 50 + Number(blending.offset || 0) + if (blending.centered) return `-webkit-mask-image: linear-gradient(${blending.rotate ?? 90}deg, rgb(0, 0, 0) ${center - blending.left}%, rgba(0, 0, 0, ${opacity}) ${center}%, rgb(0, 0, 0) ${center + Number(blending.right)}%);` + return `-webkit-mask-image: linear-gradient(${blending.rotate ?? 90}deg, rgba(0, 0, 0, ${opacity}) 0%, rgb(0, 0, 0) ${blending.left}%, rgb(0, 0, 0) ${100 - blending.right}%, rgba(0, 0, 0, ${opacity}) 100%);` +} diff --git a/src/frontend/components/helpers/setShow.ts b/src/frontend/components/helpers/setShow.ts index 9e2d7234..9155e82e 100644 --- a/src/frontend/components/helpers/setShow.ts +++ b/src/frontend/components/helpers/setShow.ts @@ -6,6 +6,7 @@ import { getShowCacheId, updateCachedShow } from "./show" import { uid } from "uid" import { destroy } from "../../utils/request" import { fixShowIssues } from "../../converters/importHelpers" +import type { ShowObj } from "../../classes/Show" export function setShow(id: string, value: "delete" | Show): Show { let previousValue: Show @@ -27,6 +28,12 @@ export function setShow(id: string, value: "delete" | Show): Show { if (!value.slides) value.slides = {} if (!value.layouts) value.layouts = {} if (!value.media) value.media = {} + + // set metadata (CCLI) in quickAccess + if (value.meta.CCLI && !value.quickAccess?.metadata?.CCLI) { + if (!value.quickAccess?.metadata) value.quickAccess.metadata = {} + value.quickAccess.metadata.CCLI = value.meta.CCLI + } } } @@ -174,3 +181,13 @@ export function saveTextCache(id: string, show: Show) { tempCache = {} }, 1000) } + +export function setQuickAccessMetadata(show: ShowObj, key: string, value: string) { + if (!value) return show + + if (!show.quickAccess) show.quickAccess = {} + if (!show.quickAccess.metadata) show.quickAccess.metadata = {} + show.quickAccess.metadata[key] = value + + return show +} diff --git a/src/frontend/components/helpers/show.ts b/src/frontend/components/helpers/show.ts index 4ce93f7e..c6de0590 100644 --- a/src/frontend/components/helpers/show.ts +++ b/src/frontend/components/helpers/show.ts @@ -14,7 +14,8 @@ export function checkName(name: string = "", showId: string = "") { let number = 1 while (Object.entries(get(shows)).find(([id, a]: any) => (!showId || showId !== id) && a.name?.toLowerCase() === (number > 1 ? name.toLowerCase() + " " + number : name.toLowerCase()))) number++ - return number > 1 ? name + " " + number : name + // add number if existing name, and trim away spaces from the start/end + return (number > 1 ? name + " " + number : name).trim() } export function formatToFileName(name: string = "") { @@ -142,6 +143,7 @@ export function updateShowsList(shows: Shows) { } else { // sort by name sortedShows = sortByNameAndNumber(showsList) + if (sortType === "name_des") sortedShows = sortedShows.reverse() } let filteredShows: ShowList[] = removeValues(sortedShows, "private", true) diff --git a/src/frontend/components/helpers/showActions.ts b/src/frontend/components/helpers/showActions.ts index 92b0fd6c..488dacd8 100644 --- a/src/frontend/components/helpers/showActions.ts +++ b/src/frontend/components/helpers/showActions.ts @@ -32,6 +32,7 @@ import { templates, timers, triggers, + variables, videosData, videosTime, } from "./../../stores" @@ -798,13 +799,13 @@ export function playNextGroup(globalGroupIds: string[], { showRef, outSlide, cur } // go to next slide if current output slide has nextAfterMedia action -let nextActive = false +let nextActive: string[] = [] export function checkNextAfterMedia(endedId: string, type: "media" | "audio" | "timer" = "media", outputId: string = "") { - if (nextActive) return false + if (nextActive.includes(outputId)) return false - nextActive = true + nextActive.push(outputId) setTimeout(() => { - nextActive = false + nextActive.splice(nextActive.indexOf(outputId), 1) }, 600) // MAKE SURE NEXT SLIDE HAS TRANSITIONED if (!outputId) outputId = getActiveOutputs(get(outputs), true, true, true)[0] @@ -920,8 +921,9 @@ export const dynamicValueText = (id: string) => `{${id}}` export function getDynamicIds() { let mainValues = Object.keys(dynamicValues) let metaValues = Object.keys(initializeMetadata({})).map((id) => `meta_` + id) + let variableValues = Object.values(get(variables)).map(({ name }) => `variable_` + getNameId(name)) - return [...mainValues, ...metaValues] + return [...mainValues, ...metaValues, ...variableValues] } export function replaceDynamicValues(text: string, { showId, layoutId, slideIndex, type, id }: any, _updater: number = 0) { @@ -956,6 +958,18 @@ export function replaceDynamicValues(text: string, { showId, layoutId, slideInde return show.meta[key] || "" } + if (id.includes("variable_")) { + let nameId = id.slice(9) + let variable = Object.values(get(variables)).find((a) => getNameId(a.name) === nameId) + if (!variable) return "" + + if (variable.type === "number") return Number(variable.number || 0) + + if (variable.enabled === false) return "" + if (variable.text?.includes(id)) return variable.text || "" + return replaceDynamicValues(variable.text || "", { showId, layoutId, slideIndex, type, id }) + } + let outputId: string = getActiveOutputs()[0] if (id.includes("video_") && get(currentWindow) === "output") { @@ -997,3 +1011,7 @@ const dynamicValues = { video_duration: ({ videoDuration }) => joinTime(secondsToTime(videoDuration)), video_countdown: ({ videoTime, videoDuration }) => joinTime(secondsToTime(videoDuration - videoTime)), } + +function getNameId(name) { + return name.toLowerCase().trim().replaceAll(" ", "_") +} diff --git a/src/frontend/components/inputs/Color.svelte b/src/frontend/components/inputs/Color.svelte index c93cddbf..d814d561 100644 --- a/src/frontend/components/inputs/Color.svelte +++ b/src/frontend/components/inputs/Color.svelte @@ -11,6 +11,7 @@ export let enableNoColor: boolean = false export let showDisabled: boolean = false export let custom: boolean = false + export let rightAlign: boolean = false export let height: number = 0 export let width: number = 0 @@ -43,7 +44,7 @@ } let colorElem - let clipRight: boolean = false + let clipRight: boolean = rightAlign || false $: if (colorElem) { let pickerRect = colorElem.getBoundingClientRect() let pickerRight = pickerRect.left + 200 diff --git a/src/frontend/components/inputs/ProjectButton.svelte b/src/frontend/components/inputs/ProjectButton.svelte index a4f3d7a2..33bc0ace 100644 --- a/src/frontend/components/inputs/ProjectButton.svelte +++ b/src/frontend/components/inputs/ProjectButton.svelte @@ -1,6 +1,7 @@ - -
- - -
- -
    - {#each items as item, i} -
  • -
    - - - - {@html item.text} - - - - - - -
    -
  • - {/each} -
- - - - diff --git a/src/frontend/components/main/popups/ManageTags.svelte b/src/frontend/components/main/popups/ManageTags.svelte new file mode 100644 index 00000000..b8dc9a63 --- /dev/null +++ b/src/frontend/components/main/popups/ManageTags.svelte @@ -0,0 +1,98 @@ + + +
+ {#if tags.length} + {#key tags} + {#each tags as tag} + + updateKey(e, tag.id, "name")} autofocus={!tag.name} /> + updateKey(e, tag.id, "color")} rightAlign /> + + + + {/each} + {/key} + {:else} +
+ +
+ {/if} + +
+ + + + +
+ + diff --git a/src/frontend/components/main/popups/SlideShortcut.svelte b/src/frontend/components/main/popups/SlideShortcut.svelte index ca4889e7..63cf6b0f 100644 --- a/src/frontend/components/main/popups/SlideShortcut.svelte +++ b/src/frontend/components/main/popups/SlideShortcut.svelte @@ -38,7 +38,7 @@ -

+

{#if currentShortcut}
diff --git a/src/frontend/components/main/popups/Trigger.svelte b/src/frontend/components/main/popups/Trigger.svelte index e7c7669e..e076c69d 100644 --- a/src/frontend/components/main/popups/Trigger.svelte +++ b/src/frontend/components/main/popups/Trigger.svelte @@ -31,7 +31,7 @@ let sortedTriggers = sortByName(globalList) function updateValue(e: any, key: string) { - let value = e?.target?.value || e + let value = e?.target?.value ?? e if (!value) return currentTrigger[key] = value diff --git a/src/frontend/components/main/popups/Unsaved.svelte b/src/frontend/components/main/popups/Unsaved.svelte index 97c48373..1b91aadd 100644 --- a/src/frontend/components/main/popups/Unsaved.svelte +++ b/src/frontend/components/main/popups/Unsaved.svelte @@ -46,7 +46,7 @@ {:else} diff --git a/src/frontend/components/output/Output.svelte b/src/frontend/components/output/Output.svelte index 87c61caf..af229a7d 100644 --- a/src/frontend/components/output/Output.svelte +++ b/src/frontend/components/output/Output.svelte @@ -249,7 +249,7 @@ {#if $colorbars} - + {/if} diff --git a/src/frontend/components/output/VideoSlider.svelte b/src/frontend/components/output/VideoSlider.svelte index 8a0c0413..7b22d3fa 100644 --- a/src/frontend/components/output/VideoSlider.svelte +++ b/src/frontend/components/output/VideoSlider.svelte @@ -9,6 +9,7 @@ export let activeOutputIds: string[] = [] export let unmutedId: string = "" export let toOutput: boolean = false + export let big: boolean = false export let disabled: boolean = false export let changeValue: number = 0 @@ -103,7 +104,7 @@ }} /> -
+
{#if hover} {time} @@ -146,6 +147,9 @@ margin: 0 5px; font-size: 0.8em; } + .main.big { + font-size: 1em; + } .slider { flex: 1; @@ -154,4 +158,7 @@ display: flex; align-items: center; } + .main.big .slider { + margin: 0 10px; + } diff --git a/src/frontend/components/output/clear.ts b/src/frontend/components/output/clear.ts index bb6b0926..2f20632a 100644 --- a/src/frontend/components/output/clear.ts +++ b/src/frontend/components/output/clear.ts @@ -41,7 +41,7 @@ export function clearAll(button: boolean = false) { clearBackground() clearSlide(true) clearOverlays() - clearAudio() + clearAudio("", true, false, true) clearTimers() } diff --git a/src/frontend/components/output/layers/BackgroundMedia.svelte b/src/frontend/components/output/layers/BackgroundMedia.svelte index f0673e8c..79cf1551 100644 --- a/src/frontend/components/output/layers/BackgroundMedia.svelte +++ b/src/frontend/components/output/layers/BackgroundMedia.svelte @@ -4,7 +4,7 @@ import { OUTPUT } from "../../../../types/Channels" import type { MediaStyle } from "../../../../types/Main" import type { OutBackground, Transition } from "../../../../types/Show" - import { allOutputs, audioChannels, outputs, playingVideos, special, videosData, videosTime } from "../../../stores" + import { allOutputs, audioChannels, media, outputs, playingVideos, special, videosData, videosTime, volume } from "../../../stores" import { destroy, receive, send } from "../../../utils/request" import BmdStream from "../../drawer/live/BMDStream.svelte" import NdiStream from "../../drawer/live/NDIStream.svelte" @@ -163,7 +163,7 @@ // FADE OUT AUDIO $: if (fadingOut && !videoData.muted) fadeoutVideo() - $: if (!fadingOut && !videoData.muted && id) setVolume(1) + $: if (!fadingOut && !videoData.muted && id) setVolume($volume * (($media[id]?.volume ?? 100) / 100)) const speed = 0.01 const margin = 0.9 // video should fade to 0 before clearing function fadeoutVideo() { diff --git a/src/frontend/components/output/preview/ClearButtons.svelte b/src/frontend/components/output/preview/ClearButtons.svelte index 52db13ac..6201ad70 100644 --- a/src/frontend/components/output/preview/ClearButtons.svelte +++ b/src/frontend/components/output/preview/ClearButtons.svelte @@ -1,5 +1,5 @@
@@ -112,7 +134,7 @@
{#if !allCleared} - + {/if} @@ -159,6 +163,13 @@ padding: 0.3em !important; } + .group.big { + background-color: var(--primary-darkest); + } + .group.big :global(.slider input) { + background-color: var(--primary); + } + .name { display: flex; justify-content: center; diff --git a/src/frontend/components/settings/Screens.svelte b/src/frontend/components/settings/Screens.svelte index 13a6ca0d..4178c2e0 100644 --- a/src/frontend/components/settings/Screens.svelte +++ b/src/frontend/components/settings/Screens.svelte @@ -1,12 +1,15 @@ -

- - - - -
- -
- {#if screens.length} -
- {#if !currentScreen.screen || !screens.find((a) => a.id.toString() === currentScreen.screen)} -
- -
- {/if} - - {#each screens as screen, i} -
{ - if (!currentScreen?.forcedResolution) changeOutputScreen({ detail: { id: screen.id, bounds: screen.bounds } }) - }} - > - {i + 1} -
- {/each} +{#if edgeBlending} + + +

+ + +

+ updateBlending(e.detail, "left")} /> +
+ +

+ updateBlending(e.detail, "right")} /> +
+ +

+ updateBlending(e.detail, "rotate")} /> +
+ +

+ updateBlending(e.detail, "opacity")} /> +
+ +

+
+ updateBlending(isChecked(e), "centered")} />
- {:else} - +
+ {#if blending.centered} + +

+ updateBlending(e.detail, "offset")} /> +
{/if} -
+{:else} +

+ + + + + + +
+ +
+ {#if screens.length} +
+ {#if !currentScreen.screen || !screens.find((a) => a.id.toString() === currentScreen.screen)} +
+ +
+ {/if} + + {#each screens as screen, i} +
{ + if (!currentScreen?.forcedResolution) changeOutputScreen({ detail: { id: screen.id, bounds: screen.bounds } }) + }} + > + {i + 1} +
+ {/each} +
+ {:else} + + {/if} +
+{/if} diff --git a/src/frontend/components/show/VideoShow.svelte b/src/frontend/components/show/VideoShow.svelte index 953e5afb..6e4d8870 100644 --- a/src/frontend/components/show/VideoShow.svelte +++ b/src/frontend/components/show/VideoShow.svelte @@ -1,6 +1,6 @@