Skip to content

Commit

Permalink
✨ Get slide output thumbnail from API
Browse files Browse the repository at this point in the history
- Drop timer & variable on slide in edit
- Better file name number sorting
- Fixed Unsplash image credits remaining active sometimes
- Toggle clock seconds in stage display
- Option to not include media on project export
- Project export includes media data
- Fixed draw not aligned to mouse
- Option to not overwrite user config data if existing data when changing
  • Loading branch information
vassbo committed Dec 16, 2024
1 parent 66e00ce commit 4fa444c
Show file tree
Hide file tree
Showing 30 changed files with 304 additions and 75 deletions.
6 changes: 6 additions & 0 deletions public/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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!",
Expand Down
33 changes: 21 additions & 12 deletions src/electron/data/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
38 changes: 35 additions & 3 deletions src/electron/data/thumbnails.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
})
}
15 changes: 8 additions & 7 deletions src/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/electron/output/helpers/OutputLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class OutputLifecycle {
})
// window.setVisibleOnAllWorkspaces(true)

loadWindowContent(window, true)
loadWindowContent(window, "output")
this.setWindowListeners(window, { id, name })

// open devtools
Expand Down
21 changes: 19 additions & 2 deletions src/electron/utils/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -23,6 +23,7 @@ import {
bundleMediaFiles,
checkShowsFolder,
dataFolderNames,
doesPathExist,
getDataFolder,
getDocumentsFolder,
getFileInfo,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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),
Expand Down
28 changes: 17 additions & 11 deletions src/electron/utils/windowOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
1 change: 1 addition & 0 deletions src/frontend/classes/Show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
4 changes: 3 additions & 1 deletion src/frontend/components/actions/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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(),
}

Expand Down
6 changes: 4 additions & 2 deletions src/frontend/components/draw/Slide.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/components/drawer/media/MediaCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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("")
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/components/drawer/pages/Variables.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{#if sortedVariables.length}
<div class="variables">
{#each sortedVariables as variable}
<SelectElem id="variable" data={variable}>
<SelectElem id="variable" data={variable} draggable>
<div class="variable context #variable">
<span style="padding-left: 5px;">
<Icon id={variable.type} right />
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/edit/scripts/itemHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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") {
Expand Down
Loading

0 comments on commit 4fa444c

Please sign in to comment.