outputPdf(e, page)}>
+ {#if viewports[page]}
+
+
+ {/if}
+
+ {#key $slidesOptions.columns}
+
+ {/key}
+
+ diff --git a/package.json b/package.json index 6cb01541..4139ee50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.2.6-beta.1", + "version": "1.2.6", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -170,7 +170,6 @@ "jzz": "^1.5.9", "node-machine-id": "^1.1.12", "npm-run-all": "^4.1.5", - "pdf-poppler": "^0.2.1", "pptx2json": "^0.0.10", "protobufjs": "^7.2.3", "qrcode-generator": "^1.4.4", diff --git a/public/lang/en.json b/public/lang/en.json index d372d9db..89e73054 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -434,6 +434,7 @@ "midi_no_velocity": "Received MIDI signal, but no velocity, defaults to first index." }, "new": { + "create": "Create new", "show": "New show", "empty_show": "New empty show", "project": "New project", @@ -515,6 +516,8 @@ "zoomIn": "Zoom In", "zoomOut": "Zoom Out", "reset": "Reset", + "convert_to_images": "Convert to images", + "converting": "Converting...", "remove_template_from_show": "Remove template from show", "reset_defaults": "Reset defaults", "to_all": "Apply to all", @@ -695,6 +698,7 @@ "enabledTabs": "Toggle tabs", "filterByTags": "Filter by tags", "addToProject": "Add to project", + "add_to_show": "Add to show", "lockForChanges": "Lock for editing", "newCategory": "New category", "changeIcon": "Change icon", @@ -1140,8 +1144,13 @@ "media": "Media transition", "slide_transition": "Slide transition", "background_transition": "Background transition", + "specific": "Enable more specific transitions", + "between": "Between", + "in": "In", + "out": "Out", "duration": "Duration", "easing": "Easing", + "direction": "Direction", "type": "Type", "none": "None", "fade": "Fade", diff --git a/public/lang/es.json b/public/lang/es.json index 06161308..8a1ec401 100644 --- a/public/lang/es.json +++ b/public/lang/es.json @@ -2,7 +2,7 @@ "main": { "welcome": "Bienvenido", "quit": "Salir", - "docs": "Documentos", + "docs": "Documentación", "about": "Acerca", "unnamed": "Sin nombre", "drop": "Soltar aquí", @@ -88,14 +88,16 @@ "search_web": "Buscar canción en la web", "more_options": "Más opciones", "format_new_show": "Dar formato al texto", + "format_new_show_tip": "Mejore el formato del texto con capitalización automática, división de texto, asignación de grupos y más.", "split_lines": "Número de líneas", + "split_lines_tip": "Número de lineas permitidas por diapositiva antes de la división automática", "quick_lyrics_example_text": "Línea" }, "preview": { "_previous_show": "Programa anterior", "_previous_slide": "Diapositiva anterior", "_lock": "Bloquear salida", - "_unlock": "Resultado desbloqueado", + "_unlock": "Desbloquear salida", "_start": "Iniciar programa", "_update": "Actualizar resultado", "_next_slide": "Siguiente diapositiva", @@ -120,7 +122,7 @@ "slide": "Borrar diapositiva", "overlays": "Borrar capas", "audio": "Borrar audio", - "nextTimer": "Borrar temporizador de siguiente diapositiva", + "nextTimer": "Borrar temporizador de la siguiente diapositiva", "drawing": "Borrar dibujo" }, "remove": { @@ -135,7 +137,7 @@ "media": { "_loop": "Ciclo", "play": "Reproducir", - "play_multiple": "Reproducir varios", + "play_multiple": "Reproducción múltiple", "toggle_shuffle": "Activar reproducción aleatoria", "next": "Siguiente", "previous": "Anterior", @@ -161,11 +163,14 @@ "fill": "Llenar", "cover": "Cubrir", "online": "En línea", - "recommended": "Recomendado" + "recommended": "Recomendado", + "bundle_media_files": "Agrupar todos los archivos de medios", + "bundle_media_files_tip": "Copiar los archivos de medios de todos los programas en una carpeta" }, "audio": { "settings": "Configuración de Audio", "playlist_settings": "Ajuste de Lista de Reproducción", + "custom_output": "Salida de audio personalizada", "mute_when_video_plays": "Silenciar cuando un vídeo se reproduce", "metronome": "Metrónomo", "toggle_metronome": "Activar metrónomo", @@ -198,7 +203,7 @@ "slide": "Ningún tema seleccionado", "items": "No se encontró", "search": "No hay coincidencia", - "media": "Ningún programa en escena", + "media": "No hay medios en el programa", "stage_show": "Sin programas en escena", "stage_shows": "Sin videos", "player": "Sin grupos", @@ -222,7 +227,8 @@ "loading": "Cargando...", "submit": "Enviar", "password": "Contraseña", - "wrong_password": "Contraseña incorrecta" + "wrong_password": "Contraseña incorrecta", + "quick_play": "Reproducción rápida" }, "error": { "no_show": "No se encuentra el programa", @@ -243,9 +249,10 @@ "author": "Autor", "composer": "Compositor", "publisher": "Editor", - "copyright": "Derecho de autor", + "copyright": "Derechos de autor", "CCLI": "Licencia (CCLI)", "year": "Año", + "key": "Clave", "message": "Mensaje", "message_tip": "Mostrar algo en todas las diapositivas", "auto_media": "Obtener metadatos del contenido multimedia", @@ -273,10 +280,10 @@ "black": "Negro" }, "theme": { - "primary": "Básico", - "primary-lighter": "Claro", - "primary-darker": "Oscuro", - "primary-darkest": "Muy oscuro", + "primary": "Primario", + "primary-lighter": "Primario claro", + "primary-darker": "Primario oscuro", + "primary-darkest": "Primario más oscuro", "text": "Texto", "textInvert": "Texto invertido", "secondary-text": "Texto secundario", @@ -344,11 +351,11 @@ "break": "Pausa", "tag": "Etiqueta", "bridge": "Puente", - "outro": "Salida" + "outro": "Cierre" }, "popup": { "show": "Nuevo programa", - "select_show": "Select show", + "select_show": "Seleccionar programa", "rename": "Renombrar", "color": "Color", "find_replace": "Buscar y reemplazar", @@ -362,6 +369,7 @@ "delete_show_confirmation": "¿Estás seguro de querer eliminar este programa?", "change_name": "Cambiar de nombre", "choose_screen": "Elegir pantalla", + "choose_output": "Elegir el tipo de salida", "change_output_values": "Cambiar valores de salida", "choose_chord": "Elige acorde", "set_time": "Establecer tiempo", @@ -415,7 +423,6 @@ "media_replaced": "El archivo multimedia faltante fue reemplazado por uno coincidente.", "lyrics_undefined": "¡No se pudo encontrar ninguna letra!", "lyrics_copied": "Letra copiada de", - "no_pdf_linux": "No se puede exportar como PDF en Linux.", "one_output": "¡Debes tener al menos una salida activa!", "empty_cache": "La caché está vacía.", "deleted_cache": "Se eliminó el caché de miniaturas de medios.", @@ -458,6 +465,8 @@ "lyrics": "Vista de letras", "text": "Edición de texto", "update": "Actualizar programa", + "locked": "¡Éste programa ha sido bloqueado!", + "locked_info": "Éste programa ha sido bloqueado para edición. Desbloquéalo nuevamente en el cajón de programas", "slide_template": "Plantilla de diapositiva", "search_results": "Resultados de búsqueda", "source": "Fuente", @@ -499,12 +508,14 @@ "speech": "Discurso", "startSpeaking": "Comenzar a hablar", "stopSpeaking": "Dejar de Hablar", + "focus_mode": "Alternar modo de enfoque", "fullscreen": "Cambiar a Pantalla Completa", "resetZoom": "Restablecer Zoom", "zoom": "Zoom", "zoomIn": "Acercarse", "zoomOut": "Alejarse", "reset": "Reiniciar", + "remove_template_from_show": "Quitar plantilla del programa", "reset_defaults": "Restablecer valores predeterminados", "to_all": "Aplicar a todo", "to_following": "Aplicar al siguiente", @@ -519,6 +530,11 @@ "bind_to": "Salidas específicas", "remove_binding": "Eliminar salidas específicas", "dynamic_values": "Valores dinámicos", + "rearrange": "Reorganizar", + "to_front": "Mover al frente", + "forward": "Mover hacia adelante", + "backward": "Mover hacia atrás", + "to_back": "Mover al fondo", "show_timer": "Tiempo hasta el espectáculo", "hide_timer": "Tiempo hasta esconderse", "choose_custom": "Elegir personalizado", @@ -555,7 +571,7 @@ "chord_tension": "Tensión", "chord_bass": "Bajo", "roman_keys": "Claves romanas", - "set_key": "Set key", + "set_key": "Establecer clave", "custom_key": "Establecer valor personalizado", "select_chord": "Seleccionar este acorde", "play_on_midi": "Activar en la señal MIDI", @@ -598,6 +614,7 @@ "start_slide_timers": "Iniciar temporizadores en la diapositiva activa", "id_select_output_style": "Seleccionar estilo de salida por ID", "change_output_style": "Cambiar el estilo de salida", + "change_stage_output_layout": "Cambiar el diseño de salida del escenario", "change_transition": "Cambiar transición", "change_variable": "Cambiar variable", "start_trigger": "Iniciar disparador", @@ -677,6 +694,7 @@ "enabledTabs": "Mostrar/ocultar pestañas", "filterByTags": "Filtrar por etiquetas", "addToProject": "Agregar al proyecto", + "lockForChanges": "Bloquear para edición", "newCategory": "Nueva categoría", "changeIcon": "Cambiar ícono", "changeGroup": "Cambiar grupo", @@ -686,6 +704,7 @@ "align_with_screen": "Alinear con la pantalla", "toggle_output": "Alternar salida", "move_to_front": "Mover al frente", + "hide_from_preview": "Ocultar de la vista previa", "lock_to_output": "Bloquear a la salida", "place_under_slide": "Colocar debajo de la diapositiva", "toggle_clock": "Alternar reloj", @@ -757,7 +776,7 @@ "width": "Anchura", "height": "Altura", "rotation": "Rotación", - "tilt": "Tilt", + "tilt": "Inclinar", "perspective": "Perspectiva", "opacity": "Opacidad", "corner_radius": "Radio de esquina", @@ -907,6 +926,7 @@ }, "stage": { "slide": "Diapositiva", + "stage_layout": "Diseño del escenario", "current_slide_text": "Texto de diapositiva actual", "current_slide": "Diapositiva actual", "current_slide_notes": "Notas de diapositiva actual", @@ -945,7 +965,7 @@ "media_import": "Medios", "other": "Otro", "language": "Idioma", - "autosave": "Autosave", + "autosave": "Autoguardado", "never": "Nunca", "minutes": "minutos", "use24hClock": "Usar reloj de 24 horas", @@ -960,6 +980,7 @@ "allow_main_screen": "Permitir salida en la pantalla principal", "identify_screens": "Identificar pantallas", "new_output": "Nueva salida", + "normal": "Normal", "enable_key_output": "Habilitar salida de clave alfa", "always_on_top": "Siempre arriba", "kiosk_mode": "Modo quiosco", @@ -978,7 +999,8 @@ "auto_updates": "Actualizaciones automáticas", "disable_labels": "Etiquetas desactivadas", "group_numbers": "Números de grupo", - "full_colors": "Despliega todos los grupos de colores", + "full_colors": "Grupo de colores de alto contraste", + "slide_number_keys": "Reproducir diapositivas con las teclas numéricas", "auto_output": "Muestra la ventana de salida en la pantalla de inicio", "hide_cursor_in_output": "Ocultar el cursor en la salida", "clear_media_when_finished": "Borrar los medios cuando termine", @@ -1006,6 +1028,8 @@ "data_location": "Ubicación de los datos", "user_data_location": "Guardar la configuración del usuario en 'Ubicación de datos'", "popup_before_close": "Mostrar siempre la ventana emergente antes de cerrar", + "disable_hardware_acceleration": "Deshabilitar aceleración por hardware", + "restart_for_change": "¡Tienes que reiniciar el programa para que el cambio surta efecto!", "font": "Fuente", "font_family": "Familia de fuentes", "font_size": "Tamaño de fuente", diff --git a/src/electron/data/import.ts b/src/electron/data/import.ts index 09135df6..870a38a2 100644 --- a/src/electron/data/import.ts +++ b/src/electron/data/import.ts @@ -8,7 +8,6 @@ import sqlite3 from "sqlite3" import WordExtractor from "word-extractor" import { toApp } from ".." import { IMPORT } from "../../types/Channels" -import { dataFolderNames, getDataFolder, makeDir, readFolder } from "../utils/files" const specialImports: any = { powerpoint: async (files: string[]) => { @@ -35,50 +34,7 @@ const specialImports: any = { return data }, - pdf: async (files: string[], settings: { path: string }) => { - let data: any[] = [] - - // TODO: linux don't support pdf-poppler! - const pdf = require("pdf-poppler") - - let opts: any = { format: "png", scale: 1920, out_prefix: "img", page: null } - let importPath = getDataFolder(settings.path, dataFolderNames.imports) - - await Promise.all(files.map(pdfToImages)) - - async function pdfToImages(filePath: string) { - let name = getFileName(filePath) - let outputPath = path.join(importPath, name) - makeDir(outputPath) - opts.out_dir = outputPath - - // WIP use pdf-to-img? (recuires canvas) - // canvas needs some binarise for compiling: https://www.npmjs.com/package/canvas?activeTab=readme#compiling - // const doc = await pdf(filePath) - - // let index = 0 - // for await (const page of doc) { - // index++ - // console.log(index, `img-${index}.png`) - // fs.writeFile(path.join(outputPath, `img-${index}.png`), page, (err) => { - // console.error(err) - // }) - // } - - // data.push({ name, path: outputPath, pages: doc.length }) - - try { - await pdf.convert(filePath, opts) - - let files = readFolder(outputPath) - if (files.length) data.push({ name, path: outputPath, pages: files.length }) - } catch (err) { - console.error(err) - } - } - - return data - }, + pdf: (files: string[]) => files, sqlite: async (files: string[]) => { let data: any[] = [] diff --git a/src/electron/data/pdfToImage.ts b/src/electron/data/pdfToImage.ts new file mode 100644 index 00000000..3371acfe --- /dev/null +++ b/src/electron/data/pdfToImage.ts @@ -0,0 +1,80 @@ +import { BrowserWindow, NativeImage } from "electron" +import { writeFile } from "original-fs" +import path from "path" +import { toApp } from ".." +import { MAIN } from "../../types/Channels" +import { dataFolderNames, makeDir } from "../utils/files" +import { captureOptions } from "../utils/windowOptions" + +export function convertPDFToImages(data: { dataPath: string; path: string; viewports: { width: number; height: number }[]; pages: number }) { + createPDFWindow(data) +} + +// changing scale will triple the loading time... +const SCALE: number = 1 // 0.25 - 5 + +let exportWindow: any = null +export function createPDFWindow(data: any) { + exportWindow = new BrowserWindow(captureOptions) + + let pageIndex = 0 + + exportWindow.webContents.on("did-finish-load", () => { + // give some extra time as PDF loads after content has loaded + const timeout = Math.ceil(Math.ceil(data.viewports[pageIndex].width * data.viewports[pageIndex].height) * 0.0015) * (SCALE === 1 ? 1 : 3) + setTimeout(windowLoaded, timeout) + }) + + // this is needed to allow page to properly scroll into view + const EXTRA_MARGIN = 100 + + exportWindow.setSize(Math.floor(data.viewports[0].width) * SCALE + EXTRA_MARGIN, Math.floor(data.viewports[0].height) * SCALE) + exportWindow.loadFile(data.path, getQuery()) + + let images: string[] = [] + const name = path.basename(data.path, ".pdf") + const SAVE_FOLDER_PATH = path.join(data.dataPath, dataFolderNames.imports, name) + makeDir(SAVE_FOLDER_PATH) + + function windowLoaded() { + // remove scroll bar + let size = exportWindow.getBounds() + // if (SCALE === 2) size = { x: EXTRA_MARGIN / 2 + 5, y: 4, width: size.width - EXTRA_MARGIN - 18, height: size.height - 12 } // optimized for SCALE 2 + // else size = { x: EXTRA_MARGIN / 2 + 5, y: 3, width: size.width - EXTRA_MARGIN - 16, height: size.height - 9 } // optimized for SCALE 1 + size = { x: EXTRA_MARGIN / 2 + 5, y: 3, width: size.width - EXTRA_MARGIN - 16, height: size.height - 9 } + + exportWindow.webContents.capturePage(size).then((image: NativeImage) => { + const buffer = image.toPNG() + const p = path.join(SAVE_FOLDER_PATH, pageIndex + 1 + ".png") + + writeFile(p, buffer, {}, (err) => { + if (err) console.error("Error writing image file: " + err) + else images.push(p) + + next() + }) + }) + + function next() { + pageIndex++ + if (pageIndex >= data.pages) return close() + + exportWindow.setSize(Math.floor(data.viewports[pageIndex].width) * SCALE + EXTRA_MARGIN, Math.floor(data.viewports[pageIndex].height) * SCALE) + exportWindow.loadFile(data.path, getQuery()) + + // window content will not update if this is not set + setTimeout(() => { + exportWindow.webContents.reload() + }, 50) + } + } + + function close() { + exportWindow.destroy() + if (images.length) toApp(MAIN, { channel: "IMAGES_TO_SHOW", data: { images, name } }) + } + + function getQuery() { + return { hash: `#toolbar=0&view=fit&page=${pageIndex + 1}&zoom=${SCALE * 100}` } + } +} diff --git a/src/electron/output/OutputHelper.ts b/src/electron/output/OutputHelper.ts index 7b8f0e15..dccfc69f 100644 --- a/src/electron/output/OutputHelper.ts +++ b/src/electron/output/OutputHelper.ts @@ -29,6 +29,8 @@ export class OutputHelper { IDENTIFY_SCREENS: (data: any) => OutputHelper.Identify.identifyScreens(data), //PREVIEW_BOUNDS: (data: any) => OutputHelper.Bounds.setPreviewBounds(data), + + FOCUS: (data: any) => OutputHelper.Lifecycle.focusOutput(data.id), } if (msg.channel.includes("MAIN")) return toApp(OUTPUT, msg) diff --git a/src/electron/output/helpers/OutputLifecycle.ts b/src/electron/output/helpers/OutputLifecycle.ts index b2592af6..ef782cf6 100644 --- a/src/electron/output/helpers/OutputLifecycle.ts +++ b/src/electron/output/helpers/OutputLifecycle.ts @@ -123,6 +123,10 @@ export class OutputLifecycle { } } + static focusOutput(id: string) { + OutputHelper.getOutput(id)?.window?.focus() + } + static setWindowListeners(window: BrowserWindow, { id, name }: { [key: string]: string }) { window.on("ready-to-show", () => { mainWindow?.focus() diff --git a/src/electron/utils/responses.ts b/src/electron/utils/responses.ts index 9f1358cf..6d7b6621 100644 --- a/src/electron/utils/responses.ts +++ b/src/electron/utils/responses.ts @@ -46,6 +46,7 @@ import { LyricSearch } from "./LyricSearch" import { closeMidiInPorts, getMidiInputs, getMidiOutputs, receiveMidi, sendMidi } from "./midi" import checkForUpdates from "./updater" import { OutputHelper } from "../output/OutputHelper" +import { convertPDFToImages } from "../data/pdfToImage" // IMPORT export function startImport(_e: any, msg: Message) { @@ -126,6 +127,7 @@ const mainResponses: any = { READ_EXIF: (data: any, e: any) => readExifData(data, e), DOWNLOAD_MEDIA: (data: any) => downloadMedia(data), MEDIA_BASE64: (data: any) => storeMedia(data), + PDF_TO_IMAGE: (data: any) => convertPDFToImages(data), ACCESS_CAMERA_PERMISSION: () => getPermission("camera"), ACCESS_MICROPHONE_PERMISSION: () => getPermission("microphone"), ACCESS_SCREEN_PERMISSION: () => getPermission("screen"), diff --git a/src/electron/utils/windowOptions.ts b/src/electron/utils/windowOptions.ts index 99a477e5..c2a41bc4 100644 --- a/src/electron/utils/windowOptions.ts +++ b/src/electron/utils/windowOptions.ts @@ -85,6 +85,7 @@ export const exportOptions: any = { // show: !isProd, show: false, modal: true, + frame: false, webPreferences: { preload: join(__dirname, "..", "preload"), webSecurity: isProd, @@ -96,13 +97,15 @@ export const exportOptions: any = { }, } -// export const captureOptions: any = { -// show: false, -// resizable: false, -// frame: false, -// skipTaskbar: true, -// webPreferences: { -// backgroundThrottling: false, -// autoplayPolicy: "no-user-gesture-required", -// }, -// } +export const captureOptions: any = { + show: false, + modal: true, + frame: false, + skipTaskbar: true, + resizable: false, + webPreferences: { + webSecurity: isProd, + backgroundThrottling: false, + offscreen: true, + }, +} diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index 3c402947..d77371bb 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -72,7 +72,13 @@ if (!$redoHistory.length) disabled = true }, addToProject: () => { - if (!$activeProject) disabled = true + if ($selected.id === "media" && $selected.data.length > 1) { + id = "addToShow" + menu = { label: "context.add_to_show", icon: "slide" } + if (!$activeShow || ($activeShow.type || "show") !== "show") disabled = true + } else { + if (!$activeProject) disabled = true + } }, play_no_filters: () => { let path = $selected.data[0]?.path || $selected.data[0]?.id diff --git a/src/frontend/components/context/contextMenus.ts b/src/frontend/components/context/contextMenus.ts index 40e65802..fc79e2e7 100644 --- a/src/frontend/components/context/contextMenus.ts +++ b/src/frontend/components/context/contextMenus.ts @@ -209,8 +209,9 @@ export const contextMenuLayouts: { [key: string]: string[] } = { project_media: ["play", "play_no_filters", "remove"], project_audio: ["remove"], project_player: ["remove"], - project_show: ["rename", "private", "duplicate", "remove"], + project_show: ["private", "duplicate", "remove", "SEPERATOR", "rename", "delete"], project_section: ["remove"], + project_pdf: ["remove"], // "rename", shows: ["newSlide", "selectAll"], // TIMER timer: ["play", "edit"], // , "reset" diff --git a/src/frontend/components/context/menuClick.ts b/src/frontend/components/context/menuClick.ts index ee16ab82..cf419488 100644 --- a/src/frontend/components/context/menuClick.ts +++ b/src/frontend/components/context/menuClick.ts @@ -63,7 +63,7 @@ import { exportProject } from "../export/project" import { clone, removeDuplicates } from "../helpers/array" import { copy, cut, deleteAction, duplicate, paste, selectAll } from "../helpers/clipboard" import { GetLayoutRef } from "../helpers/get" -import { history, redo, undo } from "../helpers/history" +import { history, HistoryPages, redo, undo } from "../helpers/history" import { getExtension, getFileName, getMediaStyle, getMediaType, removeExtension } from "../helpers/media" import { defaultOutput, getActiveOutputs, setOutput } from "../helpers/output" import { select } from "../helpers/select" @@ -157,6 +157,16 @@ const actions: any = { delete_slide: (obj: any) => actions.delete(obj), delete_group: (obj: any) => actions.delete(obj), delete: (obj: any) => { + // delete shows from project + if (obj.sel.id === "show") { + // wait to delete until after they are removed from project + setTimeout(() => { + let sel = { ...obj.sel, id: "show_drawer" } + selected.set(sel) + actions.delete({ ...obj, sel }) + }) + } + if (deleteAction(obj.sel)) return if (obj.contextElem?.classList.value.includes("#video_marker")) { @@ -235,6 +245,22 @@ const actions: any = { return a }) }, + addToShow: (obj: any) => { + let data: any[] = obj.sel.data + + let slides: any[] = data.map((a: any) => ({ id: a.id || uid(), group: removeExtension(a.name || a.path || ""), color: null, settings: {}, notes: "", items: [] })) + + let videoData: any = {} + // videos are probably not meant to be background if they are added in bulk + if (data.length > 1) videoData = { muted: false, loop: false } + + data = data.map((a) => ({ ...a, path: a.path || a.id, ...(a.type === "video" ? videoData : {}) })) + let activeLayout = get(showsCache)[get(activeShow)!.id]?.settings?.activeLayout + let layoutLength = _show("active").layouts([activeLayout]).get()[0]?.length + let newData = { index: layoutLength, data: slides, layout: { backgrounds: data } } + + history({ id: "SLIDES", newData, location: { page: get(activePage) as HistoryPages, show: get(activeShow)!, layout: activeLayout } }) + }, lock_show: (obj: any) => { showsCache.update((a: any) => { obj.sel.data.forEach((b: any) => { diff --git a/src/frontend/components/drawer/bible/scripture.ts b/src/frontend/components/drawer/bible/scripture.ts index c9baf474..b5626ffc 100644 --- a/src/frontend/components/drawer/bible/scripture.ts +++ b/src/frontend/components/drawer/bible/scripture.ts @@ -2,9 +2,8 @@ import { get } from "svelte/store" import { BIBLE } from "../../../../types/Channels" import type { StringObject } from "../../../../types/Main" import { bibleApiKey, dataPath, scriptureSettings, scriptures, scripturesCache, templates } from "../../../stores" -import { getAutoSize } from "../../edit/scripts/autoSize" -import { clone, removeDuplicates } from "../../helpers/array" import { getKey } from "../../../values/keys" +import { clone, removeDuplicates } from "../../helpers/array" const api = "https://api.scripture.api.bible/v1/bibles/" let tempCache: any = {} @@ -28,7 +27,8 @@ export async function fetchBible(load: string, active: string, ref: any = { vers if (tempCache[urls[load]]) return tempCache[urls[load]] return new Promise((resolve, reject) => { - if (!get(bibleApiKey) && !getKey("bibleapi")) return reject("No API key!") + const KEY = get(bibleApiKey) || getKey("bibleapi") + if (!KEY) return reject("No API key!") if (urls[load].includes("null")) return reject("Something went wrong!") fetchTimeout[active] = setTimeout(() => { @@ -36,7 +36,7 @@ export async function fetchBible(load: string, active: string, ref: any = { vers reject("Timed out!") }, 40000) - fetch(urls[load], { headers: { "api-key": get(bibleApiKey) || getKey("bibleapi") } }) + fetch(urls[load], { headers: { "api-key": KEY } }) .then((response) => response.json()) .then((data) => { tempCache[urls[load]] = data.data @@ -51,12 +51,13 @@ export async function fetchBible(load: string, active: string, ref: any = { vers } export function searchBibleAPI(active: string, searchQuery: string) { + const KEY = get(bibleApiKey) || getKey("bibleapi") let url = `${api}${active}/search?query=${searchQuery}` return new Promise((resolve, reject) => { - if (!get(bibleApiKey) && !getKey("bibleapi")) return reject("No API key!") + if (!KEY) return reject("No API key!") - fetch(url, { headers: { "api-key": get(bibleApiKey) || getKey("bibleapi") } }) + fetch(url, { headers: { "api-key": KEY } }) .then((response) => response.json()) .then((data) => { resolve(data.data) @@ -132,7 +133,7 @@ export function getSlides({ bibles, sorted }) { let alignStyle = currentTemplate?.lines?.[0]?.align || "text-align: left;" let textStyle = currentTemplate?.lines?.[1]?.text?.[0]?.style || currentTemplate?.lines?.[0]?.text?.[0]?.style || "font-size: 80px;" - let emptyItem = { lines: [{ text: [], align: alignStyle }], style: itemStyle, specialStyle: currentTemplate?.specialStyle || {} } // scrolling, bindings + let emptyItem = { lines: [{ text: [], align: alignStyle }], style: itemStyle, specialStyle: currentTemplate?.specialStyle || {}, actions: currentTemplate?.actions || {} } // scrolling, bindings let slideIndex: number = 0 slides[slideIndex].push(clone(emptyItem)) @@ -245,16 +246,16 @@ export function getSlides({ bibles, sorted }) { // auto size slides.forEach((slide, i) => { - slide.forEach((item, j) => { + slide.forEach((_item, j) => { if (!templateTextItems[j]?.auto || !slides[i][j].lines?.[0]?.text) return - let autoSize: number = getAutoSize(item) + // let autoSize: number = getAutoSize(item) // WIP historyActions - TEMPLATE... slides[i][j].auto = true if (templateTextItems[j]?.textFit) slides[i][j].textFit = templateTextItems[j]?.textFit slides[i][j].lines![0].text.forEach((_, k) => { if (slides[i][j].lines![0].text[k].customType === "disableTemplate") return - slides[i][j].lines![0].text[k].style += "font-size: " + autoSize + "px;" + // slides[i][j].lines![0].text[k].style += "font-size: " + autoSize + "px;" }) }) }) @@ -310,6 +311,7 @@ export function getSlides({ bibles, sorted }) { lines, style: metaTemplate?.style || "top: 910px;left: 50px;width: 1820px;height: 150px;opacity: 0.8;", specialStyle: metaTemplate?.specialStyle || {}, + actions: metaTemplate?.actions || {}, }) } } diff --git a/src/frontend/components/drawer/info/ScriptureInfo.svelte b/src/frontend/components/drawer/info/ScriptureInfo.svelte index eb80e3a9..7b3ef411 100644 --- a/src/frontend/components/drawer/info/ScriptureInfo.svelte +++ b/src/frontend/components/drawer/info/ScriptureInfo.svelte @@ -18,11 +18,11 @@ import CombinedInput from "../../inputs/CombinedInput.svelte" import Dropdown from "../../inputs/Dropdown.svelte" import NumberInput from "../../inputs/NumberInput.svelte" + import Media from "../../output/layers/Media.svelte" import Notes from "../../show/tools/Notes.svelte" import Textbox from "../../slide/Textbox.svelte" import Zoomed from "../../slide/Zoomed.svelte" import { getShortBibleName, getSlides, joinRange, textKeys } from "../bible/scripture" - import Media from "../../output/layers/Media.svelte" export let bibles: Bible[] $: sorted = bibles[0]?.activeVerses?.sort((a, b) => Number(a) - Number(b)) || [] diff --git a/src/frontend/components/drawer/live/Cam.svelte b/src/frontend/components/drawer/live/Cam.svelte index 0adca2f3..5b495121 100644 --- a/src/frontend/components/drawer/live/Cam.svelte +++ b/src/frontend/components/drawer/live/Cam.svelte @@ -13,6 +13,7 @@ } export let cam: Cam export let item: boolean = false + export let style: string = "" let loaded: boolean = false // $: active = $outBackground?.type === "camera" && $outBackground.id === cam.id @@ -72,7 +73,7 @@ {#if item} {#if !error} -
- - {(slide?.index || 0) + 1}/{length} - {#if linesIndex !== null && maxLines !== null} - ({linesIndex + 1}/{maxLines}) - {/if} - + {#if maxLines} + + {(slide?.index || 0) + 1}/{length} + {#if linesIndex !== null && maxLines !== null} + ({linesIndex + 1}/{maxLines}) + {/if} + + {/if} {/if} diff --git a/src/frontend/components/output/transitions/OutputTransition.svelte b/src/frontend/components/output/transitions/OutputTransition.svelte new file mode 100644 index 00000000..a0c7d0f5 --- /dev/null +++ b/src/frontend/components/output/transitions/OutputTransition.svelte @@ -0,0 +1,28 @@ + + + +{#if disableTransition && !inTransition && !outTransition} +{show.notes}
{/if} +{:else if type === "pdf"} +