diff --git a/public/lang/en.json b/public/lang/en.json
index 715ff5ca..a7751e48 100644
--- a/public/lang/en.json
+++ b/public/lang/en.json
@@ -423,6 +423,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 +566,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 +749,7 @@
"all_shows": "All shows",
"all_projects": "All projects",
"project": "Project",
+ "include_media": "Include media files",
"preview": "Preview",
"title": "Title",
"metadata": "Metadata",
@@ -1120,6 +1123,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!",
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/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..68403d47 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()
}
}
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..335f8082 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,21 @@ 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)
+
+ toApp(MAIN, { channel: "ALERT", data: "actions.closing" })
+ // let user read message and action finish
+ setTimeout(exitApp, 2000)
+ },
LOCATE_MEDIA_FILE: (data: any) => locateMediaFile(data),
GET_SIMULAR: (data: any) => getSimularPaths(data),
BUNDLE_MEDIA_FILES: (data: any) => bundleMediaFiles(data),
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/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/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/media/MediaCard.svelte b/src/frontend/components/drawer/media/MediaCard.svelte
index ecbc576b..231c2eb9 100644
--- a/src/frontend/components/drawer/media/MediaCard.svelte
+++ b/src/frontend/components/drawer/media/MediaCard.svelte
@@ -85,6 +85,8 @@
if (credits.type === "unsplash" && credits.trigger_download) {
fetch(credits.trigger_download + "?client_id=" + getKey("unsplash"), { method: "GET" }).catch((err) => console.error("Could not trigger download:", err))
customMessageCredits.set(`Photo by ${credits.artist} on Unsplash`)
+ } else {
+ customMessageCredits.set("")
}
}
diff --git a/src/frontend/components/drawer/pages/Variables.svelte b/src/frontend/components/drawer/pages/Variables.svelte
index 7c124447..5fc7d66c 100644
--- a/src/frontend/components/drawer/pages/Variables.svelte
+++ b/src/frontend/components/drawer/pages/Variables.svelte
@@ -30,7 +30,7 @@
{#if sortedVariables.length}
{#each sortedVariables as variable}
-
+
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/export/project.ts b/src/frontend/components/export/project.ts
index 9ac0db2c..d3ae5a42 100644
--- a/src/frontend/components/export/project.ts
+++ b/src/frontend/components/export/project.ts
@@ -4,7 +4,7 @@
import { get } from "svelte/store"
import { EXPORT } from "../../../types/Channels"
import type { Project, ProjectShowRef } from "../../../types/Projects"
-import { dataPath, folders, overlays as overlayStores, showsCache } from "../../stores"
+import { dataPath, folders, media, overlays as overlayStores, showsCache, special } from "../../stores"
import { send } from "../../utils/request"
import { clone } from "../helpers/array"
import { loadShows } from "../helpers/setShow"
@@ -15,7 +15,7 @@ import type { SlideData } from "../../../types/Show"
export async function exportProject(project: Project) {
let shows: any = {}
let files: string[] = []
- let overlays: {[key: string]: any} = {}
+ let overlays: { [key: string]: any } = {}
// get project
project = clone(project)
@@ -30,8 +30,8 @@ export async function exportProject(project: Project) {
let refs = _show(showRef.id).layouts().ref()
let mediaIds: string[] = []
- refs.forEach(ref => {
- ref.forEach(({data}: {data: SlideData}) => {
+ refs.forEach((ref) => {
+ ref.forEach(({ data }: { data: SlideData }) => {
// background
let background = data.background
if (background) mediaIds.push(background)
@@ -43,12 +43,12 @@ export async function exportProject(project: Project) {
// overlays
let overlays = data.overlays || []
overlays.forEach(getOverlay)
- });
+ })
})
// get media file paths
let media = _show(showRef.id).get("media")
- mediaIds.forEach(id => {
+ mediaIds.forEach((id) => {
getFile(media[id].path || media[id].id)
})
@@ -69,7 +69,7 @@ export async function exportProject(project: Project) {
}
let projectItems = project.shows
-
+
// load shows
let showIds = projectItems.filter((a) => (a.type || "show") === "show").map((a) => a.id)
await loadShows(showIds)
@@ -79,14 +79,31 @@ export async function exportProject(project: Project) {
// remove duplicates
files = [...new Set(files)]
+ // set data
+ const projectData: any = { project, parentFolder, shows, overlays }
+ let includeMediaFiles = get(special).projectIncludeMedia ?? true
+ if (includeMediaFiles) {
+ projectData.files = files
+
+ let mediaData: any = {}
+ files.forEach((path) => {
+ if (!get(media)[path]) return
+
+ let data = clone(get(media)[path])
+ // delete data.info
+ mediaData[path] = data
+ })
+ if (Object.keys(mediaData).length) projectData.media = mediaData
+ }
+
// export to file
- send(EXPORT, ["GENERATE"], { type: "project", path: get(dataPath), name: formatToFileName(project.name), file: { project, parentFolder, shows, files, overlays } })
+ send(EXPORT, ["GENERATE"], { type: "project", path: get(dataPath), name: formatToFileName(project.name), file: projectData })
function getItem(showRef: ProjectShowRef) {
let type = showRef.type || "show"
if (!getProjectItems[type]) {
- console.log("Missing project type:", type);
+ console.log("Missing project type:", type)
return
}
@@ -102,7 +119,7 @@ export async function exportProject(project: Project) {
overlays[id] = clone(get(overlayStores)[id])
}
-
+
// store as base64 ?
// let base64: any = await toDataURL(showRef.id)
// media[showRef.id] = base64
diff --git a/src/frontend/components/helpers/array.ts b/src/frontend/components/helpers/array.ts
index d25d2a0e..dc6c8391 100644
--- a/src/frontend/components/helpers/array.ts
+++ b/src/frontend/components/helpers/array.ts
@@ -78,7 +78,7 @@ export function sortObjectNumbers(object: {}[], key: string, reverse: boolean =
}
// sort any object.name by numbers in the front of the string
-export function sortByNameAndNumber(array: any[]) {
+export function sortByNameAndNumber(array: any[]) {
return array.sort((a, b) => {
let aName = ((a.quickAccess?.number || "") + " " + a.name || "").trim()
let bName = ((b.quickAccess?.number || "") + " " + b.name || "").trim()
@@ -110,7 +110,7 @@ export function sortByNameAndNumber(array: any[]) {
export function sortFilenames(filenames) {
return filenames.sort(({ name: a }, { name: b }) => {
// extract name, number, and extension
- const regex = /^(.*?)(?:_(\d+))?(\.\w+)?$/
+ const regex = /^(.*?)(\d+)?(\.\w+)?$/
// extract parts
const [_, nameA, numA, extA] = a.match(regex) || [a, a, null, null]
diff --git a/src/frontend/components/helpers/drop.ts b/src/frontend/components/helpers/drop.ts
index 4aa8b53d..109040d9 100644
--- a/src/frontend/components/helpers/drop.ts
+++ b/src/frontend/components/helpers/drop.ts
@@ -14,7 +14,7 @@ const areas: { [key in DropAreas | string]: string[] } = {
project: ["show_drawer", "media", "audio", "overlay", "player", "scripture"],
overlays: ["slide"],
templates: ["slide"],
- edit: ["media"],
+ edit: ["media", "global_timer", "variable"],
// media_drawer: ["file"],
}
const areaChildren: { [key in DropAreas | string]: string[] } = {
diff --git a/src/frontend/components/helpers/dropActions.ts b/src/frontend/components/helpers/dropActions.ts
index 3a7fe840..247d134b 100644
--- a/src/frontend/components/helpers/dropActions.ts
+++ b/src/frontend/components/helpers/dropActions.ts
@@ -269,9 +269,17 @@ export const dropActions: any = {
},
overlays: ({ drag, drop }: any) => dropActions.templates({ drag, drop }),
edit: ({ drag }: any) => {
- if (drag.id !== "media" && drag.id !== "files") return
-
- drag.data.forEach((file: any) => addItem("media", null, { src: file.path || window.api.showFilePath(file) }))
+ if (drag.id === "media" || drag.id === "files") {
+ drag.data.forEach((file: any) => addItem("media", null, { src: file.path || window.api.showFilePath(file) }))
+ } else if (drag.id === "global_timer") {
+ drag.data.forEach((a: any) => addItem("timer", null, { timer: { id: a.id } }))
+ } else if (drag.id === "variable") {
+ drag.data.forEach((a: any) => {
+ // showActions.ts getNameId()
+ let name = a.name?.toLowerCase().trim().replaceAll(" ", "_") || ""
+ addItem("text", null, {}, `{variable_${name}}`)
+ })
+ }
},
audio_playlist: ({ drag, drop }: any, h: any) => {
h.id = "UPDATE"
diff --git a/src/frontend/components/helpers/media.ts b/src/frontend/components/helpers/media.ts
index 6994bd50..cf06de97 100644
--- a/src/frontend/components/helpers/media.ts
+++ b/src/frontend/components/helpers/media.ts
@@ -6,11 +6,13 @@ import { MAIN } from "../../../types/Channels"
import type { MediaStyle } from "../../../types/Main"
import type { Styles } from "../../../types/Settings"
import type { ShowType } from "../../../types/Show"
-import { loadedMediaThumbnails, media, tempPath } from "../../stores"
+import { loadedMediaThumbnails, media, outputs, tempPath } from "../../stores"
import { newToast, wait, waitUntilValueIsDefined } from "../../utils/common"
import { awaitRequest, send } from "../../utils/request"
-import type { API_media } from "../actions/api"
import { audioExtensions, imageExtensions, mediaExtensions, presentationExtensions, videoExtensions } from "../../values/extensions"
+import type { API_media, API_slide_thumbnail } from "../actions/api"
+import { getActiveOutputs, getResolution } from "./output"
+import { clone } from "./array"
export function getExtension(path: string): string {
if (!path) return ""
@@ -61,7 +63,7 @@ export function joinPath(path: string[]): string {
// fix for media files with special characters in file name not playing
export function encodeFilePath(path: string): string {
if (!path) return ""
-
+
// already encoded
if (path.match(/%\d+/g) || path.includes("http") || path.includes("data:")) return path
@@ -90,6 +92,27 @@ export async function getThumbnail(data: API_media) {
return await toDataURL(path)
}
+export async function getSlideThumbnail(data: API_slide_thumbnail) {
+ let outputId = getActiveOutputs(get(outputs), false, true, true)[0]
+ let outSlide = get(outputs)[outputId]?.out?.slide
+
+ if (!data.showId) data.showId = outSlide?.id
+ if (!data.layoutId) data.layoutId = outSlide?.layout
+ if (data.index === undefined) data.index = outSlide?.index
+
+ if (!data?.showId) return
+
+ let output = clone(get(outputs)[outputId])
+ if (!output.out) output.out = {}
+ output.out.slide = { id: data.showId, layout: data.layoutId, index: data.index }
+
+ let resolution = getResolution()
+ resolution = { width: resolution.width * 0.5, height: resolution.height * 0.5 }
+
+ const thumbnail = await awaitRequest(MAIN, "CAPTURE_SLIDE", { output: { [outputId]: output }, resolution })
+ return thumbnail.base64
+}
+
// convert to base64
async function toDataURL(url: string): Promise {
return new Promise((resolve: any) => {
diff --git a/src/frontend/components/main/popups/UserDataOverwrite.svelte b/src/frontend/components/main/popups/UserDataOverwrite.svelte
new file mode 100644
index 00000000..a3e82e83
--- /dev/null
+++ b/src/frontend/components/main/popups/UserDataOverwrite.svelte
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/frontend/components/main/popups/export/Export.svelte b/src/frontend/components/main/popups/export/Export.svelte
index 0eeeac5c..61ea1785 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, projects, showsCache, showsPath, special } from "../../../../stores"
import { send } from "../../../../utils/request"
import { exportProject } from "../../../export/project"
import { clone } from "../../../helpers/array"
@@ -10,6 +10,7 @@
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"
@@ -81,6 +82,14 @@
}
let pdfOptions: any = {}
+
+ function setSpecial(e: any, key: string) {
+ let value = e?.target?.checked
+ special.update((a) => {
+ a[key] = value
+ return a
+ })
+ }
@@ -98,6 +107,17 @@
{/if}
+{#if format.id === "project"}
+
+
+
+ setSpecial(e, "projectIncludeMedia")} />
+
+
+
+
+{/if}
+
{#if loading}
diff --git a/src/frontend/components/output/Output.svelte b/src/frontend/components/output/Output.svelte
index af229a7d..110b9f4f 100644
--- a/src/frontend/components/output/Output.svelte
+++ b/src/frontend/components/output/Output.svelte
@@ -296,7 +296,7 @@
{/if}
- {#if currentOutput.active || mirror}
+ {#if currentOutput.active || (mirror && !preview)}
{/if}
diff --git a/src/frontend/components/settings/tabs/Other.svelte b/src/frontend/components/settings/tabs/Other.svelte
index 48f6c9c4..50f6b2e2 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)
}
}
diff --git a/src/frontend/components/slide/Layouts.svelte b/src/frontend/components/slide/Layouts.svelte
index 3947693e..70adb40e 100644
--- a/src/frontend/components/slide/Layouts.svelte
+++ b/src/frontend/components/slide/Layouts.svelte
@@ -4,6 +4,7 @@
import { activePopup, activeProject, activeShow, alertMessage, dictionary, labelsDisabled, notFound, openToolsTab, projects, showsCache, slidesOptions } from "../../stores"
import Icon from "../helpers/Icon.svelte"
import T from "../helpers/T.svelte"
+ import { keysToID, sortByName } from "../helpers/array"
import { duplicate } from "../helpers/clipboard"
import { history } from "../helpers/history"
import { _show } from "../helpers/shows"
@@ -13,7 +14,6 @@
import Center from "../system/Center.svelte"
import SelectElem from "../system/SelectElem.svelte"
import Reference from "./Reference.svelte"
- import { keysToID, sortByName } from "../helpers/array"
$: showId = $activeShow?.id || ""
$: currentShow = $showsCache[showId] || {}
diff --git a/src/frontend/components/stage/Stagebox.svelte b/src/frontend/components/stage/Stagebox.svelte
index 476371d7..a079792f 100644
--- a/src/frontend/components/stage/Stagebox.svelte
+++ b/src/frontend/components/stage/Stagebox.svelte
@@ -204,7 +204,7 @@
{/key}
{:else if id.includes("clock")}
-
+
{:else if id.includes("video")}
{:else if id.includes("first_active_timer")}
diff --git a/src/frontend/components/stage/tools/BoxStyle.svelte b/src/frontend/components/stage/tools/BoxStyle.svelte
index c16bd2e6..72082fe2 100644
--- a/src/frontend/components/stage/tools/BoxStyle.svelte
+++ b/src/frontend/components/stage/tools/BoxStyle.svelte
@@ -31,7 +31,8 @@
delete newEdits.align
delete newEdits.chords
edits = { default: trackerEdits, font: edits.default, ...newEdits }
- } else if (items[0].includes("output")) edits = {}
+ } else if (items[0].includes("clock")) edits.default.push({ name: "clock.seconds", id: "clock.seconds", input: "checkbox", value: true })
+ else if (items[0].includes("output")) edits = {}
}
let data: { [key: string]: any } = {}
diff --git a/src/frontend/converters/project.ts b/src/frontend/converters/project.ts
index 35f04ed1..5cc16f40 100644
--- a/src/frontend/converters/project.ts
+++ b/src/frontend/converters/project.ts
@@ -3,18 +3,18 @@ import type { ShowType } from "../../types/Show"
import { history } from "../components/helpers/history"
import { getExtension, getFileName, getMediaType, removeExtension } from "../components/helpers/media"
import { checkName } from "../components/helpers/show"
-import { activeProject, activeShow, folders, projects, overlays as overlayStores, alertMessage, activePopup } from "../stores"
+import { activeProject, activeShow, folders, projects, overlays as overlayStores, media as mediaStores, alertMessage, activePopup } from "../stores"
export function importProject(files: any) {
files.forEach(({ content }: any) => {
- let { project, parentFolder, shows, overlays } = JSON.parse(content)
+ let { project, parentFolder, shows, overlays, media } = JSON.parse(content)
// find any parent folder with the same name as previous parent, or place at root
if (parentFolder) project.parent = Object.entries(get(folders)).find(([_id, folder]: any) => folder.name === parentFolder)?.[0] || "/"
// add overlays
if (overlays) {
- overlayStores.update(a => {
+ overlayStores.update((a) => {
Object.entries(overlays).forEach(([id, overlay]: any) => {
// create new or replace existing
a[id] = overlay
@@ -23,6 +23,16 @@ export function importProject(files: any) {
})
}
+ // get media data
+ if (media) {
+ mediaStores.update((a) => {
+ Object.entries(media).forEach(([path, data]: any) => {
+ a[path] = { ...(a[path] || {}), ...data }
+ })
+ return a
+ })
+ }
+
// create shows
let newShows: any[] = []
Object.entries(shows).forEach(([id, show]: any) => {
diff --git a/src/frontend/utils/listeners.ts b/src/frontend/utils/listeners.ts
index 11de9fd6..47a97418 100644
--- a/src/frontend/utils/listeners.ts
+++ b/src/frontend/utils/listeners.ts
@@ -161,12 +161,13 @@ export function storeSubscriber() {
})
draw.subscribe((data) => {
- let activeOutputs = getActiveOutputs()
+ let activeOutputs = getActiveOutputs(get(outputs), true, true, true)
activeOutputs.forEach((id) => {
send(OUTPUT, ["DRAW"], { id, data })
})
})
drawTool.subscribe((data) => {
+ // WIP changing tool while output is not active, will not update tool in output if set to active before changing tool again
let activeOutputs = getActiveOutputs()
activeOutputs.forEach((id) => {
send(OUTPUT, ["DRAW_TOOL"], { id, data })
diff --git a/src/frontend/utils/popup.ts b/src/frontend/utils/popup.ts
index d29c2ce1..34200420 100644
--- a/src/frontend/utils/popup.ts
+++ b/src/frontend/utils/popup.ts
@@ -47,6 +47,7 @@ import Trigger from "../components/main/popups/Trigger.svelte"
import Unsaved from "../components/main/popups/Unsaved.svelte"
import Variable from "../components/main/popups/Variable.svelte"
import { activePopup, popupData } from "../stores"
+import UserDataOverwrite from "../components/main/popups/UserDataOverwrite.svelte"
export const popups: { [key in Popups]: ComponentType } = {
initialize: Initialize,
@@ -91,6 +92,7 @@ export const popups: { [key in Popups]: ComponentType } = {
history: History,
action: Action,
category_action: CategoryAction,
+ user_data_overwrite: UserDataOverwrite,
connect: Connect,
cloud_update: CloudUpdate,
cloud_method: CloudMethod,
diff --git a/src/server/stage/components/Stagebox.svelte b/src/server/stage/components/Stagebox.svelte
index 46ff1c20..54aa0a62 100644
--- a/src/server/stage/components/Stagebox.svelte
+++ b/src/server/stage/components/Stagebox.svelte
@@ -140,7 +140,7 @@
{:else if id.includes("clock")}
-
+
{:else if id.includes("video")}
{:else if id.includes("first_active_timer")}
diff --git a/src/types/Main.ts b/src/types/Main.ts
index 0519433a..c5e9adcc 100644
--- a/src/types/Main.ts
+++ b/src/types/Main.ts
@@ -153,6 +153,7 @@ export type Popups =
| "history"
| "action"
| "category_action"
+ | "user_data_overwrite"
| "connect"
| "cloud_update"
| "cloud_method"