mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-15 06:35:20 -07:00
Merge branch 'feat/eleventy' into feat/nuxt-incremental
# Conflicts: # package-lock.json
This commit is contained in:
commit
b3a3d893fd
28 changed files with 8260 additions and 322 deletions
107
.eleventy.js
Normal file
107
.eleventy.js
Normal 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
5
.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
282
build-lists.js
Normal 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()
|
||||||
|
// }
|
||||||
74
components-eleventy/video/card.js
Normal file
74
components-eleventy/video/card.js
Normal 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>
|
||||||
|
`
|
||||||
|
}
|
||||||
80
components-eleventy/video/row.js
Normal file
80
components-eleventy/video/row.js
Normal 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>
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
81
components-eleventy/video/submit-card.js
Normal file
81
components-eleventy/video/submit-card.js
Normal 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>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
13
helpers/scroll.js
Normal 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' })
|
||||||
|
}
|
||||||
84
helpers/searchable-list.js
Normal file
84
helpers/searchable-list.js
Normal 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
|
||||||
|
}
|
||||||
200
layouts-eleventy/default.11ty.js
Normal file
200
layouts-eleventy/default.11ty.js
Normal 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
|
||||||
|
|
@ -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>
|
|
||||||
|
|
|
||||||
203
nuxt.config.js
203
nuxt.config.js
|
|
@ -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
7150
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
|
@ -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
94
pages-eleventy/tv.11ty.js
Normal 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
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)) )
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
7
postcss.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: { config: './tailwind.config.js' },
|
||||||
|
autoprefixer: {},
|
||||||
|
cssnano: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue