Lots of cool new stuff

This commit is contained in:
Gamebrary 2022-10-19 22:22:35 -07:00
parent c4f662da66
commit 439916cf30
41 changed files with 2104 additions and 622 deletions

View file

@ -1,5 +1,4 @@
// firebase emulators:start --only functions
// TODO: Inject token using axios middleware
// Add json object in .runtimeconfig.json to use env variables locally
const functions = require('firebase-functions');

View file

@ -0,0 +1,506 @@
{
"kind": "customsearch#search",
"url": {
"type": "application/json",
"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&safe={safe?}&cx={cx?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&orTerms={orTerms?}&relatedSite={relatedSite?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
},
"queries": {
"request": [
{
"title": "Google Custom Search - Most Watched Games on Twitch twitchtracker",
"totalResults": "227000",
"searchTerms": "Most Watched Games on Twitch twitchtracker",
"count": 10,
"startIndex": 1,
"inputEncoding": "utf8",
"outputEncoding": "utf8",
"safe": "off",
"cx": "25c3c6c97e0db4365"
}
],
"nextPage": [
{
"title": "Google Custom Search - Most Watched Games on Twitch twitchtracker",
"totalResults": "227000",
"searchTerms": "Most Watched Games on Twitch twitchtracker",
"count": 10,
"startIndex": 11,
"inputEncoding": "utf8",
"outputEncoding": "utf8",
"safe": "off",
"cx": "25c3c6c97e0db4365"
}
]
},
"context": {
"title": "gamebrary"
},
"searchInformation": {
"searchTime": 0.515786,
"formattedSearchTime": "0.52",
"totalResults": "227000",
"formattedTotalResults": "227,000"
},
"items": [
{
"kind": "customsearch#result",
"title": "Most Watched Games on Twitch, October 2022 · TwitchTracker",
"htmlTitle": "<b>Most Watched Games on Twitch</b>, October 2022 · <b>TwitchTracker</b>",
"link": "https://twitchtracker.com/games",
"displayLink": "twitchtracker.com",
"snippet": "Most Watched Games on Twitch ; #1. Just Chatting. 315K ; #2. Overwatch 2. 273K ; #3. League of Legends. 218K ; #4. Grand Theft Auto V · 113K ; #5. Counter-Strike: ...",
"htmlSnippet": "<b>Most Watched Games on Twitch</b> ; #1. Just Chatting. 315K ; #2. Overwatch 2. 273K ; #3. League of Legends. 218K ; #4. Grand Theft Auto V &middot; 113K ; #5. Counter-Strike:&nbsp;...",
"cacheId": "Txl-67EDxH0J",
"formattedUrl": "https://twitchtracker.com/games",
"htmlFormattedUrl": "https://<b>twitchtracker</b>.com/<b>games</b>",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSEvgEyyzCMnZfi_KDj9q7HcADBRt6eZgPo5_M78JgoaQsngLtPtUuJNy7D",
"width": "275",
"height": "183"
}
],
"metatags": [
{
"og:image": "https://twitchtracker.com/img/share-tt.png",
"theme-color": "#ffffff",
"twitter:card": "summary_large_image",
"og:type": "website",
"twitter:site": "@twitchtracker_",
"og:site_name": "TwitchTracker",
"viewport": "width=device-width, initial-scale=1.0",
"og:title": "Most Watched Games on Twitch, October 2022",
"og:locale": "en_US",
"og:url": "https://twitchtracker.com/games",
"og:description": "Ranked by average concurrent viewers for the last 7 days, October 2022"
}
],
"cse_image": [
{
"src": "https://twitchtracker.com/img/share-tt.png"
}
]
}
},
{
"kind": "customsearch#result",
"title": "Most Watched Games on Twitch | Esports Content and Total",
"htmlTitle": "<b>Most Watched Games on Twitch</b> | Esports Content and Total",
"link": "https://newzoo.com/insights/rankings/top-games-twitch",
"displayLink": "newzoo.com",
"snippet": "Explore the ranking below to find the most popular games on Twitch across the top channels that broadcast game-related content. Each game's position in the ...",
"htmlSnippet": "Explore the ranking below to find the <b>most popular games on Twitch</b> across the top channels that broadcast game-related content. Each game&#39;s position in the&nbsp;...",
"cacheId": "pw0bWHtq5yEJ",
"formattedUrl": "https://newzoo.com/insights/rankings/top-games-twitch",
"htmlFormattedUrl": "https://newzoo.com/insights/rankings/<b>top</b>-<b>games</b>-<b>twitch</b>",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSQSOKzf4Z2TsxKZ7nBT2w32mQueu4DtyWf3CGfG0Gnl78MukQnu_hmlVM",
"width": "300",
"height": "168"
}
],
"metatags": [
{
"og:image": "https://newzoo.com/wp-content/uploads/2022/02/Rankings_MostWatchedonTwitch.jpg",
"og:type": "article",
"og:image:width": "1920",
"twitter:card": "summary",
"og:site_name": "Newzoo",
"og:title": "Most Watched Games on Twitch | Esports Content and Total",
"og:image:height": "1080",
"twitter:label1": "Est. reading time",
"og:image:type": "image/jpeg",
"og:description": "Most viewed games on Twitch every month, including the watched esports content and overall content. Click to see the ranking!",
"article:publisher": "https://www.facebook.com/NewzooHQ/",
"twitter:data1": "1 minute",
"facebook-domain-verification": "5azu56bev83xm26g6x5d8s0xe1kkxy",
"twitter:site": "@NewzooHQ",
"article:modified_time": "2022-05-20T08:11:21+00:00",
"viewport": "width=device-width, initial-scale=1.0",
"og:locale": "en_US",
"og:url": "https://newzoo.com/insights/rankings/top-games-twitch"
}
],
"cse_image": [
{
"src": "https://newzoo.com/wp-content/uploads/2022/02/Rankings_MostWatchedonTwitch.jpg"
}
]
}
},
{
"kind": "customsearch#result",
"title": "Twitch Usage and Growth Statistics: How Many People Use Twitch ...",
"htmlTitle": "<b>Twitch</b> Usage and Growth Statistics: How Many People Use <b>Twitch</b> ...",
"link": "https://backlinko.com/twitch-users",
"displayLink": "backlinko.com",
"snippet": "Jan 5, 2022 ... Most popular Twitch channels; Most popular Twitch games; Twitch by the money. Let's get right into it: Twitch statistics (Top Picks). Twitch ...",
"htmlSnippet": "Jan 5, 2022 <b>...</b> <b>Most popular Twitch</b> channels; <b>Most popular Twitch games</b>; <b>Twitch</b> by the money. Let&#39;s get right into it: <b>Twitch</b> statistics (<b>Top</b> Picks). <b>Twitch</b>&nbsp;...",
"cacheId": "Id-xSK1XCEcJ",
"formattedUrl": "https://backlinko.com/twitch-users",
"htmlFormattedUrl": "https://backlinko.com/<b>twitch</b>-users",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSm9HjdDrDlDhMyWycw_5H0IzujUND6QHoZSuIeZBlFurrdk0PXpBOz31I",
"width": "311",
"height": "162"
}
],
"metatags": [
{
"application-name": "Backlinko",
"msapplication-tilecolor": "#2b5797",
"og:image": "https://api.backlinko.com/app/uploads/2018/07/twitch-users-post-banner.png",
"theme-color": "#ffffff",
"og:type": "article",
"og:image:width": "1600",
"article:published_time": "2021-01-26T09:15:48-05:00",
"twitter:card": "summary_large_image",
"og:site_name": "Backlinko",
"apple-mobile-web-app-title": "Backlinko",
"og:title": "Twitch Usage and Growth Statistics: How Many People Use Twitch in 2022?",
"og:image:height": "837",
"bingbot": "index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1",
"og:description": "In-depth look at the user base of the popular video streaming platform, Twitch.",
"twitter:creator": "@Backlinko",
"article:publisher": "https://www.facebook.com/Backlinko",
"twitter:image": "https://api.backlinko.com/app/uploads/2018/07/twitch-users-post-banner.png",
"next-head-count": "36",
"twitter:site": "@Backlinko",
"article:modified_time": "2022-01-05T10:52:02+00:00",
"viewport": "width=device-width, initial-scale=1, shrink-to-fit=no",
"og:locale": "en_US",
"og:url": "https://backlinko.com/twitch-users"
}
],
"cse_image": [
{
"src": "https://api.backlinko.com/app/uploads/2018/07/twitch-users-post-banner.png"
}
]
}
},
{
"kind": "customsearch#result",
"title": "10 years of Twitch: Looking back on the top games | Nerd Street",
"htmlTitle": "10 years of <b>Twitch</b>: Looking back on the <b>top games</b> | Nerd Street",
"link": "https://nerdstreet.com/news/2021/6/twitch-10-year-anniversary-top-games-by-year",
"displayLink": "nerdstreet.com",
"snippet": "Jun 7, 2021 ... Emmett Shear, who would go on to become the CEO, that's what he was watching the most.” In June 2021, Starcraft 2 was the 57th most watched game ...",
"htmlSnippet": "Jun 7, 2021 <b>...</b> Emmett Shear, who would go on to become the CEO, that&#39;s what he was <b>watching</b> the <b>most</b>.” In June 2021, Starcraft 2 was the 57th <b>most watched game</b>&nbsp;...",
"cacheId": "3-WPbSYXJjYJ",
"formattedUrl": "https://nerdstreet.com/news/.../twitch-10-year-anniversary-top-games-by-year",
"htmlFormattedUrl": "https://nerdstreet.com/news/.../<b>twitch</b>-10-year-anniversary-<b>top</b>-<b>games</b>-by-year",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRuBdjA9GdF1iLdweUs8D7KOrC_Uj_Xf6OgkdlbM7wUgnOdzFekkSsEqjw",
"width": "300",
"height": "168"
}
],
"metatags": [
{
"msapplication-tilecolor": "#da532c",
"og:image": "https://cdn.sanity.io/images/zoz4y99f/production/c1889cfcfd99d10cd31e148119c23983e806db44-1600x900.png",
"theme-color": "#000000",
"twitter:title": "10 years of Twitch: Looking back on the top games | Nerd Street",
"og:type": "article",
"twitter:card": "summary_large_image",
"og:site_name": "Nerd Street",
"og:title": "10 years of Twitch: Looking back on the top games",
"og:article:publish_time": "2021-06-07T16:19:56Z",
"og:description": "From being the go-to site for League of Legends esports to seeing the rise of the battle royale genre, heres a look back on the top games throughout Twitchs first decade.",
"twitter:creator": "@Mitch_Reames",
"next-head-count": "25",
"twitter:site": "@nerdstgamers",
"viewport": "initial-scale=1.0, width=device-width",
"og:url": "https://nerdstreet.com/news/2021/6/twitch-10-year-anniversary-top-games-by-year",
"og:article:author": "Mitch Reames"
}
],
"cse_image": [
{
"src": "https://cdn.sanity.io/images/zoz4y99f/production/c1889cfcfd99d10cd31e148119c23983e806db44-1600x900.png"
}
]
}
},
{
"kind": "customsearch#result",
"title": "SullyGnome: Twitch Stats and Analytics - Games & Channels",
"htmlTitle": "SullyGnome: <b>Twitch</b> Stats and Analytics - <b>Games</b> &amp; Channels",
"link": "https://sullygnome.com/",
"displayLink": "sullygnome.com",
"snippet": "Twitch stats and analytics for your favorite streamers and games. View the fastest growing channels and most popular games.",
"htmlSnippet": "<b>Twitch</b> stats and analytics for your favorite streamers and <b>games</b>. View the fastest growing channels and <b>most popular games</b>.",
"cacheId": "7_sU_Z2qmOcJ",
"formattedUrl": "https://sullygnome.com/",
"htmlFormattedUrl": "https://sullygnome.com/",
"pagemap": {
"metatags": [
{
"viewport": "width=device-width, initial-scale=1.0"
}
]
}
},
{
"kind": "customsearch#result",
"title": "50+ Twitch Statistics for Content Creators in 2022 | Uscreen",
"htmlTitle": "50+ <b>Twitch</b> Statistics for Content Creators in 2022 | Uscreen",
"link": "https://www.uscreen.tv/blog/twitch-statistics/",
"displayLink": "www.uscreen.tv",
"snippet": "Jun 30, 2022 ... (TwitchTracker); In 2022, people have already watched over 6 ... The most popular game played on Twitch live streams is League of Legends.",
"htmlSnippet": "Jun 30, 2022 <b>...</b> (<b>TwitchTracker</b>); In 2022, people have already <b>watched</b> over 6 ... The <b>most popular game</b> played on <b>Twitch</b> live streams is League of Legends.",
"cacheId": "0QtmwNPFIxkJ",
"formattedUrl": "https://www.uscreen.tv/blog/twitch-statistics/",
"htmlFormattedUrl": "https://www.uscreen.tv/blog/<b>twitch</b>-statistics/",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcSs_ox-gFivRoK_sE6P214g2zG9Dtdt4f7BmWie8XS_gup5PruD_TEQ77E",
"width": "297",
"height": "170"
}
],
"metatags": [
{
"p:domain_verify": "53aef21ad69a7ee8ba0300f0f20a28a1",
"og:image": "https://www.uscreen.tv/wp-content/uploads/2022/06/twitch-statistics-hero.jpg",
"og:type": "article",
"article:published_time": "2022-06-30T09:41:19+00:00",
"og:image:width": "1050",
"twitter:card": "summary_large_image",
"og:site_name": "Uscreen",
"og:title": "50+ Twitch Statistics for Content Creators in 2022",
"og:image:height": "600",
"twitter:label1": "Written by",
"twitter:label2": "Est. reading time",
"og:image:type": "image/jpeg",
"msapplication-tileimage": "https://www.uscreen.tv/wp-content/uploads/2018/04/favico.png",
"og:description": "Pore over 50+ Twitch statistics you should know as a content creator in 2022.",
"twitter:creator": "@uscreentv",
"article:publisher": "https://www.facebook.com/uscreentv/",
"twitter:data1": "Parris Johnson",
"twitter:data2": "10 minutes",
"ahrefs-site-verification": "eca3e5350dd3e828ec60185a42d51d53976ff03dd3582abda3d2616f5d520995",
"twitter:site": "@uscreentv",
"article:modified_time": "2022-10-04T07:35:32+00:00",
"viewport": "width=device-width, initial-scale=1",
"og:locale": "en_US",
"og:url": "https://www.uscreen.tv/blog/twitch-statistics/"
}
],
"cse_image": [
{
"src": "https://www.uscreen.tv/wp-content/uploads/2022/06/twitch-statistics-hero.jpg"
}
]
}
},
{
"kind": "customsearch#result",
"title": "Most watched games on Twitch - SullyGnome",
"htmlTitle": "<b>Most watched games on Twitch</b> - SullyGnome",
"link": "https://sullygnome.com/games/30/watched",
"displayLink": "sullygnome.com",
"snippet": "Statistics the most watched games on Twitch in the past 30 days. ... Most watched · Most streamed · Peak viewers · Peak channels · Viewer distribution.",
"htmlSnippet": "Statistics the <b>most watched games on Twitch</b> in the past 30 days. ... Most watched &middot; Most streamed &middot; Peak viewers &middot; Peak channels &middot; Viewer distribution.",
"cacheId": "8nUywrr01sQJ",
"formattedUrl": "https://sullygnome.com/games/30/watched",
"htmlFormattedUrl": "https://sullygnome.com/<b>games</b>/30/<b>watched</b>",
"pagemap": {
"metatags": [
{
"viewport": "width=device-width, initial-scale=1.0"
}
]
}
},
{
"kind": "customsearch#result",
"title": "Lost Ark peaks at 1.27M on launch day, becomes most-watched ...",
"htmlTitle": "Lost Ark peaks at 1.27M on launch day, becomes <b>most</b>-<b>watched</b> ...",
"link": "https://www.invenglobal.com/articles/16393/lost-ark-peaks-at-127m-on-launch-day-becomes-most-watched-game-on-twitch",
"displayLink": "www.invenglobal.com",
"snippet": "Feb 9, 2022 ... Lost Ark: Source: Smilegate Lost Ark is now the most watched game on Twitch. According to analytics website TwitchTracker.",
"htmlSnippet": "Feb 9, 2022 <b>...</b> Lost Ark: Source: Smilegate Lost Ark is now the <b>most watched game on Twitch</b>. According to analytics website <b>TwitchTracker</b>.",
"cacheId": "3xZkfSBOVfQJ",
"formattedUrl": "https://www.invenglobal.com/.../lost-ark-peaks-at-127m-on-launch-day- becomes-most-watched-game-on-twitch",
"htmlFormattedUrl": "https://www.invenglobal.com/.../lost-ark-peaks-at-127m-on-launch-day- becomes-<b>most</b>-<b>watched</b>-<b>game-on-twitch</b>",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQYWr9o2ASOTxPBGCmZTOjThHjjHbmeLppGDy8dA0g8yeXkxafIm6d8X78",
"width": "300",
"height": "168"
}
],
"metatags": [
{
"og:image": "https://static.invenglobal.com/upload/image/2022/02/09/r1644433519704165.jpeg",
"twitter:card": "summary_large_image",
"twitter:title": "Lost Ark peaks at 1.27M on launch day, becomes most-watched game on Twitch",
"og:type": "article",
"article:published_time": "2022-02-09T18:34:05+00:00",
"article:section": "Lost Ark",
"og:site_name": "InvenGlobal",
"twitter:url": "https://www.invenglobal.com/articles/16393/lost-ark-peaks-at-127m-on-launch-day-becomes-most-watched-game-on-twitch",
"og:title": "Lost Ark peaks at 1.27M on launch day, becomes most-watched game on Twitch",
"og:date": "Feb 9, 2022",
"_token": "BoHbtAOpx952okGPGYiMFoE508xwElpOsmruvfEB",
"title": "Lost Ark peaks at 1.27M on launch day, becomes most-watched game on Twitch - Inven Global",
"twitter:creator": "@InvenGlobal",
"og:description": "Source: Smilegate Lost Ark is now the most watched game on Twitch. According to analytics website TwitchTracker.",
"twitter:image": "https://static.invenglobal.com/upload/thumb/2022/02/09/w/b1644433519704165.jpg",
"article:tag": "Asmongold",
"fb:app_id": "173953323052298",
"twitter:site": "@InvenGlobal",
"article:modified_time": "2022-02-09T19:12:23+00:00",
"viewport": "width=device-width, initial-scale=1.0",
"twitter:description": "Source: Smilegate Lost Ark is now the most watched game on Twitch. According to analytics website TwitchTracker.",
"og:author": "John Popko",
"og:url": "https://www.invenglobal.com/articles/16393/lost-ark-peaks-at-127m-on-launch-day-becomes-most-watched-game-on-twitch"
}
],
"cse_image": [
{
"src": "https://static.invenglobal.com/upload/image/2022/02/09/r1644433519704165.jpeg"
}
],
"hatomfeed": [
{}
]
}
},
{
"kind": "customsearch#result",
"title": "Twitch Revenue and Usage Statistics (2022) - Business of Apps",
"htmlTitle": "<b>Twitch</b> Revenue and Usage Statistics (2022) - Business of Apps",
"link": "https://www.businessofapps.com/data/twitch-statistics/",
"displayLink": "www.businessofapps.com",
"snippet": "Sep 6, 2022 ... Most viewed games on Twitch. League of Legends is by far the most popular game on Twitch, with Riot Games e-sports tournaments streamed on the ...",
"htmlSnippet": "Sep 6, 2022 <b>...</b> <b>Most viewed games on Twitch</b>. League of Legends is by far the <b>most popular game on Twitch</b>, with Riot Games e-sports tournaments streamed on the&nbsp;...",
"cacheId": "O0iecpsbRoIJ",
"formattedUrl": "https://www.businessofapps.com/data/twitch-statistics/",
"htmlFormattedUrl": "https://www.businessofapps.com/data/<b>twitch</b>-statistics/",
"pagemap": {
"cse_thumbnail": [
{
"src": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRtN_0Lka-RbCSDVQ2YWOV_KHCGyQIu0NvKCX0aCLKKpqUlcOox5yFNME_2",
"width": "278",
"height": "181"
}
],
"metatags": [
{
"og:image": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/uploads/2019/02/twitch.png",
"msapplication-square70x70logo": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/themes/boa/lib/assets/images/mstile-70x70.png",
"article:published_time": "2019-02-15T15:47:57+00:00",
"twitter:card": "summary_large_image",
"og:image:width": "292",
"og:site_name": "Business of Apps",
"twitter:label1": "Est. reading time",
"msapplication-wide310x150logo": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/themes/boa/lib/assets/images/mstile-310x150.png",
"og:image:type": "image/png",
"msapplication-tileimage": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/themes/boa/lib/assets/images/mstile-144x144.png",
"og:description": "Twitch is a livestreaming platform focused on video games. It was founded by Justin Kan in 2011, originally as a spin-off of Justin.tv. The latter started life in 2007 as a single channel, broadcasting Kans life live around the clock, pioneering the concept of lifecasting. The website attracted interest from others who were more interested in broadcasting their own lives than viewing that of Kans, which had nonetheless served as great exposure for Justin.TV. Acting on this interest, the site relaunched later in 2007, allowing users to create their own channels and broadcast their own content through the platform. Streaming games was not the original idea, but after seeing the interest from many users wanting to stream video games, the gaming category of Justin.TV was",
"twitter:image": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/uploads/2019/02/twitch.png",
"article:publisher": "https://www.facebook.com/thebusinessofapps",
"twitter:data1": "4 minutes",
"article_author": "Mansoor Iqbal",
"twitter:site": "@businessofapps",
"article:modified_time": "2022-09-06T13:15:15+01:00",
"msapplication-square310x310logo": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/themes/boa/lib/assets/images/mstile-310x310.png",
"msapplication-tilecolor": "#FFFFFF",
"og:type": "article",
"twitter:title": "Twitch Revenue and Usage Statistics (2022)",
"og:title": "Twitch Revenue and Usage Statistics (2022)",
"article_publisher": "Mansoor Iqbal",
"og:image:height": "190",
"og:updated_time": "2022-09-06T13:15:15+01:00",
"msapplication-square150x150logo": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/themes/boa/lib/assets/images/mstile-150x150.png",
"fb:app_id": "529576650555031",
"viewport": "width=device-width, initial-scale=1",
"twitter:description": "Twitch is a livestreaming platform focused on video games. It was founded by Justin Kan in 2011, originally as a spin-off of Justin.tv. The latter started life in 2007 as a single channel, broadcasting Kans life live around the clock, pioneering the concept of lifecasting. The website attracted interest from others who were more interested in broadcasting their own lives than viewing that of Kans, which had nonetheless served as great exposure for Justin.TV. Acting on this interest, the site relaunched later in 2007, allowing users to create their own channels and broadcast their own content through the platform. Streaming games was not the original idea, but after seeing the interest from many users wanting to stream video games, the gaming category of Justin.TV was",
"og:locale": "en_US",
"og:url": "https://www.businessofapps.com/data/twitch-statistics/"
}
],
"cse_image": [
{
"src": "https://1z1euk35x7oy36s8we4dr6lo-wpengine.netdna-ssl.com/wp-content/uploads/2019/02/twitch.png"
}
]
}
},
{
"kind": "customsearch#result",
"title": "Grand Theft Auto V in Top 5 Most Watched Games on Twitch in ...",
"htmlTitle": "Grand Theft Auto V in Top 5 <b>Most Watched Games on Twitch</b> in ...",
"link": "https://www.whatgadget.net/grand-theft-auto-v-in-top-5-most-watched-games-on-twitch-in-august-83-3m-hours-in-viewership-more-than-double-dota-2/",
"displayLink": "www.whatgadget.net",
"snippet": "Sep 20, 2020 ... Grand Theft Auto V (GTA V) was the fifth most-watched game on Twitch in August 2020. It had 112140 concurrent viewers on average.",
"htmlSnippet": "Sep 20, 2020 <b>...</b> Grand Theft Auto V (GTA V) was the fifth <b>most</b>-<b>watched game on Twitch</b> in August 2020. It had 112140 concurrent viewers on average.",
"cacheId": "CXPa3QP1wMsJ",
"formattedUrl": "https://www.whatgadget.net/grand-theft-auto-v-in-top-5-most-watched-games -on-twitch-in-august-83-3m-hours-in-viewership-more-than-double-dota-2/",
"htmlFormattedUrl": "https://www.whatgadget.net/grand-theft-auto-v-in-<b>top</b>-5-<b>most</b>-<b>watched</b>-<b>games -on-twitch</b>-in-august-83-3m-hours-in-viewership-<b>more</b>-than-double-dota-2/",
"pagemap": {
"hcard": [
{
"url_text": "What Gadget",
"fn": "What Gadget",
"photo": "https://cdn.whatgadget.net/wp-content/uploads/2020/09/05082235/WG-copy-440x440.png",
"url": "https://www.whatgadget.net/author/what-gadget/"
}
],
"cse_thumbnail": [
{
"src": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcRuEymPx8cHe4O2txUXe18ka_XzAv6dbWN6WI9W6A4zutMDBKYw9CuKbmOc",
"width": "329",
"height": "153"
}
],
"metatags": [
{
"og:image": "https://www.whatgadget.net/wp-content/uploads/2020/09/header.jpg",
"og:type": "article",
"article:published_time": "2020-09-20T13:22:35+00:00",
"og:image:width": "460",
"twitter:card": "summary_large_image",
"og:site_name": "What Gadget",
"og:title": "Grand Theft Auto V in Top 5 Most Watched Games on Twitch in August; 83.3M Hours in Viewership, More than Double Dota 2 - What Gadget",
"og:image:height": "215",
"twitter:label1": "Written by",
"twitter:label2": "Estimated reading time",
"og:image:type": "image/jpeg",
"msapplication-tileimage": "https://cdn.whatgadget.net/wp-content/uploads/2020/09/05082311/cropped-WG-copy-e1599134950257-270x270.png",
"og:description": "Grand Theft Auto V (GTA V) was the fifth most-watched game on Twitch in August 2020. It had 112,140 concurrent viewers on average.",
"twitter:creator": "@WhatGadgetUK",
"article:publisher": "https://www.facebook.com/WhatGadget.net",
"twitter:data1": "What Gadget",
"twitter:data2": "1 minute",
"twitter:site": "@WhatGadgetUK",
"article:modified_time": "2020-09-22T17:12:06+00:00",
"viewport": "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1",
"og:locale": "en_GB",
"og:url": "https://www.whatgadget.net/grand-theft-auto-v-in-top-5-most-watched-games-on-twitch-in-august-83-3m-hours-in-viewership-more-than-double-dota-2/"
}
],
"cse_image": [
{
"src": "https://www.whatgadget.net/wp-content/uploads/2020/09/header.jpg"
}
],
"hatomfeed": [
{}
]
}
}
]
}

View file

@ -17,6 +17,10 @@
"@bbob/core": "^2.8.1",
"@bbob/html": "^2.8.1",
"@bbob/preset-html5": "^2.8.1",
"@milkdown/core": "^6.4.1",
"@milkdown/preset-commonmark": "^6.4.2",
"@milkdown/prose": "^6.4.1",
"@milkdown/theme-nord": "^6.4.1",
"@vue-stripe/vue-stripe": "^4.4.2",
"axios": "^0.21.1",
"bootstrap": "^4.5.2",
@ -28,6 +32,7 @@
"firebaseui": "^4.8.0",
"lodash.chunk": "^4.2.0",
"lodash.groupby": "^4.6.0",
"lodash.merge": "^4.6.2",
"lodash.orderby": "^4.6.0",
"lodash.sortby": "^4.7.0",
"marked": "^4.0.14",

View file

@ -1,3 +1,9 @@
<!-- TODO: translate strings -->
<!-- TODO: allow for anonymous boards, prompt to sign up -->
<!-- TODO: switch toggle -->
<!-- TODO: add markdown wysiwyg -->
<!-- TODO: add help section -->
<!-- TODO: bring notifications back! -->
<!-- TODO: fix favicon broken link -->
<template>
<div
@ -103,7 +109,6 @@ export default {
},
updateWallpaperUrl(value) {
console.log(value);
this.backgroundImageUrl = value;
},
@ -116,7 +121,7 @@ export default {
},
init() {
this.$store.dispatch('LOAD_IGDB_PLATFORMS');
// TODO: get platforms from constants
if (this.isPublicRoute) {
return;

View file

@ -3,9 +3,7 @@
<!-- TODO: clone/fork board -->
<!-- TODO: like/favorite board -->
<template lang="html">
<div
:class="['board px-3 pb-3', { dragging, empty }]"
>
<div>
<board-placeholder v-if="loading" />
<template v-else-if="showBoard">
@ -37,17 +35,8 @@
</b-button>
</portal>
<game-list
v-for="(list, listIndex) in board.lists"
:list="list"
:listIndex="listIndex"
:key="list.name"
/>
<add-list
v-if="isBoardOwner"
:empty="empty"
/>
<basic-board v-if="board.type === 'basic'" />
<kanban-board v-else />
</template>
<b-alert
@ -62,16 +51,16 @@
<script>
import BoardPlaceholder from '@/components/Board/BoardPlaceholder';
import AddList from '@/components/Board/AddList';
import GameList from '@/components/Lists/GameList';
import KanbanBoard from '@/components/Board/KanbanBoard';
import BasicBoard from '@/components/Board/BasicBoard';
import chunk from 'lodash.chunk';
import { mapState, mapGetters } from 'vuex';
export default {
components: {
GameList,
BoardPlaceholder,
AddList,
KanbanBoard,
BasicBoard,
},
data() {
@ -99,10 +88,6 @@ export default {
return this.$route.params?.id;
},
empty() {
return this.board?.lists?.length === 0;
},
isBoardCached() {
return this.board.id === this.boardId;
},

View file

@ -26,27 +26,9 @@
/>
</b-form-group>
<!-- <b-form-group
label="Board template"
>
<b-form-radio-group
v-model="selectedTemplate"
:options="boardTemplatesOptions"
name="radios-btn-default"
description="Optional"
/>
<b-row v-if="selectedTemplate" class="mt-3">
<b-col v-for="column in boardTemplates[selectedTemplate]" :key="column">
<b-card
:header="column"
header-tag="header"
header-class="p-1 pl-2"
hide-footer
/>
</b-col>
</b-row>
</b-form-group> -->
<pre>
{{ board.type }}
</pre>
<b-button
variant="primary"
@ -69,20 +51,10 @@ export default {
name: '',
description: '',
lists: [],
type: 'kanban',
},
saving: false,
selectedTemplate: null,
// boardTemplatesOptions: [
// { value: null, text: 'Blank' },
// { value: 'standard', text: 'Standard' },
// { value: 'detailed', text: 'Detailed' },
// { value: 'completionist', text: 'Completionist' },
// ],
// boardTemplates: {
// standard: ['Owned', 'Wishlist'],
// detailed: ['Physical', 'Digital', 'Wishlist'],
// completionist: ['Owned', 'Playing', 'Completed'],
// },
};
},
@ -94,16 +66,17 @@ export default {
// TODO: put default board in constant
const payload = {
...this.board,
// TODO: set default lists based on board type
games: [],
lastUpdated: Date.now(),
lists: [{
name: 'Click to rename',
games: [358],
games: [],
settings: {
showReleaseDates: false,
sortOrder: 'sortByCustom',
showGameTags: false,
showGameNotes: false,
showGameProgress: false,
highlightCompletedGames: false,
showGameCount: false,
view: 'single'
},

View file

@ -1,3 +1,4 @@
<!-- TODO: refactor saving, create payload locally -->
<template lang="html">
<section>
<b-container>
@ -44,6 +45,17 @@
/>
</b-form-group>
<b-form-group
label="Board type"
label-for="name"
>
<b-dropdown id="dropdown-1" :text="board.type">
<b-dropdown-item @click="setBoardType('kanban')">Kanban</b-dropdown-item>
<!-- <b-dropdown-item @click="setBoardType('tiers')">Tiers</b-dropdown-item> -->
<b-dropdown-item @click="setBoardType('basic')">Basic</b-dropdown-item>
</b-dropdown>
</b-form-group>
<b-form-group
:label="$t('board.settings.descriptionLabel')"
label-for="description"
@ -145,7 +157,6 @@ import { mapState, mapGetters } from 'vuex';
import WallpapersList from '@/components/WallpapersList';
import VSwatches from 'vue-swatches'
import MiniBoard from '@/components/Board/MiniBoard';
import orderby from 'lodash.orderby';
export default {
components: {
@ -180,10 +191,17 @@ export default {
this.board = await this.$store.dispatch('LOAD_BOARD', this.boardId);
if (!this.board.type) this.board.type = 'kanban';
this.loading = false;
},
setBoardType(type) {
console.log('type', type);
// TODO: check if switching to basic while having more than 1 list
this.board.type = type;
},
confirmDelete() {
this.$bvModal.msgBoxConfirm('Are you sure you want to delete this board?', {
title: 'Delete board',
@ -208,7 +226,7 @@ export default {
this.loading = false;
this.$bvToast.toast('Board removed');
this.$router.push({ name: 'home' });
this.$router.push({ name: 'boards' });
},
selectWallpaper(wallpaper) {
@ -219,17 +237,7 @@ export default {
async saveBoard() {
this.saving = true;
// const { board } = this;
//
// const payload = {
// ...board,
// description: this.description,
// name: this.name,
// isPublic: this.isPublic,
// theme: this.theme,
// };
// this.$store.commit('SET_ACTIVE_BOARD', this.board);
this.board.lastUpdated = Date.now();
await this.$store.dispatch('SAVE_BOARD')
.catch(() => {

View file

@ -120,14 +120,14 @@
<!-- TODO: add release date styles: countdown/simple date -->
<b-form-checkbox
<!-- <b-form-checkbox
v-model="list.settings.showGameProgress"
name="check-button"
class="mb-2"
switch
>
{{ $t('board.list.showGameProgress') }}
</b-form-checkbox>
</b-form-checkbox> -->
<b-form-checkbox
v-model="list.settings.highlightCompletedGames"

View file

@ -2,7 +2,6 @@
<b-dropdown
no-caret
toggle-class="p-0 px-2 mt-n2"
size="sm"
>
<template #button-content>
<i class="fa fa-plus small pr-1" aria-hidden="true" />
@ -66,7 +65,6 @@ export default {
computed: {
...mapState(['games', 'boards', 'wallpapers']),
// TODO: handle this at action/mutation level OR use getter at least
filteredBoards() {
return this.boards
.filter(({ name }) => name.toLowerCase().includes(this.searchText.toLowerCase()));

View file

@ -0,0 +1,34 @@
<template lang="html">
<div class="basic-board my-3">
<basic-game-list :list="list" />
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import BasicGameList from '@/components/Lists/BasicGameList';
export default {
components: {
BasicGameList,
},
computed: {
...mapState(['board']),
list() {
const [firstList] = this.board?.lists;
return firstList || [];
}
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.basic-board {
width: 600px;
max-width: 100%;
margin: 0 auto;
}
</style>

View file

@ -0,0 +1,40 @@
<template lang="html">
<div class="board px-3 pb-3">
<game-list
v-for="(list, listIndex) in board.lists"
:list="list"
:listIndex="listIndex"
:key="list.name"
/>
<add-list
v-if="isBoardOwner"
:empty="empty"
/>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import AddList from '@/components/Board/AddList';
import GameList from '@/components/Lists/GameList';
export default {
components: {
GameList,
AddList,
},
computed: {
...mapState(['board']),
...mapGetters(['isBoardOwner']),
empty() {
return this.board?.lists?.length === 0;
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
</style>

View file

@ -1,10 +1,13 @@
<!-- TODO: make steam description match standard desc -->
<!-- TODO: http://localhost:4000/g/114146/angry-video-game-nerd-i-and-ii-deluxe -->
<template lang="html">
<div class="game-description">
<div :class="['game-description', source]">
<b-spinner v-if="loading" class="spinner-centered" />
<template v-else>
<div v-html="description" />
<span class="text-muted mt-n3 mb-3">Source: {{ source }}</span>
<!-- TODO: link to source -->
<span class="text-muted mt-n3 mb-3 text-capitalize">Source: {{ source }}</span>
</template>
</div>
</template>
@ -29,8 +32,8 @@ export default {
},
source() {
if (this.wikipediaExtract) return 'Wikipedia';
if (this.steamDescription) return 'Steam';
if (this.wikipediaExtract) return 'wikipedia';
if (this.steamDescription) return 'steam';
return 'IGDB';
},
@ -78,6 +81,10 @@ export default {
<style lang="scss" rel="stylesheet/scss">
.game-description {
&.steam {
// Steam overrides
}
h2, h3 {
margin: 0;
}

View file

@ -1,8 +1,8 @@
<template lang="html">
d<template lang="html">
<b-card class="mt-3 small">
<!-- TODO: merge release Dates and platofmrs -->
<div v-if="gameGenres" class="pr-2 pb-2">
<strong>Genres:</strong>
<strong class="text-muted">Genres:</strong>
{{ gameGenres }}
</div>
@ -30,11 +30,18 @@
<div class="pr-2 pb-2">
<strong class="text-muted">Available for: </strong>
<span class="text-wrap">{{ gamePlatforms || 'N/A' }}</span>
<span
v-for="(platform, index) in gamePlatforms"
:key="platform.id"
>
<b-link :to="{ name: 'platform', params: { id: platform.id }}">{{ platform.name }}</b-link>
<template v-if="index < gamePlatforms.length - 1">, </template>
</span>
</div>
<div class="pr-2 pb-2">
<strong>{{ $t('board.gameModal.releaseDate') }}</strong>
<!-- TODO: merge release Dates and platforms -->
<!-- <div class="pr-2 pb-2">
<strong class="text-muted">{{ $t('board.gameModal.releaseDate') }}</strong>
<ol v-if="releaseDates" class="list-unstyled mb-0">
<li
v-for="{ id, platform, date } in releaseDates"
@ -47,8 +54,33 @@
<div v-else>
Not released yet
</div>
</div> -->
<div class="pr-2 pb-2">
<strong class="text-muted">Tags</strong>
<br />
<b-button
v-for="({ bgColor, textColor, name, index }) in tagsApplied"
:key="index"
rounded
size="sm"
variant="transparent"
class="mr-1 mb-2"
:style="`background-color: ${bgColor}; color: ${textColor}`"
:to="{ name: 'tag.edit', params: { id: index } }"
>
<i class="fa-solid fa-tag mr-1" />
{{ name }}
</b-button>
<game-tags-dropdown v-if="user" />
</div>
<strong class="text-muted">Other links</strong>
<br>
<b-button
v-for="{ url, id, icon, svg } in gameLinks"
:href="url"
@ -77,14 +109,34 @@
<script>
import { mapGetters, mapState } from 'vuex';
import GameTagsDropdown from '@/components/Game/GameTagsDropdown';
export default {
components: {
GameTagsDropdown,
},
data() {
return {
progress: 0,
saving: false,
deleting: false,
}
},
computed: {
...mapGetters(['platformNames', 'gameLinks']),
...mapState(['game']),
...mapState(['game', 'tags', 'user', 'progresses']),
tagsApplied() {
if (!this.tags) return [];
return this.tags?.map((tag, index) => ({ ...tag, index }))
.filter((tag) => tag?.games?.includes(this.game?.id));
},
gamePlatforms() {
return this.game?.platforms?.map(({ name }) => name).join(', ');
return this.game?.platforms;
},
gameDevelopers() {
@ -135,5 +187,50 @@ export default {
});
},
},
async mounted() {
if (!this.tags) {
await this.$store.dispatch('LOAD_TAGS');
}
this.progress = this.progresses?.[this.game?.id]
? JSON.parse(JSON.stringify(this.progresses?.[this.game?.id]))
: 0;
},
methods: {
async deleteProgress() {
const { id, name } = this.game;
this.deleting = true;
this.$store.commit('REMOVE_GAME_PROGRESS', id);
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE')
.catch(() => {
this.$bvToast.toast('There was an error deleting your progress', { title: `${name} progress`, variant: 'error' });
this.deleting = false;
});
this.deleting = false;
},
async saveProgress() {
this.saving = true;
this.$store.commit('SET_GAME_PROGRESS', {
progress: this.progress,
gameId: this.game?.id,
});
await this.$store.dispatch('SAVE_PROGRESSES')
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error saving your progress', { variant: 'error' });
});
this.saving = false;
},
},
};
</script>

View file

@ -1,13 +1,10 @@
<template lang="html">
<div v-if="boardsWithGame.length" class="mt-4">
<p class="small mb-2">Found in</p>
<div class="mt-4">
<b-button
v-for="board in boardsWithGame"
:to="{ name: 'board', params: { id: board.id } }"
:key="board.id"
variant="light"
size="sm"
:to="{ name: 'board', params: { id: board.id } }"
variant="outline-primary"
class="mr-2 py-0 mb-2"
>
<small>{{ board.name }}</small>
@ -30,8 +27,9 @@ export default {
...mapState(['board', 'game', 'boards']),
boardsWithGame() {
return this.boards
?.filter(({ lists }) => lists.some(({ games }) => games.includes(this.game.id))) || [];
const filteredBoards = this.boards?.filter(({ lists }) => lists.some(({ games }) => games.includes(this.game.id))) || [];
return filteredBoards;
},
},
};

View file

@ -1,33 +1,45 @@
<template lang="html">
<div class="mt-3 d-flex flex-wrap">
<div
v-for="({ imageUrl, isVideo, isCover }, index) in gameMedia"
v-show="index > 0"
:key="index"
class="mr-2 align-items-center text-center mb-2 rounded cursor-pointer position-relative"
>
<i
v-if="isVideo"
class="fa-solid fa-play video-indicator position-absolute text-white"
/>
<div class="mt-3">
<b-row no-gutters>
<b-col
v-for="({ imageUrl, isVideo, isCover }, index) in previewThumbs"
:key="index"
cols="3"
>
<div
class="mr-2 align-items-center text-center mb-2 rounded cursor-pointer position-relative"
>
<i
v-if="isVideo"
class="fa-solid fa-play video-indicator position-absolute text-white"
/>
<div v-if="isCover" class="position-absolute cover-indicator text-light small w-100 bg-dark rounded-bottom">
Cover
</div>
<div v-if="isCover" class="position-absolute cover-indicator text-light small w-100 bg-dark rounded-bottom">
Cover
</div>
<b-img
:src="imageUrl"
rounded
height="80"
@click="viewMedia(index)"
/>
</div>
<b-img
:src="imageUrl"
rounded
fluid
@click="viewMedia(index)"
/>
</div>
</b-col>
<b-button
v-if="totalMedia > 3"
@click="viewMedia(3)"
>
<i class="fa-solid fa-photo-film" />
{{ totalMedia - 3 }} more
</b-button>
</b-row>
<b-modal
id="mediaModal"
centered
hide-footer
size="xl"
:visible="visible"
@show="open"
@hidden="close"
@ -56,7 +68,7 @@
<div v-if="selectedMedia && gameMedia.length" class="game-media">
<b-embed
v-if="selectedMedia && selectedMedia.isVideo"
v-if="isSelectedMediaVideo"
type="iframe"
aspect="16by9"
:src="selectedMedia.videoUrl"
@ -71,7 +83,6 @@
/>
<footer class="mt-2 d-flex overflow-auto pb-2">
<pre class="bg-success">{{ activeIndex }}</pre>
<b-img
v-for="(media, index) in gameMedia"
:key="media.imageUrl"
@ -97,7 +108,7 @@ export default {
data() {
return {
activeIndex: null,
maxThumbnails: 3,
maxThumbnails: 4,
saving: false,
};
},
@ -106,10 +117,24 @@ export default {
...mapGetters(['isBoardOwner', 'gameMedia']),
...mapState(['board', 'game']),
previewThumbs() {
const previewThumbs = this.gameMedia.slice(0, this.maxThumbnails);
return previewThumbs;
},
isSelectedMediaVideo() {
return this.selectedMedia?.isVideo;
},
selectedMedia() {
return this.gameMedia?.[this.activeIndex];
},
totalMedia() {
return this.gameMedia?.length || 0;
},
visible() {
return this.activeIndex !== null;
},
@ -158,7 +183,7 @@ export default {
.game-media {
display: grid;
grid-gap: 1rem;
grid-template-rows: 50vh auto;
grid-template-rows: 1fr auto;
}
.selected-image {
@ -173,4 +198,9 @@ export default {
.cover-indicator {
bottom: 0;
}
.media-button {
padding: 21px 16px;
height: 100%;
}
</style>

View file

@ -0,0 +1,40 @@
<template lang="html">
<div>
<strong class="text-muted">Completed: {{ progress }}</strong>
<b-form-input
size="lg"
v-model="progress"
type="range"
max="100"
step="1"
/>
<b-button
variant="primary"
:disabled="saving"
class="mr-2"
@click="saveProgress"
>
<b-spinner small v-if="saving" />
<span v-else>{{ $t('global.save') }}</span>
</b-button>
<b-button
:disabled="deleting"
variant="danger"
@click="deleteProgress"
>
<b-spinner small v-if="deleting" />
<i v-else class="fas fa-trash fa-fw" aria-hidden />
</b-button>
</div>
</template>
<script>
export default {
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
</style>

View file

@ -61,12 +61,12 @@ export default {
<style lang="scss" rel="stylesheet/scss" scoped>
.similar-games {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-columns: repeat(10, 1fr);
grid-gap: 1rem;
margin-bottom: 20vh;
@media(max-width: 780px) {
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(5, 1fr);
}
}
</style>

View file

@ -35,7 +35,7 @@
sm="6"
md="4"
lg="3"
class="px-3 pb-4"
class="px-1 pb-2"
>
<mini-board
:board="board"

View file

@ -0,0 +1,17 @@
<template lang="html">
<div>
game card here
type: <pre>{{ type }}</pre>
</div>
</template>
<script>
export default {
props: {
type: string,
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
</style>

View file

@ -0,0 +1,155 @@
<template lang="html">
<draggable
class="games centered"
handle=".card"
ghost-class="card-placeholder"
drag-class="border-selected"
chosen-class="border-primary"
filter=".drag-filter"
delay="50"
animation="500"
:list="list.games"
:move="validateMove"
:disabled="draggingDisabled"
:group="{ name: 'games' }"
@end="dragEnd"
@start="dragStart"
>
<b-card
no-body
class="mb-2 flex-row align-items-center cursor-pointer"
v-for="gameId in sortedGames"
:key="gameId"
@click="openGame(gameId, list)"
>
<b-img
:src="$options.getThumbnailUrl(games[gameId])"
alt="Image"
class="m-2"
rounded
fluid
width="160"
/>
<span class="d-flex w-100 justify-content-center mr-2">{{ games[gameId].name }}</span>
</b-card>
<div v-if="isEmpty && isBoardOwner">
<b-button
variant="light"
block
class="mb-2"
:disabled="!isBoardOwner"
:to="{ name: 'search', query: { boardId: board.id, listIndex: 0 } }"
>
<template v-if="isBoardOwner">Add games</template>
<template v-else>Empty list</template>
</b-button>
</div>
</draggable>
</template>
<script>
import draggable from 'vuedraggable';
import slugify from 'slugify'
import orderby from 'lodash.orderby';
import { DEFAULT_LIST_VIEW } from '@/constants';
import { mapState, mapGetters } from 'vuex';
import { getThumbnailUrl } from '@/utils';
export default {
getThumbnailUrl,
components: {
draggable,
},
props: {
list: {
type: Object,
default: () => {},
},
},
data() {
return {
draggingId: null,
editing: false,
};
},
computed: {
...mapState(['games', 'dragging', 'progresses', 'board', 'user', 'settings']),
...mapGetters(['isBoardOwner']),
draggingDisabled() {
return !this.user || !this.isBoardOwner;
},
autoSortEnabled() {
return ['sortByName', 'sortByRating', 'sortByReleaseDate', 'sortByProgress'].includes(this.list?.settings?.sortOrder);
},
sortedGames() {
const { settings, games } = this.list;
const sortOrder = settings.sortOrder || 'sortByCustom';
switch (sortOrder) {
case 'sortByCustom': return this.list.games;
case 'sortByProgress': return orderby(games, [game => this.progresses[game] || 0], ['desc']);
case 'sortByRating': return orderby(games, [game => this.games[game].rating || 0], ['desc']);
case 'sortByName': return orderby(games, [game => this.games[game].name]);
default:
return this.list.games;
}
},
isEmpty() {
return this.list.games.length === 0;
},
singleList() {
return this.board.lists.length === 1;
},
},
methods: {
openGame(id, list) {
const slug = slugify(this.games[id].slug, { lower: true });
this.$router.push({
name: 'game',
params: {
id,
slug,
boardId: this.board.id,
},
});
},
validateMove({ from, to }) {
const sameList = from.id === to.id;
const notInList = !this.board?.lists?.[to.id]?.games?.includes(Number(this.draggingId));
return sameList || notInList && !sameList;
},
dragStart({ item }) {
this.$store.commit('SET_DRAGGING_STATUS', true);
this.draggingId = item.id;
},
dragEnd() {
this.$store.commit('SET_DRAGGING_STATUS', false);
this.saveBoard();
},
async saveBoard() {
await this.$store.dispatch('SAVE_BOARD')
.catch(() => {
this.$store.commit('SET_SESSION_EXPIRED', true);
});
},
},
};
</script>

View file

@ -18,11 +18,11 @@
<div class="align-items-center d-flex ml-auto mr-2">
<portal-target name="headerActions" />
<!-- <b-button v-if="user" class="mr-2" variant="success" :to="{ name: 'upgrade' }">
Upgrade
</b-button> -->
<b-button :to="{ name: 'search' }" class="d-sm-none">
<i class="fa fa-search" aria-hidden="true"></i>
</b-button>
<search-box />
<search-box class="d-none d-sm-block" />
<b-button
v-if="!user"

View file

@ -112,30 +112,6 @@ export const KEYBOARD_SHORTCUTS = {
ROUTE_settings: ['shift', 's'],
};
export const PLATFORM_FILTER_FIELDS = [
null,
'all',
'console',
// 'arcade',
// 'platform',
'operating_system',
'portable_console',
'computer',
];
export const PLATFORM_SORT_FILEDS = [
'generation',
'name',
];
export const PLATFORM_BG_HEX = {
167: '#222',
166: '#000',
48: '#2e6db4',
49: '#177d3e',
130: '#ce181e',
};
export const LIST_VIEWS = {
single: 'Single',
grid: 'Grid',

View file

@ -15,14 +15,6 @@ const routes = [
title: 'Notes'
},
},
{
name: 'game.progress',
path: '/g/:id/:slug/progress',
component: () => import(/* webpackChunkName: "game" */ '@/game/pages/GameProgressPage'),
meta: {
title: 'Track game progress'
},
},
{
name: 'game',
path: '/g/:id/:slug',

View file

@ -30,6 +30,8 @@
</portal>
<b-col cols="12" sm="6">
<div ref="editor" />
<game-note
v-if="showPreview"
:note="{ note }"

View file

@ -26,6 +26,7 @@
cols="12"
md="4"
xl="3"
class="text-center"
>
<b-img
:src="gameCoverUrl"
@ -34,27 +35,12 @@
fluid
/>
<game-note
v-if="note"
:note="note"
class="cursor-pointer mt-3 d-none d-md-block"
@click.native="$router.push({ name: 'game.notes', params: { id: game.id, slug: game.slug } })"
/>
<b-button
v-else
size="sm"
variant="warning"
:to="{ name: 'game.notes', params: { id: game.id, slug: game.slug } }"
class="mt-2"
>
Add note
</b-button>
<game-media />
<b-button
v-if="gameNews.length"
size="sm"
class="mt-2 ml-2"
class="mt-2 ml-2 d-none d-md-block"
:to="{ name: 'game.news', params: { id: game.id, slug: game.slug } }"
>
<b-badge>{{ gameNews.length }}</b-badge>
@ -62,14 +48,8 @@
</b-button>
<!-- <amazon-links class="mt-2" /> -->
<game-in-list :class="{ 'text-white': hasWallpaper }" />
<!-- <game-speedruns /> -->
<!-- <div v-if="gameAchievements"> -->
<!-- <pre>{{ gameAchievements }}</pre> -->
<!-- </div> -->
<!-- <pre>{{ gameAchievements }}</pre> -->
</b-col>
<b-col
@ -80,46 +60,30 @@
<article :class="[' rounded', hasWallpaper ? 'bg-white mt-2 mt-md-0 p-3' : 'px-sm-3 p-0']">
<div class="d-flex justify-content-between">
<game-titles />
<b-link
class="align-self-end ml-2 small"
:to="{ name: 'game.progress', params: { id: game.id, slug: game.slug } }"
>
<template v-if="progress > 0">
{{ progress }}% completed
</template>
<template v-else>
Set progress
</template>
</b-link>
</div>
<template v-if="tagsApplied.length">
<b-button
v-for="({ bgColor, textColor, name, index }) in tagsApplied"
:key="name"
rounded
size="sm"
variant="transparent"
class="mr-1 mb-2"
:style="`background-color: ${bgColor}; color: ${textColor}`"
:to="{ name: 'tag.edit', params: { id: index } }"
>
<i class="fa-solid fa-tag mr-1" />
{{ name }}
</b-button>
</template>
<game-tags-dropdown v-if="user" />
<aside class="supplemental-info bg-white field float-right ml-5 pb-2">
<game-details />
<game-note
v-if="note"
:note="note"
class="cursor-pointer mt-3 d-none d-md-block"
@click.native="$router.push({ name: 'game.notes', params: { id: game.id, slug: game.slug } })"
/>
<b-button
v-else
size="sm"
variant="warning"
:to="{ name: 'game.notes', params: { id: game.id, slug: game.slug } }"
class="mt-2"
>
Add note
</b-button>
</aside>
<game-description />
<game-media-viewer />
<game-in-list :class="{ 'text-white': hasWallpaper }" />
<game-ratings />
</article>
@ -160,8 +124,7 @@ import { mapState, mapGetters } from 'vuex';
import { WEBSITE_CATEGORIES } from '@/constants';
// import AmazonLinks from '@/components/Game/AmazonLinks';
import GameDetails from '@/components/Game/GameDetails';
import GameTagsDropdown from '@/components/Game/GameTagsDropdown';
import GameMediaViewer from '@/components/Game/GameMediaViewer';
import GameMedia from '@/components/Game/GameMedia';
import GameTitles from '@/components/Game/GameTitles';
import GameRatings from '@/components/Game/GameRatings';
import GameDescription from '@/components/Game/GameDescription';
@ -177,9 +140,8 @@ export default {
GameNote,
GameDescription,
GameDetails,
GameTagsDropdown,
GameTitles,
GameMediaViewer,
GameMedia,
GameRatings,
// GameSpeedruns,
SimilarGames,
@ -219,6 +181,7 @@ export default {
originBoardId() {
return this.$route?.params?.boardId;
return this.$route?.params?.boardId;
},
// gameAchievements() {
@ -229,13 +192,6 @@ export default {
return this.notes[this.game?.id] || null;
},
tagsApplied() {
if (!this.tags) return [];
return this.tags?.map((tag, index) => ({ ...tag, index }))
.filter((tag) => tag?.games?.includes(this.game?.id));
},
legalNotice() {
return this.game?.steam?.legal_notice;
},
@ -256,12 +212,6 @@ export default {
: '/no-image.jpg';
},
progress() {
const { gameId, progresses } = this;
return progresses[gameId] || null;
},
gameId() {
return this.$route.params.id;
},
@ -288,9 +238,6 @@ export default {
async mounted() {
if (!this.twitchToken) return this.waitAndLoadGame();
if (!this.tags) {
await this.$store.dispatch('LOAD_TAGS');
}
this.loadGame();
},
@ -337,7 +284,7 @@ export default {
// TODO: find more precise way to load GOG game, based on id?
const gogPage = this.game?.websites?.find(({ category }) => category !== GOG_CATEGORY_ID);
if (gogPage) await this.$store.dispatch('LOAD_GOG_GAME', this.game.name).catch((e) => {});
if (gogPage) await this.$store.dispatch('LOAD_GOG_GAME', this.game?.name).catch((e) => {});
// const wikipediaData = this.game?.websites?.find(({ url, category }) => url && category === WEBSITE_CATEGORIES.WIKIPEDIA);
const wikipediaSlug = this.game?.websites

View file

@ -1,126 +0,0 @@
<!-- TODO: switch to modal or dropdown -->
<template lang="html">
<section>
<b-container>
<portal to="pageTitle">
<div>
<b-button
:to="{ name: 'game', params: { id: game.id, slug: game.slug } }"
variant="light"
class="mr-2"
>
<i class="fa-solid fa-chevron-left" />
</b-button>
Track progress
</div>
</portal>
<b-row>
<b-col cols="12" sm="6">
<router-link :to="{ name: 'game', params: { id: game.id, slug: game.slug }}" class="float-right">
<b-img :src="gameCoverUrl" fluid rounded />
</router-link>
</b-col>
<b-col cols="12" sm="6" class="mt-3 mt-sm-0 mb-3">
<b-input-group :prepend="`${progress}%`" class="field mb-2">
<b-form-input
size="lg"
v-model="progress"
type="range"
max="100"
step="1"
/>
</b-input-group>
<b-button
variant="primary"
:disabled="saving"
class="mr-2"
@click="saveProgress"
>
<b-spinner small v-if="saving" />
<span v-else>{{ $t('global.save') }}</span>
</b-button>
<b-button
:disabled="deleting"
variant="danger"
@click="deleteProgress"
>
<b-spinner small v-if="deleting" />
<i v-else class="fas fa-trash fa-fw" aria-hidden />
</b-button>
</b-col>
</b-row>
</b-container>
</section>
</template>
<script>
import { mapState } from 'vuex';
import { getGameCoverUrl } from '@/utils';
export default {
data() {
return {
progress: 0,
saving: false,
deleting: false,
};
},
computed: {
...mapState(['progresses', 'game']),
gameCoverUrl() {
return getGameCoverUrl(this.game);
},
},
mounted() {
this.progress = this.progresses?.[this.game?.id]
? JSON.parse(JSON.stringify(this.progresses?.[this.game?.id]))
: 0;
},
methods: {
async deleteProgress() {
const { id, name } = this.game;
this.deleting = true;
this.$store.commit('REMOVE_GAME_PROGRESS', id);
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE')
.catch(() => {
this.$bvToast.toast('There was an error deleting your progress', { title: `${name} progress`, variant: 'error' });
this.deleting = false;
});
this.deleting = false;
this.$router.push({ name: 'game', params: { id: this.game.id, slug: this.game.slug }});
},
async saveProgress() {
this.saving = true;
this.$store.commit('SET_GAME_PROGRESS', {
progress: this.progress,
gameId: this.game?.id,
});
await this.$store.dispatch('SAVE_PROGRESSES')
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error saving your progress', { variant: 'error' });
});
this.saving = false;
this.$router.push({ name: 'game', params: { id: this.game.id, slug: this.game.slug }});
},
},
};
</script>

View file

@ -1,3 +1,5 @@
// TODO: make one game card component, use props for type
// TODO: disolve
import { mapState, mapGetters } from 'vuex';
export default {

View file

@ -1,22 +1,113 @@
<!-- TODO: only load platforms once -->
<!-- TODO: add infinite loader / pagination -->
<!-- TODO: add sorting -->
<template lang="html">
<div>
<pre>{{ platform }}</pre>
</div>
<section>
<b-container>
<b-spinner v-if="loading" class="spinner-centered" />
<section v-else-if="platform">
<portal to="pageTitle">
{{ platform.name }}
</portal>
<portal to="headerActions">
<b-button :to="{ name: 'platforms' }" class="mr-2">
All platforms
</b-button>
</portal>
<b-row class="mb-5">
<b-col v-for="game in platformGames" :key="game.id" cols="3">
<game-card-search :game="game" />
</b-col>
<b-button block class="mt-5 mb-5" @click="loadMoreGames">
Load more games
</b-button>
</b-row>
</section>
</b-container>
</section>
</template>
<script>
import merge from 'lodash.merge';
import GameCardSearch from '@/components/GameCards/GameCardSearch';
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['platforms']),
data() {
return {
loading: false,
platforms: [],
platformGames: [],
offset: 0,
}
},
components: {
GameCardSearch,
},
computed: {
platform() {
return this.platforms.find(({ slug }) => slug === this.$route.params.slug);
return this.platforms?.find(({ id }) => id == this.$route.params.id);
},
},
async mounted() {
this.platforms = await this.$store.dispatch('LOAD_IGDB_PLATFORMS');
this.loadGames();
},
methods: {
async loadGames() {
this.loading = this.offset === 0;
const data = `
fields platforms,slug,release_dates,rating,cover.image_id;
limit 50;
offset: ${this.offset};
sort name asc;
where release_dates.platform = ${this.platform.id};
`;
console.log('this.offset', this.offset);
if (this.offset === 0) {
this.platformGames = await this.$store.dispatch('IGDB', { path: 'games', data });
} else {
const games = await this.$store.dispatch('IGDB', { path: 'games', data });
console.log('games', games.length);
console.log(typeof games);
console.log('platformGames', this.platformGames);
console.log(typeof this.platformGames);
this.platformGames = merge(this.platformGames, games);
console.log('this.platformGames', this.platformGames);
}
this.loading = false;
},
async loadMoreGames() {
this.offset = this.platformGames.length;
this.loadGames();
// const data = `
// fields platforms,slug,release_dates,rating,cover.image_id;
// limit 50;
// offset: ${this.offset};
// sort name asc;
// where release_dates.platform = ${this.platform.id};
// `;
//
// const moreGames = await this.$store.dispatch('IGDB', { path: 'games', data });
//
// this.loading = false;
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
</style>

View file

@ -1,126 +1,26 @@
<!-- TODO: finish or kill it -->
<template lang="html">
<div>
<b-button
variant="link"
block
v-for="platform in platforms"
:to="{ name: 'platform.page', params: { slug: platform.slug } }"
:to="{ name: 'platform', params: { id: platform.id } }"
:key="platform.id"
>
<img :src="`static/logos/platforms-new/${platform.slug}.png`" />
{{ platform.slug }} | {{ platform.id }}
{{ platform.name }}
</b-button>
<!-- <img src="static/logos/platforms-new/3do.png" /> -->
<!-- <img src="static/logos/platforms-new/action-max.png" /> -->
<!-- <img src="static/logos/platforms-new/amiga-cd-3 2.png" /> -->
<!-- <img src="static/logos/platforms-new/arcadia-2001.png" /> -->
<!-- <img src="static/logos/platforms-new/astrocade.png" /> -->
<!-- <img src="static/logos/platforms-new/atari-2600.png" /> -->
<!-- <img src="static/logos/platforms-new/atari-5200.png" /> -->
<!-- <img src="static/logos/platforms-new/atari-7800.png" /> -->
<!-- <img src="static/logos/platforms-new/atari-xe.png" /> -->
<!-- <img src="static/logos/platforms-new/beena.png" /> -->
<!-- <img src="static/logos/platforms-new/cassette-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/cd-i.png" /> -->
<!-- <img src="static/logos/platforms-new/channel-f.png" /> -->
<!-- <img src="static/logos/platforms-new/coleco-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/commodore-cdtv.png" /> -->
<!-- <img src="static/logos/platforms-new/commodore64.png" /> -->
<!-- <img src="static/logos/platforms-new/cps-changer.png" /> -->
<!-- <img src="static/logos/platforms-new/creativision.png" /> -->
<!-- <img src="static/logos/platforms-new/dreamcast.png" /> -->
<!-- <img src="static/logos/platforms-new/family-computer.png" /> -->
<!-- <img src="static/logos/platforms-new/fm-towns-marty.png" /> -->
<!-- <img src="static/logos/platforms-new/game-wave.png" /> -->
<!-- <img src="static/logos/platforms-new/gamecube.png" /> -->
<!-- <img src="static/logos/platforms-new/genesis-32x.png" /> -->
<!-- <img src="static/logos/platforms-new/genesis.png" /> -->
<!-- <img src="static/logos/platforms-new/gx4000.png" /> -->
<!-- <img src="static/logos/platforms-new/halcyon.png" /> -->
<!-- <img src="static/logos/platforms-new/hyper-scan.png" /> -->
<!-- <img src="static/logos/platforms-new/imagination-machine.png" /> -->
<!-- <img src="static/logos/platforms-new/intellivision.png" /> -->
<!-- <img src="static/logos/platforms-new/interactive-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/ique.png" /> -->
<!-- <img src="static/logos/platforms-new/jaguar-cd.png" /> -->
<!-- <img src="static/logos/platforms-new/jaguar.png" /> -->
<!-- <img src="static/logos/platforms-new/konix.png" /> -->
<!-- <img src="static/logos/platforms-new/laser-active.png" /> -->
<!-- <img src="static/logos/platforms-new/leisure-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/loopy.png" /> -->
<!-- <img src="static/logos/platforms-new/mark-iii.png" /> -->
<!-- <img src="static/logos/platforms-new/mega-cd-ii.png" /> -->
<!-- <img src="static/logos/platforms-new/mega-cd.png" /> -->
<!-- <img src="static/logos/platforms-new/mega-drive.png" /> -->
<!-- <img src="static/logos/platforms-new/mega-ld.png" /> -->
<!-- <img src="static/logos/platforms-new/mp1000.png" /> -->
<!-- <img src="static/logos/platforms-new/my-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/neo-geo-cd.png" /> -->
<!-- <img src="static/logos/platforms-new/neo-geo.png" /> -->
<!-- <img src="static/logos/platforms-new/nes.png" /> -->
<!-- <img src="static/logos/platforms-new/nintendo-64-dd.png" /> -->
<!-- <img src="static/logos/platforms-new/nintendo-64.png" /> -->
<!-- <img src="static/logos/platforms-new/nuon.png" /> -->
<!-- <img src="static/logos/platforms-new/odyssey2.png" /> -->
<!-- <img src="static/logos/platforms-new/pc-engine.png" /> -->
<!-- <img src="static/logos/platforms-new/pc-fx.png" /> -->
<!-- <img src="static/logos/platforms-new/picno.png" /> -->
<!-- <img src="static/logos/platforms-new/pico.png" /> -->
<!-- <img src="static/logos/platforms-new/pippin.png" /> -->
<!-- <img src="static/logos/platforms-new/playdia.png" /> -->
<!-- <img src="static/logos/platforms-new/playstation.png" /> -->
<!-- <img src="static/logos/platforms-new/ps-2.png" /> -->
<!-- <img src="static/logos/platforms-new/ps3.png" /> -->
<!-- <img src="static/logos/platforms-new/ps4.png" /> -->
<!-- <img src="static/logos/platforms-new/pv-1000.png" /> -->
<!-- <img src="static/logos/platforms-new/rca-studio-ii.png" /> -->
<!-- <img src="static/logos/platforms-new/satellaview.png" /> -->
<!-- <img src="static/logos/platforms-new/sega-cd.png" /> -->
<!-- <img src="static/logos/platforms-new/sega-master-system.png" /> -->
<!-- <img src="static/logos/platforms-new/sega-saturn.png" /> -->
<!-- <img src="static/logos/platforms-new/sg-1000.png" /> -->
<!-- <img src="static/logos/platforms-new/socrates.png" /> -->
<!-- <img src="static/logos/platforms-new/super-acan.png" /> -->
<!-- <img src="static/logos/platforms-new/super-cassette-vision.png" /> -->
<!-- <img src="static/logos/platforms-new/super-cd-rom.png" /> -->
<!-- <img src="static/logos/platforms-new/super-famicom.png" /> -->
<!-- <img src="static/logos/platforms-new/super-grafx.png" /> -->
<!-- <img src="static/logos/platforms-new/super-nintendo.png" /> -->
<!-- <img src="static/logos/platforms-new/super-vision-8000.png" /> -->
<!-- <img src="static/logos/platforms-new/switch.png" /> -->
<!-- <img src="static/logos/platforms-new/turbo-grafx-16.png" /> -->
<!-- <img src="static/logos/platforms-new/tutor.png" /> -->
<!-- <img src="static/logos/platforms-new/tv-boy.png" /> -->
<!-- <img src="static/logos/platforms-new/ultravision.png" /> -->
<!-- <img src="static/logos/platforms-new/v-flash.png" /> -->
<!-- <img src="static/logos/platforms-new/v-smile.png" /> -->
<!-- <img src="static/logos/platforms-new/vc4000.png" /> -->
<!-- <img src="static/logos/platforms-new/vectrex.png" /> -->
<!-- <img src="static/logos/platforms-new/video-art.png" /> -->
<!-- <img src="static/logos/platforms-new/video-challenger.png" /> -->
<!-- <img src="static/logos/platforms-new/video-driver.png" /> -->
<!-- <img src="static/logos/platforms-new/videopac.png" /> -->
<!-- <img src="static/logos/platforms-new/vis.png" /> -->
<!-- <img src="static/logos/platforms-new/wii-u.png" /> -->
<!-- <img src="static/logos/platforms-new/wii.png" /> -->
<!-- <img src="static/logos/platforms-new/xavix.png" /> -->
<!-- <img src="static/logos/platforms-new/xbox-360.png" /> -->
<!-- <img src="static/logos/platforms-new/xbox-one.png" /> -->
<!-- <img src="static/logos/platforms-new/xbox.png" /> -->
<!-- <img src="static/logos/platforms-new/zeebo.png" /> -->
<!-- <img src="static/logos/platforms-new/zemina.png" /> -->
<!-- <pre class="text-dark small">{{ platforms }}</pre> -->
</div>
</template>
<script>
import orderby from 'lodash.orderby';
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['platforms']),
data() {
return {
platforms: null,
}
},
mounted() {
@ -129,10 +29,12 @@ export default {
methods: {
async loadPlatforms() {
await this.$store.dispatch('LOAD_IGDB_PLATFORMS')
const platforms = await this.$store.dispatch('LOAD_IGDB_PLATFORMS')
.catch(() => {
this.$bvToast.toast('There was an error loading platforms', { variant: 'error' });
});
this.platforms = orderby(platforms, 'name');
},
},
};

View file

@ -8,8 +8,8 @@ const routes = [
},
},
{
path: '/platforms/:slug',
name: 'platform.page',
path: '/p/:id',
name: 'platform',
component: () => import(/* webpackChunkName: "platforms" */ '@/platforms/pages/PlatformPage'),
},
];

View file

@ -1,4 +1,4 @@
<!-- TODO: use custom login page -->
<!-- TODO: use custom login page, modal? -->
<template lang="html">
<section>
<b-container>

View file

@ -1,11 +1,13 @@
<template lang="html">
<b-container>
<game-boards />
<section class="pb-5">
<b-container>
<game-boards />
<!-- <div class="game-deals">
<twitter-feed twitter-user="wario64" />
</div> -->
</b-container>
<!-- <div class="game-deals">
<twitter-feed twitter-user="wario64" />
</div> -->
</b-container>
</section>
</template>
<script>

View file

@ -20,9 +20,14 @@
variant="light"
class="mr-2"
right
no-caret
>
<template #button-content>
Filter <template v-if="selectedPlatforms.length">({{ selectedPlatforms.length }})</template>
<i class="fa-solid fa-filter fa-fw" />
<b-badge v-if="selectedPlatforms.length">
{{ selectedPlatforms.length }}
</b-badge>
</template>
<b-dropdown-item
@ -45,45 +50,47 @@
</b-dropdown>
</portal>
<b-col cols="12" class="bg-light py-2 mb-3" v-if="activeBoard">
<b-col cols="12" class="py-2 mb-3" v-if="activeBoard">
<div class="d-flex align-items-center">
<span class="d-none d-sm-block">
Add games to:
</span>
<b-button-group class="ml-sm-2">
<b-dropdown
split
variant="light"
:split-to="{ name: 'board', params: { id: boardId } }"
:text="activeBoard.name"
<b-dropdown
split
variant="light"
size="sm"
class="ml-2"
:split-to="{ name: 'board', params: { id: boardId } }"
:text="activeBoard.name"
>
<b-dropdown-item
v-for="board in boards"
:key="board.id"
:disabled="!board.lists.length"
:to="{ name: 'search', query: { boardId: board.id, listIndex: 0, q: query } }"
>
<b-dropdown-item
v-for="board in boards"
:key="board.id"
:disabled="!board.lists.length"
:to="{ name: 'search', query: { boardId: board.id, listIndex: 0, q: query } }"
>
{{ board.name }}
</b-dropdown-item>
</b-dropdown>
{{ board.name }}
</b-dropdown-item>
</b-dropdown>
<b-dropdown
v-if="activeBoardList"
split
variant="light"
:split-to="{ name: 'board', params: { id: boardId } }"
:text="activeBoardList.name"
<b-dropdown
v-if="activeBoardList"
split
variant="light"
size="sm"
class="ml-2"
:split-to="{ name: 'board', params: { id: boardId } }"
:text="activeBoardList.name"
>
<b-dropdown-item
v-for="(list, listIndex) in activeBoard.lists"
:key="list.id"
:to="{ name: 'search', query: { boardId: activeBoard.id, listIndex, q: query } }"
>
<b-dropdown-item
v-for="(list, listIndex) in activeBoard.lists"
:key="list.id"
:to="{ name: 'search', query: { boardId: activeBoard.id, listIndex, q: query } }"
>
{{ list.name }}
</b-dropdown-item>
</b-dropdown>
</b-button-group>
{{ list.name }}
</b-dropdown-item>
</b-dropdown>
<b-button :to="{ name: 'search' }" class="ml-auto" variant="light">
<i class="fas fa-times fa-fw" aria-hidden />
@ -104,6 +111,10 @@
>
<game-card-search :game="game" />
</b-col>
<b-button @click="loadMoreResults">
load more
</b-button>
</b-form-row>
<div
@ -207,10 +218,10 @@ export default {
},
async mounted() {
if (this.showEmptyState) {
} else {
this.search();
}
this.search();
// if (this.showEmptyState) {
// } else {
// }
},
methods: {
@ -221,7 +232,10 @@ export default {
? `search "${this.query}";`
: '';
const data = `${search} fields platforms,slug,cover.image_id; limit 50;`;
const filter = !this.query
? 'where rating >= 80;'
: '';
const data = `${search} fields platforms,slug,rating,cover.image_id; limit 50; ${filter}`;
this.searchResults = await this.$store.dispatch('IGDB', { path: 'games', data });

View file

@ -3,7 +3,15 @@
<b-container>
<portal to="pageTitle">Account</portal>
<p>Your account</p>
<p>Logged in as {{ user.displayName }} / {{ user.email }}</p>
<b-alert show class="field" variant="light">
<small>
<strong>User ID:</strong>
{{ user.uid }}
</small>
</b-alert>
<b-button
variant="light"
@click="session_signOut"
@ -11,10 +19,10 @@
Log out
</b-button>
<p class="mt-2">Delete account</p>
<hr />
<b-button
variant="warning"
variant="outline-dark"
@click="$bvModal.show('deleteAccount');"
>
Delete account

View file

@ -2,22 +2,21 @@
<section>
<b-container>
<b-row>
<b-col cols="8">
<div class="d-flex align-items-center justify-content-between mb-2">
Boards
<b-col>
<div class="d-flex align-items-center justify-content-between mb-2 pt-2">
Recent boards
<b-button
v-if="boards.length > 10"
v-if="sortedBoards.length > 10"
size="sm"
title="Boards"
variant="link"
:to="{ name: 'boards' }"
>
View all boards
</b-button>
</div>
<b-row>
<b-form-row>
<b-col
v-for="board in recentBoards"
:key="board.id"
@ -25,44 +24,20 @@
sm="6"
md="4"
lg="3"
class="mb-4"
>
<mini-board
:board="board"
style="height: 140px"
class="mb-3"
style="height: 180px"
@click.native="$router.push({ name: 'board', params: { id: board.id } })"
/>
</b-col>
</b-row>
</b-col>
<b-col cols="3 offset-1" class="text-center">
<b-avatar
class="d-flex ml-auto mr-auto mb-3 mt-5"
rounded
:src="avatarImage"
size="140px"
/>
<router-link
v-if="profile.userName"
:to="{ name: 'public.profile', params: { userName: profile.userName } }"
>
@{{ profile.userName }}
</router-link>
<b-button
v-else
:to="{ name: 'profile.settings' }"
variant="success"
>
Create profile <b-badge>New!</b-badge>
</b-button>
</b-form-row>
</b-col>
</b-row>
<b-row class="mt-3">
<b-col cols="6" md="3">
<b-col cols="12" sm="6" md="3">
<settings-card
title="Wallpapers"
description="Manage your wallpapers"
@ -70,7 +45,7 @@
@click.native="$router.push({ name: 'wallpapers' })"
/>
</b-col>
<b-col cols="6" md="3">
<b-col cols="12" sm="6" md="3">
<settings-card
title="Notes"
description="View all your notes"
@ -79,22 +54,65 @@
/>
</b-col>
<b-col cols="6" md="3">
<!-- <b-col cols="12" sm="6" md="3">
<b-card>
<h4>Tags</h4>
<b-button
v-for="({ textColor, bgColor, name, games }, index) in tags"
@click="$router.push({ name: 'tag.edit', params: { id: index } })"
rounded
size="sm"
class="mr-1 mb-1"
variant="transparent"
:style="`background-color: ${bgColor}; color: ${textColor}`"
:key="name"
>
{{ name }} {{ games.length ? `(${games.length})` : '' }}
</b-button>
</b-card>
</b-col> -->
<b-col cols="12" sm="6" md="3">
<settings-card
title="Tags"
description="View all your tags"
description="Manage your tags"
icon="fa-tags"
@click.native="$router.push({ name: 'tags' })"
/>
</b-col>
<b-col cols="6" md="3">
<b-col cols="12" sm="6" md="3">
<settings-card
title="Account"
description="Manage your account"
icon="fa-user"
@click.native="$router.push({ name: 'account' })"
/>
<b-avatar
class="d-flex ml-auto mr-auto mb-3 mt-5"
rounded
:src="avatarImage"
size="120px"
/>
<b-button
v-if="profile.userName"
variant="secondary"
:to="{ name: 'public.profile', params: { userName: profile.userName } }"
>
@{{ profile.userName }}
</b-button>
<b-button
v-else
:to="{ name: 'profile.settings' }"
variant="success"
>
Create profile <b-badge>New!</b-badge>
</b-button>
</b-col>
</b-row>
@ -178,7 +196,7 @@ import MiniBoard from '@/components/Board/MiniBoard';
// import SteamSettingsPage from '@/pages/SteamSettingsPage';
// import LanguageSettings from '@/components/Settings/LanguageSettings';
import { getImageThumbnail } from '@/utils';
import { mapState } from 'vuex';
import { mapState, mapGetters } from 'vuex';
export default {
components: {
@ -192,7 +210,9 @@ export default {
data() {
return {
popularGames: [115, 125174],
profile: {},
recentBoards: [],
avatarImage: null,
}
},
@ -216,11 +236,8 @@ export default {
},
computed: {
...mapState(['user', 'releases', 'boards']),
recentBoards() {
return this.boards.slice(0, 8);
},
...mapState(['user', 'releases', 'tags']),
...mapGetters(['sortedBoards']),
latestRelease() {
return this.releases?.[0]?.tag_name;
@ -229,6 +246,11 @@ export default {
methods: {
async load() {
await this.$store.dispatch('LOAD_BOARDS');
await this.$store.dispatch('LOAD_TAGS');
this.recentBoards = this.sortedBoards.slice(0, 8);
this.profile = await this.$store.dispatch('LOAD_PROFILE').catch(() => null);
if (this.profile?.avatar) this.loadAvatarImage();
},

View file

@ -41,7 +41,6 @@ export default {
return new Promise((resolve, reject) => {
axios.get(`${API_BASE}/platforms?token=${state.twitchToken.access_token}`)
.then(({ data }) => {
commit('SET_PLATFORMS', data);
resolve(data);
}).catch(reject);
});
@ -120,8 +119,9 @@ export default {
const board = doc.data();
return {
id: doc.id,
...board,
id: doc.id,
lastUpdated: board?.lastUpdated || 0,
};
})
: null;

View file

@ -6,7 +6,7 @@ import presetHTML5 from '@bbob/preset-html5'
import orderby from 'lodash.orderby';
export default {
sortedBoards: ({ boards }) => orderby(boards, 'name'),
sortedBoards: ({ boards }) => orderby(boards, 'lastUpdated', 'desc'),
isBoardOwner: ({ board, user }) => {
return board?.owner === user?.uid;

View file

@ -2,7 +2,6 @@ import Vue from 'vue';
// import {
// // PLATFORM_CATEGORIES,
// // EXCLUDED_PLATFORMS,
// // PLATFORM_BG_HEX,
// // PLATFORM_LOGO_FORMAT,
// // PLATFORM_NAME_OVERRIDES,
// // POPULAR_PLATFORMS,
@ -65,28 +64,6 @@ export default {
state.profiles = profiles;
},
SET_PLATFORMS(state, platforms) {
// TODO: use getter instead to get fresh data right away instead of once per session
state.platforms = platforms;
// state.platforms = platforms
// .filter(({ id }) => !EXCLUDED_PLATFORMS.includes(id))
// .map((platform) => {
// const formattedPlatform = {
// id: platform.id,
// name: PLATFORM_NAME_OVERRIDES[platform.id] || platform.name,
// slug: platform.slug,
// category: PLATFORM_CATEGORIES[platform.category],
// popular: POPULAR_PLATFORMS.includes(platform.id),
// // categoryId: platform.category,
// generation: platform.generation || 0,
// bgHex: PLATFORM_BG_HEX[platform.id] || null,
// logoFormat: PLATFORM_LOGO_FORMAT[platform.id] || 'svg',
// };
//
// return formattedPlatform;
// });
},
SET_BOARD_GAMES(state, boardGames) {
state.boardGames = boardGames;
},

View file

@ -1,4 +1,3 @@
// Colors
$primary: #3d6692 !default;
$muted: #7d7f8c !default;
$danger: #C0392B !default;
@ -6,19 +5,10 @@ $warning: #F39C12 !default;
$success: #23cd69 !default;
$white: #f8f8ff !default;
$black: #222222 !default;
$info: #0071bc !default;
$info: #3C5275 !default;
$light: #dee4e7 !default;
$dark: #37474f !default;
$secondary: #b0bec5 !default;
$secondary: #6D6969 !default;
$theme-colors: () !default;
$theme-colors: map-merge((
"primary": $primary,
"secondary": $secondary,
"success": $success,
"info": $info,
"warning": $warning,
"danger": $danger,
"light": $light,
"dark": $dark,
), $theme-colors);
$theme-colors: map-merge(( "primary": $primary, "secondary": $secondary, "success": $success, "info": $info, "warning": $warning, "danger": $danger, "light": $light, "dark": $dark, ), $theme-colors);

View file

@ -26,8 +26,9 @@ $modal-header-border-width: 0;
$modal-footer-border-width: 0 !default;
$modal-footer-padding: 0 !default;
$modal-header-padding: 1rem 1rem 0 !default;
//
// $modal-xl: 1140px !default;
// TODO: make xl modal take most of the screen
// $modal-xl: 100vw !default;
// $modal-lg: 800px !default;
$modal-md: 600px !default;
$modal-sm: 400px !default;

793
yarn.lock

File diff suppressed because it is too large Load diff