diff --git a/public/assets/boot/ctrl_boot_frontoffice.js b/public/assets/boot/ctrl_boot_frontoffice.js index 7c3784b1..d19390f8 100644 --- a/public/assets/boot/ctrl_boot_frontoffice.js +++ b/public/assets/boot/ctrl_boot_frontoffice.js @@ -82,17 +82,6 @@ async function setup_blue_death_screen() { }; } -// async function setup_chromecast() { -// if (!window.CONFIG["enable_chromecast"]) { -// return Promise.resolve(); -// } else if (!("chrome" in window)) { -// return Promise.resolve(); -// } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { -// return Promise.resolve(); -// } -// return window.Chromecast.init(); -// } - async function setup_history() { window.history.replaceState({}, ""); } diff --git a/public/assets/model/chromecast.js b/public/assets/model/chromecast.js index 89228173..7427b246 100644 --- a/public/assets/model/chromecast.js +++ b/public/assets/model/chromecast.js @@ -6,76 +6,60 @@ export async function init() { } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { return Promise.resolve(); } - // return Chromecast.init(); + return Chromecast.init(); } -// import { Session } from "./session"; -// import { currentShare, objectGet } from "../helpers/"; +export const Chromecast = new class ChromecastManager { + init() { + return new Promise((resolve) => { + const script = document.createElement("script"); + script.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; + script.onerror = () => resolve(null); + window["__onGCastApiAvailable"] = function(isAvailable) { + if (isAvailable) window.cast.framework.CastContext.getInstance().setOptions({ + receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, + autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, + }); + resolve(null); + }; + document.head.appendChild(script); + }); + } -// class ChromecastManager { -// init() { -// return new Promise((done) => { -// const script = document.createElement("script"); -// script.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; -// script.onerror = () => done() -// window["__onGCastApiAvailable"] = function(isAvailable) { -// if (isAvailable) cast.framework.CastContext.getInstance().setOptions({ -// receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, -// autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, -// }); -// done(); -// }; -// document.head.appendChild(script) -// }); -// } + createLink(apiPath) { + const target = new URL(location.origin + apiPath); + const shareID = new URLSearchParams(location.search).get("search"); + if (shareID) target.searchParams.append("share", shareID); + return target.toString(); + } -// origin() { -// return location.origin; -// }; + createRequest(mediaInfo) { + if (!window.BEARER_TOKEN) throw new Error("Invalid account"); + // TODO: it would be much much nicer to set the authorization from an HTTP header + // but this would require to create a custom web receiver app, setup accounts on + // google, etc,... Until that happens, we're setting the authorization within the + // url. Once we have that app, the authorisation will come from a customData field + // of a chrome.cast.media.LoadRequest + const target = new URL(mediaInfo.contentId); + target.searchParams.append("authorization", window.BEARER_TOKEN); + mediaInfo.contentId = target.toString(); + return new window.chrome.cast.media.LoadRequest(mediaInfo); + } -// createLink(apiPath) { -// const shareID = currentShare(); -// if (shareID) { -// const target = new URL(this.origin() + apiPath); -// target.searchParams.append("share", shareID); -// return target.toString(); -// } -// const target = new URL(this.origin() + apiPath) -// return target.toString(); -// } + context() { + if (!window.chrome?.cast?.isAvailable) return; + return window.cast.framework.CastContext.getInstance(); + } -// createRequest(mediaInfo) { -// let prior = Promise.resolve(); -// if (!Session.authorization) prior = Session.currentUser(); -// return prior.then(() => { -// if (!Session.authorization) throw new Error("Invalid account"); -// // TODO: it would be much much nicer to set the authorization from an HTTP header -// // but this would require to create a custom web receiver app, setup accounts on -// // google, etc,... Until that happens, we're setting the authorization within the -// // url. Once we have that app, the authorisation will come from a customData field -// // of a chrome.cast.media.LoadRequest -// const target = new URL(mediaInfo.contentId); -// target.searchParams.append("authorization", Session.authorization); -// mediaInfo.contentId = target.toString(); -// return new chrome.cast.media.LoadRequest(mediaInfo); -// }); -// } + session() { + const context = this.context(); + if (!context) return; + return context.getCurrentSession(); + } -// context() { -// if (!objectGet(window.chrome, ["cast", "isAvailable"])) { -// return; -// } -// return cast.framework.CastContext.getInstance(); -// } -// session() { -// const context = this.context(); -// if (!context) return; -// return context.getCurrentSession(); -// } -// media() { -// const session = this.session(); -// if (!session) return; -// return session.getMediaSession(); -// } -// } -// export const Chromecast = new ChromecastManager(); + media() { + const session = this.session(); + if (!session) return; + return session.getMediaSession(); + } +}(); diff --git a/public/assets/model/session.js b/public/assets/model/session.js index 6c4d9831..a7491500 100644 --- a/public/assets/model/session.js +++ b/public/assets/model/session.js @@ -8,7 +8,10 @@ export function getSession() { method: "GET", responseType: "json" }).pipe( - rxjs.map(({ responseJSON }) => responseJSON.result) + rxjs.map(({ responseJSON }) => responseJSON.result), + rxjs.tap(({ authorization }) => { + if (authorization) window.BEARER_TOKEN = authorization; + }), ); } diff --git a/public/assets/pages/connectpage/ctrl_form.js b/public/assets/pages/connectpage/ctrl_form.js index ea285170..fae0a0e8 100644 --- a/public/assets/pages/connectpage/ctrl_form.js +++ b/public/assets/pages/connectpage/ctrl_form.js @@ -198,7 +198,7 @@ export default async function(render) { return rxjs.of(null).pipe( rxjs.tap(() => toggleLoader(true)), rxjs.mergeMap(() => createSession(formData)), - rxjs.tap(({ responseJSON, responseHeaders }) => { + rxjs.tap(({ responseJSON }) => { let redirectURL = toHref("/files/"); const GET = getURLParams(); if (GET["next"]) redirectURL = GET["next"]; diff --git a/public/assets/pages/ctrl_logout.js b/public/assets/pages/ctrl_logout.js index 0339df4c..8cab6f13 100644 --- a/public/assets/pages/ctrl_logout.js +++ b/public/assets/pages/ctrl_logout.js @@ -13,7 +13,7 @@ export default function(render) { effect(deleteSession().pipe( rxjs.mergeMap(setup_config), rxjs.tap(() => { - window.CONFIG["logout"] ? location.href = window.CONFIG["logout"] : navigate(toHref("/")) + window.CONFIG["logout"] ? location.href = window.CONFIG["logout"] : navigate(toHref("/")); }), rxjs.catchError(ctrlError(render)), )); diff --git a/public/assets/pages/viewerpage/application_image.js b/public/assets/pages/viewerpage/application_image.js index f6a1411d..84419acd 100644 --- a/public/assets/pages/viewerpage/application_image.js +++ b/public/assets/pages/viewerpage/application_image.js @@ -1,11 +1,15 @@ -import { createElement, createRender } from "../../lib/skeleton/index.js"; +import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js"; +import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, onLoad, onClick } from "../../lib/rx.js"; import { animate } from "../../lib/animate.js"; +import { extname } from "../../lib/path.js"; import { loadCSS } from "../../helpers/loader.js"; import { qs } from "../../lib/dom.js"; import { createLoader } from "../../components/loader.js"; +import notification from "../../components/notification.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; +import { Chromecast } from "../../model/chromecast.js"; import { transition, getFilename, getDownloadUrl } from "./common.js"; @@ -44,6 +48,7 @@ export default function(render) { buttonDownload(getFilename(), getDownloadUrl()), buttonFullscreen(qs($page, ".component_image_container")), buttonInfo({ toggle: toggleInfo }), + buttonChromecast(getFilename(), getDownloadUrl()), ); effect(onLoad($photo).pipe( @@ -85,6 +90,13 @@ export default function(render) { componentPager(createRender(qs($page, ".component_pager"))); } +export function init() { + return Promise.all([ + loadCSS(import.meta.url, "./application_image.css"), + initPager(), initMetadata(), + ]); +} + function buttonInfo({ toggle }) { const $el = createElement(` @@ -98,9 +110,51 @@ function buttonInfo({ toggle }) { return $el; } -export function init() { - return Promise.all([ - loadCSS(import.meta.url, "./application_image.css"), - initPager(), initMetadata(), - ]); +function buttonChromecast(filename, downloadURL) { + const context = Chromecast.context(); + if (!context) return; + + const chromecastSetup = (event) => { + switch (event.sessionState) { + case window.cast.framework.SessionState.SESSION_STARTED: + chromecastLoader(); + break; + } + }; + const chromecastLoader = () => { + const session = Chromecast.session(); + if (!session) return; + + const link = Chromecast.createLink("/" + toHref(downloadURL)); + const media = new window.chrome.cast.media.MediaInfo( + link, + window.CONFIG.mime[extname(filename)], + ); + media.metadata = new window.chrome.cast.media.PhotoMediaMetadata(); + media.metadata.title = filename; + media.metadata.images = [ + new window.chrome.cast.Image(location.origin + "/" + toHref("/assets/icons/photo.png")), + ]; + try { + const req = Chromecast.createRequest(media); + session.loadMedia(req); + } catch (err) { + console.error(err); + notification.error(t("Cannot establish a connection")); + } + }; + + context.addEventListener( + window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED, + chromecastSetup, + ); + onDestroy(() => context.removeEventListener( + window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED, + chromecastSetup, + )); + + const media = Chromecast.media(); + if (media && media.media && media.media.mediaCategory === "IMAGE") chromecastLoader(); + + return document.createElement("google-cast-launcher"); } diff --git a/public/assets/pages/viewerpage/application_map.js b/public/assets/pages/viewerpage/application_map.js index d252416c..e10fdffd 100644 --- a/public/assets/pages/viewerpage/application_map.js +++ b/public/assets/pages/viewerpage/application_map.js @@ -30,7 +30,6 @@ export default async function(render) { const map = window.L.map("map"); - const fileview = [getFilename()]; for (let i=0; i { diff --git a/public/global.d.ts b/public/global.d.ts index c568ef0c..9fa71652 100644 --- a/public/global.d.ts +++ b/public/global.d.ts @@ -1,10 +1,12 @@ interface Window { - chrome: object; + chrome: any; + cast: any; overrides: { [key: string]: any; "xdg-open"?: (mime: string) => void; }; CONFIG: Config; + BEARER_TOKEN?: string; } interface Config {