diff --git a/public/lang/en.json b/public/lang/en.json index 16db7668..715ff5ca 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -38,7 +38,9 @@ "top": "Top", "right": "Right", "bottom": "Bottom", - "left": "Left" + "left": "Left", + "centered": "Centered", + "edge_blending_tip": "Blend together multiple outputs/projectors for a more seamless transition" }, "about": { "check_updates": "Look for updates", @@ -849,6 +851,7 @@ "outline": "Outline", "shadow": "Shadow", "shadow_inset": "Shadow inset", + "offset": "Offset", "offsetX": "Offset X", "offsetY": "Offset Y", "blur": "Blur", @@ -1067,6 +1070,7 @@ "manual_input_hint": "Can't find the display? Click here to manually change the position.", "manual_drag_hint": "You can also hold ctrl/cmd over an active output window to manually drag it around.", "allow_main_screen": "Allow custom output position & size", + "edge_blending": "Edge blending", "identify_screens": "Identify screens", "new_output": "New output", "normal": "Normal", diff --git a/src/frontend/App.svelte b/src/frontend/App.svelte index 674d89b5..d0240b2e 100644 --- a/src/frontend/App.svelte +++ b/src/frontend/App.svelte @@ -4,6 +4,7 @@ import ContextMenu from "./components/context/ContextMenu.svelte" import Pdf from "./components/export/Pdf.svelte" import Guide from "./components/guide/Guide.svelte" + import { getBlending } from "./components/helpers/output" import { startEventTimer, startTimer } from "./components/helpers/timerTick" import Loader from "./components/main/Loader.svelte" import MenuBar from "./components/main/MenuBar.svelte" @@ -12,7 +13,7 @@ import Toast from "./components/main/Toast.svelte" import QuickSearch from "./components/quicksearch/QuickSearch.svelte" import Center from "./components/system/Center.svelte" - import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay } from "./stores" + import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay, outputs } from "./stores" import { focusArea, logerror, startAutosave, toggleRemoteStream } from "./utils/common" import { keydown } from "./utils/shortcuts" import { startup } from "./utils/startup" @@ -37,6 +38,10 @@ // close youtube ad $: if ($closeAd) setTimeout(() => closeAd.set(false), 10) + + // edge blending + let blending = "" + $: if ($currentWindow === "output" && Object.values($outputs)[0]?.blending) blending = getBlending() @@ -49,7 +54,7 @@ {/if} -
+
{#if $currentWindow === "output"} diff --git a/src/frontend/components/helpers/output.ts b/src/frontend/components/helpers/output.ts index a5465372..e622e052 100644 --- a/src/frontend/components/helpers/output.ts +++ b/src/frontend/components/helpers/output.ts @@ -42,6 +42,7 @@ import { getExtension, getFileName, removeExtension } from "./media" import { replaceDynamicValues } from "./showActions" import { _show } from "./shows" import { newToast } from "../../utils/common" +import { getStyles } from "./style" export function displayOutputs(e: any = {}, auto: boolean = false) { // sort so display order can be changed! (needs app restart) @@ -65,7 +66,7 @@ export function setOutput(key: string, data: any, toggle: boolean = false, outpu let outs = outputId ? [outputId] : allOutputs let inputData = clone(data) - let firstOutputWithBackground = allOutputs.findIndex((id) => (get(styles)[get(outputs)[id]?.style || ""]?.layers || ["background"]).includes("background")) + let firstOutputWithBackground = allOutputs.findIndex((id) => !a[id]?.isKeyOutput && !a[id]?.stageOutput && (get(styles)[get(outputs)[id]?.style || ""]?.layers || ["background"]).includes("background")) firstOutputWithBackground = Math.max(0, firstOutputWithBackground) // reset slide cache (after update) @@ -570,7 +571,16 @@ export function mergeWithTemplate(slideItems: Item[], templateItems: Item[], add line.align = templateLine?.align || "" line.text?.forEach((text: any, k: number) => { let templateText = templateLine?.text[k] || templateLine?.text[0] - if (!text.customType?.includes("disableTemplate")) text.style = templateText?.style || "" + if (!text.customType?.includes("disableTemplate")) { + let style = templateText?.style || "" + + // add original text color + let textColor = getStyles(text.style)["color"] || "#FFFFFF" + // use template color if text is white (default) + if (textColor !== "#FFFFFF") style += `color: ${textColor};` + + text.style = style + } let firstChar = templateText?.value?.[0] || "" @@ -930,3 +940,15 @@ export function getSlideFilter(slideData: any) { return slideFilter } + +export function getBlending() { + let blending = Object.values(get(outputs))[0]?.blending + if (!blending) return "" + + if (!blending.left && !blending.right) return "" + + const opacity = (blending.opacity ?? 50) / 100 + const center = 50 + Number(blending.offset || 0) + if (blending.centered) return `-webkit-mask-image: linear-gradient(${blending.rotate ?? 90}deg, rgb(0, 0, 0) ${center - blending.left}%, rgba(0, 0, 0, ${opacity}) ${center}%, rgb(0, 0, 0) ${center + Number(blending.right)}%);` + return `-webkit-mask-image: linear-gradient(${blending.rotate ?? 90}deg, rgba(0, 0, 0, ${opacity}) 0%, rgb(0, 0, 0) ${blending.left}%, rgb(0, 0, 0) ${100 - blending.right}%, rgba(0, 0, 0, ${opacity}) 100%);` +} diff --git a/src/frontend/components/helpers/setShow.ts b/src/frontend/components/helpers/setShow.ts index 9e2d7234..9155e82e 100644 --- a/src/frontend/components/helpers/setShow.ts +++ b/src/frontend/components/helpers/setShow.ts @@ -6,6 +6,7 @@ import { getShowCacheId, updateCachedShow } from "./show" import { uid } from "uid" import { destroy } from "../../utils/request" import { fixShowIssues } from "../../converters/importHelpers" +import type { ShowObj } from "../../classes/Show" export function setShow(id: string, value: "delete" | Show): Show { let previousValue: Show @@ -27,6 +28,12 @@ export function setShow(id: string, value: "delete" | Show): Show { if (!value.slides) value.slides = {} if (!value.layouts) value.layouts = {} if (!value.media) value.media = {} + + // set metadata (CCLI) in quickAccess + if (value.meta.CCLI && !value.quickAccess?.metadata?.CCLI) { + if (!value.quickAccess?.metadata) value.quickAccess.metadata = {} + value.quickAccess.metadata.CCLI = value.meta.CCLI + } } } @@ -174,3 +181,13 @@ export function saveTextCache(id: string, show: Show) { tempCache = {} }, 1000) } + +export function setQuickAccessMetadata(show: ShowObj, key: string, value: string) { + if (!value) return show + + if (!show.quickAccess) show.quickAccess = {} + if (!show.quickAccess.metadata) show.quickAccess.metadata = {} + show.quickAccess.metadata[key] = value + + return show +} diff --git a/src/frontend/components/inputs/ProjectButton.svelte b/src/frontend/components/inputs/ProjectButton.svelte index a4f3d7a2..33bc0ace 100644 --- a/src/frontend/components/inputs/ProjectButton.svelte +++ b/src/frontend/components/inputs/ProjectButton.svelte @@ -1,6 +1,7 @@ -

- - - - -
- -
- {#if screens.length} -
- {#if !currentScreen.screen || !screens.find((a) => a.id.toString() === currentScreen.screen)} -
- -
- {/if} - - {#each screens as screen, i} -
{ - if (!currentScreen?.forcedResolution) changeOutputScreen({ detail: { id: screen.id, bounds: screen.bounds } }) - }} - > - {i + 1} -
- {/each} +{#if edgeBlending} + + +

+ + +

+ updateBlending(e.detail, "left")} /> +
+ +

+ updateBlending(e.detail, "right")} /> +
+ +

+ updateBlending(e.detail, "rotate")} /> +
+ +

+ updateBlending(e.detail, "opacity")} /> +
+ +

+
+ updateBlending(isChecked(e), "centered")} />
- {:else} - +
+ {#if blending.centered} + +

+ updateBlending(e.detail, "offset")} /> +
{/if} -
+{:else} +

+ + + + + + +
+ +
+ {#if screens.length} +
+ {#if !currentScreen.screen || !screens.find((a) => a.id.toString() === currentScreen.screen)} +
+ +
+ {/if} + + {#each screens as screen, i} +
{ + if (!currentScreen?.forcedResolution) changeOutputScreen({ detail: { id: screen.id, bounds: screen.bounds } }) + }} + > + {i + 1} +
+ {/each} +
+ {:else} + + {/if} +
+{/if}