Josh Davenport-Smith

Josh Davenport-Smith

// Name: Preview CSS Color
// Description: Preview any CSS color accepted by background-color
// Author: Josh Davenport-Smith
// Twitter: @joshdprts
import "@johnlindquist/kit";
const color = await arg("Color");
await div(`
<div class="pt-2 h-full w-full">
<div class="checker h-full w-full">
<div style="background-color: ${color}" class="h-full w-full flex items-start">
<div class="bg-white">
<div style="background-color: ${color}" class="p-2">
<div style="color: ${color}; filter: invert(100%); text-shadow: 0 0 1px hsla(0, 0%, 100%, 0.2)" class="w-fit text-xs">${color}</div>
</div>
</div>
</div>
</div>
</div>
<style type="text/css">
.checker {
background-color: #fff;
background-size: 24px 24px;
background-position: 0 0, 12px 12px;
background-image: linear-gradient(45deg, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef), linear-gradient(45deg, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef);
}
</style>
`);

// Name: Bandcamp Album Player
// Description: Stream a bandcamp album
// Author: Josh Davenport-Smith
// Twitter: @jdprts
import "@johnlindquist/kit";
import { WidgetAPI } from "@johnlindquist/kit/types/pro";
const jsdom = await npm('jsdom');
const INITIAL_PLAYER_WIDTH = 500;
const MAX_PLAYER_WIDTH = 740;
const INITIAL_PLAYER_HEIGHT = 120;
const htmlDecode = (input: string) => {
const doc = new jsdom.JSDOM(`<!DOCTYPE html><div>${input}</div>`);
return doc.window.document.querySelector('div').textContent;
}
const getStyles = () => `
:root {
--dragbar-width: 30px;
}
iframe {
position: absolute;
left: var(--dragbar-width);
top: 0;
width: calc(100% - var(--dragbar-width));
height: 100%;
-webkit-app-region: no-drag;
}
#dragbar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: var(--dragbar-width);
cursor: grab;
background: #F1F1F1;
}
#dragbar:after {
content: '';
position: absolute;
inset: 6px;
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjRjFGMUYxIj48L3JlY3Q+CjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIGZpbGw9IiNjY2MiPjwvcmVjdD4KPC9zdmc+");
}
#dragbar:active {
cursor: grabbing;
}
`;
const getScripts = (albumId: number) => `
(() => {
const url = 'https://bandcamp.com/EmbeddedPlayer/album=${albumId}/size=large/bgcol=ffffff/linkcol=0687f5/tracklist=false/artwork=small/transparent=true/';
const iframe = document.createElement('iframe');
iframe.setAttribute('src', url);
iframe.setAttribute('seamless', true);
document.body.appendChild(iframe);
})();
`;
// Obtain the album (or track) URL
const albumUrl = await arg('What is the album URL?');
// Bail if the URL is invalid
if (!albumUrl.includes('bandcamp.com/album') && !albumUrl.includes('bandcamp.com/track')) {
throw new Error('Sorry, I don\'t recognise that URL');
}
// Give feedback that we're metadata for the album
div(md(`
Loading album...
---
Note: Some URLs will not work because of permissions set by the artist
`));
// Grab the bandcamp page for bc-page-properties which stores meta about the album
const pageContents = await fetch(albumUrl).then((res) => res.text());
const bandcampMetaParse = pageContents.match(/<meta name="bc-page-properties".*?content="([^"]+)">/);
await Promise.all([
// Give the user a chance to read the message (some URLs will not work and there's no obious way to tell in advance)
new Promise((resolve) => setTimeout(resolve, 2000)),
// Also wait for the data to load, so we continue when both promises resolve
pageContents,
])
if (!bandcampMetaParse) {
throw new Error('Sorry, I couldn\'t find the album ID');
}
// Parse the meta we found
const bandcampMeta: { item_type?: string, item_id?: number, tralbum_page_version?: number } | undefined = JSON.parse(htmlDecode(bandcampMetaParse[1]));
if (!bandcampMeta?.item_id) {
throw new Error('Sorry, I couldn\'t find the album ID');
}
// Create a widget with the player!
const w: WidgetAPI = await widget(
`
<div id="dragbar"></div>
<style type="text/css">${getStyles()}</style>
<script type="text/javascript">${getScripts(bandcampMeta.item_id)}</script>
`,
{
alwaysOnTop: true,
width: INITIAL_PLAYER_WIDTH,
maxWidth: MAX_PLAYER_WIDTH,
height: INITIAL_PLAYER_HEIGHT,
minHeight: INITIAL_PLAYER_HEIGHT,
maxHeight: INITIAL_PLAYER_HEIGHT,
roundedCorners: false,
thickFrame: false,
}
);
w.setSize(INITIAL_PLAYER_WIDTH, INITIAL_PLAYER_HEIGHT);

// Name: BBC Sounds Player
// Description: Stream a radio station from BBC Sounds
// Author: Josh Davenport-Smith
// Twitter: @jdprts
import "@johnlindquist/kit";
import { WidgetAPI } from "@johnlindquist/kit/types/pro";
const PLAYER_WIDTH = 180;
const PLAYER_HEIGHT = 195;
const getStyles = () => `
:root {
--player-width: ${PLAYER_WIDTH}px;
--player-height: ${PLAYER_HEIGHT}px;
--header-offset: 25px;
--footer-offset: 25px;
--scale: 2.2;
}
#player {
width: var(--player-width);
height: var(--player-height);
overflow: hidden;
position: relative;
}
iframe {
position: absolute;
left: 0;
width: calc(var(--player-width) * var(--scale));
height: calc((var(--player-height) * var(--scale)) + (var(--header-offset) * var(--scale)) + (var(--footer-offset) * var(--scale)));
top: calc(var(--header-offset) * -1);
transform: scale(calc(1 / var(--scale)));
transform-origin: 0 0;
}
`;
const getScripts = (station: string) => `
(() => {
const playerContainer = document.getElementById('player');
const url = 'https://www.bbc.co.uk/sounds/player/bbc_${station}';
const iframe = document.createElement('iframe');
iframe.setAttribute('src', url);
iframe.setAttribute('enablejsapi', true);
playerContainer.appendChild(iframe);
})();
`;
const station = await arg('Select station', [
{
name: '6 Music',
value: '6music',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_6music/colour_default.svg',
},
{
name: 'Radio 1',
value: 'radio_one',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_radio_one/colour_default.svg',
},
{
name: 'Radio 2',
value: 'radio_two',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_radio_two/colour_default.svg',
},
{
name: 'Radio 3',
value: 'radio_three',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_radio_three/colour_default.svg',
},
{
name: 'Radio 4',
value: 'radio_fourfm',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_radio_four/colour_default.svg',
},
{
name: 'Radio Five Live',
value: 'radio_five_live',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_radio_five_live/colour_default.svg',
},
{
name: 'World Service',
value: 'world_service',
img: 'https://sounds.files.bbci.co.uk/3.1.5/networks/bbc_world_service/colour_default.svg',
},
])
const w: WidgetAPI = await widget(
`
<div id="player"></div>
<style type="text/css">${getStyles()}</style>
<script type="text/javascript">${getScripts(station)}</script>
`,
{ alwaysOnTop: true }
);
w.setSize(PLAYER_WIDTH, PLAYER_HEIGHT);
w.onClick(event => event.targetId === "x" && w.close());
w.onResized(() => w.setSize(PLAYER_WIDTH, PLAYER_HEIGHT));