diff --git a/.gitignore b/.gitignore index b1acffc..8bc8a27 100644 --- a/.gitignore +++ b/.gitignore @@ -86,5 +86,6 @@ dist /README-temp.md /static/game-list.json /static/homebrew-list.json -.DS_Store +/static/video-list.json /commits-data.json +.DS_Store diff --git a/README.md b/README.md index 5c12975..f37a8dd 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [Attributed String Creator](https://apps.apple.com/us/app/attributed-string-creator-pro/id730928349) - ✅ Yes, full native support as of v1.9.6 - [Release Notes](https://www.bridgetech.io) * [BBEdit](https://www.barebones.com/products/bbedit/download.html) - ✅ Yes, full native support as of v13.5 - [Release Notes](https://www.barebones.com/support/bbedit/notes-13.5.html) * [Beyond Compare](https://www.scootersoftware.com/) - ✳️ Yes, works via Rosetta 2 - [Facebook Post](https://www.facebook.com/ScooterSoftware/posts/5178865142127412) +* [Charles Web Debugging Proxy](https://www.charlesproxy.com/download/) - 🔶 Unknown, more info needed - [Contribute](https://github.com/ThatGuySam/doesitarm/issues/122) * [CocoaPods](https://cocoapods.org/) - ✳️ Yes, it works via Rosetta 2 - [Issue](https://github.com/CocoaPods/CocoaPods/issues/9907) * [CotEditor](https://coteditor.com) - ✅ Yes, full native support as of 4.0.0 - [App Store](https://itunes.apple.com/app/coteditor/id1024640650) * [Cyberduck](https://cyberduck.io/download/) - ✳️ Yes, works via Rosetta 2 with native build in development - [Source](https://github.com/ThatGuySam/doesitarm/issues/333) @@ -44,6 +45,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [Filezilla](https://filezilla-project.org/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/17#issuecomment-729976000) * [Flutter](https://flutter.dev/docs/get-started/install/macos) - ✳️ Yes, works via Rosetta 2 with native support in development - [Github Issue](https://github.com/flutter/flutter/issues/60118#issuecomment-695341296) * [Fork](https://git-fork.com/) - ✅ Yes, full native support as of v2.1.0 - [Release notes](https://git-fork.com/releasenotes) +* [GCC ARM Embedded](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm) - ✳️ Yes, runs via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/105#issuecomment-750419946) * [Git Version Control](https://git-scm.com/download/mac) - ✅ Yes, Full Native Apple Silicon Support - [Source](https://github.com/ThatGuySam/doesitarm/issues/54#issuecomment-730568063) * [GitHub Desktop](https://desktop.github.com/) - ✳️ Yes, works via Rosetta 2 as of v2.6.0 with native support in development - [GitHub issue](https://github.com/ThatGuySam/doesitarm/issues/293) * [Go (golang)](https://golang.org/) - ✳️ Runs via Rosetta 2, with native builds available in beta - [Golang M1 Benchmark](https://docs.google.com/spreadsheets/d/1g4U7LAImfEcXRihJbySZcRr32tn6WSWAtslfXltds58/edit#gid=342445681) [Issue](https://github.com/golang/go/issues/38485) [Beta Download](https://golang.org/dl/#go1.16beta1) [Release Notes](https://tip.golang.org/doc/go1.16) @@ -56,6 +58,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [IntelliJ IDEA](https://www.jetbrains.com/idea/download/#section=mac) - ✳️ Runs via Rosetta 2, native support available as preview - [Official Jetbrains Issue](https://youtrack.jetbrains.com/issue/JBR-2526) [Download Preview](https://youtrack.jetbrains.com/issue/JBR-2526#focus=Comments-27-4589077.0-0) * [iTerm](https://iterm2.com/downloads.html) - ✅ Yes, fully supported as of v3.4.0 - [PR](https://github.com/gnachman/iTerm2/pull/421) * [Julia Language](https://julialang.org/downloads/) - ✳️ Yes, it works via Rosetta 2 - [Github Issue](https://github.com/JuliaLang/julia/issues/36617) +* [KiCad EDA](https://kicad.org/download/macos/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/199#issuecomment-736253625) * [LLVM Clang](https://releases.llvm.org/download.html) - ✳️ Yes, it works via Rosetta 2 - [Apple Forums](https://developer.apple.com/forums/thread/649992) * [MacDown](https://macdown.uranusjr.com/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/382) * [MacPorts](https://www.macports.org/install.php) - ✳️ Yes, some ports are native while others work via Rosetta 2. - [Discussion](https://github.com/ThatGuySam/doesitarm/issues/302). @@ -181,6 +184,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [Handbrake](https://handbrake.fr/) - ✅ Yes, natively supported as of v1.4.0 - [Github Issue](https://github.com/HandBrake/HandBrake/issues/2951) * [MKVToolNix](https://mkvtoolnix.download/downloads.html#macosx) - ✳️ Yes, works via Rosetta 2 - [GitHub issue](https://github.com/ThatGuySam/doesitarm/issues/344) * [OBS](https://obsproject.com/) - ✳️ Yes, works via Rosetta 2 - [MacRumors Discussion](https://forums.macrumors.com/threads/so-hows-m1-for-streamers-obs-streamlabs-obs-etc.2269239/) [Mention in Issue](https://github.com/obsproject/obs-studio/pull/3444#issuecomment-690216403) +* [OpenISS](https://github.com/OpenISS/OpenISS) - 🔶 Unknown, more info needed - [GitHub Issue](https://github.com/OpenISS/OpenISS/issues/72) [Contribute](https://github.com/ThatGuySam/doesitarm/issues/475) * [Premiere Pro](https://www.adobe.com/products/premiere.html) - ✳️ Yes, works via Rosetta 2 - [Official Adobe Status Page](https://helpx.adobe.com/download-install/kb/apple-silicon-m1-chip.html) * [Premiere Rush](https://www.adobe.com/products/premiere-rush.html) - ✳️ Yes, works via Rosetta 2 - [Official Adobe Status Page](https://helpx.adobe.com/download-install/kb/apple-silicon-m1-chip.html) * [Tumult Hype](https://tumult.com/hype/) - ✅ Yes, Full Native Apple Silicon Support as of v4.1 - [Blog Post](https://blog.tumult.com/2020/11/23/introducing-tumult-hype-v4-1-with-apple-silicon-and-big-sur-compatibility/) @@ -218,6 +222,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [BetterTouchTool](https://folivora.ai/) - ✅ Yes, fully supported as of v3.502 - [Issue Tracker](https://community.folivora.ai/) * [Blackmagic Disk Speed Test](https://apps.apple.com/app/id425264550) - ✅ Yes, Full Native Apple Silicon Support - [Verification](https://github.com/ThatGuySam/doesitarm/issues/359#issuecomment-736255914) * [Box Drive](https://www.box.com/resources/downloads) - ⏹ Not yet, but it's currently in development. - [Official Post](https://support.box.com/hc/en-us/community/posts/360051323454-Box-Drive-s-system-extension-failed-to-load?page=1#community_comment_1500000009302) +* [Calibre](https://calibre-ebook.com/download_osx) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/26#issuecomment-736778254) * [Chrome](https://www.google.com/chrome/) - ✅ Yes, fully supported as of v87 - [Article](https://9to5google.com/2020/11/17/chrome-mac-apple-silicon/) * [coconutBattery](https://www.coconut-flavour.com/coconutbattery/) - ✅ Yes, full native support as of v3.9.2 * [Coloban](https://www.coloban.com) - ⏹ Not yet, but it's currently in development. - [Coloban Forum Issue](https://forum.coloban.com/index.php?u=/topic/21/new-arm-based-apple-computers) @@ -264,6 +269,7 @@ Any comments, suggestions? [Let us know!](https://github.com/ThatGuySam/doesitar * [Notability](https://www.gingerlabs.com/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/417#issue-760864996) * [Notion Desktop](https://www.notion.so) - ✅ Yes, Full Native Apple Silicon Support - [Official Tweet](https://twitter.com/NotionHQ/status/1333867094463582208?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Etweet) [Verification](https://github.com/ThatGuySam/doesitarm/issues/378#issue-755529762) * [Noto](http://noto.ink/) - ✅ Yes, Full Native Apple Silicon Support - [App Store Story](https://apps.apple.com/us/story/id1540024103) +* [Obsidian](https://obsidian.md/download) - ✅ Yes, full native support as of v0.10.1 * [OmniFocus](https://www.omnigroup.com/omnifocus) - ✅ Yes, Full Native Apple Silicon Support as of v3.10 - [Release Notes](https://www.omnigroup.com/releasenotes/omnifocus) * [OmniGraffle](https://www.omnigroup.com/omnigraffle) - ✅ Yes, Full Native Apple Silicon Support as of v7.18 - [Release Notes](https://www.omnigroup.com/releasenotes/omnigraffle) * [OmniOutliner](https://www.omnigroup.com/omnioutliner) - ✅ Yes, Full Native Apple Silicon Support as of v5.8 - [Release Notes](https://www.omnigroup.com/releasenotes/omnioutliner) diff --git a/assets/css/tailwind.css b/assets/css/tailwind.css index cbbcd0e..b01a3fc 100644 --- a/assets/css/tailwind.css +++ b/assets/css/tailwind.css @@ -61,6 +61,26 @@ * @import "utilities/skew-transforms"; */ + +.ease { + transition-property: all; + transition-duration: 400ms; + /* easeInOutQuart */ + /* https://easings.net/en#easeInOutQuart */ + transition-timing-function: cubic-bezier(0.77, 0, 0.175, 1); +} + +.lazyload, +.lazyloading { + opacity: 0; +} +.lazyloaded { + @apply ease; + transition-property: opacity; + + opacity: 1; +} + .neumorphic-shadow, .hover\:neumorphic-shadow:hover { /* box-shadow: -0.25rem -0.25rem 0.5rem rgba(255, 255, 255, 0.07); */ diff --git a/components/link-button.vue b/components/link-button.vue index 4803a97..20d3aa5 100644 --- a/components/link-button.vue +++ b/components/link-button.vue @@ -4,8 +4,8 @@ :href="href" :target="target" :rel="rel" + :class="classlist" role="button" - class="relative inline-flex items-center px-4 py-2 border border-transparent leading-5 font-medium rounded-md text-white bg-darker neumorphic-shadow hover:bg-indigo-400 focus:outline-none focus:shadow-outline-indigo focus:border-indigo-600 active:bg-indigo-600 transition duration-150 ease-in-out" > @@ -23,6 +23,10 @@ export default { target: { type: String, default: null + }, + classGroups: { + type: Object, + default: () => {} } }, computed: { @@ -30,6 +34,30 @@ export default { if (this.href.charAt(0) === '/') return null return 'noopener' + }, + classlist () { + const defaultClassGroups = { + general: 'relative inline-flex items-center rounded-md px-4 py-2', + font: 'leading-5 font-medium', + text: 'text-white', + border: 'border border-transparent focus:outline-none focus:border-indigo-600', + shadow: 'neumorphic-shadow focus:shadow-outline-indigo', + bg: 'bg-darker hover:bg-indigo-400 active:bg-indigo-600', + transition: 'transition duration-150 ease-in-out' + } + + const mergedClassGroups = { + ...defaultClassGroups, + ...this.classGroups + } + + // if (this.isFocused) { + // delete mergedClassGroups.blur + // } else { + // delete mergedClassGroups.focus + // } + + return Object.values(mergedClassGroups) } } } diff --git a/components/navbar.vue b/components/navbar.vue index 22fe431..b6d7baf 100644 --- a/components/navbar.vue +++ b/components/navbar.vue @@ -146,6 +146,10 @@ export default { label: 'Categories', url: '/categories', }, + { + label: 'Benchmarks', + url: '/benchmarks', + }, { label: 'Homebrew', url: '/kind/homebrew', diff --git a/components/search.vue b/components/search.vue index 05a887d..84e1ddb 100644 --- a/components/search.vue +++ b/components/search.vue @@ -79,7 +79,7 @@ @@ -118,48 +118,71 @@ - - - - - - - + + + @@ -174,7 +197,7 @@ import { getAppCategory } from '~/helpers/categories.js' import { getAppEndpoint } from '~/helpers/app-derived.js' // import appList from '~/static/app-list.json' -// import EmailSubscribe from '~/components/email-subscribe.vue' +import LinkButton from '~/components/link-button.vue' // import RelativeTime from '~/components/relative-time.vue' import ListSummary from '~/components/list-summary.vue' @@ -183,6 +206,7 @@ export default { components: { // EmailSubscribe: () => process.client ? import('~/components/email-subscribe.vue') : null, ListSummary, + LinkButton, RelativeTime: () => process.client ? import('~/components/relative-time.vue') : null }, props: { @@ -325,6 +349,11 @@ export default { methods: { getAppCategory, getAppEndpoint, + getSearchLinks (app) { + if (typeof app.searchLinks === 'undefined') return [] + + return app.searchLinks + }, // Search priorities titleStartsWith (query, app) { const matches = app.name.toLowerCase().startsWith(query) diff --git a/components/video/bg-player.vue b/components/video/bg-player.vue new file mode 100644 index 0000000..8bac188 --- /dev/null +++ b/components/video/bg-player.vue @@ -0,0 +1,133 @@ + + + + + + + + + + + diff --git a/components/video/card.vue b/components/video/card.vue new file mode 100644 index 0000000..c2fcdc6 --- /dev/null +++ b/components/video/card.vue @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + {{ pill }} + + + + + + + {{ video.name }} + + + + + + + + diff --git a/components/video/channel-credit.vue b/components/video/channel-credit.vue new file mode 100644 index 0000000..0e31e79 --- /dev/null +++ b/components/video/channel-credit.vue @@ -0,0 +1,34 @@ + + + Subscribe to {{ video.channel.name }} + + + + diff --git a/components/video/player.vue b/components/video/player.vue new file mode 100644 index 0000000..b7a2bc9 --- /dev/null +++ b/components/video/player.vue @@ -0,0 +1,264 @@ + + + + + + + + + + + {{ timestamp.fullText }} + + + + + + + + + diff --git a/components/video/row.vue b/components/video/row.vue new file mode 100644 index 0000000..2ed5a68 --- /dev/null +++ b/components/video/row.vue @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/components/video/submit-card.vue b/components/video/submit-card.vue new file mode 100644 index 0000000..5dadc10 --- /dev/null +++ b/components/video/submit-card.vue @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + Submit Video + + + + + + + + diff --git a/helpers/app-derived.js b/helpers/app-derived.js index 8ca339f..e501653 100644 --- a/helpers/app-derived.js +++ b/helpers/app-derived.js @@ -3,9 +3,20 @@ export function getAppEndpoint ( app ) { // console.log('app', app) + if(app.category !== Object(app.category)) { + console.warn('app has no categories', app) + } + if (app.category.slug === 'homebrew') return `/formula/${app.slug}` if (app.category.slug === 'games') return `/game/${app.slug}` return `/app/${app.slug}` } + +export function getVideoEndpoint ( video ) { + + return `/tv/${video.slug}` +} + + diff --git a/helpers/build-homebrew-list.js b/helpers/build-homebrew-list.js index 7e7567c..9524f0d 100644 --- a/helpers/build-homebrew-list.js +++ b/helpers/build-homebrew-list.js @@ -84,8 +84,8 @@ class MakeHomebrewList { return false } - console.log('formulaData', formulaData) - console.log('formulae', formulae) + // console.log('formulaData', formulaData) + // console.log('formulae', formulae) // Check the official list first since it's data is newer and more frequently updated const hasStableFormula = (formulaData.bottle.stable !== undefined) diff --git a/helpers/build-video-list.js b/helpers/build-video-list.js new file mode 100644 index 0000000..94d3e6f --- /dev/null +++ b/helpers/build-video-list.js @@ -0,0 +1,161 @@ + +import slugify from 'slugify' +import axios from 'axios' + +import { byTimeThenNull } from './sort-list.js' +import { getVideoEndpoint } from './app-derived.js' +import parseGithubDate from './parse-github-date' + +export function matchesWholeWord (needle, haystack) { + return new RegExp('\\b' + needle + '\\b').test(haystack) +} + +const videoFeaturesApp = function (app, video) { + const appFuzzyName = app.name.toLowerCase() + if (video.title.toLowerCase().includes(appFuzzyName)) return true + + const appIsInTimestamps = video.timestamps.map( timestamp => timestamp.fullText.toLowerCase()).includes(appFuzzyName) + + if (appIsInTimestamps) return true + + if (matchesWholeWord(appFuzzyName, video.description.toLowerCase())) return true + + return false +} + +const generateVideoTags = function ( video ) { + const tags = { + 'benchmark': { + relatedWords: [ + 'benchmarks', + 'comparison', + 'speed test', + 'bench mark', + 'bench marks' + ] + }, + 'performance': { + relatedWords: [ + 'speed' + ] + } + } + + const videoTags = new Set() + + video.tags.forEach( tag => { + videoTags.add(tag) + }) + + // Match tags against video titles and descriptions + for (const tagKey in tags) { + + // Skip if this video already has this tag + // then skip it + if (videoTags.has(tagKey)) continue + + const matchingWords = [ + tagKey, + ...tags[tagKey].relatedWords + ] + + for (const tagWord of matchingWords) { + + // Skip if this video already has this tag + // then stop this loop + if (videoTags.has(tagKey)) break + + // Check title + if (matchesWholeWord(tagWord.toLowerCase(), video.title.toLowerCase())) { + videoTags.add(tagKey) + + // console.log(video.title, 'has', tagKey, 'tag') + + // We're done since the tag matched for the title + continue + } + + // Check description + if (matchesWholeWord(tagWord.toLowerCase(), video.description.toLowerCase())) { + videoTags.add(tagKey) + + // console.log(video.title, 'has', tagKey, 'tag') + } + } + } + + return videoTags +} + +export default async function ( applist ) { + + // Fetch Commits + const response = await axios.get(process.env.VIDEO_SOURCE) + // Extract commit from response data + const fetchedVideos = response.data + + // console.log('fetchedVideos', fetchedVideos) + + const videos = [] + + for (const videoId in fetchedVideos) { + + // Skip private videos + if (fetchedVideos[videoId].title === 'Private video') continue + + // Skip deleted videos + if (fetchedVideos[videoId].title === 'Deleted video') continue + + // Build video slug + const slug = slugify(`${fetchedVideos[videoId].title}-i-${videoId}`, { + lower: true, + strict: true + }) + + const apps = [] + // Generate new tag set based on api data + const tags = generateVideoTags(fetchedVideos[videoId]) + + for ( const app of applist ) { + if (videoFeaturesApp(app, fetchedVideos[videoId])) { + apps.push(app.slug) + + tags.add(app.category.slug) + } + } + + // console.log('fetchedVideos[videoId].rawData.snippet', fetchedVideos[videoId].rawData.snippet) + + const lastUpdated = { + raw: fetchedVideos[videoId].rawData.snippet.publishedAt, + timestamp: parseGithubDate(fetchedVideos[videoId].rawData.snippet.publishedAt).timestamp, + } + + // console.log('fetchedVideos[videoId].thumbnails', fetchedVideos[videoId].thumbnails) + + videos.push({ + name: fetchedVideos[videoId].title, + id: videoId, + lastUpdated, + apps, + slug, + 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, + endpoint: getVideoEndpoint({ + slug + }) + }) + } + + // console.log('videos', videos) + + // publishedAt + + return videos.sort(byTimeThenNull) +} diff --git a/helpers/get-list.js b/helpers/get-list.js index 5629673..f67787e 100644 --- a/helpers/get-list.js +++ b/helpers/get-list.js @@ -3,6 +3,14 @@ import gameList from '~/static/game-list.json' import homebrewList from '~/static/homebrew-list.json' import { byTimeThenNull } from '~/helpers/sort-list.js' +import { videosRelatedToApp } from '~/helpers/related.js' +import { getAppEndpoint } from '~/helpers/app-derived.js' + + +export const allVideoAppsList = [ + ...appList.sort(byTimeThenNull), + ...gameList, +] export const sortedAppList = appList.sort(byTimeThenNull) @@ -11,3 +19,54 @@ export const allList = [ ...homebrewList, ...gameList, ] + + +export function makeAppSearchLinks (app) { + + const videos = videosRelatedToApp(app) + + // If there are no videos + // then skip + if (videos.length === 0) return [] + + const searchLinks = [] + + const appBenchmarksUrl = `${getAppEndpoint(app)}/benchmarks` + + let hasPerformanceVideo = false + + for (const video of videos) { + // If there are no video tags + // then skip + if (video.tags.length === 0) continue + + // If there's any benchmark video then add + if (video.tags.includes('benchmark')) { + // Add a benchmark link + searchLinks.push({ + href: appBenchmarksUrl, + label: 'Benchmarks' + }) + + // then stop looking + break + } + + if (video.tags.includes('performance')) { + hasPerformanceVideo = true + } + } + + // If there was no bechmark video found + // but there was a performance video found + // then push Performance link + if (searchLinks.length === 0 && hasPerformanceVideo) { + // Add a performance link + searchLinks.push({ + href: appBenchmarksUrl, + label: 'Performance' + }) + } + + return searchLinks +} diff --git a/helpers/related.js b/helpers/related.js new file mode 100644 index 0000000..c22a510 --- /dev/null +++ b/helpers/related.js @@ -0,0 +1,57 @@ +import { allVideoAppsList } 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 ) { + const relatedApps = [] + + // Find the apps listed in this video + for (const app of allVideoAppsList) { + // Skip this app if it's not listed in the videos apps + if (!video.apps.includes(app.slug)) continue + + // Add this app to our featured app list + relatedApps.push(app) + } + + return relatedApps +} + +export function videosRelatedToVideo ( video ) { + const relatedVideos = {} + + const featuredApps = appsRelatedToVideo( video ) + + // Find other videos that also feature this video's app + for (const otherVideo of videoList) { + for (const app of featuredApps) { + // Skip if this app is not in the other video's apps + if (!otherVideo.apps.includes(app.slug)) continue + + // Skip if the other video is, in fact, this video + if (otherVideo.slug === video.slug) continue + + // Add this video to our related videos list + relatedVideos[otherVideo.id] = otherVideo + } + } + + return Object.values(relatedVideos) +} + + +export function videosRelatedToApp ( app ) { + const relatedVideos = {} + + // Find other videos that also feature this video's app + for (const video of videoList) { + if (!video.apps.includes(app.slug)) continue + + relatedVideos[video.id] = video + } + + return Object.values(relatedVideos) +} diff --git a/nuxt.config.js b/nuxt.config.js index bf27b80..7b6f456 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -5,9 +5,10 @@ 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 { categories } from './helpers/categories.js' -import { getAppEndpoint } from './helpers/app-derived.js' +import { getAppEndpoint, getVideoEndpoint } from './helpers/app-derived.js' const listsOptions = [ @@ -25,40 +26,62 @@ const listsOptions = [ } ] +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(async list => { + 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 - const methodName = `Building ${list.path}` - console.time(methodName) + // Build a video app list with apps and games only + const videoAppList = [ + ...appList, + ...gameList + ].flat(1) - // Run the build method - const builtList = await list.buildMethod() - - // 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 - })) + return await saveList(videoListOptions, videoAppList) + }) console.log('Build Lists finished') @@ -94,7 +117,12 @@ export default { ] }, routes() { - return Promise.all(listsOptions.map(async list => { + return Promise.all([ + ...listsOptions, + videoListOptions + ].map(async list => { + // Read saved lists + const methodName = `Reading ${list.path}` console.time(methodName) @@ -117,13 +145,27 @@ export default { const [ appRoutes, gameRoutes, + videoRoutes, homebrewRoutes ] = lists.map((list, listI) => { return list.map( app => { + + const isVideo = (app.category === undefined) + + if (isVideo) { + return getVideoEndpoint(app) + } + return getAppEndpoint(app) }) }) + // Build routes for app types that support benchmark endpoints + const benchmarkRoutes = [ + ...appRoutes, + // ...gameRoutes, + ].flat(1).map( route => `${route}/benchmarks`) + // console.log('homebrewRoutes', homebrewRoutes) const categoryRoutes = Object.keys(categories).map( slug => ({ @@ -135,7 +177,11 @@ export default { ...appRoutes, ...gameRoutes, ...homebrewRoutes, - ...categoryRoutes + + // Non-app routes + ...videoRoutes, + ...categoryRoutes, + ...benchmarkRoutes ] }) } diff --git a/package-lock.json b/package-lock.json index 0341195..2855571 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7522,6 +7522,11 @@ "launch-editor": "^2.2.1" } }, + "lazysizes": { + "version": "5.3.0-beta1", + "resolved": "https://registry.npmjs.org/lazysizes/-/lazysizes-5.3.0-beta1.tgz", + "integrity": "sha512-4ZEoV4UXvz5L96XBuaFphGlq8BVi78kIA4kL3ls2qSYMEgGBdXMgsh8u+bTVjbdwbXfzXtWc71OH4EfEEPVZoA==" + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", diff --git a/package.json b/package.json index f9ed288..22e2b91 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@nuxtjs/sitemap": "^2.4.0", "axios": "^0.21.0", "cross-env": "^5.2.0", + "lazysizes": "^5.3.0-beta1", "markdown-it": "^11.0.1", "marked": "^1.2.7", "node-html-parser": "^2.0.0", diff --git a/pages/app/_slug/benchmarks.vue b/pages/app/_slug/benchmarks.vue new file mode 100644 index 0000000..a04e829 --- /dev/null +++ b/pages/app/_slug/benchmarks.vue @@ -0,0 +1,265 @@ + + + + + + + + + + + + + No videos yet + + + + + + + + + + + Benchmark Videos + + + + + + + + Performance Videos + + + + + + + + More Videos + + + + + + + + + + + + + + diff --git a/pages/app/_slug.vue b/pages/app/_slug/index.vue similarity index 66% rename from pages/app/_slug.vue rename to pages/app/_slug/index.vue index 0c5b92a..3734fc6 100644 --- a/pages/app/_slug.vue +++ b/pages/app/_slug/index.vue @@ -1,14 +1,16 @@ - - - Does {{ app.name }} work on Apple Silicon? - - - {{ app.text }} - + + + + Does {{ app.name }} work on Apple Silicon? + + + {{ app.text }} + + - + @@ -24,6 +26,18 @@ >{{ (i === 0) ? 'View' : link.label }} + + + Related Videos + + + + (app.slug === slug)) + + const relatedVideos = videosRelatedToApp(app) + + // Find other videos that also feature this video's app + // for (const video of videoList) { + // if (!video.apps.includes(app.slug)) continue + + // relatedVideos.push(video) + // } + return { slug, - app: appList.find(app => (app.slug === slug)) + app, + relatedVideos: relatedVideos.map(video => { + // console.log('video', video) + return { + ...video, + endpoint: `${slug}/benchmarks#${video.id}` + } + }) } }, computed: { diff --git a/pages/benchmarks.vue b/pages/benchmarks.vue new file mode 100644 index 0000000..2d19357 --- /dev/null +++ b/pages/benchmarks.vue @@ -0,0 +1,252 @@ + + + + + + + + Benchmarks + + + + + + + + + + + + + + {{ video.name }} + + + + + + + + {{ app.name }} + + + + + + + {{ row.heading }} + + + + + + + + + + diff --git a/pages/game/_slug/benchmarks.vue b/pages/game/_slug/benchmarks.vue new file mode 100644 index 0000000..4fdc00d --- /dev/null +++ b/pages/game/_slug/benchmarks.vue @@ -0,0 +1,220 @@ + + + + + + + + + + + + + No videos yet + + + + + + + + + + {{ row.heading }} + + + + + + + + + + + diff --git a/pages/game/_slug.vue b/pages/game/_slug/index.vue similarity index 100% rename from pages/game/_slug.vue rename to pages/game/_slug/index.vue diff --git a/pages/games.vue b/pages/games.vue index b04b428..75e50ac 100644 --- a/pages/games.vue +++ b/pages/games.vue @@ -34,6 +34,27 @@ import ThomasCredit from '~/components/thomas-credit.vue' import gameList from '~/static/game-list.json' export default { + async asyncData () { + const { sortedAppList, allList, allVideoAppsList, makeAppSearchLinks } = await import('~/helpers/get-list.js') + const { default: gameList } = await import('~/static/game-list.json') + + return { + // Map game list + gameList: gameList.map( app => { + + return { + name: app.name, + status: app.status, + slug: app.slug, + // endpoint: app.endpoint, + text: app.text, + lastUpdated: app.lastUpdated, + category: app.category, + searchLinks: makeAppSearchLinks(app) + } + }) + } + }, components: { Search, LinkButton, @@ -59,9 +80,9 @@ export default { } }, computed: { - gameList() { - return gameList - } + // gameList() { + // return gameList + // } }, head() { return { diff --git a/pages/index.vue b/pages/index.vue index deb56ec..b1b96c2 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -11,7 +11,7 @@ @@ -71,13 +71,33 @@ export default { // const { default: appList } = await import('~/static/app-list.json') // const { default: gamelist } = await import('~/static/game-list.json') - const { sortedAppList, allList } = await import('~/helpers/get-list.js') + const { sortedAppList, allList, allVideoAppsList, makeAppSearchLinks } = await import('~/helpers/get-list.js') + const allAppSearchLinks = {} + // console.log('allVideoAppsList', allVideoAppsList) + + allVideoAppsList.forEach( app => { + // Make the search links + const searchLinks = makeAppSearchLinks(app) + + // If there are more than zero + // add them to our list + if (searchLinks.length > 0) { + allAppSearchLinks[app.slug] = searchLinks + } + }) return { // Filter app list to leave out data not needed for search initialAppList: sortedAppList.map( app => { + + let searchLinks = [] + + if (typeof allAppSearchLinks[app.slug] !== 'undefined') { + searchLinks = allAppSearchLinks[app.slug] + } + return { name: app.name, status: app.status, @@ -86,8 +106,10 @@ export default { text: app.text, lastUpdated: app.lastUpdated, category: app.category, + searchLinks } }), + allAppSearchLinks, customSummaryNumbers: getListSummaryNumbers(allList) } }, @@ -163,6 +185,8 @@ export default { // then stop if (this.fetchedAppList.length !== 0 || this.query.trim().length === 0) return + // console.log('this.allAppSearchLinks', this.allAppSearchLinks) + const fetchedListUrls = [ '/game-list.json', '/homebrew-list.json' @@ -179,7 +203,19 @@ export default { // console.log('fetchedLists', fetchedLists) - this.fetchedAppList = fetchedLists.flat(1) + this.fetchedAppList = fetchedLists.flat(1).map( app => { + + let searchLinks = [] + + if (typeof this.allAppSearchLinks[app.slug] !== 'undefined') { + searchLinks = this.allAppSearchLinks[app.slug] + } + + return { + ...app, + searchLinks + } + }) return } diff --git a/pages/kind/_slug.vue b/pages/kind/_slug.vue index ed81c88..f6c4947 100644 --- a/pages/kind/_slug.vue +++ b/pages/kind/_slug.vue @@ -55,26 +55,31 @@ import Search from '~/components/search.vue' import LinkButton from '~/components/link-button.vue' -import { byTimeThenNull } from '~/helpers/sort-list.js' - import { categories, getAppCategory } from '~/helpers/categories.js' -import appList from '~/static/app-list.json' -import gamelist from '~/static/game-list.json' -import homebrewList from '~/static/homebrew-list.json' - -const allList = [ - ...appList.sort(byTimeThenNull), - ...homebrewList, - ...gamelist, -] - export default { async asyncData ({ params: { slug } }) { - // Maybe I could import() here to reduce client script size + const { sortedAppList, allList, allVideoAppsList, makeAppSearchLinks } = await import('~/helpers/get-list.js') + const { default: gameList } = await import('~/static/game-list.json') + + const filteredList = allList.filter(app => { + return app.category.slug === slug + }) + return { slug, - // app: appList.find(app => (app.slug === slug)) + categoryAppList: filteredList.map( app => { + return { + name: app.name, + status: app.status, + slug: app.slug, + // endpoint: app.endpoint, + text: app.text, + lastUpdated: app.lastUpdated, + category: app.category, + searchLinks: makeAppSearchLinks(app) + } + }) } }, components: { @@ -104,20 +109,6 @@ export default { category () { return categories[this.slug] }, - categoryAppList () { - - const filteredList = allList.filter(app => { - return app.category.slug === this.slug - }) - - // const sortedList = list.sort(byTimeThenNull) - - // if (this.category.slug === 'homebrew') { - // return filteredList.slice(0, 300) - // } - - return filteredList - }, supportedAppList () { return this.categoryAppList.filter(app => { return app.status.includes('yes') diff --git a/pages/tv/_slug.vue b/pages/tv/_slug.vue new file mode 100644 index 0000000..a01f227 --- /dev/null +++ b/pages/tv/_slug.vue @@ -0,0 +1,153 @@ + + + + + + + + {{ video.name }} + + + + + + + + + + + Related Apps + + + {{ app.name }} + + + + + + Related Videos + + + + + + + + + + + + + diff --git a/tailwind.config.js b/tailwind.config.js index 8c3268d..0eaa5ff 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -75,7 +75,13 @@ module.exports = { '1/2-screen': '50vh', 'full-screen': '100vh' }, + width: { + '1/2-screen': '50vw', + 'full-screen': '100vw', + '2x-screen': '200vw' + }, minHeight: { + '1/2-screen': '50vh', '3/4-screen': '75vh', }, spacing: {