Skip to content

Commit

Permalink
Add custom apps tab (#243)
Browse files Browse the repository at this point in the history
* ♻️ Use a single `getTaggedRepos` function for all tags

* ✨ Add custom apps tab

* Make download button open repo in browser for apps

* chore: fix type errors & cleanup

* Update README.md

* Bump version, clean up changelog fetching

Co-authored-by: Nam Anh <phamnamanh25@gmail.com>
  • Loading branch information
theRealPadster and kyrie25 authored Jun 19, 2022
1 parent 9b70dc5 commit 79012d0
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 112 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ CSS snippets are rather basic to implement. We fetch them from this repo, so you
1. `componentDidMount` triggers `newRequest`, which triggers `loadAmount(30)`
2. `loadAmount` calls `loadPage` in a loop until it has the requested amount of cards or runs out of results
3. `loadPage` calls `getRepos(page)` to get the next page of extensions. It queries the GitHub API for any repos with the "spicetify-extensions" topic. We'll likely add our own tag in the future, like "spicetify-marketplace".
4. The it loops through all the results and runs `fetchRepoExtensions()` or `getThemeRepos()`, which fetches a `manifest.json` file from the repo's root folder. If it finds one, we generate a card based on the info.
4. Then it loops through all the results and runs `getTaggedRepos()`, which fetches a `manifest.json` file from the repo's root folder. If it finds one, we generate a card based on the info.
* Or if the active tab is "Installed", `loadPage` calls `getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.installedSnippets)` to get the extensions from the localstorage and generate the cards from there.
* Or if the active tab is "Snippets", `loadPage` calls `fetchCssSnippets()` and generates cards from the most recent `snippets.json` on GitHub.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spicetify-marketplace",
"version": "0.6.1",
"version": "0.7.0",
"homepage": "https://github.com/spicetify/spicetify-marketplace",
"repository": {
"type": "git",
Expand Down
96 changes: 63 additions & 33 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default class Card extends React.Component<CardProps, {
// TODO: Can I remove `stars` from `this`? Or maybe just put everything in `state`?
stars: number;
tagsExpanded: boolean;
externalUrl: string;
}> {
// Theme stuff
// cssURL?: string;
Expand All @@ -47,7 +48,7 @@ export default class Card extends React.Component<CardProps, {
tags: string[];

// Added locally
menuType;
menuType: typeof Spicetify.ReactComponent.Menu;
localStorageKey: string;

constructor(props: CardProps) {
Expand All @@ -59,10 +60,21 @@ export default class Card extends React.Component<CardProps, {

const prefix = props.type === "snippet" ? "snippet:" : `${props.item.user}/${props.item.repo}/`;

let cardId = "";
if (props.type === "snippet") cardId = props.item.title.replaceAll(" ", "-");
else if (props.type === "theme") cardId = props.item.manifest?.usercss || "";
else if (props.type === "extension") cardId = props.item.manifest?.main || "";
let cardId: string;
switch (props.type) {
case "snippet":
cardId = props.item.title.replaceAll(" ", "-");
break;
case "theme":
cardId = props.item.manifest?.usercss || "";
break;
case "extension":
cardId = props.item.manifest?.main || "";
break;
case "app":
cardId = props.item.manifest?.name?.replaceAll(" ", "-") || "";
break;
}

this.localStorageKey = `marketplace:installed:${prefix}${cardId}`;

Expand All @@ -80,6 +92,9 @@ export default class Card extends React.Component<CardProps, {
// TODO: Can I remove `stars` from `this`? Or maybe just put everything in `state`?
stars: this.props.item.stars || 0,
tagsExpanded: false,
externalUrl: (this.props.item.user && this.props.item.repo) // These won't exist for snippets
? `https://github.com/${this.props.item.user}/${this.props.item.repo}`
: "",
};
}

Expand Down Expand Up @@ -133,6 +148,9 @@ export default class Card extends React.Component<CardProps, {

// If the new or previous theme has JS, prompt to reload
if (this.props.item.manifest?.include || previousTheme.include) openModal("RELOAD");
} else if (this.props.type === "app") {
// Open repo in new tab
window.open(this.state.externalUrl, "_blank");
} else if (this.props.type === "snippet") {
if (this.isInstalled()) {
console.log("Snippet already installed, removing");
Expand All @@ -149,18 +167,23 @@ export default class Card extends React.Component<CardProps, {
console.log(`Installing extension ${this.localStorageKey}`);
// Add to localstorage (this stores a copy of all the card props in the localstorage)
// TODO: can I clean this up so it's less repetition?
if (!this.props.item) {
Spicetify.showNotification("There was an error installing extension");
return;
}
const { manifest, title, subtitle, authors, user, repo, branch, imageURL, extensionURL, readmeURL } = this.props.item;
localStorage.setItem(this.localStorageKey, JSON.stringify({
manifest: this.props.item?.manifest,
manifest,
type: this.props.type,
title: this.props.item.title,
subtitle: this.props.item.subtitle,
authors: this.props.item.authors,
user: this.props.item.user,
repo: this.props.item.repo,
branch: this.props.item.branch,
imageURL: this.props.item.imageURL,
extensionURL: this.props.item.extensionURL,
readmeURL: this.props.item.readmeURL,
title,
subtitle,
authors,
user,
repo,
branch,
imageURL,
extensionURL,
readmeURL,
stars: this.state.stars,
}));

Expand Down Expand Up @@ -195,11 +218,16 @@ export default class Card extends React.Component<CardProps, {
}

async installTheme() {
const { item } = this.props;
if (!item) {
Spicetify.showNotification("There was an error installing theme");
return;
}
console.log(`Installing theme ${this.localStorageKey}`);

let parsedSchemes: SchemeIni = {};
if (this.props.item.schemesURL) {
const schemesResponse = await fetch(this.props.item.schemesURL);
if (item.schemesURL) {
const schemesResponse = await fetch(item.schemesURL);
const colourSchemes = await schemesResponse.text();
parsedSchemes = parseIni(colourSchemes);
}
Expand All @@ -210,25 +238,27 @@ export default class Card extends React.Component<CardProps, {

// Add to localstorage (this stores a copy of all the card props in the localstorage)
// TODO: refactor/clean this up

const { manifest, title, subtitle, authors, user, repo, branch, imageURL, extensionURL, readmeURL, cssURL, schemesURL, include } = item;

localStorage.setItem(this.localStorageKey, JSON.stringify({
// TODO: can I clean this up so it's less repetition?
manifest: this.props.item?.manifest,
manifest,
type: this.props.type,
title: this.props.item.title,
subtitle: this.props.item.subtitle,
authors: this.props.item.authors,
user: this.props.item.user,
repo: this.props.item.repo,
branch: this.props.item.branch,
imageURL: this.props.item.imageURL,
extensionURL: this.props.item.extensionURL,
readmeURL: this.props.item.readmeURL,
title,
subtitle,
authors,
user,
repo,
branch,
imageURL,
extensionURL,
readmeURL,
stars: this.state.stars,
tags: this.tags,
// Theme stuff
cssURL: this.props.item.cssURL,
schemesURL: this.props.item.schemesURL,
include: this.props.item.include,
cssURL,
schemesURL,
include,
// Installed theme localstorage item has schemes, nothing else does
schemes: parsedSchemes,
activeScheme,
Expand All @@ -251,7 +281,7 @@ export default class Card extends React.Component<CardProps, {
// TODO: We'll also need to actually update the usercss etc, not just the colour scheme
// e.g. the stuff from extension.js, like injectUserCSS() etc.

if (!this.props.item.include) {
if (!item.include) {
// Add new theme css
this.fetchAndInjectUserCSS(this.localStorageKey);
// Update the active theme in Grid state, triggers state change and re-render
Expand Down Expand Up @@ -426,7 +456,7 @@ export default class Card extends React.Component<CardProps, {
className="main-cardHeader-link"
dir="auto"
href={this.props.type !== "snippet"
? `https://github.com/${this.props.item.user}/${this.props.item.repo}`
? this.state.externalUrl
: SNIPPETS_PAGE_URL
}
target="_blank"
Expand Down
54 changes: 47 additions & 7 deletions src/components/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { getLocalStorageDataFromKey, generateSchemesOptions, injectColourScheme
import { LOCALSTORAGE_KEYS, ITEMS_PER_REQUEST, MARKETPLACE_VERSION, LATEST_RELEASE } from "../constants";
import { openModal } from "../logic/LaunchModals";
import {
getExtensionRepos, fetchExtensionManifest,
getThemeRepos, fetchThemeManifest,
getTaggedRepos,
fetchExtensionManifest, fetchThemeManifest, fetchAppManifest,
fetchCssSnippets, getBlacklist,
} from "../logic/FetchRemotes";
import LoadMoreIcon from "./Icons/LoadMoreIcon";
Expand Down Expand Up @@ -178,9 +178,14 @@ export default class Grid extends React.Component<
async loadPage(queue: never[], query?: string) {
switch (this.CONFIG.activeTab) {
case "Extensions": {
const pageOfRepos = await getExtensionRepos(this.requestPage, this.BLACKLIST, query);
const pageOfRepos = await getTaggedRepos("spicetify-extensions", this.requestPage, this.BLACKLIST, query);
for (const repo of pageOfRepos.items) {
const extensions = await fetchExtensionManifest(repo.contents_url, repo.default_branch, repo.stargazers_count, this.CONFIG.visual.hideInstalled);
const extensions = await fetchExtensionManifest(
repo.contents_url,
repo.default_branch,
repo.stargazers_count,
this.CONFIG.visual.hideInstalled,
);

// I believe this stops the requests when switching tabs?
if (this.requestQueue.length > 1 && queue !== this.requestQueue[0]) {
Expand Down Expand Up @@ -232,10 +237,14 @@ export default class Grid extends React.Component<
// Don't need to return a page number because
// installed extension do them all in one go, since it's local
} case "Themes": {
const pageOfRepos = await getThemeRepos(this.requestPage, this.BLACKLIST, query);
const pageOfRepos = await getTaggedRepos("spicetify-themes", this.requestPage, this.BLACKLIST, query);
for (const repo of pageOfRepos.items) {

const themes = await fetchThemeManifest(repo.contents_url, repo.default_branch, repo.stargazers_count);
const themes = await fetchThemeManifest(
repo.contents_url,
repo.default_branch,
repo.stargazers_count,
);
// I believe this stops the requests when switching tabs?
if (this.requestQueue.length > 1 && queue !== this.requestQueue[0]) {
// Stop this queue from continuing to fetch and append to cards list
Expand All @@ -257,6 +266,36 @@ export default class Grid extends React.Component<
if (remainingResults > 0) return currentPage + 1;
else console.log("No more theme results");
break;
} case "Apps": {
const pageOfRepos = await getTaggedRepos("spicetify-apps", this.requestPage, this.BLACKLIST, query);
for (const repo of pageOfRepos.items) {

const apps = await fetchAppManifest(
repo.contents_url,
repo.default_branch,
repo.stargazers_count,
);
// I believe this stops the requests when switching tabs?
if (this.requestQueue.length > 1 && queue !== this.requestQueue[0]) {
// Stop this queue from continuing to fetch and append to cards list
return -1;
}

if (apps && apps.length) {
apps.forEach((app) => this.appendCard(app, "app"));
}
}

// First request is null, so coerces to 1
const currentPage = this.requestPage > -1 && this.requestPage ? this.requestPage : 1;
// -1 because the page number is 1-indexed
const soFarResults = ITEMS_PER_REQUEST * (currentPage - 1) + pageOfRepos.page_count;
const remainingResults = pageOfRepos.total_count - soFarResults;

console.log(`Parsed ${soFarResults}/${pageOfRepos.total_count} apps`);
if (remainingResults > 0) return currentPage + 1;
else console.log("No more app results");
break;
} case "Snippets": {
const snippets = await fetchCssSnippets();

Expand Down Expand Up @@ -325,7 +364,7 @@ export default class Grid extends React.Component<
this.CONFIG.theme.activeScheme = activeScheme;

if (schemes && activeScheme && schemes[activeScheme]) {
injectColourScheme(this.CONFIG.theme.schemes?.[activeScheme]);
injectColourScheme(this.CONFIG.theme.schemes[activeScheme]);
} else {
// Reset schemes if none sent
injectColourScheme(null);
Expand Down Expand Up @@ -496,6 +535,7 @@ export default class Grid extends React.Component<
{ handle: "extension", name: "Extensions" },
{ handle: "theme", name: "Themes" },
{ handle: "snippet", name: "Snippets" },
{ handle: "app", name: "Apps" },
].map((cardType) => {
const cardsOfType = this.cardList.filter((card) => card.props.type === cardType.handle)
.filter((card) => {
Expand Down
49 changes: 14 additions & 35 deletions src/components/Modals/Changelog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,24 @@ import whatsNew from "spcr-whats-new";
import ReactMarkdown from "react-markdown";
import { RELEASE_CHANGELOG, MARKETPLACE_VERSION } from "../../../constants";

/* const changelogDetails = (
<>
<h2>0.6.1</h2>
<ul>
<li>Fixed Readme pages sometimes missing scrollbar{" "}
<a href="https://github.com/spicetify/spicetify-marketplace/issues/113">(#113)</a>
</li>
<li>General improvements</li>
</ul>
<h2>0.6.0</h2>
<ul>
<li>Patched{" "}
<a href="https://github.com/spicetify/spicetify-marketplace/commit/6636908f86be91b84e381c2a2424a37b394b5119">createPortal</a>
{" "} error that makes Marketplace unable to start
</li>
<li>You can now preview snippets&apos; content by clicking on its card!{" "}
<a href="https://github.com/spicetify/spicetify-marketplace/pull/225">(#225)</a>
</li>
<li>Snippets now have preview images, and you can add your own also{" "}
<a href="https://github.com/spicetify/spicetify-marketplace/pull/226">(#226)</a>
</li>
<li>General improvements</li>
</ul>
</>
); */
let changelogBody: string;

const fetchRelease = async () => {
await fetch(RELEASE_CHANGELOG)
.then(res => res.json())
.then(result => {
const fetchReleaseBody = async () => {
return fetch(RELEASE_CHANGELOG)
.then((res) => res.json())
.then((result) => {
// If API returns no error message then get release body
if (!result.message) changelogBody = result.body;
const body = !result.message
? result.body
: null;
return body;
})
.catch(err => console.error(err));
.catch((err) => {
console.error(err);
return null;
});
};

const Changelog = async () => {
await fetchRelease();
const changelogBody = await fetchReleaseBody();
// If a release is not found then don't display changelog modal
if (!changelogBody) return;
whatsNew(
Expand All @@ -54,4 +33,4 @@ const Changelog = async () => {
},
);
};
export default Changelog;
export default Changelog;
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ALL_TABS: TabItemConfig[] = [
{ name: "Extensions", enabled: true },
{ name: "Themes", enabled: true },
{ name: "Snippets", enabled: true },
{ name: "Apps", enabled: true },
{ name: "Installed", enabled: true },
];

Expand Down
Loading

0 comments on commit 79012d0

Please sign in to comment.