Skip to content

Commit 738bfa2

Browse files
committed
* Added support for media urls from the domain "youtu.be". (so yt's share button/panel is compatible)
* Added support for timestamps included in youtube video urls. * Updated "cloud-project-init" readme-module to latest instructions. (sourced from the "to-react-19" branch)
1 parent a69a575 commit 738bfa2

3 files changed

Lines changed: 35 additions & 15 deletions

File tree

Packages/client/Source/UI/@Shared/Maps/Node/NodeBox/SubPanel.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {Button, Row, Text} from "react-vcomponents";
77
import {E, ModifyString} from "js-vextensions";
88
import {ButtonChain} from "Utils/ReactComponents/ButtonChain.js";
99
import {SourcesUI} from "./SourcesUI.js";
10+
import {ParseYoutubeVideoInfo} from "../../../../Database/Medias/MediaDetailsUI.js";
1011

1112
@Observer
1213
export class SubPanel extends BaseComponent<{node: NodeL2, toolbarShowing: boolean} & HTMLProps_Fixed<"div">, {}> {
@@ -109,15 +110,15 @@ export class SubPanel_Media extends BaseComponentPlus({} as {mediaAttachment: Me
109110
const media = GetMedia(mediaAttachment.id); // nn: db-ref, bail
110111
if (media == null) return null;
111112

112-
const videoID = ParseYoutubeVideoID(media.url);
113+
const ytInfo = ParseYoutubeVideoInfo(media.url);
113114
return (
114115
<div style={{position: "relative"}}>
115116
{/*<Row mt={5} style={{display: "flex", alignItems: "center"}}>*/}
116117
{media.type == MediaType.image &&
117118
<img src={media.url} style={{width: mediaAttachment.previewWidth != null ? `${mediaAttachment.previewWidth}%` : undefined, maxWidth: "100%"}}/>}
118119
{media.type == MediaType.video && <>
119-
{videoID == null && <div>Invalid YouTube video url: {media.url}</div>}
120-
{videoID != null && <YoutubePlayerUI videoID={videoID} /*startTime={0}*/ heightVSWidthPercent={.5625}
120+
{ytInfo.videoID == null && <div>Invalid YouTube video url: {media.url}</div>}
121+
{ytInfo.videoID != null && <YoutubePlayerUI videoID={ytInfo.videoID} startTime={ytInfo.startTime ?? 0} heightVSWidthPercent={.5625}
121122
onPlayerInitialized={player=>{
122123
player.GetPlayerUI().style.position = "absolute";
123124
}}/>}

Packages/client/Source/UI/Database/Medias/MediaDetailsUI.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,35 @@ import {RunCommand_AddMedia} from "Utils/DB/Command.js";
1313
import {PolicyPicker} from "../Policies/PolicyPicker.js";
1414
import {store} from "../../../Store/index.js";
1515

16+
export function ParseYoutubeVideoInfo(urlStr: string) {
17+
const videoID = ParseYoutubeVideoID_New(urlStr);
18+
const url = new URL(urlStr);
19+
const startTimeStr = url.searchParams.get("t") || url.searchParams.get("start");
20+
const startTime = startTimeStr ? parseInt(startTimeStr) : null; // in seconds
21+
return {videoID, startTime};
22+
}
23+
export function ParseYoutubeVideoID_New(urlStr: string) {
24+
const url = new URL(urlStr);
25+
const pathParts = url.pathname.split("/");
26+
if (url.hostname == "youtu.be") {
27+
if (pathParts.length > 1) return pathParts[1];
28+
}
29+
if (url.hostname == "www.youtube.com" || url.hostname == "youtube.com") {
30+
if (pathParts[1] == "watch" && url.searchParams.has("v")) return url.searchParams.get("v");
31+
if (pathParts[1] == "embed" && pathParts.length > 1) return pathParts[2];
32+
if (pathParts[1] == "shorts" && pathParts.length > 1) return pathParts[2];
33+
}
34+
return null;
35+
}
36+
1637
@Observer
1738
export class MediaDetailsUI extends DetailsUI_Base<Media, MediaDetailsUI> {
1839
scrollView: ScrollView;
1940
render() {
2041
const {baseData, style, onChange} = this.props;
2142
const {newData, dataError} = this.state;
2243
const {Change, creating, editing} = this.helpers;
23-
const videoID = ParseYoutubeVideoID(newData.url);
44+
const ytInfo = ParseYoutubeVideoInfo(newData.url);
2445
const accessPolicy = GetAccessPolicy(newData.accessPolicy);
2546
const enabled = creating || editing;
2647

@@ -48,7 +69,7 @@ export class MediaDetailsUI extends DetailsUI_Base<Media, MediaDetailsUI> {
4869
/*pattern={Media_urlPattern}*/ required
4970
enabled={enabled} style={{width: "100%"}}
5071
value={newData.url} onChange={val=>Change(newData.url = val)}/>
51-
{newData.type == MediaType.video && newData.url && videoID == null &&
72+
{newData.type == MediaType.video && newData.url && ytInfo.videoID == null &&
5273
<Span ml={5} style={{color: HSLA(30, 1, .6, 1), whiteSpace: "pre"}}>Only YouTube urls supported currently.</Span>}
5374
</RowLR>
5475
<RowLR mt={5} splitAt={splitAt} style={{width: "100%"}}>
@@ -63,10 +84,10 @@ export class MediaDetailsUI extends DetailsUI_Base<Media, MediaDetailsUI> {
6384
<img src={newData.url} style={{width: "100%"}}/>
6485
</Row>}
6586
{newData.type == MediaType.video &&
66-
// use wrapper div (with video-id as key), to ensure element cleanup when video-id changes
67-
<div key={videoID}>
68-
{!videoID && <div>Invalid YouTube video url: {newData.url}</div>}
69-
{videoID && <YoutubePlayerUI videoID={videoID} /*startTime={0}*/ heightVSWidthPercent={.5625}
87+
// use wrapper div (with media url as key), to ensure element cleanup when url changes
88+
<div key={newData.url}>
89+
{!ytInfo.videoID && <div>Invalid YouTube video url: {newData.url}</div>}
90+
{ytInfo.videoID && <YoutubePlayerUI videoID={ytInfo.videoID} startTime={ytInfo.startTime ?? 0} heightVSWidthPercent={.5625}
7091
onPlayerInitialized={player=>{
7192
player.GetPlayerUI().style.position = "absolute";
7293
}}/>}

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -661,26 +661,24 @@ Prerequisite steps: [setup-k8s](#setup-k8s)
661661

662662
Note: We use Google Cloud here, but others could be used.
663663

664-
NOTE: This section is outdated. (see "to-react-19" branch instead, for now)
665-
666664
* 1\) Ensure you have a user-account on Google Cloud Platform: https://cloud.google.com/
667665
* 2\) Install the Google Cloud SDK: https://cloud.google.com/sdk/docs/install
668666
* 3\) Authenticate the gcloud sdk/cli by providing it with the key-file for a service-account with access to the project you want to deploy to.
669667
* 3.1\) For the main Google Cloud project instance, you'll need to be supplied with the service-account key-file. (contact Venryx)
670668
* 3.2\) If you're creating your own fork/deployment, you'll need to:
671669
* 3.2.1\) Create a GCP project.
672-
* 3.2.2\) Enable the Container Registry API for your GCP project: https://console.cloud.google.com/apis/library/containerregistry.googleapis.com
670+
* 3.2.2\) Enable the Artifact Registry API for your GCP project: https://console.cloud.google.com/apis/library/artifactregistry.googleapis.com
673671
* 3.2.3\) Create a service-account: (it's possible a user account could also be granted access directly, but service-accounts are recommended anyway)
674672
* 3.2.3.1\) Go to: https://console.cloud.google.com/iam-admin/serviceaccounts/create
675-
* 3.2.3.2\) Choose a service-account name, and add the role "Container Registry Service Agent" and "Storage Admin" (*not* the weaker "Storage Object Admin").
673+
* 3.2.3.2\) Choose a service-account name (eg. "service-account-1"), and add the role "Artifact Registry Administrator" and "Storage Admin" (*not* the weaker "Storage Object Admin").
676674
* 3.2.3.3\) In the "Service account admins role" box, enter your email.
677675
* 3.2.3.4\) In the "Service account users role" box, enter your email, and the email of anyone else you want to have access.
678676
* 3.2.3.5\) Create a key for your service account, and download it as a JSON file (using the "Keys" tab): https://console.cloud.google.com/iam-admin/serviceaccounts
679677
* 3.3\) Move (or copy) the JSON file to the following path: `Others/Secrets/gcs-key.json` (if there is an empty file here already, it's fine to overwrite it, as this would just be the placeholder you created in the [setup-k8s](#setup-k8s) module)
680678
* 3.4\) Add the service-account to your gcloud-cli authentication, by passing it the service-account key-file (obtained from step 3.1 or 3.2.3.5): `gcloud auth activate-service-account FULL_SERVICE_ACCOUNT_NAME_AS_EMAIL --key-file=Others/Secrets/gcs-key.json`
681679
* 3.5\) Add the service-account to your Docker authentication, in a similar way:
682-
* 3.5.1\) If on Windows, run: `Get-Content Others/Secrets/gcs-key.json | & docker login -u _json_key --password-stdin https://gcr.io` (if you're using a specific subdomain of GCR, eg. us.gcr.io or eu.gcr.io, fix the domain part in this command)
683-
* 3.5.2\) If on Linux/Mac, run: `cat Others/Secrets/gcs-key.json | docker login -u _json_key --password-stdin https://gcr.io`
680+
* 3.5.1\) If on Windows, run: `Get-Content Others/Secrets/gcs-key.json | & docker login -u _json_key --password-stdin https://GEOGRAPHICAL_LOCATION_IN_GCP-docker.pkg.dev` (if you're using a specific subdomain of GCR, eg. us.gcr.io or eu.gcr.io, fix the domain part in this command)
681+
* 3.5.2\) If on Linux/Mac, run: `cat Others/Secrets/gcs-key.json | docker login -u _json_key --password-stdin https://GEOGRAPHICAL_LOCATION_IN_GCP-docker.pkg.dev`
684682

685683

686684
</details>

0 commit comments

Comments
 (0)