diff --git a/public/lang/hu.json b/public/lang/hu.json index 0250cb66..7592524a 100644 --- a/public/lang/hu.json +++ b/public/lang/hu.json @@ -256,10 +256,10 @@ }, "show_at": { "never": "Egyetlen dián sem", - "always": "Minden dia", - "first": "Első dia", - "last": "Utolsó dia", - "first_last": "Első és utolsó dia" + "always": "Minden dián", + "first": "Első dián", + "last": "Utolsó dián", + "first_last": "Első és utolsó dián" }, "themes": { "default": "Alapértelmezett", @@ -359,6 +359,7 @@ "change_name": "Név módosítása", "choose_screen": "Képernyő kiválasztása", "change_output_values": "Kimeneti értékek módosítása", + "choose_chord": "Akkord kiválasztása", "set_time": "Idő beállítása", "animate": "Animálás", "next_timer": "Következő dia időzítője", @@ -530,6 +531,7 @@ "svg_clipboard": "SVG importálása vágólapról", "fullscreen_preview": "Teljes képernyős előnézet átváltása", "toggle_output": "Kimeneti képernyő átváltása", + "toggle_panels": "Panel átváltása", "change_tab": "Fül módosítása", "change_drawer_tab": "Rajzoló fül módosítása", "change_slide": "Dia módosítása", @@ -540,8 +542,14 @@ "slide_actions": "Diaműveletek", "item_actions": "Elemművelet", "clear_history": "Előzmények törlése", + "chord_info": "Kérjük, kattintson bármelyik betűre akkord hozzáadásához.", + "chord_key": "Kulcs", + "chord_type": "Típus", + "chord_tension": "Színezőhang", + "chord_bass": "Basszus", "set_key": "Billentyű beállítása", "custom_key": "Egyéni érték beállítása", + "select_chord": "Akkord kijelölése", "play_on_midi": "Aktiválás MIDI jelre", "play_on_midi_tip": "A kiválasztott MIDI jel fogadásakor aktiválja ezt a speciális diát.", "send_midi": "MIDI jel küldése", @@ -593,6 +601,7 @@ "activate_video_ending": "Aktiválás videó befejezésekor", "activate_timer_ending": "Aktiválás időzítő befejezésekor", "activate_scripture_start": "Aktiválás szentírás indításakor", + "activate_slide_cleared": "Aktiválás dia törlésekor", "activate_show_created": "Aktiválás műsor létrehozásakor" }, "animate": { @@ -694,6 +703,7 @@ "_title_underline": "Aláhúzott", "_title_strikethrough": "Áthúzott", "color": "Szín", + "accent_color": "Ékezet színe", "background_color": "Háttérszín", "background_opacity": "Háttérátlátszatlanság", "background_image": "Háttérkép", @@ -766,7 +776,8 @@ "start_days_from_today": "Kezdés napokban a mai naptól", "just_one_day": "Csak egy nap", "enable_start_date": "Kezdési dátum engedélyezése", - "disable_navigation": "Navigációs billentyűk letiltása" + "disable_navigation": "Navigációs billentyűk letiltása", + "progress_bar": "Folyamatjelző" }, "items": { "text": "Szövegdoboz", @@ -783,6 +794,7 @@ "timer": "Időzítő", "variable": "Változó", "web": "Weboldal", + "slide_tracker": "Folyamat", "visualizer": "Vizualizáló", "captions": "Feliratok", "icon": "Ikon" @@ -888,6 +900,7 @@ "next_slide_notes": "Következő dia jegyzetei", "output": "Kimenet", "current_output": "Aktuális kimenet", + "slide_tracker": "Folyamat", "time": "Idő", "system_clock": "Rendszeróra", "video_time": "Videó idő", @@ -921,7 +934,7 @@ "minutes": "perc", "use24hClock": "24 órás időformátum használata", "styles_hint": "Különböző stílusok létrehozása, amelyeket alkalmazni lehet a kimenetre a megjelenés módosítására.", - "hide_output_hint": "Duplán kattintás a kimeneti ablakra a elrejtéséhez. Húzás lenyomva tartott Ctrl billentyűvel.", + "hide_output_hint": "Dupla kattintással a kimeneti ablakon az elrejtéséhez. Lenyomva tartott Ctrl billentyűvel a húzásához.", "hide_menubar_hint": "Menüsor elrejtése a kioszk mód engedélyezesével, vagy „A menüsor automatikus elrejtése vagy megjelenítése” engedélyezésével a MacOS általános beállításaiban.", "show_output_hint": "Megjelenítés kényszerítése a második monitoron a Ctrl/Cmd billentyű lenyomva tartásával a megjelenítés gombra kattintáskor.", "move_output_hint": "Nem található a kijelző? Változtassa meg az elhelyezését, amíg az ablak meg nem jelenik a második képernyőn.", @@ -958,6 +971,7 @@ "audio_fade_duration": "Halkítás időtartama", "audio_crossfade": "Hang kereszthalkítása", "max_auto_font_size": "Maximális automatikus betűméret", + "clear_style_background_on_text": "Stílusháttér törlése, amikor a dia aktív", "resolution": "Felbontás", "cropping": "Vágás", "frame_rate": "Képkockasebesség", @@ -1132,12 +1146,12 @@ "info": { "created": "Létrehozás dátuma", "modified": "Módosítva", - "used": "Alkalmazott", + "used": "Alkalmazva", "changed": "Módosított", "extension": "Fájlkiterjesztés", "size": "Méret", - "slides": "Diák", - "words": "Szavak", + "slides": "Dia", + "words": "Szó", "template": "Sablon", "category": "Kategória" }, diff --git a/src/frontend/components/drawer/bible/Scripture.svelte b/src/frontend/components/drawer/bible/Scripture.svelte index e76225d0..3544d2a3 100644 --- a/src/frontend/components/drawer/bible/Scripture.svelte +++ b/src/frontend/components/drawer/bible/Scripture.svelte @@ -857,8 +857,9 @@ {id} draggable="true" on:mouseup={(e) => selectVerse(e, id)} - on:mousedown={() => { - if (!activeVerses.includes(id)) activeVerses = [...activeVerses, id] + on:mousedown={(e) => { + if (e.ctrlKey || e.metaKey || e.shiftKey) return + if (!activeVerses.includes(id)) activeVerses = [id] updateActiveVerses() }} on:dblclick={() => playOrClearScripture(true)} diff --git a/src/frontend/components/helpers/audio.ts b/src/frontend/components/helpers/audio.ts index c04b5cff..d61a0dbd 100644 --- a/src/frontend/components/helpers/audio.ts +++ b/src/frontend/components/helpers/audio.ts @@ -10,15 +10,7 @@ import { encodeFilePath } from "./media" import { checkNextAfterMedia } from "./showActions" import { customActionActivation } from "../actions/actions" -// let currentlyStarting: string[] = [] export async function playAudio({ path, name = "", audio = null, stream = null }: any, pauseIfPlaying: boolean = true, startAt: number = 0, playMultiple: boolean = false, crossfade: number = 0) { - // // prevent starting the same song rapidly - // if (currentlyStarting.includes(path)) return - // currentlyStarting.push(path) - // setTimeout(() => { - // currentlyStarting.splice(currentlyStarting.indexOf(path), 1) - // }, 3000) - let existing: any = get(playingAudio)[path] if (existing) { if (!pauseIfPlaying) { @@ -88,7 +80,10 @@ export async function playAudio({ path, name = "", audio = null, stream = null } audio.addEventListener("canplay", () => { setTimeout(() => { - audio.play() + // audio might have been cleared + if (!get(playingAudio)[path]?.audio) return + + get(playingAudio)[path].audio.play() analyseAudio() }, waitToPlay * 1000) }) @@ -225,8 +220,6 @@ export function playlistNext(previous: string = "", specificSong: string = "", c return } - console.log("PLAY NEXT", nextSong, crossfade) - // prevent playing the same song twice (while it's fading) to stop duplicate audio if (Object.keys(playingAudio).includes(nextSong)) return @@ -343,7 +336,11 @@ export function analyseAudio() { }, audioUpdateInterval) } +let previousMerge = 0 function mergeAudio(allAudio) { + let timeSinceLast = Date.now() - previousMerge + if (timeSinceLast > 100 && timeSinceLast < 200) return // skip if overloaded + let allLefts: number[] = [] let allRights: number[] = [] @@ -362,6 +359,7 @@ function mergeAudio(allAudio) { if (allLefts.length || allRights.length) merged = { left: getHighestNumber(allLefts), right: getHighestNumber(allRights) } audioChannels.set(merged) + previousMerge = Date.now() } const extraMargin = 0.1 // s @@ -399,11 +397,11 @@ function getPlayingAudio() { let playlist = get(audioPlaylists)[audioPath] || {} playingAudio.update((a: any) => { + a[audioPath]?.audio?.pause() delete a[audioPath] return a }) - console.log("ENDED", playlist, playlist.loop !== false) playlistNext(audioPath, "", 0, playlist.loop !== false) return false } else { @@ -412,6 +410,7 @@ function getPlayingAudio() { // a[audioPath].audio?.pause() a[audioPath].paused = true } else { + a[audioPath]?.audio?.pause() delete a[audioPath] } diff --git a/src/frontend/components/helpers/show.ts b/src/frontend/components/helpers/show.ts index c2bd73b8..225f9fb0 100644 --- a/src/frontend/components/helpers/show.ts +++ b/src/frontend/components/helpers/show.ts @@ -3,6 +3,7 @@ import type { Show, ShowList, Shows, Slide } from "../../../types/Show" import { activeShow, cachedShowsData, dictionary, groupNumbers, groups, shows, showsCache, sorted, sortedShowsList, stageShows } from "../../stores" import { clone, keysToID, removeValues, sortByNameAndNumber } from "./array" import { GetLayout } from "./get" +import { _show } from "./shows" // check if name exists and add number export function checkName(name: string = "", showId: string = "") { @@ -60,6 +61,38 @@ export function getGlobalGroup(group: string, returnInputIfNull: boolean = false return globalGroup || (returnInputIfNull ? groupId : "") } +// get group number (dynamic counter) +export function getGroupName(show: Show, slideID: string, groupName: string | null, layoutIndex: number) { + let name = groupName + if (name === null) return name // child slide + + if (!name.length) name = "—" + if (!get(groupNumbers)) return name + + // sort by order when just one layout + let slides = keysToID(clone(show.slides || {})) + if (Object.keys(show.layouts || {}).length < 2) { + let layoutSlides = Object.values(show.layouts || {})[0]?.slides?.map(({ id }) => id) || [] + slides = slides.sort((a, b) => layoutSlides.indexOf(a.id) - layoutSlides.indexOf(b.id)) + } + + // different slides with same name + let currentSlide = show.slides?.[slideID] || {} + let allSlidesWithSameGroup = slides.filter((a) => a.group === currentSlide.group) + let currentIndex = allSlidesWithSameGroup.findIndex((a) => a.id === slideID) + let currentGroupNumber = allSlidesWithSameGroup.length > 1 ? " " + (currentIndex + 1) : "" + name += currentGroupNumber + + // same group - count + let layoutRef = _show().layouts("active").ref()[0] + let allGroupLayoutSlides = layoutRef.filter((a) => a.id === slideID) + let currentGroupLayoutIndex = allGroupLayoutSlides.findIndex((a) => a.layoutIndex === layoutIndex) + let currentLayoutNumber = allGroupLayoutSlides.length > 1 ? " (" + (currentGroupLayoutIndex + 1) + ")" : "" + name += currentLayoutNumber + + return name +} + // mirror & events export function getListOfShows(removeCurrent: boolean = false) { let list: any[] = Object.entries(get(shows)).map(([id, show]: any) => ({ id, name: show.name })) @@ -142,16 +175,15 @@ export function updateCachedShow(id: string, show: Show) { slidesUpdated: cachedShowsData[id]?.template?.slidesUpdated || false, } - // create groups - let showSlides = keysToID(clone(show.slides || {})) - let addedGroups: any = {} - // sort by order when just one layout + let showSlides = keysToID(clone(show.slides || {})) if (Object.keys(show.layouts || {}).length < 2) { let layoutSlides = Object.values(show.layouts || {})[0]?.slides?.map(({ id }) => id) || [] showSlides = showSlides.sort((a, b) => layoutSlides.indexOf(a.id) - layoutSlides.indexOf(b.id)) } + // create groups + let addedGroups: any = {} let showGroups: any[] = showSlides.map(createGroups) function createGroups(slide) { // update if global group @@ -173,13 +205,20 @@ export function updateCachedShow(id: string, show: Show) { } } - if (!slide.group || !get(groupNumbers)) return { ...slide, id: slide.id } + if (slide.group === null || !get(groupNumbers)) return { ...slide, id: slide.id } + if (!slide.group) slide.group = "—" // add numbers to different slides with same name if (addedGroups[slide.group]) { addedGroups[slide.group]++ slide.group += " " + addedGroups[slide.group] - } else addedGroups[slide.group] = 1 + } else { + addedGroups[slide.group] = 1 + + // find all groups with same name + let allSameGroups = showSlides.filter((a) => a.group !== null && (a.group || "—") === slide.group) + if (allSameGroups.length > 1) slide.group += " 1" + } return { ...slide, id: slide.id } } diff --git a/src/frontend/components/main/popups/Import.svelte b/src/frontend/components/main/popups/Import.svelte index adae8336..3c1428cd 100644 --- a/src/frontend/components/main/popups/Import.svelte +++ b/src/frontend/components/main/popups/Import.svelte @@ -28,13 +28,13 @@ name: "EasyWorship", extensions: ["db"], id: "easyworship", - tutorial: "Import the SongsWords.db file from the Data folder
Optionally select Songs.db to import title/metadata", + tutorial: "Import the SongsWords.db file from the Data folder - Often located in the Documents folder
Optionally select Songs.db to import title/metadata", }, { name: "VideoPsalm", extensions: ["json"], id: "videopsalm", - tutorial: "1. Find the .vpc or .json file(s)
2. If it's .vpc, add .zip to the end & extract
3. Import the .json file(s)", + tutorial: "1. Find the .vpc or .json file(s) - Often located in Documents\\VideoPsalm\\Songbooks
2. If it's .vpc, add .zip to the end & extract
3. Import the .json file(s)", }, { name: "OpenLP/OpenLyrics", extensions: ["xml", "sqlite"], id: "openlp" }, { name: "OpenSong", extensions: [], id: "opensong" }, diff --git a/src/frontend/components/output/tools/Audio.svelte b/src/frontend/components/output/tools/Audio.svelte index 6d741dae..dcf46a24 100644 --- a/src/frontend/components/output/tools/Audio.svelte +++ b/src/frontend/components/output/tools/Audio.svelte @@ -19,7 +19,9 @@ $: paused = playing.paused !== false let duration = 0 - $: if (Object.keys($playingAudio).length === 1) getDuration() + $: justOneAudio = Object.keys($playingAudio).length === 1 + $: if (justOneAudio && path) getDuration() + else duration = 0 async function getDuration() { duration = 0 duration = await getAudioDuration(Object.keys($playingAudio)[0]) diff --git a/src/frontend/components/slide/Slide.svelte b/src/frontend/components/slide/Slide.svelte index 12eb842f..a93a66af 100644 --- a/src/frontend/components/slide/Slide.svelte +++ b/src/frontend/components/slide/Slide.svelte @@ -3,37 +3,18 @@ import { MAIN } from "../../../types/Channels" import type { MediaStyle } from "../../../types/Main" import type { Media, Show, Slide, SlideData } from "../../../types/Show" - import { - activeShow, - activeTimers, - audioFolders, - checkedFiles, - dictionary, - driveData, - fullColors, - groupNumbers, - groups, - media, - mediaFolders, - outputs, - overlays, - refreshListBoxes, - refreshSlideThumbnails, - showsCache, - slidesOptions, - styles, - } from "../../stores" + import { activeShow, activeTimers, audioFolders, checkedFiles, dictionary, driveData, fullColors, groups, media, mediaFolders, outputs, overlays, refreshListBoxes, refreshSlideThumbnails, showsCache, slidesOptions, styles } from "../../stores" import { wait } from "../../utils/common" import { send } from "../../utils/request" import { slideHasAction } from "../actions/actions" import MediaLoader from "../drawer/media/MediaLoader.svelte" import Editbox from "../edit/editbox/Editbox.svelte" import { getItemText } from "../edit/scripts/textStyle" - import { clone, keysToID } from "../helpers/array" + import { clone } from "../helpers/array" import { getContrast } from "../helpers/color" - import { GetLayoutRef } from "../helpers/get" import { checkMedia, getFileName, getMediaStyle, getThumbnailPath, loadThumbnail, mediaSize, splitPath } from "../helpers/media" import { getActiveOutputs, getResolution } from "../helpers/output" + import { getGroupName } from "../helpers/show" import SelectElem from "../system/SelectElem.svelte" import Actions from "./Actions.svelte" import Icons from "./Icons.svelte" @@ -173,44 +154,7 @@ // history({ id: "UPDATE", save: false, newData: { data: color, key: "slides", keys: [layoutSlide.id], subkey: "color" }, oldData: { id: $activeShow?.id }, location: { page: "show", id: "show_key" } }) } - $: name = getGroupName(layoutSlide.id) - // dynamic counter - function getGroupName(slideID: string) { - let name = group - if (name === null || name === undefined) return name - - if (!name.length) name = "—" - let added: any = {} - if (!$groupNumbers) return name - - // different slides with same name - let slides = keysToID(show.slides || []) - // sort by order when just one layout - if (Object.keys(show.layouts || {}).length < 2) { - let layoutSlides = Object.values(show.layouts)[0]?.slides?.map(({ id }) => id) || [] - slides = slides.sort((a, b) => layoutSlides.indexOf(a.id) - layoutSlides.indexOf(b.id)) - } - slides.forEach((slide: any) => { - if (!slide) return - if (added[slide.group]) { - added[slide.group]++ - if (slide.id === slideID) name += " " + added[slide.group] - } else added[slide.group] = 1 - }) - - // same group count - added = {} - GetLayoutRef().forEach((a: any, i: number) => { - if (a.type === "parent") { - if (added[a.id]) { - added[a.id]++ - if (i === index) name += " (" + added[a.id] + ")" - } else added[a.id] = 1 - } - }) - - return name - } + $: name = getGroupName(show, layoutSlide.id, group, index) // quick edit let html: string = ""