diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 4c1c5aa6..906df06f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,11 @@ name: Playwright Tests on: - push: - branches: [dev] - pull_request: - branches: [dev] + # turning off temporarily as it does not work properly! + # Error: electron.launch: Process failed to launch! + # push: + # branches: [dev] + # pull_request: + # branches: [dev] workflow_dispatch: jobs: @@ -15,6 +17,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + - name: Install libraries + run: sudo apt-get install libfontconfig1-dev - name: Install dependencies run: npm install - name: Install Playwright Browsers diff --git a/package-lock.json b/package-lock.json index 60cdefe1..61cbdb50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "freeshow", - "version": "1.3.3-beta.1", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freeshow", - "version": "1.3.3-beta.1", + "version": "1.3.3", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 22d63a6f..4c9928d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.3.3-beta.2", + "version": "1.3.3", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", diff --git a/public/import-logos/bible_json.webp b/public/import-logos/bible_json.webp new file mode 100644 index 00000000..87f94968 Binary files /dev/null and b/public/import-logos/bible_json.webp differ diff --git a/public/import-logos/json.webp b/public/import-logos/json.webp new file mode 100644 index 00000000..cdbebb71 Binary files /dev/null and b/public/import-logos/json.webp differ diff --git a/public/import-logos/xml.webp b/public/import-logos/xml.webp new file mode 100644 index 00000000..25c06996 Binary files /dev/null and b/public/import-logos/xml.webp differ diff --git a/public/import-logos/zefania.webp b/public/import-logos/zefania.webp deleted file mode 100644 index d566dfe6..00000000 Binary files a/public/import-logos/zefania.webp and /dev/null differ diff --git a/public/import-logos/zip.webp b/public/import-logos/zip.webp new file mode 100644 index 00000000..5ff16407 Binary files /dev/null and b/public/import-logos/zip.webp differ diff --git a/public/lang/en.json b/public/lang/en.json index 715ff5ca..257c6a67 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -40,7 +40,8 @@ "bottom": "Bottom", "left": "Left", "centered": "Centered", - "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition" + "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition", + "cropping_tip": "Fine adjust the window content position by cropping the sides" }, "about": { "check_updates": "Look for updates", @@ -423,6 +424,7 @@ "history": "History", "action": "Action", "category_action": "Category action", + "user_data_overwrite": "Found existing data", "connect": "Connect", "cloud_update": "Syncing with cloud", "cloud_method": "Data location", @@ -565,6 +567,7 @@ "pdf_single_page": "Render as one document", "convert_to_images": "Convert to images", "converting": "Converting...", + "closing": "Closing app...", "remove_template_from_show": "Remove template from show", "reset_defaults": "Reset defaults", "to_all": "Apply to all", @@ -747,6 +750,9 @@ "all_shows": "All shows", "all_projects": "All projects", "project": "Project", + "option_type": "What would you like to export?", + "option_format": "What format would you like?", + "include_media": "Include media files", "preview": "Preview", "title": "Title", "metadata": "Metadata", @@ -821,6 +827,7 @@ "background_media": "Background media", "overlay_content": "Add overlay content", "different_first_template": "Custom template on first slide", + "max_lines_per_slide": "Max lines per slide", "media_fit": "Media fit", "one_letter": "One letter mode", "sub_indexes": "Sub indexes", @@ -1120,6 +1127,9 @@ "show_location": "Show location", "data_location": "Data location", "user_data_location": "Save user settings at 'Data location'", + "user_data_exists": "Found existing data at custom location, would you like to overwrite it?", + "user_data_yes": "Yes, keep current data", + "user_data_no": "No, import existing data", "popup_before_close": "Enable close confirmation popup", "disable_hardware_acceleration": "Disable hardware acceleration", "restart_for_change": "You have to restart the program for the change to take effect!", @@ -1199,7 +1209,7 @@ }, "scripture": { "bibles": "Bibles from API.Bible", - "custom": "Or import your own", + "local": "Import local file", "max_verses": "Max verses per slide", "verse_numbers": "Verse numbers", "verses_on_individual_lines": "Verses on individual lines", diff --git a/public/lang/hu.json b/public/lang/hu.json index c53bd06f..76efcd1b 100644 --- a/public/lang/hu.json +++ b/public/lang/hu.json @@ -38,7 +38,10 @@ "top": "Felső", "right": "Jobb", "bottom": "Alsó", - "left": "Bal" + "left": "Bal", + "centered": "Középre igazított", + "edge_blending_tip": "Több kimenet/projektor összekeverése a zökkenőmentesebb átmenetekért", + "cropping_tip": "Az ablak tartalmi pozíciójának finom módosítása a szélek vágásával" }, "about": { "check_updates": "Frissítések ellenőrzése", @@ -173,7 +176,6 @@ "volume": "Hangerő", "gain": "Erősítés", "speed": "Sebesség", - "show": "Megjelenítés", "flip": "Tükrözés", "flip_horizontally": "Vízszintes tükrözés", "flip_vertically": "Függőleges tükrözés", @@ -197,13 +199,13 @@ "mute_when_video_plays": "Némítás videó lejátszása alatt", "allow_gaining": "Túlerősítés engedélyezése", "allow_gaining_tip": "Hangerő beállításának engedélyezése 100% felett (torzítást okozhat)", - "pre_fader_volume_meter": "Hangerő-szabályozó előszabolyozó", + "pre_fader_volume_meter": "Hangerő-szabályozó előszabályozó", "mixer": "Keverő", "metronome": "Metronóm", "toggle_metronome": "Metronóm átváltása", "tempo": "Tempó", "bpm": "BPM", - "beats": "Ütemek" + "beats": "Ütem" }, "menu": { "show": "Bemutató", @@ -317,7 +319,7 @@ "primary-darkest": "Elsődleges legsötétebb", "text": "Szöveg", "textInvert": "Inverz szöveg", - "secondary-text": "Másodlagos szöveg", + "secondary-text": "Kijelölések", "secondary": "Másodlagos", "secondary-opacity": "Másodlagos átlátszatlanság", "hover": "Rámutatás", @@ -414,7 +416,7 @@ "import": "Importálás", "songbeamer_import": "Songbeamer importálása", "export": "Exportálás", - "importing": "Importálás folyamatban", + "importing": "Importálás…", "import_scripture": "Szentírás importálása", "player": "Lejátszó", "edit_event": "Esemény szerkesztése", @@ -422,6 +424,7 @@ "history": "Előzmények", "action": "Művelet", "category_action": "Kategória művelet", + "user_data_overwrite": "Meglévő adatok találhatóak", "connect": "Csatlakozás", "cloud_update": "Szinkronizálás a felhővel", "cloud_method": "Adatok helye", @@ -430,6 +433,7 @@ "manage_icons": "Ikonok kezelése", "manage_colors": "Színek kezelése", "choose_camera": "Kamera kiválasztása", + "manage_tags": "Címkék kezelése", "initialize": "Üdvözöljük a FreeShow-ban", "unsaved": "Biztosan ki szeretne léptni?", "cancel": "Mége", @@ -466,6 +470,7 @@ "deleted_cache": "Médiabélyegkép gyorsítótár törölve.", "no_songswords_easyworship": "Hiányzik a SongsWords.db fájl.", "delete_shows_empty": "Nincsenek törlendő bemutatók.", + "output_capture_enabled": "Képernyőkimenet felvétele engedélyezve. Ez teljesítményproblémákat okozhat, csak akkor használja, ha valóban szükséges!", "midi_no_project": "Eseményindítót kaptunk a projekt módosítására, de nem található projekt az indexen:", "midi_no_show": "Eseményindítót kaptunk a dia indítására, de nincs aktív bemutató.", "midi_no_slide": "Eseményindítót kaptunk a dia indítására, de nem található dia az indexen:", @@ -528,6 +533,7 @@ "view_private": "Magán bemutatók megjelenítése", "import": "Importálás", "export": "Exportálás", + "imported": "Importálva!", "duplicate": "Duplikálás", "delete": "Törlés", "delete_slide": "Dia törlése", @@ -558,8 +564,10 @@ "reset": "Visszaállítás", "create_template": "Sablon létrehozása", "project_template_tip": "Új projekt létrehozása e sablon alapján", + "pdf_single_page": "Megjelenítés egy dokumentumként", "convert_to_images": "Konvertálás képpé", "converting": "Konvertálás…", + "closing": "Alkalmazás bezárása…", "remove_template_from_show": "Sablon eltávolítása a műsorból", "reset_defaults": "Alapértelmezések visszaállítása", "to_all": "Alkalmazás mindre", @@ -734,6 +742,7 @@ "export": { "export": "Exportálás", "export_as": "{} exportálása, mint", + "exporting": "Exportálás…", "exported": "Exportálva!", "oneFile": "Egy fájl", "selected_shows": "Kijelölt bemutatók", @@ -741,6 +750,7 @@ "all_shows": "Összes bemutató", "all_projects": "Összes projekt", "project": "Projekt", + "include_media": "Médiafájl felvétele", "preview": "Előnézet", "title": "Cím", "metadata": "Metaadatok", @@ -772,6 +782,7 @@ "toggle_output": "Kimenet átváltása", "move_to_front": "Előre mozgatás", "hide_from_preview": "Előnézet elrejtése", + "enable_preview": "Előnézet engedélyezése", "lock_to_output": "Rögzítés a kimenethez", "place_under_slide": "Dia alá helyezés", "toggle_clock": "Óra átváltása", @@ -814,12 +825,14 @@ "background_media": "Háttérmédia", "overlay_content": "Átfedéses tartalom hozzáadása", "different_first_template": "Egyedi sablon az első dián", + "max_lines_per_slide": "Sorok maximális száma diánként", "media_fit": "Média méretezése", "one_letter": "Egyetlen betű mód", "sub_indexes": "Alindex", "size": "Méret", "max_lines": "Maximum sor", "invert_items": "Elemek megfordítása", + "list": "Lista", "chords": "Akkordok", "transpose": "Transzponálás", "auto_size": "Automatikus méret", @@ -843,6 +856,7 @@ "outline": "Körvonal", "shadow": "Árnyék", "shadow_inset": "Zsugorított árnyék", + "offset": "Eltolás", "offsetX": "X eltolás", "offsetY": "Y eltolás", "blur": "Elmosás", @@ -889,7 +903,6 @@ }, "items": { "text": "Szövegdoboz", - "list": "Lista", "media": "Média", "image": "Kép", "camera": "Kamera", @@ -1062,6 +1075,7 @@ "manual_input_hint": "Nem található a kijelző? Kattintson ide a helyzete kézi módosításához.", "manual_drag_hint": "Az aktív kimeneti ablak fölé történő manuális húzáshoz tartsa lenyomva a Ctrl/Cmd billentyűt.", "allow_main_screen": "Egyedi kimeneti pozíció és méret engedélyezése", + "edge_blending": "Sarkok keverése", "identify_screens": "Képernyők azonosítása", "new_output": "Új kimenet", "normal": "Normál", @@ -1111,11 +1125,15 @@ "show_location": "Bemutatók helye", "data_location": "Adatok helye", "user_data_location": "Felhasználói beállítások mentése az „Adatok helyére\"", + "user_data_exists": "Meglévő adatok találhatóak ezen az egyéni helyen, szeretné felülírni azokat?", + "user_data_yes": "Igen, jelenlegi adatok megőrzése", + "user_data_no": "Nem, meglévő adatok importálása", "popup_before_close": "Zárást megerősítő felugró ablak engedélyezése", "disable_hardware_acceleration": "Hardveres gyorsítás letiltása", "restart_for_change": "Újra kell indítani a programot a módosítás érvénybe lépéséhez!", "font": "Betűkészlet", "font_family": "Betűkészlet család", + "font_style": "Betűkészlet stílusa", "font_size": "Betűméret", "border_radius": "Keret sugara", "colors": "Színek", @@ -1153,6 +1171,7 @@ "sort": { "sort_by": "Sorbarendezés", "name": "Név", + "name_des": "Név, csőkkenő", "date": "Dátum", "size": "Méret", "type": "Típus", diff --git a/public/lang/no.json b/public/lang/no.json index 74624c68..08026432 100644 --- a/public/lang/no.json +++ b/public/lang/no.json @@ -38,7 +38,10 @@ "top": "Topp", "right": "Høyre", "bottom": "Bunn", - "left": "Venstre" + "left": "Venstre", + "centered": "Sentrert", + "edge_blending_tip": "Bland sammen flere utganger/projektorer for en mer sømløs overgang", + "cropping_tip": "Finjuster plasseringen til innholdet i vinduet ved å beskjære sidene" }, "about": { "check_updates": "Se etter oppdateringer", @@ -173,7 +176,6 @@ "volume": "Volum", "gain": "Forsterkning", "speed": "Hastighet", - "show": "Vis", "flip": "Vend", "flip_horizontally": "Vend horisontalt", "flip_vertically": "Vend vertikalt", @@ -317,7 +319,7 @@ "primary-darkest": "Primær mørkest", "text": "Tekst", "textInvert": "Motsatt tekst", - "secondary-text": "Sekundær tekst", + "secondary-text": "Markering", "secondary": "Sekundær", "secondary-opacity": "Sekundær gjennomsiktig", "hover": "Sveve", @@ -414,7 +416,7 @@ "import": "Importer", "songbeamer_import": "Songbeamer importering", "export": "Eksporter", - "importing": "Importerer", + "importing": "Importerer...", "import_scripture": "Importer en bibel", "player": "Spiller", "edit_event": "Rediger hendelse", @@ -422,6 +424,7 @@ "history": "Historie", "action": "Handling", "category_action": "Handling for kategori", + "user_data_overwrite": "Fant eksisterende data", "connect": "Koble til", "cloud_update": "Synkroniserer med sky", "cloud_method": "Dataplassering", @@ -430,6 +433,7 @@ "manage_icons": "Endre ikoner", "manage_colors": "Endre farger", "choose_camera": "Velg kamera", + "manage_tags": "Administrer etiketter", "initialize": "Velkommen til FreeShow", "unsaved": "Er du sikkert på at du vil avslutte?", "cancel": "Avbryt", @@ -466,6 +470,7 @@ "deleted_cache": "Slettet hurtigbuffer med medie-miniatyrbilder.", "no_songswords_easyworship": "Mangler SongsWords.db filen.", "delete_shows_empty": "Ingen shows å slette.", + "output_capture_enabled": "Opptak av utgangsskjerm aktivert, dette kan forårsake ytelsesproblemer. Bruk kun ved behov!", "midi_no_project": "Mottok utløser til å endre prosjekt, men ingen prosjekt funnet på indeks:", "midi_no_show": "Mottok utløser til å starte lysbilde, men ingen show aktiv.", "midi_no_slide": "Mottok utløser til å starte lysbilde, men ingen lysbilde funnet på indeks:", @@ -528,6 +533,7 @@ "view_private": "Vis private", "import": "Importer", "export": "Eksporter", + "imported": "Importert!", "duplicate": "Dupliser", "delete": "Slett", "delete_slide": "Slett lysbilde", @@ -558,8 +564,10 @@ "reset": "Tilbakestill", "create_template": "Opprett mal", "project_template_tip": "Lag et nytt prosjekt fra denne malen", + "pdf_single_page": "Vis som ett dokument", "convert_to_images": "Konverter til bilder", "converting": "Konverterer...", + "closing": "Lukker app...", "remove_template_from_show": "Fjern mal fra show", "reset_defaults": "Tilbakestill standarder", "to_all": "Legg til alle", @@ -734,6 +742,7 @@ "export": { "export": "Eksporter", "export_as": "Eksporter {} som", + "exporting": "Eksporterer...", "exported": "Eksportert!", "oneFile": "En fil", "selected_shows": "Valgte show", @@ -741,6 +750,7 @@ "all_shows": "Alle show", "all_projects": "Alle prosjekter", "project": "Prosjekt", + "include_media": "Inkluder mediefiler", "preview": "Forhåndsvisning", "title": "Tittel", "metadata": "Metadata", @@ -772,6 +782,7 @@ "toggle_output": "Veksle utgangsskjerm", "move_to_front": "Flytt fremst", "hide_from_preview": "Skjul fra forhåndsvisning", + "enable_preview": "Aktiver forhåndsvisning", "lock_to_output": "Lås til utgang", "place_under_slide": "Plasser under lysbilde", "toggle_clock": "Veksle klokke", @@ -814,12 +825,14 @@ "background_media": "Bakgrunnsmedia", "overlay_content": "Legg til innhold fra overlegg", "different_first_template": "Egen mal på første lysbilde", + "max_lines_per_slide": "Maks linjer per lysbilde", "media_fit": "Tilpass medier", "one_letter": "Én bokstav-modus", "sub_indexes": "Underindekser", "size": "Størrelse", "max_lines": "Maks linjer", "invert_items": "Inverter elementer", + "list": "Liste", "chords": "Akkorder", "transpose": "Transponer", "auto_size": "Auto-størrelse", @@ -843,6 +856,7 @@ "outline": "Strek", "shadow": "Skygge", "shadow_inset": "Innfelt skygge", + "offset": "Forskyvning", "offsetX": "Forskyvning X", "offsetY": "Forskyvning Y", "blur": "Uklarhet", @@ -889,7 +903,6 @@ }, "items": { "text": "Tekstboks", - "list": "Liste", "media": "Media", "image": "Bilde", "camera": "Kamera", @@ -1062,6 +1075,7 @@ "manual_input_hint": "Finner ikke skjermen? Trykk her for å endre plasseringen manuelt.", "manual_drag_hint": "Du kan også holde ctrl/cmd over en aktiv utgangsskjerm for å flytte den manuelt.", "allow_main_screen": "Tillat egendefinert posisjon og størrelse på utgang", + "edge_blending": "Edge blending", "identify_screens": "Identifiser skjermer", "new_output": "Ny utgang", "normal": "Normal", @@ -1111,11 +1125,15 @@ "show_location": "Show-plassering", "data_location": "Data-plassering", "user_data_location": "Lagre bruker-innstillinger i 'Data-plassering'", + "user_data_exists": "Fant eksisterende data på egendefinert plassering, vil du overskrive det?", + "user_data_yes": "Ja, behold nåværende data", + "user_data_no": "Nei, importer eksisterende data", "popup_before_close": "Aktiver popup for bekreftelse av lukking", "disable_hardware_acceleration": "Deaktiver maskinvareakselerasjon", "restart_for_change": "Du må starte programmet på nytt for at endringen skal tre i kraft!", "font": "Skrift", "font_family": "Skrifttype", + "font_style": "Skriftstil", "font_size": "Skriftstørrelse", "border_radius": "Hjørneradius", "colors": "Farger", @@ -1153,6 +1171,7 @@ "sort": { "sort_by": "Sorter etter", "name": "Navn", + "name_des": "Navn Synkende", "date": "Dato", "size": "Størrelse", "type": "Type", diff --git a/public/lang/zh_CN.json b/public/lang/zh_CN.json index ba39dfb7..56cf43d8 100644 --- a/public/lang/zh_CN.json +++ b/public/lang/zh_CN.json @@ -38,7 +38,10 @@ "top": "上", "right": "右", "bottom": "下", - "left": "左" + "left": "左", + "centered": "Centered", + "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition", + "cropping_tip": "Fine adjust the window content position by cropping the sides" }, "about": { "check_updates": "检查更新...", @@ -163,6 +166,7 @@ "toggle_shuffle": "切换随机播放", "next": "下一个", "previous": "上一个", + "play_no_audio": "无声播放", "play_no_filters": "无滤镜播放", "favourite": "收藏", "pause": "暂停", @@ -172,7 +176,6 @@ "volume": "音量", "gain": "增益", "speed": "速度", - "show": "节目", "flip": "翻转", "flip_horizontally": "水平翻转", "flip_vertically": "垂直翻转", @@ -194,7 +197,10 @@ "playlist_settings": "播放列表设置", "custom_output": "自定义音频输出", "mute_when_video_plays": "视频播放时静音", + "allow_gaining": "允许获取", + "allow_gaining_tip": "允许将音量设置超过100%(可能导致失真)", "pre_fader_volume_meter": "推子前音量表", + "mixer": "调音台", "metronome": "节拍器", "toggle_metronome": "显示/隐藏节拍器", "tempo": "节奏", @@ -269,15 +275,17 @@ "ip": "无法获取你的设备IP,请转到计算机的 Wi-Fi 设置中查找本地 IPv4 地址。" }, "meta": { + "number": "数字", "title": "标题", "artist": "艺术家", "author": "作者", "composer": "作曲家", "publisher": "发行商", "copyright": "版权", - "CCLI": "授权协议(CCLI)", + "CCLI": "歌曲 ID(CCLI)", "year": "年份", "key": "调号", + "autofill": "自动填充", "message": "消息", "message_tip": "在全部幻灯片上显示内容", "auto_media": "从媒体内容中获取元数据", @@ -311,7 +319,7 @@ "primary-darkest": "主要最暗色", "text": "文本", "textInvert": "反转文字", - "secondary-text": "辅助文本", + "secondary-text": "Selections", "secondary": "辅助色", "secondary-opacity": "辅助透明度", "hover": "悬浮", @@ -362,7 +370,8 @@ "music": "音乐", "offers": "报价", "notice": "通知", - "visuals": "视觉" + "visuals": "视觉", + "action_tip": "每次展示该类别的节目时触发的操作。" }, "groups": { "current": "当前", @@ -400,19 +409,22 @@ "change_output_values": "修改输出值", "choose_chord": "选择和弦", "set_time": "设置时间", + "slide_shortcut": "幻灯片快捷键", "animate": "动画", "translate": "本地化", "next_timer": "下一张幻灯片计时器", "import": "导入", "songbeamer_import": "Songbeamer 导入", "export": "导出", - "importing": "正在导入", + "importing": "Importing...", "import_scripture": "导入圣经", "player": "播放器", "edit_event": "编辑事件", "about": "关于", "history": "历史记录", "action": "动作", + "category_action": "分类动作", + "user_data_overwrite": "Found existing data", "connect": "连接", "cloud_update": "与云端同步", "cloud_method": "数据位置", @@ -421,6 +433,7 @@ "manage_icons": "管理图标", "manage_colors": "管理颜色", "choose_camera": "选择相机", + "manage_tags": "Manage tags", "initialize": "欢迎使用 FreeShow", "unsaved": "你确定要退出吗?", "cancel": "取消", @@ -457,6 +470,7 @@ "deleted_cache": "已删除媒体缩略图缓存。", "no_songswords_easyworship": "缺少 SongsWords.db 文件。", "delete_shows_empty": "没有节目可删除。", + "output_capture_enabled": "Output screen capture enabled, this may cause performance issues. Only use if needed!", "midi_no_project": "收到更改项目的触发信号,但未在指定索引处未找到项目:", "midi_no_show": "接收到开始播放幻灯片的指令,但当前没有正在进行的节目。", "midi_no_slide": "收到开始幻灯片的触发信号,但未在以下序号处找到幻灯片:", @@ -519,6 +533,7 @@ "view_private": "节目私有", "import": "导入", "export": "导出", + "imported": "Imported!", "duplicate": "创建副本", "delete": "删除", "delete_slide": "删除幻灯片", @@ -547,8 +562,12 @@ "zoomIn": "放大", "zoomOut": "缩小", "reset": "重置", + "create_template": "创建模板", + "project_template_tip": "使用此模板创建新项目", + "pdf_single_page": "Render as one document", "convert_to_images": "转换成图片", "converting": "正在转换…", + "closing": "Closing app...", "remove_template_from_show": "从节目中移除模板", "reset_defaults": "重置默认设置", "to_all": "全部应用", @@ -608,6 +627,8 @@ "set_key": "设置按键", "custom_key": "设置自定义值", "select_chord": "选择这个和弦", + "play_with_shortcut": "使用快捷键激活", + "press_to_assign": "按任意字母键进行分配", "play_on_midi": "在 MIDI 信号上激活", "play_on_midi_tip": "接收到指定的 MIDI 信号时激活此特定幻灯片", "send_midi": "发送 MIDI 信号", @@ -624,6 +645,7 @@ "next_after_media": "媒体播放完成后下一个", "remove_media": "移除媒体", "remove_layers": "移除图层", + "toggle_checkbox_tip": "如果复选框未更改,动作将切换", "start_recording": "开始录制", "stop_recording": "停止录制", "export_recording": "停止录制并导出", @@ -632,6 +654,7 @@ "previous_project_item": "上一个项目元素", "index_select_project_item": "按序号选择项目元素", "name_select_show": "按名称选择节目", + "set_template_active": "在活动节目中设置模板", "random_slide": "随机播放幻灯片", "index_select_slide": "按序号选择幻灯片", "name_select_slide": "按名称选择幻灯片", @@ -671,7 +694,8 @@ "activate_scripture_start": "当经文开始时激活", "activate_slide_cleared": "清除幻灯片时激活", "activate_background_cleared": "背景清除时激活", - "activate_show_created": "创建节目时启动", + "activate_show_created": "创建节目时激活", + "activate_show_opened": "节目打开时激活", "activate_audio_playlist_ended": "音频播放列表结束时激活" }, "recording": { @@ -679,7 +703,9 @@ "tip": "记录并重放幻灯片的时间。与第一张幻灯片上的音轨同步。", "layout_changed": "布局自上次录制后已更改!", "audio_synced": "与音频同步!", - "start": "开始幻灯片录制" + "start": "开始幻灯片录制", + "use_duration": "使用持续时间", + "use_duration_tip": "使用持续时间时间而非时间戳时间" }, "animate": { "change": "更改", @@ -716,6 +742,7 @@ "export": { "export": "导出", "export_as": "将 {} 导出为", + "exporting": "Exporting...", "exported": "已导出!", "oneFile": "单文件", "selected_shows": "已选的节目", @@ -723,6 +750,7 @@ "all_shows": "全部节目", "all_projects": "全部项目", "project": "项目", + "include_media": "Include media files", "preview": "预览", "title": "标题", "metadata": "元数据", @@ -738,6 +766,7 @@ }, "context": { "enabledTabs": "切换标签", + "setTag": "设置标签", "filterByTags": "按标签过滤", "addToProject": "添加到项目", "add_to_show": "添加到节目", @@ -753,6 +782,7 @@ "toggle_output": "切换输出", "move_to_front": "移到最前", "hide_from_preview": "在预览中隐藏", + "enable_preview": "Enable preview", "lock_to_output": "锁定输出", "place_under_slide": "置于幻灯片下方", "toggle_clock": "切换时间", @@ -795,10 +825,14 @@ "background_media": "背景媒体", "overlay_content": "添加叠加内容", "different_first_template": "在第一张幻灯片上使用自定义模板", + "max_lines_per_slide": "Max lines per slide", "media_fit": "媒体适应", + "one_letter": "单字母模式", + "sub_indexes": "子索引", "size": "大小", "max_lines": "最大行数", "invert_items": "反转元素", + "list": "List", "chords": "合弦", "transpose": "转置", "auto_size": "自动调整大小", @@ -822,6 +856,7 @@ "outline": "描边", "shadow": "阴影", "shadow_inset": "内阴影", + "offset": "Offset", "offsetX": "X 轴偏移", "offsetY": "Y 轴偏移", "blur": "模糊", @@ -868,7 +903,6 @@ }, "items": { "text": "文本框", - "list": "列表", "media": "媒体", "image": "图片", "camera": "相机", @@ -916,6 +950,7 @@ "to_event": "距离事件时间", "counter": "倒计时", "time": "时间", + "minutes": "分钟", "seconds": "秒", "from": "从", "to": "到", @@ -1033,19 +1068,20 @@ "use24hClock": "显示24小时制时间", "styles_hint": "创建不同的样式,应用于输出以改变外观。", "hide_output_hint": "双击输出窗口可隐藏。按住 Ctrl 键可拖动。", - "hide_menubar_hint": "要隐藏菜单栏,请启用独占模式,或在 macOS 设置中启用“自动隐藏和显示菜单栏”。", + "hide_menubar_hint": "要隐藏菜单栏,请启用展台模式,或在 macOS 设置中启用“自动隐藏和显示菜单栏”。", "show_output_hint": "按住 Ctrl/Cmd 并点击显示按钮,可以强制显示内容在此屏幕。", "move_output_hint": "找不到显示器?调整窗口位置,直到它出现在第二屏幕上。", "select_display": "点击你希望显示输出窗口的屏幕。", "manual_input_hint": "找不到显示屏?点击这里手动改变位置。", "manual_drag_hint": "可通过按住 Ctrl/Cmd 键并拖动激活的输出窗口来手动更改其位置。", "allow_main_screen": "允许自定义输出位置和大小", + "edge_blending": "Edge blending", "identify_screens": "识别屏幕", "new_output": "新建输出", "normal": "正常", "enable_key_output": "启用 Alpha 密钥输出", "always_on_top": "总在最前", - "kiosk_mode": "独占模式", + "kiosk_mode": "展台模式", "change_key_output_position": "更改关键信息输出位置", "position": "位置", "enabled": "启用", @@ -1063,6 +1099,7 @@ "group_numbers": "分组编号", "full_colors": "高对比度分组颜色", "slide_number_keys": "使用数字键播放幻灯片", + "auto_shortcut_first_letter": "文本中首字母的自动快捷键", "auto_output": "启动时激活输出屏幕", "hide_cursor_in_output": "在输出中隐藏鼠标指针", "clear_media_when_finished": "播放完成时清除媒体", @@ -1088,11 +1125,15 @@ "show_location": "节目位置", "data_location": "数据位置", "user_data_location": "将个人设置存储在“数据位置”", + "user_data_exists": "Found existing data at custom location, would you like to overwrite it?", + "user_data_yes": "Yes, keep current data", + "user_data_no": "No, import existing data", "popup_before_close": "关闭时显示确认弹窗", "disable_hardware_acceleration": "禁用硬件加速", "restart_for_change": "你必须重启程序才能使修改生效!", "font": "字体", "font_family": "字体", + "font_style": "Font style", "font_size": "字体大小", "border_radius": "边框圆角", "colors": "颜色", @@ -1124,11 +1165,13 @@ "auto": "自动", "optimized": "已优化", "reduced": "减少的", - "full": "完整" + "full": "完整", + "section_trigger_action": "导航到演示文稿的某个部分时触发操作" }, "sort": { "sort_by": "排序", "name": "名称", + "name_des": "Name Descending", "date": "日期", "size": "大小", "type": "类型", @@ -1172,6 +1215,7 @@ "reference": "演出偏好设置", "split_reference": "拆分偏好设置", "combine_with_text": "合并到文本", + "first_slide_reference": "在第一页幻灯片上引用", "reference_at_bottom": "移到底部", "red_jesus": "耶稣的话用红色显示", "search": "在圣经中搜索" diff --git a/src/electron/data/export.ts b/src/electron/data/export.ts index b2b00160..d858683e 100644 --- a/src/electron/data/export.ts +++ b/src/electron/data/export.ts @@ -243,37 +243,46 @@ function exportAllShows(data: any) { // ----- PROJECT ----- export function exportProject(data: any) { - toApp(MAIN, {channel: "ALERT", data: "export.exporting"}) + toApp(MAIN, { channel: "ALERT", data: "export.exporting" }) + + const files = data.file.files || [] + if (!files.length) { + // export as plain JSON + writeFile(join(data.path, data.name), ".project", JSON.stringify(data.file), "utf-8", (err: any) => doneWritingFile(err, data.path)) + return + } // create archive const zip = new AdmZip() // copy files - const files = data.file.files || [] files.forEach((path: string) => { zip.addLocalFile(path) - }); + }) // add project file zip.addFile("data.json", Buffer.from(JSON.stringify(data.file))) - - const outputPath = join(data.path, data.name + ".project") - zip.writeZip(outputPath, (err: any) => doneWritingFile(err, data.path)); - // plain JSON - // writeFile(join(data.path, data.name), ".project", JSON.stringify(data.file), "utf-8", (err: any) => doneWritingFile(err, data.path)) + const outputPath = join(data.path, data.name) + let p = getUniquePath(outputPath, ".project") + zip.writeZip(p, (err: any) => doneWritingFile(err, data.path)) } // ----- HELPERS ----- function writeFile(path: string, extension: string, data: any, options: any = undefined, callback: any) { + let p = getUniquePath(path, extension) + fs.writeFile(p, data, options, callback) +} + +function getUniquePath(path: string, extension: string) { let number = -1 - let tempPath: string = path + let p: string = path do { number++ - tempPath = path + (number ? "_" + number : "") + extension - } while (doesPathExist(tempPath)) + p = path + (number ? "_" + number : "") + extension + } while (doesPathExist(p)) - fs.writeFile(tempPath, data, options, callback) + return p } diff --git a/src/electron/data/store.ts b/src/electron/data/store.ts index 255026cc..4b42ceed 100644 --- a/src/electron/data/store.ts +++ b/src/electron/data/store.ts @@ -9,6 +9,7 @@ import path from "path" import { STORE } from "../../types/Channels" import { dataFolderNames, deleteFile, doesPathExist, readFile } from "../utils/files" import { defaultConfig, defaultSettings, defaultSyncedSettings } from "./defaults" +import { forceCloseApp } from "../utils/responses" const fileNames: { [key: string]: string } = { error_log: "error_log", @@ -169,7 +170,10 @@ function updateStoresPath(load: boolean = false) { Object.keys(portableData).forEach((id) => createStoreAtNewLocation(id, load)) } +let error: boolean = false function createStoreAtNewLocation(id: string, load: boolean = false) { + if (error) return + let key = portableData[id].key let tempData: any = {} if (!load) { @@ -181,7 +185,21 @@ function createStoreAtNewLocation(id: string, load: boolean = false) { } // set new stores to export - stores[key] = new Store({ name: fileNames[id], defaults: portableData[id].defaults || {}, cwd: userDataPath! }) + try { + stores[key] = new Store({ name: fileNames[id], defaults: portableData[id].defaults || {}, cwd: userDataPath! }) + } catch (err) { + error = true + console.log("Can't create store at set location!", err) + + // revert + let special = stores.SETTINGS.get("special") + special.customUserDataLocation = false + stores.SETTINGS.set("special", special) + stores.SETTINGS.set("dataPath", "") + stores.SETTINGS.set("showsPath", "") + + forceCloseApp() + } if (load || !Object.keys(tempData).length) return diff --git a/src/electron/data/thumbnails.ts b/src/electron/data/thumbnails.ts index 751ff905..f98789c7 100644 --- a/src/electron/data/thumbnails.ts +++ b/src/electron/data/thumbnails.ts @@ -1,11 +1,13 @@ -import { NativeImage, ResizeOptions, app, nativeImage } from "electron" +import { BrowserWindow, NativeImage, ResizeOptions, app, nativeImage } from "electron" import fs from "fs" import path from "path" -import { isProd, toApp } from ".." -import { MAIN } from "../../types/Channels" +import { isProd, loadWindowContent, toApp } from ".." +import { MAIN, OUTPUT } from "../../types/Channels" import { doesPathExist, doesPathExistAsync, makeDir } from "../utils/files" import { waitUntilValueIsDefined } from "../utils/helpers" import { imageExtensions, videoExtensions } from "./media" +import { captureOptions } from "../utils/windowOptions" +import { OutputHelper } from "../output/OutputHelper" export function getThumbnail(data: any) { let output = createThumbnail(data.input, data.size || 500) @@ -188,3 +190,33 @@ function saveToDisk(savePath: string, image: NativeImage, nextOnFinished: boolea if (nextOnFinished) generationFinished() }) } + +///// CAPTURE SLIDE ///// + +export function captureSlide(data: any) { + const OUTPUT_ID = "capture" + if (OutputHelper.getOutput(OUTPUT_ID)) return + + let window = new BrowserWindow({ ...captureOptions, width: data.resolution?.width, height: data.resolution?.height }) + loadWindowContent(window, "output") + + OutputHelper.setOutput(OUTPUT_ID, { window }) + + window.on("ready-to-show", () => { + // send correct output data after load + setTimeout(() => { + window.webContents.send(OUTPUT, { channel: "OUTPUTS", data: data.output }) + // WIP mute videos + + // wait for content load + setTimeout(async () => { + const page = await window.capturePage() + const base64 = page.toDataURL({ scaleFactor: 1 }) + toApp(MAIN, { channel: "CAPTURE_SLIDE", data: { listenerId: data.listenerId, base64 } }) + + window.destroy() + OutputHelper.deleteOutput(OUTPUT_ID) + }, 3000) + }, 1000) + }) +} diff --git a/src/electron/index.ts b/src/electron/index.ts index 733e3da8..1cee8645 100644 --- a/src/electron/index.ts +++ b/src/electron/index.ts @@ -158,21 +158,22 @@ function createMain() { if (RECORD_STARTUP_TIME) console.timeEnd("Main window") } -export function loadWindowContent(window: BrowserWindow, isOutput: boolean = false) { - if (!isOutput && RECORD_STARTUP_TIME) console.time("Main window content") - if (!isOutput) console.log("Loading main window content") +export function loadWindowContent(window: BrowserWindow, type: null | "output" = null) { + let mainOutput = type === null + if (mainOutput && RECORD_STARTUP_TIME) console.time("Main window content") + if (mainOutput) console.log("Loading main window content") if (isProd) window.loadFile("public/index.html").catch(error) else window.loadURL("http://localhost:3000").catch(error) window.webContents.on("did-finish-load", () => { - if (window === mainWindow) isOutput = false // make sure window is not output - window.webContents.send(STARTUP, { channel: "TYPE", data: isOutput ? "output" : null }) - if (!isOutput) retryLoadingContent() + // if (window === mainWindow) type = null // make sure type is correct + window.webContents.send(STARTUP, { channel: "TYPE", data: type }) + if (mainOutput) retryLoadingContent() }) function error(err: any) { console.error("Failed to load window:", JSON.stringify(err)) - if (isLoaded && !isOutput) app.quit() + if (isLoaded && mainOutput) app.quit() } } @@ -251,15 +252,16 @@ export async function exitApp() { stopMidi() - if (!isProd) { - console.log("Dev mode active - Relaunching...") - app.relaunch() - } else { - // this has to be called to actually remove the process! - // https://stackoverflow.com/a/43520274 - mainWindow?.removeAllListeners("close") - ipcMain.removeAllListeners() - } + // relaunch does not work very well as it launched new processes + // if (!isProd) { + // console.log("Dev mode active - Relaunching...") + // app.relaunch() + // } else { + // this has to be called to actually remove the process! + // https://stackoverflow.com/a/43520274 + mainWindow?.removeAllListeners("close") + ipcMain.removeAllListeners() + // } mainWindow = null diff --git a/src/electron/output/helpers/OutputLifecycle.ts b/src/electron/output/helpers/OutputLifecycle.ts index 18b16cb8..7d492e7d 100644 --- a/src/electron/output/helpers/OutputLifecycle.ts +++ b/src/electron/output/helpers/OutputLifecycle.ts @@ -89,7 +89,7 @@ export class OutputLifecycle { }) // window.setVisibleOnAllWorkspaces(true) - loadWindowContent(window, true) + loadWindowContent(window, "output") this.setWindowListeners(window, { id, name }) // open devtools diff --git a/src/electron/utils/responses.ts b/src/electron/utils/responses.ts index 87bfe75e..17393fcd 100644 --- a/src/electron/utils/responses.ts +++ b/src/electron/utils/responses.ts @@ -6,14 +6,14 @@ import { app, BrowserWindow, desktopCapturer, DesktopCapturerSource, Display, sc import { machineIdSync } from "node-machine-id" import os from "os" import path from "path" -import { closeMain, isProd, mainWindow, maximizeMain, setGlobalMenu, toApp } from ".." +import { closeMain, exitApp, isProd, mainWindow, maximizeMain, setGlobalMenu, toApp } from ".." import { BIBLE, MAIN, SHOW } from "../../types/Channels" import { restoreFiles } from "../data/backup" import { downloadMedia } from "../data/downloadMedia" import { importShow } from "../data/import" import { convertPDFToImages } from "../data/pdfToImage" import { config, error_log, stores } from "../data/store" -import { getThumbnail, getThumbnailFolderPath, saveImage } from "../data/thumbnails" +import { captureSlide, getThumbnail, getThumbnailFolderPath, saveImage } from "../data/thumbnails" import { OutputHelper } from "../output/OutputHelper" import { getPresentationApplications, presentationControl, startSlideshow } from "../output/ppt/presentation" import { closeServers, startServers } from "../servers" @@ -23,6 +23,7 @@ import { bundleMediaFiles, checkShowsFolder, dataFolderNames, + doesPathExist, getDataFolder, getDocumentsFolder, getFileInfo, @@ -127,6 +128,7 @@ const mainResponses: any = { MEDIA_CODEC: (data: any) => getMediaCodec(data), DOWNLOAD_MEDIA: (data: any) => downloadMedia(data), MEDIA_BASE64: (data: any) => storeMedia(data), + CAPTURE_SLIDE: (data: any) => captureSlide(data), PDF_TO_IMAGE: (data: any) => convertPDFToImages(data), ACCESS_CAMERA_PERMISSION: () => getPermission("camera"), ACCESS_MICROPHONE_PERMISSION: () => getPermission("microphone"), @@ -162,6 +164,19 @@ const mainResponses: any = { // FILES RESTORE: (data: any) => restoreFiles(data), SYSTEM_OPEN: (data: any) => openSystemFolder(data), + DOES_PATH_EXIST: (data: any) => { + let p = data.path + if (p === "data_config") p = path.join(data.dataPath, dataFolderNames.userData) + return { ...data, exists: doesPathExist(p) } + }, + UPDATE_DATA_PATH: () => { + // updateDataPath({ ...data, load: true }) + let special = stores.SETTINGS.get("special") + special.customUserDataLocation = true + stores.SETTINGS.set("special", special) + + forceCloseApp() + }, LOCATE_MEDIA_FILE: (data: any) => locateMediaFile(data), GET_SIMULAR: (data: any) => getSimularPaths(data), BUNDLE_MEDIA_FILES: (data: any) => bundleMediaFiles(data), @@ -242,6 +257,12 @@ function getScreens(type: "window" | "screen" = "screen") { } } +export function forceCloseApp() { + toApp(MAIN, { channel: "ALERT", data: "actions.closing" }) + // let user read message and action finish + setTimeout(exitApp, 2000) +} + // RECORDER // only open once per session let systemOpened: boolean = false diff --git a/src/electron/utils/windowOptions.ts b/src/electron/utils/windowOptions.ts index a590f33a..8635f376 100644 --- a/src/electron/utils/windowOptions.ts +++ b/src/electron/utils/windowOptions.ts @@ -97,14 +97,20 @@ export const exportOptions: BrowserWindowConstructorOptions = { }, } -// export const captureOptions: BrowserWindowConstructorOptions = { -// show: false, -// modal: true, -// frame: false, -// skipTaskbar: true, -// webPreferences: { -// webSecurity: isProd, -// backgroundThrottling: false, -// offscreen: true, -// }, -// } +export const captureOptions: BrowserWindowConstructorOptions = { + show: false, + backgroundColor: "#000000", + frame: false, + skipTaskbar: true, + webPreferences: { + preload: join(__dirname, "..", "preload"), + webSecurity: isProd, + nodeIntegration: !isProd, + contextIsolation: true, + allowRunningInsecureContent: false, + webviewTag: true, + backgroundThrottling: false, + autoplayPolicy: "no-user-gesture-required", + offscreen: true, + }, +} diff --git a/src/frontend/MainLayout.svelte b/src/frontend/MainLayout.svelte index 3e93acd1..b3b0826c 100644 --- a/src/frontend/MainLayout.svelte +++ b/src/frontend/MainLayout.svelte @@ -92,7 +92,7 @@ {:else if $activeEdit.type === "effect"} {:else if $activeEdit.type === "overlay" || $activeEdit.type === "template" || $showsCache[$activeShow?.id || ""]} - {#if !$focusMode && !$textEditActive} + {#if !$focusMode && (($activeEdit.type || "show") !== "show" || !$textEditActive)} {/if} {/if} diff --git a/src/frontend/classes/Show.ts b/src/frontend/classes/Show.ts index a089df4a..6681ba3d 100644 --- a/src/frontend/classes/Show.ts +++ b/src/frontend/classes/Show.ts @@ -32,6 +32,7 @@ export class ShowObj implements Show { if (template !== false) { // get template from active show (if it's not default with the "Header" template) if (typeof template !== "string" && get(activeShow)?.id !== "default") template = _show().get("settings.template") || null + else if (template === true) template = "" if (!template && get(templates).default) template = "default" } diff --git a/src/frontend/components/actions/api.ts b/src/frontend/components/actions/api.ts index 31263187..5f4f966a 100644 --- a/src/frontend/components/actions/api.ts +++ b/src/frontend/components/actions/api.ts @@ -4,7 +4,7 @@ import { send } from "../../utils/request" import { updateTransition } from "../../utils/transitions" import { startMetronome } from "../drawer/audio/metronome" import { audioPlaylistNext, clearAudio, startPlaylist, updateVolume } from "../helpers/audio" -import { getThumbnail } from "../helpers/media" +import { getSlideThumbnail, getThumbnail } from "../helpers/media" import { changeStageOutputLayout, displayOutputs, startCamera } from "../helpers/output" import { activateTriggerSync, changeOutputStyle, nextSlideIndividual, playSlideTimers, previousSlideIndividual, randomSlide, selectProjectShow, sendMidi, startAudioStream, startShowSync } from "../helpers/showActions" import { playSlideRecording } from "../helpers/slideRecording" @@ -70,6 +70,7 @@ export type API_id_value = { id: string; value: string } export type API_rearrange = { showId: string; from: number; to: number } export type API_group = { showId: string; groupId: string } export type API_layout = { showId: string; layoutId: string } +export type API_slide_thumbnail = { showId?: string; layoutId?: string; index?: number } export type API_media = { path: string } export type API_scripture = { id: string; reference: string } export type API_toggle = { id: string; value?: boolean } @@ -219,6 +220,7 @@ export const API_ACTIONS = { get_groups: (data: API_id) => getShowGroups(data.id), get_thumbnail: (data: API_media) => getThumbnail(data), + get_slide_thumbnail: (data: API_slide_thumbnail) => getSlideThumbnail(data), get_cleared: () => getClearedState(), } diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index fdec729c..903f7c2a 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -261,7 +261,7 @@ {#if shortcut} - {shortcut} + {shortcut} {/if} diff --git a/src/frontend/components/context/contextMenus.ts b/src/frontend/components/context/contextMenus.ts index f6e5b972..4e82a1bc 100644 --- a/src/frontend/components/context/contextMenus.ts +++ b/src/frontend/components/context/contextMenus.ts @@ -245,7 +245,7 @@ export const contextMenuLayouts: { [key: string]: string[] } = { // SHOWS // , "copy", "paste" 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"], + slideChild: ["edit", "SEPERATOR", "slideGroups", "actions", "bind_to", "format", "remove_layers", "slide_transition", "disable", "SEPERATOR", "duplicate", "delete_slide", "remove_slide"], slideFocus: ["editSlideText"], group: ["rename", "recolor", "SEPERATOR", "selectAll", "SEPERATOR", "duplicate", "delete_group"], global_group: ["edit"], diff --git a/src/frontend/components/context/loadItems.ts b/src/frontend/components/context/loadItems.ts index 97868ae2..3ed466d0 100644 --- a/src/frontend/components/context/loadItems.ts +++ b/src/frontend/components/context/loadItems.ts @@ -143,7 +143,7 @@ const loadActions = { let audioItems = sortByName( audio.map((id: string) => ({ id, - label: showMedia[id].name.indexOf(".") > -1 ? showMedia[id].name.slice(0, showMedia[id].name.lastIndexOf(".")) : showMedia[id].name, + label: showMedia[id]?.name ? (showMedia[id].name.indexOf(".") > -1 ? showMedia[id].name.slice(0, showMedia[id].name.lastIndexOf(".")) : showMedia[id].name) : "", translate: false, icon: "music", })), diff --git a/src/frontend/components/draw/DrawTools.svelte b/src/frontend/components/draw/DrawTools.svelte index 8b846043..d19bcedf 100644 --- a/src/frontend/components/draw/DrawTools.svelte +++ b/src/frontend/components/draw/DrawTools.svelte @@ -5,7 +5,7 @@ import T from "../helpers/T.svelte" import Button from "../inputs/Button.svelte" - const tools: DrawTools[] = ["focus", "pointer", "zoom", "particles", "fill", "paint"] + const tools: DrawTools[] = ["focus", "pointer", "particles", "zoom", "fill", "paint"] function keydown(e: any) { if (e.target?.closest(".edit") || e.ctrlKey || e.metaKey) return diff --git a/src/frontend/components/draw/Slide.svelte b/src/frontend/components/draw/Slide.svelte index 1adad424..fc87e9d0 100644 --- a/src/frontend/components/draw/Slide.svelte +++ b/src/frontend/components/draw/Slide.svelte @@ -24,8 +24,10 @@ return } - let x = (e.clientX - slide.offsetLeft - (slide.closest(".parent").offsetLeft || 0)) / ratio - let y = (e.clientY - slide.offsetTop - (slide.closest(".parent").offsetTop || 0)) / ratio + let centerElem = slide.closest(".parent")?.closest(".center") + + let x = (e.clientX - slide.offsetLeft - (centerElem?.offsetLeft || 0)) / ratio + let y = (e.clientY - slide.offsetTop - (centerElem?.offsetTop || 0)) / ratio if ($drawTool === "pointer" || $drawTool === "focus") { let size = $drawSettings[$drawTool]?.size diff --git a/src/frontend/components/drawer/audio/Audio.svelte b/src/frontend/components/drawer/audio/Audio.svelte index b256b6e3..fcfd36d3 100644 --- a/src/frontend/components/drawer/audio/Audio.svelte +++ b/src/frontend/components/drawer/audio/Audio.svelte @@ -247,7 +247,7 @@ {/key} {/key} {:else} -
+
{/if} @@ -269,6 +269,9 @@ > + +
+ {/if} @@ -342,4 +345,10 @@ .grid :global(.selectElem:not(.isSelected):nth-child(even)) { background-color: rgb(0 0 20 / 0.08); } + + .seperator { + width: 1px; + height: 100%; + background-color: var(--primary); + } diff --git a/src/frontend/components/drawer/bible/Scripture.svelte b/src/frontend/components/drawer/bible/Scripture.svelte index cab2cd1e..1ddcab18 100644 --- a/src/frontend/components/drawer/bible/Scripture.svelte +++ b/src/frontend/components/drawer/bible/Scripture.svelte @@ -1128,7 +1128,7 @@ background-color: var(--primary-darkest); } .seperator { - width: 2px; + width: 1px; height: 100%; background-color: var(--primary); } diff --git a/src/frontend/components/drawer/calendar/Calendar.svelte b/src/frontend/components/drawer/calendar/Calendar.svelte index 16ebb99e..f9f3f363 100644 --- a/src/frontend/components/drawer/calendar/Calendar.svelte +++ b/src/frontend/components/drawer/calendar/Calendar.svelte @@ -378,7 +378,7 @@ } .seperator { - width: 2px; + width: 1px; height: 100%; background-color: var(--primary); } diff --git a/src/frontend/components/drawer/media/Media.svelte b/src/frontend/components/drawer/media/Media.svelte index c51ec9d1..69430c95 100644 --- a/src/frontend/components/drawer/media/Media.svelte +++ b/src/frontend/components/drawer/media/Media.svelte @@ -378,7 +378,7 @@
{:else}
-
+
@@ -431,7 +431,7 @@ - + {#key folderName} {#if folderName.includes(".")} @@ -439,9 +439,13 @@ {folderName} {/if} {/key} + + {#if content} + ({content}) + {/if} -
+ {/if} {#if active !== "screens"} @@ -477,10 +481,14 @@ + +
{:else} + +
{/if} @@ -340,8 +340,8 @@
{/if} @@ -440,7 +440,7 @@ } .seperator { - width: 2px; + width: 1px; height: 100%; background-color: var(--primary); /* margin: 0 10px; */ diff --git a/src/frontend/components/edit/scripts/itemHelpers.ts b/src/frontend/components/edit/scripts/itemHelpers.ts index ec998b5e..ca4417bb 100644 --- a/src/frontend/components/edit/scripts/itemHelpers.ts +++ b/src/frontend/components/edit/scripts/itemHelpers.ts @@ -10,7 +10,7 @@ import { clone, keysToID, sortByName } from "../../helpers/array" export const DEFAULT_ITEM_STYLE = "top:120px;left:50px;height:840px;width:1820px;" -export function addItem(type: ItemType, id: any = null, options: any = {}) { +export function addItem(type: ItemType, id: any = null, options: any = {}, value: string = "") { let activeTemplate: string | null = get(activeShow)?.id ? get(showsCache)[get(activeShow)!.id!]?.settings?.template : null let template = activeTemplate ? get(templates)[activeTemplate]?.items : null @@ -20,7 +20,7 @@ export function addItem(type: ItemType, id: any = null, options: any = {}) { } if (id) newData.id = id - if (type === "text") newData.lines = [{ align: template?.[0]?.lines?.[0]?.align || "", text: [{ value: "", style: template?.[0]?.lines?.[0]?.text?.[0]?.style || "" }] }] + if (type === "text") newData.lines = [{ align: template?.[0]?.lines?.[0]?.align || "", text: [{ value, style: template?.[0]?.lines?.[0]?.text?.[0]?.style || "" }] }] if (type === "list") newData.list = { items: [] } // else if (type === "timer") newData.timer = { id: uid(), name: get(dictionary).timer?.counter || "Counter", type: "counter", start: 300, end: 0 } else if (type === "timer") { diff --git a/src/frontend/components/edit/tools/Items.svelte b/src/frontend/components/edit/tools/Items.svelte index 83a16cd6..b41dfa68 100644 --- a/src/frontend/components/edit/tools/Items.svelte +++ b/src/frontend/components/edit/tools/Items.svelte @@ -1,6 +1,6 @@
@@ -32,9 +27,12 @@ } .error { + margin-bottom: 2px; + font-size: 0.9em; - /* font-style: italic; - font-weight: bold; */ - opacity: 0.7; + opacity: 0.9; + + text-decoration-line: underline; + text-decoration-color: rgb(255 0 0 / 0.7); } diff --git a/src/frontend/components/main/popups/CloudMethod.svelte b/src/frontend/components/main/popups/CloudMethod.svelte index a8060bf1..5338d890 100644 --- a/src/frontend/components/main/popups/CloudMethod.svelte +++ b/src/frontend/components/main/popups/CloudMethod.svelte @@ -34,11 +34,11 @@
diff --git a/src/frontend/components/main/popups/History.svelte b/src/frontend/components/main/popups/History.svelte index fc04c8c6..47b5dfe7 100644 --- a/src/frontend/components/main/popups/History.svelte +++ b/src/frontend/components/main/popups/History.svelte @@ -5,6 +5,7 @@ import T from "../../helpers/T.svelte" import { getDateAndTimeString, timeAgo } from "../../helpers/time" import Button from "../../inputs/Button.svelte" + import CombinedInput from "../../inputs/CombinedInput.svelte" import Center from "../../system/Center.svelte" const INITIAL: any = { id: "initial", time: 0 } @@ -103,47 +104,60 @@ } -{#if rHistory.length || uHistory.length} - {#each rHistory as item, i} - {@const itemId = getItemId(item)} - - {/each} - {#each uHistory as item, i} - {@const itemId = getItemId(item)} - - {/each} - -
- - - -{:else} -
- -
-{/if} + {:else} +
+ +
+ {/if} +
diff --git a/src/frontend/components/main/popups/UserDataOverwrite.svelte b/src/frontend/components/main/popups/UserDataOverwrite.svelte new file mode 100644 index 00000000..6d984c8a --- /dev/null +++ b/src/frontend/components/main/popups/UserDataOverwrite.svelte @@ -0,0 +1,62 @@ + + +

+ +
+ +
+ + +
+ + diff --git a/src/frontend/components/main/popups/export/Export.svelte b/src/frontend/components/main/popups/export/Export.svelte index 0eeeac5c..5c1a40a7 100644 --- a/src/frontend/components/main/popups/export/Export.svelte +++ b/src/frontend/components/main/popups/export/Export.svelte @@ -2,7 +2,7 @@ import { EXPORT } from "../../../../../types/Channels" import type { Project } from "../../../../../types/Projects" import { Show } from "../../../../../types/Show" - import { activePopup, activeProject, dataPath, projects, showsCache, showsPath } from "../../../../stores" + import { activePopup, activeProject, dataPath, dictionary, projects, showsCache, showsPath, special } from "../../../../stores" import { send } from "../../../../utils/request" import { exportProject } from "../../../export/project" import { clone } from "../../../helpers/array" @@ -10,8 +10,8 @@ import { loadShows } from "../../../helpers/setShow" import T from "../../../helpers/T.svelte" import Button from "../../../inputs/Button.svelte" + import Checkbox from "../../../inputs/Checkbox.svelte" import CombinedInput from "../../../inputs/CombinedInput.svelte" - import Dropdown from "../../../inputs/Dropdown.svelte" import Center from "../../../system/Center.svelte" import Loader from "../../Loader.svelte" import { exportFormats, exportTypes, getActiveShowId, getShowIdsFromType } from "./exportHelper" @@ -21,18 +21,33 @@ let showIds: string[] = [] let loading: boolean = false - let type: any = exportTypes[0] - let format: any = exportFormats[0] + let exportType: string = "" + let exportFormat: string = "" - $: if (type.id) updateActive() - async function updateActive() { - // type not supported by format - if (exportFormats.find((a) => a.id === format.id)?.data?.hide.includes(type.id)) { - format = exportFormats[0] - } + const excludedFormats = { + project: ["show", "txt"], + all_shows: ["project", "pdf"], + } + function filterFormats(exportFormats) { + return clone(exportFormats).filter((a) => !(excludedFormats[exportType] || []).find((id) => id === a.id)) + } + + const formatIcons = { + show: "json", + txt: "txt", + pdf: "pdf", + project: "zip", + } + + $: typeName = exportTypes.find((a) => a.id === exportType)?.name || "" + $: formatName = exportFormats.find((a) => a.id === exportFormat)?.name || "" + + /// + $: if (exportType && exportFormat) updateActive() + async function updateActive() { loading = true - showIds = getShowIdsFromType[type.id]?.() || [] + showIds = getShowIdsFromType[exportType]?.() || [] let showId = showIds[0] if (!showId && !previewShow) showId = getActiveShowId() @@ -43,22 +58,21 @@ } loading = false + + // TODO: get & display preview example of first txt!/show? export } async function exportClick() { - // type not supported by format - if (exportFormats.find((a) => a.id === format.id)?.data?.hide.includes(type.id)) return - - if (type.id === "all_shows") { + if (exportType === "all_shows") { loading = true - send(EXPORT, ["ALL_SHOWS"], { type: format.id, path: $dataPath, showsPath: $showsPath }) + send(EXPORT, ["ALL_SHOWS"], { type: exportFormat, path: $dataPath, showsPath: $showsPath }) return } - if (format.id === "project") { + if (exportFormat === "project") { if (!showIds.length) return - let project: Project | null = type.id === "project" && $activeProject ? $projects[$activeProject] : null + let project: Project | null = exportType === "project" && $activeProject ? $projects[$activeProject] : null if (!project) { if (!previewShow) return @@ -74,58 +88,140 @@ loading = true await exportProject(project) } else { - send(EXPORT, ["GENERATE"], { type: format.id, path: $dataPath, showsPath: $showsPath, showIds, options: format.id === "pdf" ? pdfOptions : {} }) + send(EXPORT, ["GENERATE"], { type: exportFormat, path: $dataPath, showsPath: $showsPath, showIds, options: exportFormat === "pdf" ? pdfOptions : {} }) } activePopup.set(null) } let pdfOptions: any = {} + + function setSpecial(e: any, key: string) { + let value = e?.target?.checked + special.update((a) => { + a[key] = value + return a + }) + } -
-

- (type = e.detail)} /> -

 

- !a.data?.hide.includes(type.id))} value={format.name} on:click={(e) => (format = e.detail)} /> -
+{#if !exportType} +

+ +
+ {#each exportTypes as type, i} + + {/each} +
+{:else if !exportFormat} + -
+

+ +
+ {#each filterFormats(exportFormats) as format, i} + + {/each} +
+{:else} + -{#if format.id === "pdf"} - + +
+

+

+

 

+

+ {#if formatName.includes("$:")}{:else}{formatName}{/if} +

+

-{/if} -{#if loading} -
- -
-{/if} + {#if exportFormat === "pdf"} + + +
+ {:else if exportFormat === "project"} + +

+
+ setSpecial(e, "projectIncludeMedia")} /> +
+
- - - - - + + {#if loading} +
+ +
+ {/if} + + + + +{/if} diff --git a/src/frontend/components/settings/tabs/Other.svelte b/src/frontend/components/settings/tabs/Other.svelte index 48f6c9c4..b91b4b82 100644 --- a/src/frontend/components/settings/tabs/Other.svelte +++ b/src/frontend/components/settings/tabs/Other.svelte @@ -2,7 +2,7 @@ import { onDestroy, onMount } from "svelte" import { EXPORT, MAIN } from "../../../../types/Channels" import { activePage, activePopup, alertMessage, alertUpdates, dataPath, deletedShows, dictionary, popupData, shows, showsCache, showsPath, special, usageLog } from "../../../stores" - import { destroy, receive, send } from "../../../utils/request" + import { awaitRequest, destroy, receive, send } from "../../../utils/request" import { save } from "../../../utils/save" import Icon from "../../helpers/Icon.svelte" import T from "../../helpers/T.svelte" @@ -41,12 +41,21 @@ const isChecked = (e: any) => e.target.checked - function toggle(e: any, key: string) { + async function toggle(e: any, key: string) { let checked = e.target.checked - updateSpecial(checked, key) if (key === "customUserDataLocation") { - save(false, { backup: true, changeUserData: { reset: !checked, dataPath: $dataPath } }) + let existingData = false + if (checked) { + existingData = (await awaitRequest(MAIN, "DOES_PATH_EXIST", { path: "data_config", dataPath: $dataPath }))?.exists + if (existingData) activePopup.set("user_data_overwrite") + } + if (!existingData) { + updateSpecial(checked, key) + save(false, { backup: true, changeUserData: { reset: !checked, dataPath: $dataPath } }) + } + } else { + updateSpecial(checked, key) } } @@ -377,7 +386,7 @@ {:else} @@ -397,18 +406,18 @@

- a.id === ($special.autoBackup || "never"))?.name || ""} on:click={(e) => updateSpecial(e.detail.id, "autoBackup")} /> + a.id === ($special.autoBackup || "never"))?.name || ""} on:click={(e) => updateSpecial(e.detail.id, "autoBackup")} up />
diff --git a/src/frontend/components/settings/tabs/Outputs.svelte b/src/frontend/components/settings/tabs/Outputs.svelte index cc7edb74..190d9186 100644 --- a/src/frontend/components/settings/tabs/Outputs.svelte +++ b/src/frontend/components/settings/tabs/Outputs.svelte @@ -350,7 +350,7 @@

{#if currentOutput.bounds?.width} - ({currentOutput.bounds.width}x{currentOutput.bounds.height}) + ({currentOutput.bounds.width}x{currentOutput.bounds.height}) {/if}

diff --git a/src/frontend/components/settings/tabs/Styles.svelte b/src/frontend/components/settings/tabs/Styles.svelte index 2fb3487b..24a50224 100644 --- a/src/frontend/components/settings/tabs/Styles.svelte +++ b/src/frontend/components/settings/tabs/Styles.svelte @@ -39,12 +39,6 @@ const isChecked = (e: any) => e.target.checked - function updateCropping(newValue: number, key: string) { - let cropping = currentStyle.cropping || { top: 0, right: 0, bottom: 0, left: 0 } - cropping[key] = newValue - updateStyle(cropping, "cropping") - } - // pre v0.8.4: generate styles from old output "show" formatting if (!Object.keys($styles).length) createStylesFromOutputs() function createStylesFromOutputs() { @@ -258,16 +252,6 @@
- -

- - updateCropping(Number(e.detail), "top")} /> - updateCropping(Number(e.detail), "right")} /> - updateCropping(Number(e.detail), "bottom")} /> - updateCropping(Number(e.detail), "left")} /> - -
-

@@ -346,12 +330,12 @@

- a.id === (currentStyle.displayMetadata || "never"))?.name || "—"} on:click={(e) => updateStyle(e.detail.id, "displayMetadata")} /> + a.id === (currentStyle.displayMetadata || "never"))?.name || "—"} on:click={(e) => updateStyle(e.detail.id, "displayMetadata")} up />
{#if (currentStyle.displayMetadata || "never") !== "never"}

- updateStyle(e.detail.id, "metadataTemplate")} /> + updateStyle(e.detail.id, "metadataTemplate")} up />

@@ -364,7 +348,7 @@ {/if}

- updateStyle(e.detail.id, "messageTemplate")} /> + updateStyle(e.detail.id, "messageTemplate")} up />
diff --git a/src/frontend/components/show/Section.svelte b/src/frontend/components/show/Section.svelte index 79144d57..b9262e63 100644 --- a/src/frontend/components/show/Section.svelte +++ b/src/frontend/components/show/Section.svelte @@ -96,10 +96,9 @@ h4 :global(input) { background-color: var(--primary-darkest); } - /* ::placeholder does not work here for some reason */ - h4.empty :global(input) { - opacity: 0.4; - /* font-size: 0.9em; */ + /* ::placeholder opacity does not work for some reason */ + h4.empty :global(input::placeholder) { + color: rgb(255 255 255 / 0.4); } .settings { diff --git a/src/frontend/components/show/TextEditor.svelte b/src/frontend/components/show/TextEditor.svelte index e010ea5e..4f58eeca 100644 --- a/src/frontend/components/show/TextEditor.svelte +++ b/src/frontend/components/show/TextEditor.svelte @@ -1,7 +1,7 @@