From 03514c00c42108913c2e8b945180c70e5f131534 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Mon, 31 Jul 2023 16:44:33 +0100 Subject: [PATCH] [feature] Add val town embed (#1777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds val town to tldraw's collection of embeddable things. ### Change Type - [x] `minor` — New feature ### Test Plan 1. Add links to val town, e.g. https://www.val.town/v/steveruizok.mathFact ### Release Notes - (feature) val town --- assets/embed-icons/val_town.png | Bin 0 -> 540 bytes packages/assets/imports.js | 2 + packages/assets/selfHosted.js | 1 + packages/assets/types.d.ts | 1 + packages/assets/urls.js | 1 + packages/tlschema/api-report.md | 52 +++--- packages/tlschema/src/shapes/TLEmbedShape.ts | 165 +++++++++++-------- 7 files changed, 134 insertions(+), 88 deletions(-) create mode 100644 assets/embed-icons/val_town.png diff --git a/assets/embed-icons/val_town.png b/assets/embed-icons/val_town.png new file mode 100644 index 0000000000000000000000000000000000000000..90f71857635f222c4d4b88ec3807d0f92a119504 GIT binary patch literal 540 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8oCO|{#S9E$svykh8Km+7D9BhG z)n>}ue10NlJu{>g0YK_GCsI_5NCd3_nZ~l3v)~Xso zJ82fj5ccz*?T)u6A6)wR`fF3m(7bumrnMbj_|lo@%+{`_MKhz@cUtvLe&ap!{PUAC z>gCH>W+a?S+x#>6)Xmb{+ftV={aQ8GI(CkV`kJQWj}3LjxQ*Rdp08UL!5Xh8ez`qh zO8=b=oO*1l4nI6lx2wU`pyk-X(>;nM`|n@2PS~ld7?4_=CmTG^Z~3x*`HsU<@BBDD zOf`>YY3Vs?e-90fJ9;H-^VCm5lkMi8UpC+N=6@zpcY*4gzY~|t&zW$Dl_8U3{&ok= zC>{60i4NHw>%TqG=jY60{N*Hr7Jfi(iQk4r8eNyt>KGK5H?Djb5Gn(VOa@O^KbLh* G2~7alp6&bq literal 0 HcmV?d00001 diff --git a/packages/assets/imports.js b/packages/assets/imports.js index b7017d862..a7fe73fee 100644 --- a/packages/assets/imports.js +++ b/packages/assets/imports.js @@ -19,6 +19,7 @@ import embedIconsReplit from './embed-icons/replit.png' import embedIconsScratch from './embed-icons/scratch.png' import embedIconsSpotify from './embed-icons/spotify.png' import embedIconsTldraw from './embed-icons/tldraw.png' +import embedIconsValTown from './embed-icons/val_town.png' import embedIconsVimeo from './embed-icons/vimeo.png' import embedIconsYoutube from './embed-icons/youtube.png' import fontsMonospace from './fonts/IBMPlexMono-Medium.woff2' @@ -451,6 +452,7 @@ export function getAssetUrlsByImport(opts) { scratch: formatAssetUrl(embedIconsScratch, opts), spotify: formatAssetUrl(embedIconsSpotify, opts), tldraw: formatAssetUrl(embedIconsTldraw, opts), + val_town: formatAssetUrl(embedIconsValTown, opts), vimeo: formatAssetUrl(embedIconsVimeo, opts), youtube: formatAssetUrl(embedIconsYoutube, opts), }, diff --git a/packages/assets/selfHosted.js b/packages/assets/selfHosted.js index ec7dede4d..14517b581 100644 --- a/packages/assets/selfHosted.js +++ b/packages/assets/selfHosted.js @@ -236,6 +236,7 @@ export function getAssetUrls(opts) { scratch: formatAssetUrl('./embed-icons/scratch.png', opts), spotify: formatAssetUrl('./embed-icons/spotify.png', opts), tldraw: formatAssetUrl('./embed-icons/tldraw.png', opts), + val_town: formatAssetUrl('./embed-icons/val_town.png', opts), vimeo: formatAssetUrl('./embed-icons/vimeo.png', opts), youtube: formatAssetUrl('./embed-icons/youtube.png', opts), }, diff --git a/packages/assets/types.d.ts b/packages/assets/types.d.ts index 8b56c4165..124eb2505 100644 --- a/packages/assets/types.d.ts +++ b/packages/assets/types.d.ts @@ -226,6 +226,7 @@ export type AssetUrls = { scratch: string spotify: string tldraw: string + val_town: string vimeo: string youtube: string } diff --git a/packages/assets/urls.js b/packages/assets/urls.js index 26a3bbdfd..415b78e49 100644 --- a/packages/assets/urls.js +++ b/packages/assets/urls.js @@ -623,6 +623,7 @@ export function getAssetUrlsByMetaUrl(opts) { scratch: formatAssetUrl(new URL('./embed-icons/scratch.png', import.meta.url).href, opts), spotify: formatAssetUrl(new URL('./embed-icons/spotify.png', import.meta.url).href, opts), tldraw: formatAssetUrl(new URL('./embed-icons/tldraw.png', import.meta.url).href, opts), + val_town: formatAssetUrl(new URL('./embed-icons/val_town.png', import.meta.url).href, opts), vimeo: formatAssetUrl(new URL('./embed-icons/vimeo.png', import.meta.url).href, opts), youtube: formatAssetUrl(new URL('./embed-icons/youtube.png', import.meta.url).href, opts), }, diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md index dfab22c23..38ab72857 100644 --- a/packages/tlschema/api-report.md +++ b/packages/tlschema/api-report.md @@ -213,6 +213,38 @@ export const EMBED_DEFINITIONS: readonly [{ readonly canUnmount: true; readonly toEmbedUrl: (url: string) => string | undefined; readonly fromEmbedUrl: (url: string) => string | undefined; +}, { + readonly type: "figma"; + readonly title: "Figma"; + readonly hostnames: readonly ["figma.com"]; + readonly width: 720; + readonly height: 500; + readonly doesResize: true; + readonly canUnmount: true; + readonly toEmbedUrl: (url: string) => string | undefined; + readonly fromEmbedUrl: (url: string) => string | undefined; +}, { + readonly type: "google_maps"; + readonly title: "Google Maps"; + readonly hostnames: readonly ["google.*"]; + readonly width: 720; + readonly height: 500; + readonly doesResize: true; + readonly canUnmount: false; + readonly toEmbedUrl: (url: string) => string | undefined; + readonly fromEmbedUrl: (url: string) => string | undefined; +}, { + readonly type: "val_town"; + readonly title: "Val Town"; + readonly hostnames: readonly ["val.town"]; + readonly minWidth: 260; + readonly minHeight: 100; + readonly width: 720; + readonly height: 500; + readonly doesResize: true; + readonly canUnmount: false; + readonly toEmbedUrl: (url: string) => string | undefined; + readonly fromEmbedUrl: (url: string) => string | undefined; }, { readonly type: "codesandbox"; readonly title: "CodeSandbox"; @@ -261,26 +293,6 @@ export const EMBED_DEFINITIONS: readonly [{ readonly isAspectRatioLocked: true; readonly toEmbedUrl: (url: string) => string | undefined; readonly fromEmbedUrl: (url: string) => string | undefined; -}, { - readonly type: "figma"; - readonly title: "Figma"; - readonly hostnames: readonly ["figma.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; -}, { - readonly type: "google_maps"; - readonly title: "Google Maps"; - readonly hostnames: readonly ["google.*"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; }, { readonly type: "google_calendar"; readonly title: "Google Calendar"; diff --git a/packages/tlschema/src/shapes/TLEmbedShape.ts b/packages/tlschema/src/shapes/TLEmbedShape.ts index cf658711c..2ab98de3b 100644 --- a/packages/tlschema/src/shapes/TLEmbedShape.ts +++ b/packages/tlschema/src/shapes/TLEmbedShape.ts @@ -40,6 +40,103 @@ export const EMBED_DEFINITIONS = [ return }, }, + { + type: 'figma', + title: 'Figma', + hostnames: ['figma.com'], + width: 720, + height: 500, + doesResize: true, + canUnmount: true, + toEmbedUrl: (url) => { + if ( + !!url.match( + // eslint-disable-next-line no-useless-escape + /https:\/\/([\w\.-]+\.)?figma.com\/(file|proto)\/([0-9a-zA-Z]{22,128})(?:\/.*)?$/ + ) && + !url.includes('figma.com/embed') + ) { + return `https://www.figma.com/embed?embed_host=share&url=${url}` + } + return + }, + fromEmbedUrl: (url) => { + const urlObj = safeParseUrl(url) + if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) { + const outUrl = urlObj.searchParams.get('url') + if (outUrl) { + return outUrl + } + } + return + }, + }, + { + type: 'google_maps', + title: 'Google Maps', + hostnames: ['google.*'], + width: 720, + height: 500, + doesResize: true, + canUnmount: false, + toEmbedUrl: (url) => { + if (url.includes('/maps/')) { + const match = url.match(/@(.*),(.*),(.*)z/) + let result: string + if (match) { + const [, lat, lng, z] = match + const host = new URL(url).host.replace('www.', '') + result = `https://${host}/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}¢er=${lat},${lng}&zoom=${z}` + } else { + result = '' + } + + return result + } + return + }, + fromEmbedUrl: (url) => { + const urlObj = safeParseUrl(url) + if (!urlObj) return + + const matches = urlObj.pathname.match(/^\/maps\/embed\/v1\/view\/?$/) + if (matches && urlObj.searchParams.has('center') && urlObj.searchParams.get('zoom')) { + const zoom = urlObj.searchParams.get('zoom') + const [lat, lon] = urlObj.searchParams.get('center')!.split(',') + return `https://www.google.com/maps/@${lat},${lon},${zoom}z` + } + return + }, + }, + { + type: 'val_town', + title: 'Val Town', + hostnames: ['val.town'], + minWidth: 260, + minHeight: 100, + width: 720, + height: 500, + doesResize: true, + canUnmount: false, + toEmbedUrl: (url) => { + const urlObj = safeParseUrl(url) + // e.g. extract "steveruizok.mathFact" from https://www.val.town/v/steveruizok.mathFact + const matches = urlObj && urlObj.pathname.match(/\/v\/([^/]+)\/?/) + if (matches) { + return `https://www.val.town/embed/${matches[1]}` + } + return + }, + fromEmbedUrl: (url) => { + const urlObj = safeParseUrl(url) + // e.g. extract "steveruizok.mathFact" from https://www.val.town/v/steveruizok.mathFact + const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/) + if (matches) { + return `https://www.val.town/v/${matches[1]}` + } + return + }, + }, { type: 'codesandbox', title: 'CodeSandbox', @@ -166,74 +263,6 @@ export const EMBED_DEFINITIONS = [ return }, }, - { - type: 'figma', - title: 'Figma', - hostnames: ['figma.com'], - width: 720, - height: 500, - doesResize: true, - canUnmount: true, - toEmbedUrl: (url) => { - if ( - !!url.match( - // eslint-disable-next-line no-useless-escape - /https:\/\/([\w\.-]+\.)?figma.com\/(file|proto)\/([0-9a-zA-Z]{22,128})(?:\/.*)?$/ - ) && - !url.includes('figma.com/embed') - ) { - return `https://www.figma.com/embed?embed_host=share&url=${url}` - } - return - }, - fromEmbedUrl: (url) => { - const urlObj = safeParseUrl(url) - if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) { - const outUrl = urlObj.searchParams.get('url') - if (outUrl) { - return outUrl - } - } - return - }, - }, - { - type: 'google_maps', - title: 'Google Maps', - hostnames: ['google.*'], - width: 720, - height: 500, - doesResize: true, - canUnmount: false, - toEmbedUrl: (url) => { - if (url.includes('/maps/')) { - const match = url.match(/@(.*),(.*),(.*)z/) - let result: string - if (match) { - const [, lat, lng, z] = match - const host = new URL(url).host.replace('www.', '') - result = `https://${host}/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}¢er=${lat},${lng}&zoom=${z}` - } else { - result = '' - } - - return result - } - return - }, - fromEmbedUrl: (url) => { - const urlObj = safeParseUrl(url) - if (!urlObj) return - - const matches = urlObj.pathname.match(/^\/maps\/embed\/v1\/view\/?$/) - if (matches && urlObj.searchParams.has('center') && urlObj.searchParams.get('zoom')) { - const zoom = urlObj.searchParams.get('zoom') - const [lat, lon] = urlObj.searchParams.get('center')!.split(',') - return `https://www.google.com/maps/@${lat},${lon},${zoom}z` - } - return - }, - }, { type: 'google_calendar', title: 'Google Calendar',