mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-15 06:35:20 -07:00
Merge branch 'feat/build-lists'
This commit is contained in:
commit
95b692a3be
31 changed files with 8180 additions and 294 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
|
||||
/static/app-list.json
|
||||
/README-temp.md
|
||||
/static/game-list.json
|
||||
/static/homebrew-list.json
|
||||
/static/video-list.json
|
||||
/static/**/*.json
|
||||
/commits-data.json
|
||||
.DS_Store
|
||||
/static/tailwind.css
|
||||
|
|
|
|||
|
|
@ -46,6 +46,31 @@
|
|||
*/
|
||||
@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
|
||||
* box with Tailwind.
|
||||
|
|
|
|||
300
build-lists.js
Normal file
300
build-lists.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
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 { getAppType, 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 = {}
|
||||
|
||||
this.endpointSets = {
|
||||
// Where Nuxt Routes and Payloads get stored
|
||||
nuxt: new Set(),
|
||||
|
||||
// Where Eleventy Endpoints get stored
|
||||
eleventy: 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 appType = getAppType( app )
|
||||
|
||||
if ( isVideo ) {
|
||||
// this.endpointSets.eleventy.add({
|
||||
// route: getVideoEndpoint(app),
|
||||
// payload: buildVideoPayload( app, this.allVideoAppsList, this.lists.video )
|
||||
// })
|
||||
|
||||
this.endpointSets.nuxt.add({
|
||||
route: getVideoEndpoint(app),
|
||||
payload: buildVideoPayload( app, this.allVideoAppsList, this.lists.video )
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// if ( isGame ) { console.log() }
|
||||
|
||||
// Add benchmark endpoints for apps and games
|
||||
if ( appType === 'app' || appType === 'game' ) {
|
||||
this.endpointSets.nuxt.add({
|
||||
route: `${getAppEndpoint(app)}/benchmarks`,
|
||||
payload: buildAppBenchmarkPayload( app, this.allVideoAppsList, this.lists.video )
|
||||
})
|
||||
}
|
||||
|
||||
// Add app or game endpoint
|
||||
this.endpointSets.nuxt.add({
|
||||
route: getAppEndpoint(app),
|
||||
payload: { app }
|
||||
})
|
||||
// console.log('Added to nuxt endpoints', getAppEndpoint(app))
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
Object.keys(categories).forEach( slug => {
|
||||
this.endpointSets.nuxt.add({
|
||||
route: '/kind/' + slug,
|
||||
// payload: appList
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// Save Nuxt Endpoints
|
||||
// await this.saveToJson(Array.from(this.endpointSets.nuxt), './static/nuxt-endpoints.json')
|
||||
|
||||
// // Save Eleventy Endpoints
|
||||
// await this.saveToJson(Array.from(this.endpointSets.eleventy), './static/eleventy-endpoints.json')
|
||||
|
||||
for ( const [ endpointSetName, endpointSet ] of Object.entries(this.endpointSets) ) {
|
||||
// Save Endpoints
|
||||
await this.saveToJson(Array.from( endpointSet ), `./static/${endpointSetName}-endpoints.json`)
|
||||
}
|
||||
|
||||
// Save sitemap endpoints
|
||||
await this.saveToJson(Object.values(this.endpointSets).map( endpointSet => {
|
||||
return Array.from( endpointSet )
|
||||
} ).flat(1), './static/sitemap-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">
|
||||
<picture>
|
||||
<source
|
||||
:sizes="thumbnailSizes"
|
||||
:data-srcset="thumbnailSrcset"
|
||||
:sizes="video.thumbnail.sizes"
|
||||
:data-srcset="video.thumbnail.srcset"
|
||||
type="image/jpg"
|
||||
>
|
||||
<img
|
||||
:data-src="video.thumbnails.default.url"
|
||||
:data-src="video.thumbnail.src"
|
||||
:alt="video.name"
|
||||
class="lazyload absolute h-full w-full object-cover"
|
||||
>
|
||||
|
|
@ -64,8 +64,6 @@
|
|||
|
||||
import 'lazysizes'
|
||||
|
||||
// import { getVideoEndpoint } from '~/helpers/app-derived.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
video: {
|
||||
|
|
@ -74,26 +72,6 @@ export default {
|
|||
}
|
||||
},
|
||||
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 () {
|
||||
// if this video has a banchmark tag
|
||||
// then pill is 'Benchmark'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,31 @@
|
|||
// App Data that is derived from other app data
|
||||
|
||||
|
||||
export function isVideo ( app ) {
|
||||
return app.hasOwnProperty('thumbnail') && app.hasOwnProperty('timestamps')
|
||||
}
|
||||
|
||||
export function getAppType ( app ) {
|
||||
|
||||
// Videos don't have a category
|
||||
// so we check for videos here
|
||||
if ( isVideo( app ) ) {
|
||||
return 'video'
|
||||
}
|
||||
|
||||
if(app.category !== Object(app.category)) {
|
||||
console.warn('app has no categories', app)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (app.category.slug === 'homebrew') return 'formula'
|
||||
|
||||
if (app.category.slug === 'games') return 'game'
|
||||
|
||||
return 'app'
|
||||
}
|
||||
|
||||
export function getAppEndpoint ( app ) {
|
||||
// console.log('app', app)
|
||||
|
||||
|
|
@ -7,11 +33,13 @@ export function getAppEndpoint ( app ) {
|
|||
console.warn('app has no categories', app)
|
||||
}
|
||||
|
||||
if (app.category.slug === 'homebrew') return `/formula/${app.slug}`
|
||||
const appType = getAppType( app )
|
||||
|
||||
if (app.category.slug === 'games') return `/game/${app.slug}`
|
||||
// if (app.category.slug === 'homebrew') return `/formula/${app.slug}`
|
||||
|
||||
return `/app/${app.slug}`
|
||||
// if (app.category.slug === 'games') return `/game/${app.slug}`
|
||||
|
||||
return `/${appType}/${app.slug}`
|
||||
}
|
||||
|
||||
export function getVideoEndpoint ( video ) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { appsRelatedToVideo, videosRelatedToVideo, videosRelatedToApp } from './
|
|||
// 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 { 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))
|
||||
|
||||
// Get featured apps
|
||||
const featuredApps = appsRelatedToVideo( video, allVideoAppsList )
|
||||
const featuredApps = appsRelatedToVideo( video, allVideoAppsListSet )
|
||||
|
||||
// Get related videos
|
||||
const relatedVideos = videosRelatedToVideo( video, allVideoAppsList, videoList )
|
||||
const relatedVideos = videosRelatedToVideo( video, allVideoAppsListSet, videoListSet )
|
||||
|
||||
return {
|
||||
video,
|
||||
featuredApps,
|
||||
// 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 ) {
|
||||
// const { allVideoAppsList } = await import('~/helpers/get-list.js')
|
||||
export function buildAppBenchmarkPayload ( app, allVideoAppsListSet, videoListSet ) {
|
||||
// const { allVideoAppsListSet } = await import('~/helpers/get-list.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 = {
|
||||
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 relatedVideos = videosRelatedToApp( app, videoList ).map(video => {
|
||||
const relatedVideos = videosRelatedToApp( app, videoListSet ).map(video => {
|
||||
// console.log('video', video)
|
||||
return {
|
||||
...video,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,30 @@ const generateVideoTags = function ( video ) {
|
|||
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 ) {
|
||||
|
||||
// Fetch Commits
|
||||
|
|
@ -139,14 +163,15 @@ export default async function ( applist ) {
|
|||
lastUpdated,
|
||||
apps,
|
||||
slug,
|
||||
channel:{
|
||||
channel: {
|
||||
name: fetchedVideos[videoId].rawData.snippet.channelTitle,
|
||||
id: fetchedVideos[videoId].rawData.snippet.channelId
|
||||
},
|
||||
// Convert tags set into array
|
||||
tags: Array.from(tags),
|
||||
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({
|
||||
slug
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// Universal JS imports only
|
||||
|
||||
|
||||
// Contains all types of properies to keep data consistent
|
||||
export const categoryTemplate = {
|
||||
|
|
@ -10,62 +12,85 @@ export const categoryTemplate = {
|
|||
|
||||
export const categories = {
|
||||
|
||||
'no-category': {
|
||||
id: 0
|
||||
},
|
||||
|
||||
// App lists
|
||||
'developer-tools': {
|
||||
id: 1,
|
||||
...categoryTemplate,
|
||||
label: 'Developer Tools',
|
||||
pluralLabel: 'Developer Tools',
|
||||
slug: 'developer-tools',
|
||||
},
|
||||
|
||||
'productivity-tools': {
|
||||
id: 2,
|
||||
...categoryTemplate,
|
||||
label: 'Productivity Tools',
|
||||
pluralLabel: 'Productivity Tools',
|
||||
slug: 'productivity-tools',
|
||||
},
|
||||
|
||||
'video-and-motion-tools': {
|
||||
id: 3,
|
||||
...categoryTemplate,
|
||||
label: 'Video and Motion Tools',
|
||||
pluralLabel: 'Video and Motion Tools',
|
||||
slug: 'video-and-motion-tools',
|
||||
},
|
||||
|
||||
'social-and-communication': {
|
||||
id: 4,
|
||||
...categoryTemplate,
|
||||
label: 'Social and Communication',
|
||||
pluralLabel: 'Social and Communication Apps',
|
||||
slug: 'social-and-communication',
|
||||
},
|
||||
|
||||
'entertainment-and-media-apps': {
|
||||
id: 5,
|
||||
...categoryTemplate,
|
||||
label: 'Entertainment and Media Apps',
|
||||
pluralLabel: 'Entertainment and Media Apps',
|
||||
slug: 'entertainment-and-media-apps',
|
||||
},
|
||||
|
||||
'music-and-audio-tools': {
|
||||
id: 6,
|
||||
...categoryTemplate,
|
||||
label: 'Music and Audio Tools',
|
||||
pluralLabel: 'Music and Audio Tools',
|
||||
slug: 'music-and-audio-tools',
|
||||
},
|
||||
|
||||
'photo-and-graphic-tools': {
|
||||
id: 7,
|
||||
...categoryTemplate,
|
||||
label: 'Photo and Graphic Tools',
|
||||
pluralLabel: 'Photo and Graphic Tools',
|
||||
slug: 'photo-and-graphic-tools',
|
||||
},
|
||||
|
||||
'science-and-research-software': {
|
||||
id: 8,
|
||||
...categoryTemplate,
|
||||
label: 'Science and Research Software',
|
||||
pluralLabel: 'Science and Research Software',
|
||||
slug: 'science-and-research-software',
|
||||
},
|
||||
|
||||
'3d-and-architecture': {
|
||||
id: 9,
|
||||
...categoryTemplate,
|
||||
label: '3D and Architecture',
|
||||
pluralLabel: '3D and Architecture Applications',
|
||||
slug: '3d-and-architecture',
|
||||
},
|
||||
|
||||
'vpns-security-and-privacy': {
|
||||
id: 10,
|
||||
...categoryTemplate,
|
||||
label: 'VPNs, Security, and Privacy',
|
||||
pluralLabel: 'VPN, Security, and Privacy Applications',
|
||||
|
|
@ -74,6 +99,7 @@ export const categories = {
|
|||
|
||||
// Special Lists
|
||||
'games': {
|
||||
id: 100,
|
||||
...categoryTemplate,
|
||||
label: 'Games',
|
||||
pluralLabel: 'Games',
|
||||
|
|
@ -87,6 +113,7 @@ export const categories = {
|
|||
]
|
||||
},
|
||||
'homebrew': {
|
||||
id: 101,
|
||||
...categoryTemplate,
|
||||
label: 'Homebrew',
|
||||
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
|
||||
// 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'
|
||||
|
||||
export function matchesWholeWord (needle, 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 = []
|
||||
|
||||
// Find the apps listed in this video
|
||||
for (const app of allVideoAppsList) {
|
||||
for (const app of allVideoAppsListSet) {
|
||||
// console.log('video', video)
|
||||
// Skip this app if it's not listed in the videos apps
|
||||
if (!video.apps.includes(app.slug)) continue
|
||||
|
|
@ -21,16 +23,16 @@ export function appsRelatedToVideo ( video, allVideoAppsList ) {
|
|||
return relatedApps
|
||||
}
|
||||
|
||||
export function videosRelatedToVideo ( video, allVideoAppsList, videoList ) {
|
||||
export function videosRelatedToVideo ( video, allVideoAppsListSet, videoListSet ) {
|
||||
const relatedVideos = {}
|
||||
|
||||
// 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
|
||||
for (const otherVideo of videoList) {
|
||||
for (const otherVideo of videoListSet) {
|
||||
for (const app of featuredApps) {
|
||||
// console.log('otherVideo', otherVideo)
|
||||
// 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 = {}
|
||||
|
||||
// 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
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
216
nuxt.config.js
216
nuxt.config.js
|
|
@ -1,94 +1,6 @@
|
|||
import { promises as fs } from 'fs'
|
||||
// import path from 'path'
|
||||
|
||||
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 {
|
||||
|
|
@ -104,12 +16,12 @@ export default {
|
|||
* https://nuxtjs.org/api/configuration-hooks/
|
||||
*/
|
||||
hooks: {
|
||||
build: {
|
||||
before: storeAppLists
|
||||
},
|
||||
generate: {
|
||||
before: storeAppLists
|
||||
}
|
||||
// build: {
|
||||
// before: storeAppLists
|
||||
// },
|
||||
// generate: {
|
||||
// before: storeAppLists
|
||||
// }
|
||||
},
|
||||
|
||||
generate: {
|
||||
|
|
@ -121,102 +33,9 @@ export default {
|
|||
]
|
||||
},
|
||||
routes() {
|
||||
return Promise.all([
|
||||
...listsOptions,
|
||||
videoListOptions
|
||||
].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)
|
||||
return fs.readFile('./static/nuxt-endpoints.json', 'utf-8')
|
||||
.then( endpointsJson => {
|
||||
return JSON.parse(endpointsJson)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
@ -230,6 +49,7 @@ export default {
|
|||
lang: 'en',
|
||||
},
|
||||
title: 'Does it ARM',
|
||||
description: pkg.description,
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{
|
||||
|
|
@ -307,7 +127,18 @@ export default {
|
|||
],
|
||||
|
||||
sitemap: {
|
||||
hostname: 'https://doesitarm.com'
|
||||
hostname: 'https://doesitarm.com',
|
||||
routes: async () => {
|
||||
// Get routes from previous build
|
||||
const sitemapEndpoints = await fs.readFile('./static/sitemap-endpoints.json', 'utf-8')
|
||||
.then( endpointsJson => {
|
||||
return JSON.parse(endpointsJson)
|
||||
})
|
||||
|
||||
console.log('Total Sitemap Endpoints', sitemapEndpoints.length)
|
||||
|
||||
return sitemapEndpoints.map( endpoint => endpoint.route )
|
||||
}
|
||||
},
|
||||
|
||||
buildModules: [
|
||||
|
|
@ -318,6 +149,9 @@ export default {
|
|||
** Build configuration
|
||||
*/
|
||||
build: {
|
||||
// parallel: true,
|
||||
// hardSource: true,
|
||||
cache: true,
|
||||
html: {
|
||||
minify: {
|
||||
minifyCSS: false,
|
||||
|
|
|
|||
6967
package-lock.json
generated
6967
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",
|
||||
"build": "nuxt build",
|
||||
"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:fix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
|
||||
"precommit": "npm run lint",
|
||||
"clone-readme": "cp ./README.md README-temp.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"@11ty/eleventy-assets": "^1.0.5",
|
||||
"@fontsource/inter": "^4.0.1",
|
||||
"@nuxtjs/sitemap": "^2.4.0",
|
||||
"@open-wc/webpack-import-meta-loader": "^0.4.7",
|
||||
|
|
@ -33,18 +39,25 @@
|
|||
"vue-full-screen-file-drop": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^0.11.1",
|
||||
"@nuxtjs/tailwindcss": "^3.3.4",
|
||||
"autoprefixer": "^8.6.4",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"cssnano": "^4.1.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^3.1.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-prettier": "2.6.2",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"esm": "^3.2.25",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^1.11.0",
|
||||
"nuxt": "^2.14.11",
|
||||
"postcss": "^8.2.4",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "1.14.3",
|
||||
"replace-css-url": "^1.2.6",
|
||||
"tailwindcss": "^1.9.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
100
pages-eleventy/tv.11ty.js
Normal file
100
pages-eleventy/tv.11ty.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import dotenv from 'dotenv'
|
||||
|
||||
import config from '../nuxt.config'
|
||||
|
||||
import VideoRow from '../components-eleventy/video/row.js'
|
||||
import { isVideo } from '../helpers/app-derived'
|
||||
|
||||
// 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: 'eleventy-endpoints',
|
||||
size: 1,
|
||||
alias: 'payload',
|
||||
before: function( data ) {
|
||||
return data.filter( entry => {
|
||||
return entry.payload.hasOwnProperty('video') && isVideo( entry.payload.video )
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
eleventyComputed: {
|
||||
title: ({ payload: { video } }) => {
|
||||
// console.log('data', data)
|
||||
return makeTitle( video )
|
||||
},
|
||||
description: ({ payload: { video } }) => {
|
||||
return makeDescription( video )
|
||||
},
|
||||
},
|
||||
|
||||
permalink: ({ payload: { video } }) => {
|
||||
// console.log('data', data)
|
||||
return `tv/${ video.slug }/`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render({ payload: { 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 relatedVideos = videosRelatedToApp( app, videoList )
|
||||
const relatedVideos = videosRelatedToApp( app, (new Set(videoList)) )
|
||||
|
||||
// Find other videos that also feature this video's app
|
||||
// for (const video of videoList) {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ export default {
|
|||
|
||||
})
|
||||
|
||||
console.log('lengths', Object.values(this.videoRows).map(row => [row.heading, row.videos.length]))
|
||||
// console.log('lengths', Object.values(this.videoRows).map(row => [row.heading, row.videos.length]))
|
||||
|
||||
},
|
||||
head() {
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default {
|
|||
|
||||
})
|
||||
|
||||
console.log('lengths', Object.values(this.videoRows).map(row => [row.heading, row.videos.length]))
|
||||
// console.log('lengths', Object.values(this.videoRows).map(row => [row.heading, row.videos.length]))
|
||||
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export default {
|
|||
|
||||
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
|
||||
// for (const video of videoList) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
text: app.text,
|
||||
lastUpdated: app.lastUpdated,
|
||||
category: app.category,
|
||||
searchLinks: makeAppSearchLinks( app, videoList )
|
||||
searchLinks: makeAppSearchLinks( app, (new Set(videoList)) )
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export default {
|
|||
|
||||
allVideoAppsList.forEach( app => {
|
||||
// Make the search links
|
||||
const searchLinks = makeAppSearchLinks( app, videoList )
|
||||
const searchLinks = makeAppSearchLinks( app, (new Set(videoList)) )
|
||||
|
||||
// If there are more than zero
|
||||
// add them to our list
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export default {
|
|||
text: app.text,
|
||||
lastUpdated: app.lastUpdated,
|
||||
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 = {
|
||||
purge: {
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
enabled: true,//process.env.NODE_ENV === 'production',
|
||||
content: [
|
||||
'components/**/*.vue',
|
||||
'layouts/**/*.vue',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue