Merge branch 'feat/eleventy' into feat/nuxt-incremental

# Conflicts:
#	package-lock.json
This commit is contained in:
Sam Carlton 2021-02-23 19:24:33 -06:00
commit b3a3d893fd
28 changed files with 8260 additions and 322 deletions

107
.eleventy.js Normal file
View file

@ -0,0 +1,107 @@
import fs from 'fs'
import replace_css_url from 'replace-css-url'
import dotenv from 'dotenv'
import { InlineCodeManager } from '@11ty/eleventy-assets'
import nuxtConfig from './nuxt.config'
// Setup dotenv
dotenv.config()
function getAssetFilePath(componentName) {
return `./${componentName}`
}
module.exports = function ( eleventyConfig ) {
// console.log('eleventyConfig', eleventyConfig)
// Global Nuxt data
eleventyConfig.addJavaScriptFunction('getNuxt', function () {
return nuxtConfig
})
// eleventyConfig.addGlobalData('nuxt', () => nuxtConfig)
const cssManager = new InlineCodeManager()
const jsManager = new InlineCodeManager()
eleventyConfig.addJavaScriptFunction('usingComponent', function ( componentName ) {
// console.log('Getting component', componentName)
if ( componentName.includes('.js') ) {
// If a never before seen component, add the JS code
if(!jsManager.hasComponentCode(componentName)) {
const fileContents = fs.readFileSync(getAssetFilePath(componentName), { encoding: "UTF-8" })
// console.log('Got component', componentName, componentCss)
jsManager.addComponentCode(componentName, fileContents)
}
// Log usage for this url
// this.page.url is supported on Eleventy 0.11.0 and newer
jsManager.addComponentForUrl(componentName, this.page.url)
} else if ( componentName.includes('.css') ) {
// If a never before seen component, add the CSS code
if(!cssManager.hasComponentCode(componentName)) {
const fileContents = fs.readFileSync(getAssetFilePath(componentName), { encoding: "UTF-8" })
// Replace urls in css with relative urls for dist folder
const parsedCss = replace_css_url(
fileContents,
function( path ) {
const fileName = path.split('/').pop().split('#')[0].split('?')[0]
return '/fonts/' + fileName
}
)
cssManager.addComponentCode(componentName, parsedCss)
}
// Log usage for this url
// this.page.url is supported on Eleventy 0.11.0 and newer
cssManager.addComponentForUrl(componentName, this.page.url)
}
return
})
// Copy Inter font files
eleventyConfig.addPassthroughCopy({
"node_modules/@fontsource/inter/**/*.woff2": "fonts"
})
// This needs to be called in a Layout template.
eleventyConfig.addJavaScriptFunction('getJs', function ( url = this.page.url ) {
// console.log( 'jsManager.getCodeForUrl(url)', url )
return jsManager.getCodeForUrl(url)
})
// This needs to be called in a Layout template.
eleventyConfig.addJavaScriptFunction('getCss', function ( url = this.page.url ) {
// console.log( 'jsManager.getCodeForUrl(url)', url )
return cssManager.getCodeForUrl(url)
})
eleventyConfig.addJavaScriptFunction('boundComponent', function ( Component ) {
return Component.bind(this)
})
return {
dir: {
input: 'pages-eleventy',
output: 'dist',
jsDataFileSuffix: '.json',
// Relative to input directory.
data: '../static',
layouts: '../layouts-eleventy'
}
}
}

5
.gitignore vendored
View file

@ -84,8 +84,7 @@ dist
# Other # Other
/static/app-list.json /static/app-list.json
/README-temp.md /README-temp.md
/static/game-list.json /static/**/*.json
/static/homebrew-list.json
/static/video-list.json
/commits-data.json /commits-data.json
.DS_Store .DS_Store
/static/tailwind.css

View file

@ -46,6 +46,31 @@
*/ */
@tailwind utilities; @tailwind utilities;
html {
font-family: 'Inter', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 20px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
background: #191a1d;
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
}
/** /**
* Here you would add any custom utilities you need that don't come out of the * Here you would add any custom utilities you need that don't come out of the
* box with Tailwind. * box with Tailwind.

282
build-lists.js Normal file
View file

@ -0,0 +1,282 @@
import { promises as fs } from 'fs'
import dotenv from 'dotenv'
import buildAppList from './helpers/build-app-list.js'
import buildGamesList from './helpers/build-game-list.js'
import buildHomebrewList from './helpers/build-homebrew-list.js'
import buildVideoList from './helpers/build-video-list.js'
import { buildVideoPayload, buildAppBenchmarkPayload } from './helpers/build-payload.js'
import { categories, getAppCategory } from './helpers/categories.js'
import { getAppEndpoint, getVideoEndpoint } from './helpers/app-derived.js'
import { makeSearchableList } from './helpers/searchable-list.js'
// Setup dotenv
dotenv.config()
class BuildLists {
constructor () {
// Where our lists are stored
this.lists = {}
// Where Nuxt Routes and Payloads get stored
this.nuxtEndpointsSet = new Set()
// Where Eleventy Enpoints get stored
this.eleventyEndpointsSet = new Set()
this.allVideoAppsList = new Set()
}
listsOptions = [
// Mixed sources will(theoretically) go here
// then be read by follow main build methods
// Main build methods
{
name: 'app',
path: '/static/app-list.json',
buildMethod: buildAppList,
},
{
name: 'game',
path: '/static/game-list.json',
buildMethod: buildGamesList,
},
{
name: 'homebrew',
path: '/static/homebrew-list.json',
buildMethod: buildHomebrewList,
},
// Secondary Derivative built lists
// Always goes after initial lists
// since it depend on them
{
name: 'video',
path: '/static/video-list.json',
buildMethod: async () => {
// console.log('this.getAllVideoAppsList()', this.getAllVideoAppsList())
return await buildVideoList( this.getAllVideoAppsList() )
// const videoList = await buildVideoList( this.getAllVideoAppsList() )
// const extraVideos = []
// const multiplier = 12
// for (let i = 0; i < multiplier; i++) {
// videoList.forEach( video => {
// extraVideos.push({
// ...video,
// slug: video.slug + '-' + i,
// })
// })
// }
// return new Set([
// ...videoList,
// ...extraVideos
// ].slice(0, 10 * 1000))
},
beforeSave: videoListSet => {
this.allVideoAppsList = this.getAllVideoAppsList()
return Array.from(videoListSet).map( video => {
return {
...video,
payload: buildVideoPayload( video, this.allVideoAppsList, this.lists.video )
}
})
}
}
]
getAllVideoAppsList = () => {
return new Set([
...this.lists.app,
...this.lists.game,
])
}
saveToJson = async function ( content, path ) {
// Write the list to JSON
await fs.writeFile(path, JSON.stringify(content))
return
}
saveList = async function ( listOptions ) {
if (this.lists[listOptions.name].size === 0) throw new Error('Trying to save empty list')
// Make the relative path for our new JSON file
const listFullPath = `.${listOptions.path}`
const hasSaveMethod = listOptions.hasOwnProperty('beforeSave')
const saveMethod = hasSaveMethod ? listOptions.beforeSave : listSet => Array.from( listSet )
// console.log('listFullPath', listFullPath)
const saveableList = saveMethod( this.lists[listOptions.name] )
console.log('saveableList', typeof saveableList)
// Write the list to JSON
await fs.writeFile(listFullPath, JSON.stringify( saveableList ))
// Read back the JSON we just wrote to ensure it exists
const savedListJSON = await fs.readFile(listFullPath, 'utf-8')
// console.log('savedListJSON', savedListJSON)
const savedList = JSON.parse(savedListJSON)
// Import the created JSON File
return savedList
}
// Run all listsOprions methods
// and store them to this.lists
async buildLists () {
console.log('Build Lists started')
for (const listOptions of this.listsOptions) {
const methodName = `Building ${listOptions.name}`
console.time(methodName)
const builtList = await listOptions.buildMethod()
// Run the build method to get the lists
this.lists[listOptions.name] = new Set([
...builtList
])
console.timeEnd(methodName)
console.log(`Finished ${listOptions.name} list with ${this.lists[listOptions.name].size} items`)
}
console.log('Build Lists finished')
return
}
// Save app lists to JSON
saveAppLists = async function () {
if (Object.keys(this.listsOptions).length === 0) throw new Error('Trying to store empty lists')
console.log('Save lists started')
for ( const listOptionsKey in this.listsOptions ) {
const methodName = `Saving ${this.listsOptions[listOptionsKey].path}`
console.time(methodName)
const listOptions = this.listsOptions[listOptionsKey]
await this.saveList( listOptions )
const searchableList = makeSearchableList( this.lists[listOptions.name] )
// console.log('searchableList', searchableList)
// Save a searchable list
await this.saveToJson( Array.from(searchableList), `./static/${listOptions.name}-list-searchable.json` )
console.timeEnd(methodName)
}
console.log('Save lists finished')
return
}
async build () {
await this.buildLists()
await this.saveAppLists()
// console.log('appList', appList)
// console.log('this.allVideoAppsList', this.allVideoAppsList.length, this.allVideoAppsList[0])
// Add list based routes
for ( const listKey in this.lists ) {
this.lists[listKey].forEach( app => {
const isVideo = (app.category === undefined)
const isApp = (app.endpoint.includes('/app/'))
const isGame = (app.category === 'games')
if (isVideo) {
this.eleventyEndpointsSet.add({
route: getVideoEndpoint(app),
// payload: buildVideoPayload( app, this.allVideoAppsList, this.lists.video )
})
return
}
// Add benchmark endpoints for apps and games
if ( isApp || isGame ) {
this.nuxtEndpointsSet.add({
route: `${getAppEndpoint(app)}/benchmarks`,
payload: buildAppBenchmarkPayload( app, this.allVideoAppsList, this.lists.video )
})
}
this.nuxtEndpointsSet.add({
route: getAppEndpoint(app),
payload: { app }
})
return
})
}
Object.keys(categories).forEach( slug => {
this.nuxtEndpointsSet.add({
route: '/kind/' + slug,
// payload: appList
})
})
// Save Nuxt Endpoints
await this.saveToJson(Array.from(this.nuxtEndpointsSet), './static/nuxt-endpoints.json')
// Save Eleventy Endpoints
await this.saveToJson(Array.from(this.eleventyEndpointsSet), './static/eleventy-endpoints.json')
return
}
}
const listBuilder = new BuildLists()
listBuilder.build()
// export default async function () {
// const listBuilder = new BuildLists()
// return await listBuilder.build()
// }

View file

@ -0,0 +1,74 @@
function pill ( text ) {
return /* html */`
<div
class="video-pill h-5 text-xs bg-white-2 flex justify-center items-center outline-0 rounded-full ease px-2"
>
${ text }
</div>
`
}
export default function ( video, options = {} ) {
const {
width = '325px',
classes = 'w-full flex-shrink-0 flex-grow-0 border-2 border-transparent rounded-2xl overflow-hidden'
} = options
// Setup inline lazysizes
this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
// console.log('video', video)
return /* html */`
<div class="video-card ${ classes }" style="max-width: ${ width }; flex-basis: ${ width }; scroll-snap-align: start;">
<a
href="${video.endpoint}"
class=""
>
<div class="video-card-container relative overflow-hidden bg-black">
<div class="video-card-image ratio-wrapper">
<div class="relative overflow-hidden w-full pb-16/9">
<picture>
<source
sizes="${video.thumbnail.sizes}"
data-srcset="${video.thumbnail.srcset}"
type="image/jpg"
>
<img
data-src="${video.thumbnail.src}"
alt="${video.name}"
class="lazyload absolute h-full w-full object-cover"
>
</picture>
</div>
</div>
<div
style="--gradient-from-color: rgba(0, 0, 0, 1); --gradient-to-color: rgba(0, 0, 0, 0.7)"
class="video-card-overlay absolute inset-0 flex justify-between items-start bg-gradient-to-tr from-black to-transparent p-4"
>
<div class="play-circle w-8 h-8 bg-white-2 flex justify-center items-center outline-0 rounded-full ease">
<svg
viewBox="0 0 18 18"
style="width:18px;height:18px;margin-left:3px"
>
<path
fill="currentColor"
d="M15.562 8.1L3.87.225c-.818-.562-1.87 0-1.87.9v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"
/>
</svg>
</div>
${ (video.tags.includes('benchmark')) ? pill('Benchmark') : '' }
</div>
<!-- Video Text Content -->
<div class="video-card-content absolute inset-0 flex items-end py-4 px-6">
<div class="w-full text-sm text-left whitespace-normal">${ video.name }</div>
</div>
</div>
</a>
</div>
`
}

View file

@ -0,0 +1,80 @@
import VideoCard from './card.js'
import SubmitCard from './submit-card.js'
function getCardType ( video ) {
const isSubmitCard = video.endpoint.includes('https://docs.google.com/forms')
if ( isSubmitCard ) return SubmitCard
return VideoCard
}
export default function ( videos, options = {} ) {
const {
cardWidth = '325',
classes = ''
} = options
// Math.random should be unique because of its seeding algorithm.
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
// after the decimal.
const uid = Math.random().toString(36).substr(2, 9)
const rowId = `row-${ uid }`
// Setup inline lazysizes
this.usingComponent( 'helpers/scroll.js' )
// console.log('video', video)
const cardsHtml = videos.map( video => {
const Card = getCardType( video )
// console.log('Card', this.boundComponent(Card)( video ) )
return this.boundComponent(Card)( video )
} ).join('')
// console.log( 'cardsHtml', cardsHtml )
return /* html */`
<div class="video-row relative w-full ${ classes }">
<div
id="${ rowId }"
class="video-row-contents flex overflow-x-auto whitespace-no-wrap py-2 space-x-6"
style="scroll-snap-type:x mandatory;"
>
${ cardsHtml }
</div>
<button
class="absolute left-0 h-10 w-10 flex justify-center items-center transform -translate-y-1/2 -translate-x-1/2 bg-darker rounded-full"
style="top:50%;"
distance="${ cardWidth * -1 }"
scroll-target="#${ rowId }"
onclick="scrollHorizontalCarousel( event )"
>
<svg viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5 text-gray-400" style="transform: scaleX(-1);">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</button>
<button
class="absolute right-0 h-10 w-10 flex justify-center items-center transform -translate-y-1/2 translate-x-1/2 bg-darker rounded-full"
style="top:50%;"
distance="${ cardWidth }"
scroll-target="#${ rowId }"
onclick="scrollHorizontalCarousel( event )"
>
<svg viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5 text-gray-400">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
`
}

View file

@ -0,0 +1,81 @@
export default function ( video, {
width = '325px',
classes = 'w-full flex-shrink-0 flex-grow-0 border-2 border-transparent rounded-2xl overflow-hidden'
} ) {
// Setup inline lazysizes
// this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
// console.log('video', video)
return /* html */`
<div class="video-card ${ classes }" style="max-width: ${ width }; flex-basis: ${ width }; scroll-snap-align: start;">
<a href="${ video.endpoint }">
<div class="video-card-container relative overflow-hidden bg-white">
<div class="video-card-image ratio-wrapper">
<div class="relative overflow-hidden w-full pb-16/9"></div>
</div>
<div class="video-card-overlay absolute inset-0 flex justify-between items-start bg-gradient-to-tr from-black to-transparent p-4" style="--gradient-from-color:rgba(0, 0, 0, 1); --gradient-to-color:rgba(0, 0, 0, 0.7);">
<div class="plus-circle w-8 h-8 bg-white-2 flex justify-center items-center outline-0 rounded-full ease">
<svg viewBox="0 0 24 24" style="width: 18px; height: 18px;">
<path fill="currentColor" d="M11 11v-11h1v11h11v1h-11v11h-1v-11h-11v-1h11z"></path>
</svg>
</div>
</div>
<div class="video-card-content absolute inset-0 flex items-end py-4 px-6">
<div class="w-full text-sm text-left whitespace-normal">Submit Video</div>
</div>
</div>
</a>
</div>
<div class="video-card ${ classes }" style="max-width: ${ width }; flex-basis: ${ width }; scroll-snap-align: start;">
<a
href="${video.endpoint}"
class=""
>
<div class="video-card-container relative overflow-hidden bg-black">
<div class="video-card-image ratio-wrapper">
<div class="relative overflow-hidden w-full pb-16/9">
<picture>
<source
sizes="${video.thumbnail.sizes}"
data-srcset="${video.thumbnail.srcset}"
type="image/jpg"
>
<img
data-src="${video.thumbnail.src}"
alt="${video.name}"
class="lazyload absolute h-full w-full object-cover"
>
</picture>
</div>
</div>
<div
style="--gradient-from-color: rgba(0, 0, 0, 1); --gradient-to-color: rgba(0, 0, 0, 0.7)"
class="video-card-overlay absolute inset-0 flex justify-between items-start bg-gradient-to-tr from-black to-transparent p-4"
>
<div class="play-circle w-8 h-8 bg-white-2 flex justify-center items-center outline-0 rounded-full ease">
<svg
viewBox="0 0 18 18"
style="width:18px;height:18px;margin-left:3px"
>
<path
fill="currentColor"
d="M15.562 8.1L3.87.225c-.818-.562-1.87 0-1.87.9v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"
/>
</svg>
</div>
</div>
<!-- Video Text Content -->
<div class="video-card-content absolute inset-0 flex items-end py-4 px-6">
<div class="w-full text-sm text-left whitespace-normal">${ video.name }</div>
</div>
</div>
</a>
</div>
`
}

View file

@ -10,12 +10,12 @@
<div class="relative overflow-hidden w-full pb-16/9"> <div class="relative overflow-hidden w-full pb-16/9">
<picture> <picture>
<source <source
:sizes="thumbnailSizes" :sizes="video.thumbnail.sizes"
:data-srcset="thumbnailSrcset" :data-srcset="video.thumbnail.srcset"
type="image/jpg" type="image/jpg"
> >
<img <img
:data-src="video.thumbnails.default.url" :data-src="video.thumbnail.src"
:alt="video.name" :alt="video.name"
class="lazyload absolute h-full w-full object-cover" class="lazyload absolute h-full w-full object-cover"
> >
@ -64,8 +64,6 @@
import 'lazysizes' import 'lazysizes'
// import { getVideoEndpoint } from '~/helpers/app-derived.js'
export default { export default {
props: { props: {
video: { video: {
@ -74,26 +72,6 @@ export default {
} }
}, },
computed: { computed: {
thumbnailSizes () {
let maxWidth = 0
Object.entries(this.video.thumbnails).forEach(([thumbnailKey, thumbnail]) => {
if (thumbnail.width > maxWidth) maxWidth = thumbnail.width
})
// example:
// "(max-width: 640px) 100vw, 640px"
return `(max-width: ${maxWidth}px) 100vw, ${maxWidth}px`
},
thumbnailSrcset () {
// console.log('this.video', this.video)
// example:
// https://vumbnail.com/358629078.jpg 640w, https://vumbnail.com/358629078_large.jpg 640w, https://vumbnail.com/358629078_medium.jpg 200w, https://vumbnail.com/358629078_small.jpg 100w
return Object.entries(this.video.thumbnails).map(([thumbnailKey, thumbnail]) => {
// console.log('thumbnail', thumbnail)
return `${thumbnail.url} ${thumbnail.width}w`
}).join(', ')
},
pill () { pill () {
// if this video has a banchmark tag // if this video has a banchmark tag
// then pill is 'Benchmark' // then pill is 'Benchmark'

View file

@ -4,7 +4,7 @@ import { appsRelatedToVideo, videosRelatedToVideo, videosRelatedToApp } from './
// import videoList from '~/static/video-list.json' // import videoList from '~/static/video-list.json'
export function buildVideoPayload ( video, allVideoAppsList, videoList ) { export function buildVideoPayload ( video, allVideoAppsListSet, videoListSet ) {
// const { appsRelatedToVideo, videosRelatedToVideo } = await import('~/helpers/related.js') // const { appsRelatedToVideo, videosRelatedToVideo } = await import('~/helpers/related.js')
// const { default: videoList } = await import('~/static/video-list.json') // const { default: videoList } = await import('~/static/video-list.json')
@ -12,26 +12,26 @@ export function buildVideoPayload ( video, allVideoAppsList, videoList ) {
// const video = videoList.find(video => (video.slug === slug)) // const video = videoList.find(video => (video.slug === slug))
// Get featured apps // Get featured apps
const featuredApps = appsRelatedToVideo( video, allVideoAppsList ) const featuredApps = appsRelatedToVideo( video, allVideoAppsListSet )
// Get related videos // Get related videos
const relatedVideos = videosRelatedToVideo( video, allVideoAppsList, videoList ) const relatedVideos = videosRelatedToVideo( video, allVideoAppsListSet, videoListSet )
return { return {
video, video,
featuredApps, featuredApps,
// If no related video found just get the 12 newest ones // If no related video found just get the 12 newest ones
relatedVideos: (relatedVideos.length !== 0) ? relatedVideos : videoList.slice(0, 12) relatedVideos: (relatedVideos.length !== 0) ? relatedVideos.slice(0, 24) : Array.from(videoListSet).slice(0, 12)
} }
} }
export function buildAppBenchmarkPayload ( app, allVideoAppsList, videoList ) { export function buildAppBenchmarkPayload ( app, allVideoAppsListSet, videoListSet ) {
// const { allVideoAppsList } = await import('~/helpers/get-list.js') // const { allVideoAppsListSet } = await import('~/helpers/get-list.js')
// const { videosRelatedToApp } = await import('~/helpers/related.js') // const { videosRelatedToApp } = await import('~/helpers/related.js')
// const app = allVideoAppsList.find(app => (app.slug === slug)) // const app = allVideoAppsListSet.find(app => (app.slug === slug))
const submitVideoCard = { const submitVideoCard = {
endpoint: `https://docs.google.com/forms/d/e/1FAIpQLSeEVGM9vE7VcfLMy6fJkfU70X2VZ60rHDyhDQLtnAN4nso0WA/viewform?usp=pp_url&entry.1018125313=${app.name}` endpoint: `https://docs.google.com/forms/d/e/1FAIpQLSeEVGM9vE7VcfLMy6fJkfU70X2VZ60rHDyhDQLtnAN4nso0WA/viewform?usp=pp_url&entry.1018125313=${app.name}`
@ -39,7 +39,7 @@ export function buildAppBenchmarkPayload ( app, allVideoAppsList, videoList ) {
// const featuredApps = [] // const featuredApps = []
const relatedVideos = videosRelatedToApp( app, videoList ).map(video => { const relatedVideos = videosRelatedToApp( app, videoListSet ).map(video => {
// console.log('video', video) // console.log('video', video)
return { return {
...video, ...video,

View file

@ -87,6 +87,30 @@ const generateVideoTags = function ( video ) {
return videoTags return videoTags
} }
const makeThumbnailData = function ( thumbnails ) {
let maxWidth = 0
Object.entries( thumbnails ).forEach(([thumbnailKey, thumbnail]) => {
if (thumbnail.width > maxWidth) maxWidth = thumbnail.width
})
const sizes = `(max-width: ${maxWidth}px) 100vw, ${maxWidth}px`
const srcset = Object.entries( thumbnails ).map(([thumbnailKey, thumbnail]) => {
// console.log('thumbnail', thumbnail)
return `${thumbnail.url} ${thumbnail.width}w`
}).join(', ')
const src = thumbnails.default.url
return {
sizes,
srcset,
src
}
}
export default async function ( applist ) { export default async function ( applist ) {
// Fetch Commits // Fetch Commits
@ -139,14 +163,15 @@ export default async function ( applist ) {
lastUpdated, lastUpdated,
apps, apps,
slug, slug,
channel:{ channel: {
name: fetchedVideos[videoId].rawData.snippet.channelTitle, name: fetchedVideos[videoId].rawData.snippet.channelTitle,
id: fetchedVideos[videoId].rawData.snippet.channelId id: fetchedVideos[videoId].rawData.snippet.channelId
}, },
// Convert tags set into array // Convert tags set into array
tags: Array.from(tags), tags: Array.from(tags),
timestamps: fetchedVideos[videoId].timestamps, timestamps: fetchedVideos[videoId].timestamps,
thumbnails: fetchedVideos[videoId].rawData.snippet.thumbnails, // thumbnails: fetchedVideos[videoId].rawData.snippet.thumbnails,
thumbnail: makeThumbnailData( fetchedVideos[videoId].rawData.snippet.thumbnails ),
endpoint: getVideoEndpoint({ endpoint: getVideoEndpoint({
slug slug
}) })

View file

@ -1,3 +1,5 @@
// Universal JS imports only
// Contains all types of properies to keep data consistent // Contains all types of properies to keep data consistent
export const categoryTemplate = { export const categoryTemplate = {
@ -10,62 +12,85 @@ export const categoryTemplate = {
export const categories = { export const categories = {
'no-category': {
id: 0
},
// App lists // App lists
'developer-tools': { 'developer-tools': {
id: 1,
...categoryTemplate, ...categoryTemplate,
label: 'Developer Tools', label: 'Developer Tools',
pluralLabel: 'Developer Tools', pluralLabel: 'Developer Tools',
slug: 'developer-tools', slug: 'developer-tools',
}, },
'productivity-tools': { 'productivity-tools': {
id: 2,
...categoryTemplate, ...categoryTemplate,
label: 'Productivity Tools', label: 'Productivity Tools',
pluralLabel: 'Productivity Tools', pluralLabel: 'Productivity Tools',
slug: 'productivity-tools', slug: 'productivity-tools',
}, },
'video-and-motion-tools': { 'video-and-motion-tools': {
id: 3,
...categoryTemplate, ...categoryTemplate,
label: 'Video and Motion Tools', label: 'Video and Motion Tools',
pluralLabel: 'Video and Motion Tools', pluralLabel: 'Video and Motion Tools',
slug: 'video-and-motion-tools', slug: 'video-and-motion-tools',
}, },
'social-and-communication': { 'social-and-communication': {
id: 4,
...categoryTemplate, ...categoryTemplate,
label: 'Social and Communication', label: 'Social and Communication',
pluralLabel: 'Social and Communication Apps', pluralLabel: 'Social and Communication Apps',
slug: 'social-and-communication', slug: 'social-and-communication',
}, },
'entertainment-and-media-apps': { 'entertainment-and-media-apps': {
id: 5,
...categoryTemplate, ...categoryTemplate,
label: 'Entertainment and Media Apps', label: 'Entertainment and Media Apps',
pluralLabel: 'Entertainment and Media Apps', pluralLabel: 'Entertainment and Media Apps',
slug: 'entertainment-and-media-apps', slug: 'entertainment-and-media-apps',
}, },
'music-and-audio-tools': { 'music-and-audio-tools': {
id: 6,
...categoryTemplate, ...categoryTemplate,
label: 'Music and Audio Tools', label: 'Music and Audio Tools',
pluralLabel: 'Music and Audio Tools', pluralLabel: 'Music and Audio Tools',
slug: 'music-and-audio-tools', slug: 'music-and-audio-tools',
}, },
'photo-and-graphic-tools': { 'photo-and-graphic-tools': {
id: 7,
...categoryTemplate, ...categoryTemplate,
label: 'Photo and Graphic Tools', label: 'Photo and Graphic Tools',
pluralLabel: 'Photo and Graphic Tools', pluralLabel: 'Photo and Graphic Tools',
slug: 'photo-and-graphic-tools', slug: 'photo-and-graphic-tools',
}, },
'science-and-research-software': { 'science-and-research-software': {
id: 8,
...categoryTemplate, ...categoryTemplate,
label: 'Science and Research Software', label: 'Science and Research Software',
pluralLabel: 'Science and Research Software', pluralLabel: 'Science and Research Software',
slug: 'science-and-research-software', slug: 'science-and-research-software',
}, },
'3d-and-architecture': { '3d-and-architecture': {
id: 9,
...categoryTemplate, ...categoryTemplate,
label: '3D and Architecture', label: '3D and Architecture',
pluralLabel: '3D and Architecture Applications', pluralLabel: '3D and Architecture Applications',
slug: '3d-and-architecture', slug: '3d-and-architecture',
}, },
'vpns-security-and-privacy': { 'vpns-security-and-privacy': {
id: 10,
...categoryTemplate, ...categoryTemplate,
label: 'VPNs, Security, and Privacy', label: 'VPNs, Security, and Privacy',
pluralLabel: 'VPN, Security, and Privacy Applications', pluralLabel: 'VPN, Security, and Privacy Applications',
@ -74,6 +99,7 @@ export const categories = {
// Special Lists // Special Lists
'games': { 'games': {
id: 100,
...categoryTemplate, ...categoryTemplate,
label: 'Games', label: 'Games',
pluralLabel: 'Games', pluralLabel: 'Games',
@ -87,6 +113,7 @@ export const categories = {
] ]
}, },
'homebrew': { 'homebrew': {
id: 101,
...categoryTemplate, ...categoryTemplate,
label: 'Homebrew', label: 'Homebrew',
pluralLabel: 'Homebrew Formulae', pluralLabel: 'Homebrew Formulae',

View file

@ -21,9 +21,9 @@ export const allList = [
] ]
export function makeAppSearchLinks ( app, videoList ) { export function makeAppSearchLinks ( app, videoListSet ) {
const videos = videosRelatedToApp( app, videoList ) const videos = videosRelatedToApp( app, videoListSet )
// If there are no videos // If there are no videos
// then skip // then skip

View file

@ -1,15 +1,17 @@
// import { allVideoAppsList } from '~/helpers/get-list.js' // import { allVideoAppsListSet } from '~/helpers/get-list.js'
// import videoList from '~/static/video-list.json' // import videoList from '~/static/video-list.json'
export function matchesWholeWord (needle, haystack) { export function matchesWholeWord (needle, haystack) {
return new RegExp('\\b' + needle + '\\b').test(haystack) return new RegExp('\\b' + needle + '\\b').test(haystack)
} }
export function appsRelatedToVideo ( video, allVideoAppsList ) { export function appsRelatedToVideo ( video, allVideoAppsListSet ) {
// console.log('allVideoAppsListSet', allVideoAppsListSet.length)
const relatedApps = [] const relatedApps = []
// Find the apps listed in this video // Find the apps listed in this video
for (const app of allVideoAppsList) { for (const app of allVideoAppsListSet) {
// console.log('video', video) // console.log('video', video)
// Skip this app if it's not listed in the videos apps // Skip this app if it's not listed in the videos apps
if (!video.apps.includes(app.slug)) continue if (!video.apps.includes(app.slug)) continue
@ -21,16 +23,16 @@ export function appsRelatedToVideo ( video, allVideoAppsList ) {
return relatedApps return relatedApps
} }
export function videosRelatedToVideo ( video, allVideoAppsList, videoList ) { export function videosRelatedToVideo ( video, allVideoAppsListSet, videoListSet ) {
const relatedVideos = {} const relatedVideos = {}
// console.log('videoList', videoList[0]) // console.log('videoList', videoList[0])
// console.log('allVideoAppsList', allVideoAppsList[0]) // console.log('allVideoAppsListSet', allVideoAppsListSet[0])
const featuredApps = appsRelatedToVideo( video, allVideoAppsList ) const featuredApps = appsRelatedToVideo( video, allVideoAppsListSet )
// Find other videos that also feature this video's app // Find other videos that also feature this video's app
for (const otherVideo of videoList) { for (const otherVideo of videoListSet) {
for (const app of featuredApps) { for (const app of featuredApps) {
// console.log('otherVideo', otherVideo) // console.log('otherVideo', otherVideo)
// Skip if this app is not in the other video's apps // Skip if this app is not in the other video's apps
@ -48,12 +50,14 @@ export function videosRelatedToVideo ( video, allVideoAppsList, videoList ) {
} }
export function videosRelatedToApp ( app, videoList ) { export function videosRelatedToApp ( app, videoListSet ) {
// console.log('videoListSet', videoListSet)
const relatedVideos = {} const relatedVideos = {}
// Find other videos that also feature this video's app // Find other videos that also feature this video's app
for (const video of videoList) { for (const video of videoListSet) {
if (!video.apps.includes(app.slug)) continue if (!video.apps.includes(app.slug)) continue
relatedVideos[video.id] = video relatedVideos[video.id] = video

13
helpers/scroll.js Normal file
View file

@ -0,0 +1,13 @@
function scrollHorizontalCarousel ( event ) {
event.stopPropagation()
// console.log('event.target', event.currentTarget)
// console.log('distance', event.currentTarget.getAttribute('distance'))
const distance = Number(event.currentTarget.getAttribute('distance'))
const scrollTarget = document.querySelector(event.currentTarget.getAttribute('scroll-target'))
scrollTarget.scrollBy({ left: distance, behavior: 'smooth' })
}

View file

@ -0,0 +1,84 @@
// Universal JS imports only
import { getAppCategory } from './categories.js'
// Converts a list into a smaller searchable list
// similar to a table/spreadsheet
export function makeSearchableList ( listSet ) {
let firstLoop = true
const searchableList = new Set()
const tableHeader = new Set()
// const searchableKeys = new Set([
// 'name',
// 'text',
// 'lastUpdated',
// 'endpoint'
// ])
const makeSearchable = new Map([
[
'name',
item => {
// console.log('Running name method', item)
return item.name
}
],
[
'text',
item => item.text
],
[
'endpoint',
item => item.endpoint
],
[
'category',
app => {
return getAppCategory( app ).id
}
]
])
listSet.forEach( ( searchableItem ) => {
// If this is the first items
// then store the keys
if ( firstLoop ) {
Object.keys(searchableItem).forEach( key => {
// console.log(key, makeSearchable.has(key))
if ( !makeSearchable.has(key) ) return
// Add to table header so we can loop it later on
tableHeader.add( key )
})
// Add keys to table head
searchableList.add( Array.from( tableHeader ) )
firstLoop = false
}
// This could cause an issue if the keys
// are out of order
// console.log('tableHeader', tableHeader)
const tableRow = []
// Loop through keys from table header
// and push them to this row
tableHeader.forEach( key => {
// console.log('searchableValue', key, searchableItem[key], makeSearchable.get(key)(searchableItem) )
tableRow.push( makeSearchable.get(key)(searchableItem) )
})
searchableList.add( tableRow )
return
})
return searchableList
}

View file

@ -0,0 +1,200 @@
import config from '../nuxt.config'
const year = new Date().getFullYear()
const makeTag = ( tag, tagName = 'meta') => {
const attributes = Object.entries(tag).map( ([ name, value ]) => `${name}="${value}"` ).join(' ')
return `<${tagName} ${attributes}>`
}
const mapMetaTag = ( tag ) => {
if ( tag.hasOwnProperty('property') ) {
return [
`property-${tag.property}`,
makeTag(tag)
]
}
if ( tag.hasOwnProperty('name') ) {
return [
`name-${tag.name}`,
makeTag(tag)
]
}
if ( tag.hasOwnProperty('charset') ) {
return [
'charset',
makeTag(tag)
]
}
}
const mapLinkTag = ( tag ) => {
return [
`type-${tag.type}`,
makeTag(tag, 'link')
]
}
const defaultMeta = Object.fromEntries(config.head.meta.map( mapMetaTag ))
const defaultLinkTags = Object.fromEntries(config.head.link.map( mapLinkTag ))
class DefaultLayout {
generateMetaTags = function ( renderData ) {
const {
title = null,
description = null,
meta: pageMeta = []
} = renderData
// console.log('renderData', Object.keys(renderData))
const meta = {
...defaultMeta,
'property-twitter:url': `<meta property="twitter:url" content="${process.env.URL}${this.page.url}">`,
...Object.fromEntries( pageMeta.map(mapMetaTag) )
}
// console.log('renderData.description', renderData.description)
// if set
// get description from data
if ( description ) {
// Set meta description
meta['name-description'] = `<meta hid="description" name="description" content="${ description }">`
// Set twitter description
meta['property-twitter:description'] = `<meta hid="twitter:description" property="twitter:description" content="${ description }">`
}
// if set
// get title from data
if ( title ) {
// Set twitter title
meta['property-twitter:title'] = `<meta hid="twitter:title" property="twitter:title" content="${ title }">`
}
return Object.values(meta).join('')
}
generateLinkTags = ( pageLinkTags = [] ) => {
const linkTags = {
...defaultLinkTags,
...Object.fromEntries(pageLinkTags.map( mapLinkTag ))
}
return Object.values( linkTags ).join('')
}
render( data ) {
const {
content,
title = null,
description = null
} = data
// Setup inline tailwind
this.usingComponent( 'static/tailwind.css' )
// Setup inline tailwind
this.usingComponent( 'node_modules/@fontsource/inter/variable.css' )
return /* html */`
<!doctype html>
<html lang="${ this.getNuxt().head.htmlAttrs.lang }">
<head>
<title>${ title || this.getNuxt().head.title }</title>
${ this.generateMetaTags( data ) }
${ this.generateLinkTags() }
<!-- {{ Script Preloads }} -->
<style>
${ this.getCss() }
</style>
<!-- {{ External Styles }} -->
</head>
<body>
<div id="__nuxt">
<!---->
<div id="__layout">
<div class="app-wrapper text-gray-300 bg-gradient-to-bl from-dark to-darker bg-fixed">
<nav class="fixed top-0 left-0 right-0 z-navbar">
<div class="max-w-7xl mx-auto px-4 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="-ml-2 mr-2 flex items-center lg:hidden">
<button aria-expanded="false" aria-label="Main menu" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:bg-gray-700 focus:text-white transition duration-150 ease-in-out">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-6 w-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<!---->
</button>
</div>
<div class="flex-shrink-0 flex items-center text-4xl lg:text-5xl py-3">
<div>🦾</div>
</div>
<div class="hidden lg:ml-6 lg:flex lg:items-center space-x-4"><a href="/" class="px-3 py-2 rounded-md text-sm font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-darker hover:neumorphic-shadow">Home </a><a href="/categories" class="px-3 py-2 rounded-md text-sm font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-darker hover:neumorphic-shadow">Categories </a><a href="/benchmarks" class="px-3 py-2 rounded-md text-sm font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-darker hover:neumorphic-shadow">Benchmarks </a><a href="/kind/homebrew" class="px-3 py-2 rounded-md text-sm font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-darker hover:neumorphic-shadow">Homebrew </a><a href="/games" class="px-3 py-2 rounded-md text-sm font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-darker hover:neumorphic-shadow">Games</a></div>
</div>
<div class="flex items-center">
<div class="flex-shrink-0"><a href="https://www.producthunt.com/posts/does-it-arm-benchmarks?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-does-it-arm-benchmarks" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=279410&theme=light" alt="Does It ARM Benchmarks - Curated App Benchmark Videos for Apple Silicon and Apple M1 | Product Hunt" width="200" height="43" style="width: 200px; height: 43px;"></a></div>
</div>
</div>
</div>
<div class="lg:hidden hidden">
<div class="px-2 pt-2 pb-3 lg:px-3"><a href="/" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-gray-700">Home </a><a href="/categories" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-gray-700">Categories </a><a href="/benchmarks" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-gray-700">Benchmarks </a><a href="/kind/homebrew" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-gray-700">Homebrew </a><a href="/games" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out text-gray-300 hover:bg-gray-700">Games</a></div>
<hr>
</div>
</nav>
<div class="app-main min-h-screen flex items-center">
${ content }
</div>
<footer>
<div class="max-w-screen-xl mx-auto py-12 px-4 overflow-hidden space-y-8 sm:px-6 lg:px-8">
<div class="mt-8 flex justify-center space-x-6">
<div class="flex flex-col items-center space-y-4">
<div>
<div>
<form class="all-updates-subscribe text-xs relative">
<!---->
<div class="mt-1 relative rounded-md shadow-sm">
<!----> <input id="all-updates-subscribe-17332" placeholder="Send me regular app updates" aria-label="Send me regular app updates" name="all-updates-subscribe" type="email" required class="form-input block w-full rounded-md py-1 neumorphic-shadow bg-darker placeholder-white text-center border border-transparent px-3" style="width: 240px;">
</div>
</form>
</div>
<!---->
</div>
</div>
</div>
<p class="mt-8 text-center text-base leading-6 text-gray-400">© ${ year } ${ this.getNuxt().head.title } All rights reserved.</p>
</div>
</footer>
</div>
</div>
</div>
<script>
${ this.getJs() }
</script>
</body>
</html>
`;
}
}
module.exports = DefaultLayout

View file

@ -58,27 +58,3 @@ export default {
} }
</script> </script>
<style>
html {
font-family: 'Inter', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 20px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
background: #191a1d;
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
}
</style>

View file

@ -1,94 +1,6 @@
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
// import path from 'path'
import pkg from './package' import pkg from './package'
import buildAppList from './helpers/build-app-list.js'
import buildGamesList from './helpers/build-game-list.js'
import buildHomebrewList from './helpers/build-homebrew-list.js'
import buildVideoList from './helpers/build-video-list.js'
import { buildVideoPayload, buildAppBenchmarkPayload } from './helpers/build-payload.js'
import { categories } from './helpers/categories.js'
import { getAppEndpoint, getVideoEndpoint } from './helpers/app-derived.js'
const listsOptions = [
{
buildMethod: buildAppList,
path: '/static/app-list.json',
},
{
buildMethod: buildGamesList,
path: '/static/game-list.json',
},
{
buildMethod: buildHomebrewList,
path: '/static/homebrew-list.json',
}
]
const videoListOptions = {
buildMethod: buildVideoList,
path: '/static/video-list.json',
}
const saveList = async function ( list, buildArgs = null ) {
const methodName = `Building ${list.path}`
console.time(methodName)
// Run the build method
const builtList = await list.buildMethod(buildArgs)
// Make the relative path for our new JSON file
const listFullPath = `.${list.path}`
// console.log('listFullPath', listFullPath)
// Write the list to JSON
await fs.writeFile(listFullPath, JSON.stringify(builtList))
// Read back the JSON we just wrote to ensure it exists
const savedListJSON = await fs.readFile(listFullPath, 'utf-8')
// console.log('savedListJSON', savedListJSON)
const savedList = JSON.parse(savedListJSON)
console.timeEnd(methodName)
// Import the created JSON File
return savedList
}
const storeAppLists = async function (builder) {
console.log('Build Lists started')
const savedLists = await Promise.all(listsOptions.map(saveList))
// Build and save list of videos based on app lists
.then(async lists => {
const [
appList,
gameList
] = lists
// Build a video app list with apps and games only
const allVideoAppsList = [
...appList,
...gameList
].flat(1)
return await saveList(videoListOptions, allVideoAppsList)
})
console.log('Build Lists finished')
return savedLists
}
export default { export default {
@ -104,12 +16,12 @@ export default {
* https://nuxtjs.org/api/configuration-hooks/ * https://nuxtjs.org/api/configuration-hooks/
*/ */
hooks: { hooks: {
build: { // build: {
before: storeAppLists // before: storeAppLists
}, // },
generate: { // generate: {
before: storeAppLists // before: storeAppLists
} // }
}, },
generate: { generate: {
@ -121,102 +33,9 @@ export default {
] ]
}, },
routes() { routes() {
return Promise.all([ return fs.readFile('./static/nuxt-endpoints.json', 'utf-8')
...listsOptions, .then( endpointsJson => {
videoListOptions return JSON.parse(endpointsJson)
].map(async list => {
// Read saved lists
const methodName = `Reading ${list.path}`
console.time(methodName)
const listPath = `.${list.path}`
// Read JSON to ensure it exists
const savedListJSON = await fs.readFile(listPath, 'utf-8')
// Parse the saved JSON into a variable
const savedList = JSON.parse(savedListJSON)
console.timeEnd(methodName)
// Pass on the variable
return savedList
}))
.then(( lists ) => {
// console.log('appList', appList)
// Break out lists
const [
appList,
gameList,
_,//homebrewList,
videoList
] = lists
const allVideoAppsList = [
...appList,
...gameList
]
// console.log('allVideoAppsList', allVideoAppsList[0])
// console.log('videoList', videoList[0])
const [
appRoutes,
gameRoutes,
homebrewRoutes,
videoRoutes
] = lists.map((list, listI) => {
return list.map( app => {
const isVideo = (app.category === undefined)
if (isVideo) {
return {
route: getVideoEndpoint(app),
payload: buildVideoPayload(app, allVideoAppsList, videoList)
}
}
return {
route: getAppEndpoint(app),
payload: { app }
}
})
})
// Build routes for app types that support benchmark endpoints
const benchmarkRoutes = [
...appRoutes,
...gameRoutes,
].flat(1).map( ({ route, payload: { app } }) => ({
route: `${route}/benchmarks`,
payload: buildAppBenchmarkPayload( app, allVideoAppsList, videoList )
}))
// console.log('homebrewRoutes', homebrewRoutes)
const categoryRoutes = Object.keys(categories).map( slug => ({
route: '/kind/' + slug,
// payload: appList
}))
// Merge endpoints into set to ensure no duplicates
const allEndpointsSet = new Set([
...appRoutes,
...gameRoutes,
...homebrewRoutes,
// Non-app routes
...videoRoutes,
...categoryRoutes,
...benchmarkRoutes
])
return Array.from(allEndpointsSet)
}) })
} }
}, },
@ -230,6 +49,7 @@ export default {
lang: 'en', lang: 'en',
}, },
title: 'Does it ARM', title: 'Does it ARM',
description: pkg.description,
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ {
@ -318,6 +138,9 @@ export default {
** Build configuration ** Build configuration
*/ */
build: { build: {
parallel: true,
// hardSource: true,
cache: true,
html: { html: {
minify: { minify: {
minifyCSS: false, minifyCSS: false,

7150
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,13 +8,19 @@
"dev": "nuxt", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start", "start": "nuxt start",
"generate": "rm -f ./static/app-list.json && npm run clone-readme && nuxt generate", "generate": "npm run clone-readme && npm run build-lists && npm run generate-nuxt && npm run generate-eleventy",
"build-lists": "node -r esm build-lists.js",
"generate-nuxt": "nuxt generate",
"generate-eleventy": "npm run generate-postcss && node -r esm node_modules/.bin/eleventy --quiet",
"generate-postcss": "ENV=production postcss assets/css/tailwind.css --o static/tailwind.css",
"dev-eleventy": "node -r esm node_modules/.bin/eleventy --quiet --watch --serve",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .", "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"lint:fix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .", "lint:fix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint", "precommit": "npm run lint",
"clone-readme": "cp ./README.md README-temp.md" "clone-readme": "cp ./README.md README-temp.md"
}, },
"dependencies": { "dependencies": {
"@11ty/eleventy-assets": "^1.0.5",
"@fontsource/inter": "^4.0.1", "@fontsource/inter": "^4.0.1",
"@nuxtjs/sitemap": "^2.4.0", "@nuxtjs/sitemap": "^2.4.0",
"@open-wc/webpack-import-meta-loader": "^0.4.7", "@open-wc/webpack-import-meta-loader": "^0.4.7",
@ -33,18 +39,25 @@
"vue-full-screen-file-drop": "^2.0.0" "vue-full-screen-file-drop": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@11ty/eleventy": "^0.11.1",
"@nuxtjs/tailwindcss": "^3.3.4", "@nuxtjs/tailwindcss": "^3.3.4",
"autoprefixer": "^8.6.4", "autoprefixer": "^8.6.4",
"babel-eslint": "^8.2.1", "babel-eslint": "^8.2.1",
"cssnano": "^4.1.10",
"dotenv": "^8.2.0",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-config-prettier": "^3.1.0", "eslint-config-prettier": "^3.1.0",
"eslint-loader": "^2.0.0", "eslint-loader": "^2.0.0",
"eslint-plugin-prettier": "2.6.2", "eslint-plugin-prettier": "2.6.2",
"eslint-plugin-vue": "^4.0.0", "eslint-plugin-vue": "^4.0.0",
"esm": "^3.2.25",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"nodemon": "^1.11.0", "nodemon": "^1.11.0",
"nuxt": "^2.14.11", "nuxt": "^2.14.11",
"postcss": "^8.2.4",
"postcss-cli": "^8.3.1",
"prettier": "1.14.3", "prettier": "1.14.3",
"replace-css-url": "^1.2.6",
"tailwindcss": "^1.9.6" "tailwindcss": "^1.9.6"
} }
} }

94
pages-eleventy/tv.11ty.js Normal file
View file

@ -0,0 +1,94 @@
import dotenv from 'dotenv'
import config from '../nuxt.config'
import VideoRow from '../components-eleventy/video/row.js'
// Setup dotenv
dotenv.config()
export const makeTitle = function ( video ) {
return `${ video.name } - ${ config.head.title }`
}
export const makeDescription = function ( video ) {
if ( video.payload.featuredApps.length === 0 ) return 'Apple Silicon performance and support videos'
const featuredAppsString = video.payload.featuredApps.slice(0, 5).map(app => app.name).join(', ')
// console.log('video.payload.featuredApps', video.payload.featuredApps)
return `Apple Silicon performance and support videos for ${ featuredAppsString }`
}
class TV {
// or `async data() {`
// or `get data() {`
data () {
return {
layout: 'default.11ty.js',
pagination: {
data: 'video-list',
size: 1,
alias: 'video'
},
eleventyComputed: {
title: ({ video }) => {
// console.log('data', data)
return makeTitle( video )
},
description: ({ video }) => {
return makeDescription( video )
},
},
permalink: ({ video }) => {
// console.log('data', data)
return `tv/${ video.slug }/`
}
}
}
render({ video }) {
// console.log('video.payload', Object.keys(video.payload))
return /* html */`
<section class="container pb-16">
<div class="flex flex-col items-center text-center space-y-6">
<div class="video-canvas w-screen flex flex-col justify-center items-center bg-black pt-16" style="left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;">
<div class="ratio-wrapper w-full max-w-4xl">
<div class="relative overflow-hidden w-full pb-16/9">
<iframe id="youtube-player-${ video.id }-17212" src="https://www.youtube-nocookie.com/embed/${ video.id }?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen class="absolute h-full w-full"></iframe>
</div>
</div>
<!---->
</div>
<div class="md:flex w-full justify-between space-y-4 md:space-y-0 md:px-10">
<h1 class="title text-lg md:text-2xl font-bold">${ video.name }</h1>
<div class="channel-credit"><a href="https://www.youtube.com/channel/UCptwuAv0XQHo1OQUSaO6NHw" target="_blank" rel="noopener" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow focus:shadow-outline-indigo bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out">Subscribe to Max Tech</a></div>
</div>
<hr class="w-full">
<div class="related-apps w-full">
<h2 class="subtitle text-xl md:text-2xl font-bold mb-3">Related Apps</h2>
<div class="featured-apps overflow-x-auto overflow-y-visible whitespace-no-wrap py-2 space-x-2"><a href="/app/xcode" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Xcode</a><a href="/app/logic-pro" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Logic Pro</a><a href="/app/lightroom" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Lightroom</a><a href="/app/lightroom-classic" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Lightroom Classic</a><a href="/app/cinebench" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Cinebench</a><a href="/app/final-cut-pro" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Final Cut Pro</a><a href="/app/geekbench" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Geekbench</a></div>
</div>
<div class="related-videos w-full">
<h2 class="subtitle text-xl md:text-2xl font-bold mb-3">Related Videos</h2>
${ this.boundComponent(VideoRow)( video.payload.relatedVideos ) }
</div>
</div>
</section>
`
}
}
module.exports = TV

View file

@ -82,7 +82,7 @@ export default {
const app = appList.find(app => (app.slug === slug)) const app = appList.find(app => (app.slug === slug))
const relatedVideos = videosRelatedToApp( app, videoList ) const relatedVideos = videosRelatedToApp( app, (new Set(videoList)) )
// Find other videos that also feature this video's app // Find other videos that also feature this video's app
// for (const video of videoList) { // for (const video of videoList) {

View file

@ -119,7 +119,7 @@ export default {
const app = gameList.find(app => (app.slug === slug)) const app = gameList.find(app => (app.slug === slug))
const relatedVideos = videosRelatedToApp( app, videoList ) const relatedVideos = videosRelatedToApp( app, (new Set(videoList)) )
// Find other videos that also feature this video's app // Find other videos that also feature this video's app
// for (const video of videoList) { // for (const video of videoList) {

View file

@ -51,7 +51,7 @@ export default {
text: app.text, text: app.text,
lastUpdated: app.lastUpdated, lastUpdated: app.lastUpdated,
category: app.category, category: app.category,
searchLinks: makeAppSearchLinks( app, videoList ) searchLinks: makeAppSearchLinks( app, (new Set(videoList)) )
} }
}) })
} }

View file

@ -80,7 +80,7 @@ export default {
allVideoAppsList.forEach( app => { allVideoAppsList.forEach( app => {
// Make the search links // Make the search links
const searchLinks = makeAppSearchLinks( app, videoList ) const searchLinks = makeAppSearchLinks( app, (new Set(videoList)) )
// If there are more than zero // If there are more than zero
// add them to our list // add them to our list

View file

@ -78,7 +78,7 @@ export default {
text: app.text, text: app.text,
lastUpdated: app.lastUpdated, lastUpdated: app.lastUpdated,
category: app.category, category: app.category,
searchLinks: makeAppSearchLinks( app, videoList ) searchLinks: makeAppSearchLinks( app, (new Set(videoList)) )
} }
}) })
} }

7
postcss.config.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: { config: './tailwind.config.js' },
autoprefixer: {},
cssnano: {},
}
}

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
purge: { purge: {
enabled: process.env.NODE_ENV === 'production', enabled: true,//process.env.NODE_ENV === 'production',
content: [ content: [
'components/**/*.vue', 'components/**/*.vue',
'layouts/**/*.vue', 'layouts/**/*.vue',