Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/issue 45 custom youtube playlist rendering #114

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
*.log
*.DS_Store
.env
.greenwood/
.vscode/
node_modules/
public/
reports/
storybook-static/
storybook-static/
# Local Netlify folder
.netlify
7 changes: 6 additions & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@

[build.environment]
NODE_VERSION = "18.12.1"
TZ = "America/New_York"
TZ = "America/New_York"

[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
12 changes: 12 additions & 0 deletions netlify/functions/fragment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { handler as fragment } from '../../src/api/fragment';

export async function handler (event, context) {
const { rawUrl, headers } = event;
const request = new Request(rawUrl, { headers });
const response = await fragment(request);

return {
statusCode: response.status,
body: await response.text()
};
};
12 changes: 12 additions & 0 deletions netlify/functions/greeting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { handler as greeting } from '../../src/api/greeting.js';

export async function handler (event, context) {
const { rawUrl, headers } = event;
const request = new Request(rawUrl, { headers });
const response = await greeting(request);

return {
statusCode: response.status,
body: await response.text()
};
};
6 changes: 6 additions & 0 deletions netlify/functions/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
exports.handler = async function (event, context) {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello World" }),
};
};
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
"lint": "ls-lint && yarn lint:js && yarn lint:css",
"lint:js": "eslint \"*.{js,cjs,.mjs}\" \".storybook/*.{js,cjs}\" \"src/**/**/*.js\"",
"lint:css": "stylelint \"./src/**/*.js\", \"./src/**/*.css\"",
"clean": "rimraf public/ .greenwood/ ./storybook-static"
"clean": "rimraf public/ .greenwood/ ./storybook-static",
"netlify": "netlify dev"
},
"devDependencies": {
"@esm-bundle/chai": "^4.3.4",
"@greenwood/cli": "~0.28.0",
"@greenwood/plugin-postcss": "~0.28.0",
"@greenwood/cli": "~0.28.1",
"@greenwood/plugin-postcss": "~0.28.1",
"@ls-lint/ls-lint": "^1.10.0",
"@storybook/addon-actions": "^7.0.0-beta.61",
"@storybook/addon-essentials": "^7.0.0-beta.61",
Expand All @@ -46,6 +47,7 @@
"eslint-plugin-no-only-tests": "^3.1.0",
"http-server": "^14.1.1",
"lit": "^2.1.1",
"netlify-cli": "^13.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^3.0.2",
Expand Down
29 changes: 29 additions & 0 deletions src/api/fragment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { renderFromHTML } from 'wc-compiler';
import { getVideos } from '../services/youtube.js';

export async function handler(request) {
const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
const offset = params.has('offset') ? parseInt(params.get('offset'), 10) : null;
const headers = new Headers();
const videos = await getVideos(offset);
const { html } = await renderFromHTML(`
${
videos.map((item, idx) => {
return `
<tt-video-card
title="${offset + idx + 1}) ${item.title}"
thumbnail="${item.thumbnails.standard.url}"
></tt-video-card>
`;
}).join('')
}
`, [
new URL('../components/video-card/video-card.js', import.meta.url)
]);

headers.append('Content-Type', 'text/html');

return new Response(html, {
headers
});
}
12 changes: 12 additions & 0 deletions src/api/greeting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export async function handler(request) { // <-- new Request
const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
const name = params.has('name') ? params.get('name') : 'Greenwood';
const body = { message: `Hello ${name}!!!` };
const headers = new Headers();

headers.append('Content-Type', 'application/json');

return new Response(JSON.stringify(body), {
headers
});
}
22 changes: 22 additions & 0 deletions src/components/video-card/video-card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default class VideoCard extends HTMLElement {

selectVideo() {
alert(`selected video to play is => ${this.getAttribute('title')}!`);
}

connectedCallback() {
const thumbnail = this.getAttribute('thumbnail');
const title = this.getAttribute('title');

this.innerHTML = `
<div class="p-4 youtube-container">
<h1>${title}</h1>
<img src="${thumbnail}" loading="lazy">
<button onclick="this.parentNode.parentNode.selectVideo()">View Broadcast</button>
<hr/>
</div>
`;
}
}

customElements.define('tt-video-card', VideoCard);
32 changes: 18 additions & 14 deletions src/components/youtube-playlist/youtube-playlist.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import '../video-card/video-card.js';
import { getVideos } from '../../services/youtube.js';

export default class YouTubePlaylist extends HTMLElement {
connectedCallback() {
async connectedCallback() {
const offset = 0;
const videos = await getVideos(offset);

this.innerHTML = `
<h2
class="text-3xl text-center font-extrabold"
Expand All @@ -8,19 +14,17 @@ export default class YouTubePlaylist extends HTMLElement {
Past Episodes
</h2>

<div class="p-4 youtube-container">
<iframe
style="border-radius:12px"
src="https://www.youtube.com/embed?listType=playlist&list=PLrY8SmqJ5XZ_UvVurEvGg8i10g-cxMudZ"
width="100%"
height="500px"
title="Spotify Playlist"
frameBorder="0"
allowfullscreen=""
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy">
</iframe>
</div>
${
// https://developers-dot-devsite-v2-prod.appspot.com/youtube/v3/docs/videos
videos.map((item, idx) => {
return `
<tt-video-card
title="${offset + idx + 1}) ${item.title}"
thumbnail="${item.thumbnails.standard.url}"
></tt-video-card>
`;
}).join('')
}
`;
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/services/youtube.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
async function getVideos(offset = 0, limit = 5) {
// https://developers.google.com/youtube/v3/docs/playlistItems
const playlistItems = (await fetch(`https://youtube.googleapis.com/youtube/v3/playlistItems?key=${process.env.API_KEY_YOUTUBE}&playlistId=PLrY8SmqJ5XZ_UvVurEvGg8i10g-cxMudZ&maxResults=50&part=snippet`) // eslint-disable-line max-len
.then(resp => resp.json()))
.items.map(item => item.snippet)
.reverse()
.slice(offset, offset + limit);

// TODO handle array length overflow!!!!
return playlistItems;
}

export { getVideos };
43 changes: 43 additions & 0 deletions src/templates/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,45 @@
<script type="module" data-gwd-opt="static" src="../components/footer/footer.js"></script>
<script type="module" data-gwd-opt="static" src="../components/header/header.js"></script>
<script type="module" src="../components/nav/nav.js"></script>
<script type="module" src="../components/video-card/video-card.js"></script>
<!--
This is breaking SSR prerender build, due to
1) document.location -> should use globalThis + optional chaining
2) (native?) Node fetch required a full qualified URL
3) (native?) API routes not available during prerender, or something else?
```
TypeError [Error]: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at async file:///Users/analogstudios/Workspace/analogstudios/repos/www.tuesdaystunes.tv/.greenwood/826237276.js:4:20 {
cause: Error: not implemented... yet...
```

Not sure if / why this would be executing as a script block though when it's an HTML file?
---
<script type="module">
const params = new URLSearchParams(globalThis.document?.location?.search);
const qs = params.has('name') ? `?name=${params.get('name')}` : '';
const data = await fetch(new URL(`/api/greeting${qs}`, import.meta.url)).then(resp => resp.json());

document.getElementById('greeting').textContent = data.message;
</script>
-->

<script>
globalThis.addEventListener('DOMContentLoaded', () => {
const page = 5;
let offset = 0;

globalThis.document.getElementById('load-yt').addEventListener('click', async () => {
console.debug('load more fragments!');
offset = offset += page;
const html = await fetch(`/api/fragment?offset=${offset}`).then(resp => resp.text());

console.debug({ html });
document.getElementsByTagName('tt-youtube-playlist')[0].insertAdjacentHTML('beforeend', html);
});
});
</script>
</head>

<body>
Expand All @@ -28,12 +67,16 @@

<tt-nav></tt-nav>

<h1 id="greeting" style="text-align:center"></h1>

<div
class="bg-white rounded-lg drop-shadow-lg lg:p-8 m-auto mt-4 md:p-1 grid-cols-6"
>
<page-outlet></page-outlet>
</div>

<button id="load-yt" style="text-align:center">Load More Episodes...</button>

<tt-footer></tt-footer>
</div>
</body>
Expand Down
Loading