diff --git a/renderer/pages/instruments/[userId]/[pluginId].tsx b/renderer/pages/instruments/[userId]/[pluginId].tsx
index 349953b..ee3e0f1 100644
--- a/renderer/pages/instruments/[userId]/[pluginId].tsx
+++ b/renderer/pages/instruments/[userId]/[pluginId].tsx
@@ -1,15 +1,10 @@
import { Component } from 'react';
-import Crumb from '../../../components/crumb';
import Layout from '../../../components/layout';
import Head from 'next/head.js';
-import styles from '../../../styles/plugin.module.css';
import { withRouter, Router } from 'next/router.js';
-import { pluginGet, pluginInstalled } from '../../../../node_modules/@studiorack/core/build/plugin';
-import { PluginVersionLocal } from '@studiorack/core';
-import { pluginFileUrl } from '../../../../node_modules/@studiorack/core/build/utils';
-import Dependency from '../../../components/dependency';
-import Downloads from '../../../components/download';
-import { pluginLicense } from '../../../lib/plugin';
+import { PluginVersion, PluginPack, pluginFileUrl, pluginGet, pluginsGet } from '@studiorack/core';
+import { pageTitle } from '../../../lib/utils';
+import Details from '../../../components/details';
declare global {
interface Window {
@@ -18,329 +13,35 @@ declare global {
}
type PluginProps = {
- plugin: PluginVersionLocal;
+ plugin: PluginVersion;
router: Router;
};
class PluginPage extends Component<
PluginProps,
{
- isDisabled: boolean;
- isPlaying: boolean;
router: Router;
- plugin: PluginVersionLocal;
+ plugin: PluginVersion;
}
> {
constructor(props: PluginProps) {
super(props);
this.state = {
- isDisabled: false,
- isPlaying: false,
plugin: props.plugin,
router: props.router,
};
- console.log(props.plugin);
- }
-
- install = () => {
- console.log('install', this.state.plugin);
- if (typeof window !== 'undefined' && window.electronAPI) {
- this.setState({ isDisabled: true });
- window.electronAPI.pluginInstall(this.state.plugin).then((pluginInstalled: PluginVersionLocal) => {
- console.log('pluginInstall response', pluginInstalled);
- this.state.plugin.paths = pluginInstalled.paths;
- this.state.plugin.status = pluginInstalled.status;
- this.setState({
- isDisabled: false,
- plugin: this.state.plugin,
- });
- });
- }
- };
-
- uninstall = () => {
- console.log('uninstall', this.state.plugin);
- if (typeof window !== 'undefined' && window.electronAPI) {
- this.setState({ isDisabled: true });
- window.electronAPI.pluginUninstall(this.state.plugin).then((pluginInstalled: PluginVersionLocal) => {
- console.log('pluginUninstall response', pluginInstalled);
- this.state.plugin.paths = pluginInstalled.paths;
- this.state.plugin.status = pluginInstalled.status;
- this.setState({
- isDisabled: false,
- plugin: this.state.plugin,
- });
- });
- }
- };
-
- formatBytes(bytes: number, decimals = 2) {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
- }
-
- timeSince(date: string) {
- const seconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
- let interval = seconds / 31536000;
- if (interval > 2) {
- return Math.floor(interval) + ' years';
- }
- if (interval > 1) {
- return Math.floor(interval) + ' year';
- }
- interval = seconds / 2592000;
- if (interval > 2) {
- return Math.floor(interval) + ' months';
- }
- if (interval > 1) {
- return Math.floor(interval) + ' month';
- }
- interval = seconds / 86400;
- if (interval > 2) {
- return Math.floor(interval) + ' days';
- }
- if (interval > 1) {
- return Math.floor(interval) + ' day';
- }
- interval = seconds / 3600;
- if (interval > 2) {
- return Math.floor(interval) + ' hours';
- }
- if (interval > 1) {
- return Math.floor(interval) + ' hour';
- }
- interval = seconds / 60;
- if (interval > 2) {
- return Math.floor(interval) + ' minutes';
- }
- if (interval > 1) {
- return Math.floor(interval) + ' minute';
- }
- return Math.floor(seconds) + ' seconds';
- }
-
- play = () => {
- const el = document.getElementById('audio') as HTMLAudioElement;
- if (el.paused) {
- el.removeEventListener('ended', this.ended);
- el.addEventListener('ended', this.ended);
- el.currentTime = 0;
- el.play();
- this.setState({ isPlaying: true });
- }
- };
-
- pause = () => {
- const el = document.getElementById('audio') as HTMLAudioElement;
- if (!el.paused) {
- el.pause();
- this.setState({ isPlaying: false });
- }
- };
-
- ended = () => {
- this.setState({ isPlaying: false });
- };
-
- getPlayButton() {
- if (this.state.isPlaying) {
- return (
-
- );
- } else {
- return (
-
- );
- }
- }
-
- // Prototype of embedded sfz web player.
- // There are better ways to do this.
- loadSfzPlayer(event: React.MouseEvent) {
- const el = document.getElementById('sfzPlayer');
- if (!el) return;
- if (el.className === 'open') {
- el.className = '';
- return;
- }
- const name = (event.currentTarget as HTMLTextAreaElement).getAttribute('data-name') || '';
- const id = (event.currentTarget as HTMLTextAreaElement).getAttribute('data-id') || '';
- console.log('loadSfzPlayer', name, id);
- el.innerHTML = '';
- const player = new window.Sfz.Player('sfzPlayer', {
- audio: {},
- instrument: { name, id },
- interface: {},
- });
- window.setTimeout(() => {
- el.className = 'open';
- }, 0);
}
render() {
return (
- {this.state.plugin.name || ''}
+ {pageTitle(['Instruments', this.state.plugin.name])}
-
-
-
-
-
-
-
-
-
- {this.state.plugin.files.audio ? this.getPlayButton() : ''}
- {this.state.plugin.tags.includes('sfz') ? (
-
- ) : (
- ''
- )}
- {this.state.plugin.files.image ? (
-
- ) : (
- ''
- )}
-
- {this.state.plugin.files.audio ? (
-
- ) : (
- ''
- )}
-
-
-
- {this.state.plugin.name || ''} v{this.state.plugin.version}
-
-
- By{' '}
-
- {this.state.plugin.author}
-
-
-
- {this.state.plugin.description}
-
-
-
- {/*
{this.formatBytes(this.state.plugin.size)}
*/}
-
-
{' '}
- {this.timeSince(this.state.plugin.date)} ago
-
-
-
-
-
- {this.state.plugin.tags.map((tag: string, tagIndex: number) => (
- -
- {tag}
- {tagIndex !== this.state.plugin.tags.length - 1 ? ',' : ''}
-
- ))}
-
-
-
- {this.state.plugin.status !== 'installed' ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
Download and install manually:
-
-
-
-
- Install via{' '}
-
- StudioRack CLI
-
- :
-
-
-
studiorack plugin install {this.state.plugin.id}
-
-
-
-
+
);
}
@@ -355,12 +56,9 @@ type Params = {
};
export async function getServerSideProps({ params }: Params) {
- const plugin: PluginVersionLocal = (await pluginGet(`${params.userId}/${params.pluginId}`)) as PluginVersionLocal;
- plugin.status = pluginInstalled(plugin) ? 'installed' : 'available';
- console.log(plugin);
return {
props: {
- plugin,
+ plugin: await pluginGet(`${params.userId}/${params.pluginId}`),
},
};
}
diff --git a/renderer/pages/instruments/[userId]/index.tsx b/renderer/pages/instruments/[userId]/index.tsx
index a173346..411c282 100644
--- a/renderer/pages/instruments/[userId]/index.tsx
+++ b/renderer/pages/instruments/[userId]/index.tsx
@@ -1,11 +1,10 @@
import { Component } from 'react';
import Head from 'next/head';
-import Crumb from '../../../components/crumb';
-import Layout, { siteTitle } from '../../../components/layout';
-import styles from '../../../styles/plugins.module.css';
-import GridItem from '../../../components/grid-item';
-import { PluginVersion, PluginPack, pluginsGet, PluginVersionLocal, pluginInstalled } from '@studiorack/core';
+import Layout from '../../../components/layout';
+import { PluginVersion, PluginPack, pluginsGet } from '@studiorack/core';
import { getPlugin } from '../../../lib/plugin';
+import { pageTitle } from '../../../lib/utils';
+import List from '../../../components/list';
type PluginListProps = {
plugins: PluginVersion[];
@@ -33,22 +32,9 @@ class PluginList extends Component<
return (
- {siteTitle}
+ {pageTitle(['Instruments', this.state.userId])}
-
-
- {this.state.userId}
-
- {this.state.pluginsFiltered.map((plugin: PluginVersion, pluginIndex: number) => (
-
- ))}
-
-
+
);
}
@@ -63,18 +49,15 @@ type Params = {
export async function getServerSideProps({ params }: Params) {
const pluginPack: PluginPack = await pluginsGet('instruments');
- const list: PluginVersion[] = [];
+ const plugins: PluginVersion[] = [];
for (const pluginId in pluginPack) {
- const plugin: PluginVersionLocal = getPlugin(pluginPack, pluginId) as PluginVersionLocal;
- plugin.status = pluginInstalled(plugin) ? 'installed' : 'available';
- console.log(plugin.id?.split('/')[0], params.userId);
- if (plugin.id?.split('/')[0] === params.userId) {
- list.push(plugin);
+ if (pluginId.split('/')[0] === params.userId) {
+ plugins.push(getPlugin(pluginPack, pluginId));
}
}
return {
props: {
- plugins: list,
+ plugins,
userId: params.userId,
},
};
diff --git a/renderer/pages/instruments/index.tsx b/renderer/pages/instruments/index.tsx
index 709fefd..4540bf2 100644
--- a/renderer/pages/instruments/index.tsx
+++ b/renderer/pages/instruments/index.tsx
@@ -1,166 +1,38 @@
-import { Component, ChangeEvent } from 'react';
+import { ConfigList, PluginVersion } from '@studiorack/core';
+import { useRouter } from 'next/router';
+import { filterPlugins } from '../../lib/plugin';
+import Layout from '../../components/layout';
import Head from 'next/head';
-import { withRouter, Router } from 'next/router';
-import Layout, { siteTitle } from '../../components/layout';
-import styles from '../../styles/plugins.module.css';
-import GridItem from '../../components/grid-item';
import { GetServerSideProps } from 'next';
-import {
- PluginCategory,
- PluginVersion,
- PluginPack,
- pluginsGet,
- PluginVersionLocal,
- pluginInstalled,
-} from '@studiorack/core';
-import { configDefaults } from '../../../node_modules/@studiorack/core/build/config-defaults';
-import { filterPlugins, getPlugin } from '../../lib/plugin';
+import { pageTitle } from '../../lib/utils';
+import { getCategories } from '../../lib/api-browser';
+import { getPlugins } from '../../lib/api';
+import List from '../../components/list';
-type PluginListProps = {
- category: string;
- pluginTypes: { [property: string]: PluginCategory };
+type InstrumentsProps = {
plugins: PluginVersion[];
- pluginsFiltered: PluginVersion[];
- query: string;
- router: Router;
};
-class PluginList extends Component<
- PluginListProps,
- {
- category: string;
- pluginTypes: { [property: string]: PluginCategory };
- plugins: PluginVersion[];
- pluginsFiltered: PluginVersion[];
- query: string;
- router: Router;
- }
-> {
- constructor(props: PluginListProps) {
- super(props);
- const params = props.router.query;
- const category = (params.category as string) || 'all';
- const pluginTypes = configDefaults(
- 'appFolder',
- 'pluginFolder',
- 'presetFolder',
- 'projectFolder',
- ).pluginInstrumentCategories;
- const plugins = props.plugins || [];
- const query = (params.query as string) || '';
- this.state = {
- category,
- pluginTypes,
- plugins,
- pluginsFiltered: filterPlugins(category, plugins, pluginTypes, query),
- query,
- router: props.router,
- };
- }
-
- componentDidUpdate(prevProps: any) {
- const paramPrev = prevProps.router.query;
- const params = this.props.router.query;
- if (params.category !== paramPrev.category) {
- this.setState({ category: params.category as string }, () => {
- this.updateFilter();
- });
- }
- if (params.query !== paramPrev.query) {
- this.setState({ query: params.query as string }, () => {
- this.updateFilter();
- });
- }
- }
-
- updateFilter() {
- this.setState({
- pluginsFiltered: filterPlugins(this.state.category, this.state.plugins, this.state.pluginTypes, this.state.query),
- });
- }
-
- updateUrl = (category: string, query: string) => {
- this.state.router.push(`/instruments?category=${category}&query=${query}`, undefined, { shallow: true });
- };
-
- handleChange = (event: ChangeEvent) => {
- const el = event.target as HTMLInputElement;
- const query = el.value ? el.value.toLowerCase() : '';
- this.updateUrl(this.state.category, query);
- };
-
- isSelected = (path: string) => {
- return this.state.category === path ? 'selected' : '';
- };
-
- selectCategory = (event: React.MouseEvent): void => {
- const category = (event.currentTarget as HTMLTextAreaElement).getAttribute('data-category') || '';
- this.updateUrl(category, this.state.query);
- };
+const Instruments = ({ plugins }: InstrumentsProps) => {
+ const router = useRouter();
+ const categories: ConfigList = getCategories('instruments');
+ const pluginsFiltered: PluginVersion[] = filterPlugins(categories, plugins, router);
+ return (
+
+
+ {pageTitle(['Instruments'])}
+
+
+
+ );
+};
- render() {
- return (
-
-
- {siteTitle}
-
-
-
- );
- }
-}
-export default withRouter(PluginList);
+export default Instruments;
export const getServerSideProps: GetServerSideProps = async () => {
- const pluginPack: PluginPack = await pluginsGet('instruments');
- const list: PluginVersion[] = [];
- for (const pluginId in pluginPack) {
- const plugin: PluginVersionLocal = getPlugin(pluginPack, pluginId) as PluginVersionLocal;
- plugin.status = pluginInstalled(plugin) ? 'installed' : 'available';
- list.push(plugin);
- }
return {
props: {
- plugins: list,
- pluginsFiltered: list,
+ plugins: await getPlugins('instruments'),
},
};
};
diff --git a/renderer/pages/projects/index.tsx b/renderer/pages/projects/index.tsx
index e978026..5de82c8 100644
--- a/renderer/pages/projects/index.tsx
+++ b/renderer/pages/projects/index.tsx
@@ -125,7 +125,7 @@ class ProjectList extends Component<
onClick={this.selectCategory}
className={this.isSelected(projectTypeKey)}
>
- {this.state.projectTypes[projectTypeKey].name}
+ {this.state.projectTypes[projectTypeKey as keyof ProjectTypes].name}
))}
diff --git a/renderer/pages/settings/index.tsx b/renderer/pages/settings/index.tsx
index 7e98f43..a3aa710 100644
--- a/renderer/pages/settings/index.tsx
+++ b/renderer/pages/settings/index.tsx
@@ -5,7 +5,7 @@ import styles from '../../styles/settings.module.css';
import { GetServerSideProps } from 'next';
declare module 'react' {
- interface HTMLAttributes
extends AriaAttributes, DOMAttributes {
+ interface HTMLAttributes {
// extends React's HTMLAttributes
webkitdirectory?: string;
}
diff --git a/renderer/public/images/icon-arrow-down.svg b/renderer/public/images/icon-arrow-down.svg
new file mode 100644
index 0000000..8df6ad6
--- /dev/null
+++ b/renderer/public/images/icon-arrow-down.svg
@@ -0,0 +1,3 @@
+
diff --git a/renderer/public/images/icon-category.svg b/renderer/public/images/icon-category.svg
new file mode 100644
index 0000000..9d771cc
--- /dev/null
+++ b/renderer/public/images/icon-category.svg
@@ -0,0 +1,8 @@
+
diff --git a/renderer/public/images/icon-compatibility.svg b/renderer/public/images/icon-compatibility.svg
new file mode 100644
index 0000000..1d14061
--- /dev/null
+++ b/renderer/public/images/icon-compatibility.svg
@@ -0,0 +1,3 @@
+
diff --git a/renderer/public/images/icon-cost.svg b/renderer/public/images/icon-cost.svg
new file mode 100644
index 0000000..7aad7ad
--- /dev/null
+++ b/renderer/public/images/icon-cost.svg
@@ -0,0 +1,3 @@
+
diff --git a/renderer/public/images/icon-platform.svg b/renderer/public/images/icon-platform.svg
new file mode 100644
index 0000000..8762f75
--- /dev/null
+++ b/renderer/public/images/icon-platform.svg
@@ -0,0 +1,6 @@
+
diff --git a/renderer/public/images/license.svg b/renderer/public/images/license.svg
new file mode 100644
index 0000000..18fe2b6
--- /dev/null
+++ b/renderer/public/images/license.svg
@@ -0,0 +1,3 @@
+
diff --git a/renderer/styles/components/audio.module.css b/renderer/styles/components/audio.module.css
new file mode 100644
index 0000000..30b5943
--- /dev/null
+++ b/renderer/styles/components/audio.module.css
@@ -0,0 +1,7 @@
+.audio {
+ cursor: pointer;
+ left: 50%;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+}
diff --git a/renderer/styles/components/card.module.css b/renderer/styles/components/card.module.css
new file mode 100644
index 0000000..a4b1e15
--- /dev/null
+++ b/renderer/styles/components/card.module.css
@@ -0,0 +1,89 @@
+.cardLink,
+.cardLink:hover {
+ border-bottom: none;
+ padding-bottom: 0;
+ transition: none;
+ width: 100%;
+}
+
+.card {
+ background-color: #111;
+ border-radius: 0.5rem;
+ cursor: pointer;
+ height: 100%;
+ max-height: 200px;
+ overflow: hidden;
+ padding: 1.5rem 1.5rem 0 1.5rem;
+ margin-bottom: 1rem;
+ transition: background-color 0.25s ease-out;
+}
+
+.card:hover {
+ background-color: #222;
+}
+
+.cardDetails {
+ margin-bottom: 1.5rem;
+}
+
+.cardHead {
+ display: flex;
+ justify-content: space-between;
+}
+
+.cardTitle {
+ flex-shrink: 1;
+ margin: 0;
+}
+
+.cardButton,
+.cardButtonInstalled {
+ background-color: #0d51ff;
+ border-radius: 4px;
+ display: flex;
+ flex-shrink: 0;
+ height: 15px;
+ justify-content: center;
+ margin-left: 0.5rem;
+ padding: 8px;
+ width: 15px;
+}
+
+.cardButtonIcon {
+ max-width: 100%;
+}
+
+.cardButtonInstalled {
+ background-color: #48ad6a;
+}
+
+.cardVersion {
+ color: #666;
+ font-size: 0.75rem;
+}
+
+.cardTags {
+ display: inline-block;
+ list-style: none;
+ margin: 0;
+ overflow-x: hidden;
+ padding: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
+}
+
+.cardIcon {
+ margin-right: 0.5rem;
+ width: 15px;
+}
+
+.cardTag {
+ display: inline;
+ margin-right: 0.25rem;
+}
+
+.cardImage {
+ display: cover;
+ width: 100%;
+}
diff --git a/renderer/styles/components/code.module.css b/renderer/styles/components/code.module.css
new file mode 100644
index 0000000..7eb79c6
--- /dev/null
+++ b/renderer/styles/components/code.module.css
@@ -0,0 +1,20 @@
+.code {
+ margin-bottom: 1.5rem;
+}
+
+.codeLine {
+ background-color: #111;
+ border-radius: 0.25rem;
+ font-size: 1rem;
+ line-height: 1.25rem;
+ padding: 1rem 1.5rem;
+ white-space: normal;
+}
+
+@media (min-width: 48rem) {
+ .code {
+ border-left: 1px solid #666;
+ flex: 1;
+ padding-left: 2rem;
+ }
+}
diff --git a/renderer/styles/components/crumb.module.css b/renderer/styles/components/crumb.module.css
index 57d6577..4a0c93d 100644
--- a/renderer/styles/components/crumb.module.css
+++ b/renderer/styles/components/crumb.module.css
@@ -9,7 +9,7 @@
padding: 0;
}
-.crumbList li:first-child {
+.crumbItem:first-child {
display: none;
}
diff --git a/renderer/styles/components/details.module.css b/renderer/styles/components/details.module.css
new file mode 100644
index 0000000..d72f6c4
--- /dev/null
+++ b/renderer/styles/components/details.module.css
@@ -0,0 +1,111 @@
+.header {
+ background: rgb(255, 40, 79);
+ background: linear-gradient(160deg, rgba(255, 40, 79, 1) 0%, rgba(21, 71, 201, 1) 100%);
+ padding: 1.5rem;
+}
+
+.imageContainer {
+ position: relative;
+ z-index: 1;
+}
+
+.title {
+ margin: 0;
+}
+
+.author {
+ font-size: 1rem;
+}
+
+.image {
+ display: block;
+ margin: 0 auto 1rem auto;
+ max-height: 300px;
+ max-width: 100%;
+}
+
+.version {
+ color: #999;
+ font-size: 0.75rem;
+}
+
+.metadataList {
+ font-size: 0.9rem;
+}
+
+.metadataList a {
+ border-bottom: none;
+}
+
+.metadata {
+ align-items: center;
+ display: flex;
+ margin-bottom: 0.5rem;
+}
+
+.metadataFooter {
+ margin-top: 1.5rem;
+}
+
+.tags {
+ display: flex;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.icon {
+ margin-right: 0.5rem;
+ width: 20px;
+}
+
+.tag {
+ margin-right: 0.25rem;
+}
+
+.options {
+ margin: 0 auto;
+ max-width: 1024px;
+ padding: 2rem 1.5rem;
+}
+
+@media (min-width: 48rem) {
+ .header {
+ padding-bottom: 5rem;
+ padding-top: 5rem;
+ }
+
+ .headerInner {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ margin: 0 auto;
+ max-width: 1024px;
+ }
+
+ .headerInner2 {
+ margin: 0 auto;
+ max-width: 1024px;
+ }
+
+ .image {
+ margin: 0 auto 0 auto;
+ }
+
+ .media {
+ flex-basis: 33.33%;
+ }
+
+ .details {
+ flex-basis: 66.66%;
+ margin-left: 3rem;
+ }
+
+ .row {
+ display: flex;
+ }
+
+ .cell {
+ margin-right: 1rem;
+ }
+}
diff --git a/renderer/styles/components/download.module.css b/renderer/styles/components/download.module.css
new file mode 100644
index 0000000..78434a5
--- /dev/null
+++ b/renderer/styles/components/download.module.css
@@ -0,0 +1,27 @@
+.download {
+ margin-bottom: 1.5rem;
+}
+
+.downloadButton {
+ margin-right: 0.5rem;
+}
+
+.downloadButton .progress {
+ display: none;
+}
+
+.downloadButton:disabled .progress {
+ display: inline;
+}
+
+.downloadButtonIcon {
+ margin-left: 0.5rem;
+ vertical-align: middle;
+ width: 14px;
+}
+
+@media (min-width: 48rem) {
+ .download {
+ margin-right: 2rem;
+ }
+}
diff --git a/renderer/styles/components/filters.module.css b/renderer/styles/components/filters.module.css
new file mode 100644
index 0000000..8c8db90
--- /dev/null
+++ b/renderer/styles/components/filters.module.css
@@ -0,0 +1,35 @@
+.filters {
+ font-size: 1.25rem;
+ font-weight: 500;
+ margin-bottom: 1rem;
+}
+
+.filtersTitle {
+ display: inline-block;
+ margin: 0 1rem 1rem 0;
+}
+
+.filtersSearch {
+ background-color: #111;
+ border-radius: 0.25rem;
+ border: 0;
+ color: #999;
+ font-size: 1rem;
+ margin-bottom: 1rem;
+ padding: 0.75rem 1rem;
+ text-overflow: ellipsis;
+ width: 100%;
+}
+
+.filtersSearch:focus {
+ outline: 1px solid #fff;
+}
+
+@media (min-width: 48rem) {
+ .filtersSearch {
+ display: inline-block;
+ font-size: 1.15rem;
+ margin-right: 1rem;
+ max-width: 180px;
+ }
+}
diff --git a/renderer/styles/components/header.module.css b/renderer/styles/components/header.module.css
new file mode 100644
index 0000000..d8b4c78
--- /dev/null
+++ b/renderer/styles/components/header.module.css
@@ -0,0 +1,20 @@
+.header {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.headerTitle {
+ margin-bottom: 1rem;
+}
+
+.headerCount {
+ color: #666;
+ font-size: 1rem;
+}
+
+@media (min-width: 48rem) {
+ .headerCount {
+ font-size: 1.4rem;
+ }
+}
diff --git a/renderer/styles/components/layout.module.css b/renderer/styles/components/layout.module.css
index 97ae3cc..846c242 100644
--- a/renderer/styles/components/layout.module.css
+++ b/renderer/styles/components/layout.module.css
@@ -1,5 +1,5 @@
-.header {
- background-color: #000;
+.layoutHeader {
+ background-color: #111;
min-height: 70px;
position: fixed;
top: 0;
@@ -7,7 +7,7 @@
z-index: 2;
}
-.headerLink {
+.layoutLink {
align-items: center;
border: 0;
display: flex;
@@ -15,25 +15,25 @@
padding: 1.1rem 1.5rem;
}
-.headerLink:hover {
+.layoutLink:hover {
border: 0;
}
-.logoText {
+.layoutLogo {
font-size: 24px;
margin: 0 0 0 1rem;
text-transform: uppercase;
}
-.logoTextBold {
+.layoutLogoBold {
font-weight: 700;
}
@media (min-width: 64rem) {
- .headerLink {
+ .layoutLink {
padding: 1rem 1.5rem;
}
- .logoText {
+ .layoutLogo {
font-size: 30px;
margin: 0 0 0 1rem;
text-transform: uppercase;
diff --git a/renderer/styles/components/list.module.css b/renderer/styles/components/list.module.css
new file mode 100644
index 0000000..b169d33
--- /dev/null
+++ b/renderer/styles/components/list.module.css
@@ -0,0 +1,32 @@
+.list {
+ box-sizing: border-box;
+ padding: 2rem 1.5rem;
+ width: 100%;
+}
+
+@media (min-width: 48rem) {
+ .listGrid {
+ display: grid;
+ grid-auto-rows: minmax(100px, auto);
+ grid-gap: 1.5rem;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 64rem) {
+ .listGrid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 120rem) {
+ .listGrid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 160rem) {
+ .listGrid {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+}
diff --git a/renderer/styles/components/multi-select.module.css b/renderer/styles/components/multi-select.module.css
new file mode 100644
index 0000000..589f5da
--- /dev/null
+++ b/renderer/styles/components/multi-select.module.css
@@ -0,0 +1,106 @@
+.multiselect {
+ margin-bottom: 1rem;
+ position: relative;
+}
+
+.multiselect[id='license'] {
+ min-width: 15rem;
+}
+
+.multiselectTitle {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ background-color: #111;
+ background-image: url(../../public/images/icon-arrow-down.svg);
+ background-position: right 1rem center;
+ background-repeat: no-repeat;
+ border: none;
+ border-radius: 1rem;
+ color: #fff;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ padding: 0.75rem 3rem;
+ width: 100%;
+}
+
+.icon-category {
+ background-image: url(../../public/images/icon-category.svg), url(../../public/images/icon-arrow-down.svg);
+ background-position:
+ left 1rem center,
+ right 1rem center;
+}
+
+.icon-license {
+ background-image: url(../../public/images/icon-license.svg), url(../../public/images/icon-arrow-down.svg);
+ background-position:
+ left 1rem center,
+ right 1rem center;
+}
+
+.icon-cost {
+ background-image: url(../../public/images/icon-cost.svg), url(../../public/images/icon-arrow-down.svg);
+ background-position:
+ left 1rem center,
+ right 1rem center;
+}
+
+.icon-compatibility {
+ background-image: url(../../public/images/icon-compatibility.svg), url(../../public/images/icon-arrow-down.svg);
+ background-position:
+ left 1rem center,
+ right 1rem center;
+}
+
+.icon-platform {
+ background-image: url(../../public/images/icon-platform.svg), url(../../public/images/icon-arrow-down.svg);
+ background-position:
+ left 1rem center,
+ right 1rem center;
+}
+
+.multiselectCheckboxes {
+ background-color: #111;
+ border-radius: 1rem;
+ box-shadow: 0 0 0.5rem rgba(255, 255, 255, 0.1);
+ display: none;
+ max-height: 70vh;
+ overflow-y: scroll;
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+}
+
+.multiselectLabel {
+ cursor: pointer;
+ display: block;
+ font-size: 1rem;
+ font-weight: 400;
+ overflow: hidden;
+ padding: 0.75rem 1rem;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.multiselectLabel:hover {
+ background-color: #222;
+}
+
+.multiselectInput {
+ border: none;
+ margin: 0 0.9rem 0.2rem 0.25rem;
+ transform: scale(1.25);
+ vertical-align: middle;
+}
+
+@media (min-width: 48rem) {
+ .multiselect {
+ display: inline-block;
+ margin-right: 1rem;
+ }
+ .multiselectTitle,
+ .multiselectLabel {
+ font-size: 1.15rem;
+ }
+}
diff --git a/renderer/styles/components/navigation.module.css b/renderer/styles/components/navigation.module.css
index 279c11c..dc6225d 100644
--- a/renderer/styles/components/navigation.module.css
+++ b/renderer/styles/components/navigation.module.css
@@ -23,20 +23,20 @@
width: 15px;
}
-.menu {
+.navMenu {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
}
-.menu {
+.navMenu {
clear: both;
max-height: 0;
transition: max-height 0.2s ease-out;
}
-.menuIcn {
+.navMenuIcn {
cursor: pointer;
float: right;
padding: 2.1rem 1.5rem;
@@ -44,7 +44,7 @@
user-select: none;
}
-.menuIcn .menuNavIcn {
+.navMenuIcn .navMenuNavIcn {
background: #fff;
display: block;
height: 2px;
@@ -53,8 +53,8 @@
width: 18px;
}
-.menuIcn .menuNavIcn:before,
-.menuIcn .menuNavIcn:after {
+.navMenuIcn .navMenuNavIcn:before,
+.navMenuIcn .navMenuNavIcn:after {
background: #fff;
content: '';
display: block;
@@ -64,36 +64,36 @@
width: 100%;
}
-.menuIcn .menuNavIcn:before {
+.navMenuIcn .navMenuNavIcn:before {
top: 5px;
}
-.menuIcn .menuNavIcn:after {
+.navMenuIcn .navMenuNavIcn:after {
top: -5px;
}
-.menuBtn {
+.navMenuBtn {
display: none;
}
-.menuBtn:checked ~ .menu {
+.navMenuBtn:checked ~ .navMenu {
max-height: 280px;
}
-.menuBtn:checked ~ .menuIcn .menuNavIcn {
+.navMenuBtn:checked ~ .navMenuIcn .navMenuNavIcn {
background: transparent;
}
-.menuBtn:checked ~ .menuIcn .menuNavIcn:before {
+.navMenuBtn:checked ~ .navMenuIcn .navMenuNavIcn:before {
transform: rotate(-45deg);
}
-.menuBtn:checked ~ .menuIcn .menuNavIcn:after {
+.navMenuBtn:checked ~ .navMenuIcn .navMenuNavIcn:after {
transform: rotate(45deg);
}
-.menuBtn:checked ~ .menuIcn:not(.steps) .menuNavIcn:before,
-.menuBtn:checked ~ .menuIcn:not(.steps) .menuNavIcn:after {
+.navMenuBtn:checked ~ .navMenuIcn:not(.steps) .navMenuNavIcn:before,
+.navMenuBtn:checked ~ .navMenuIcn:not(.steps) .navMenuNavIcn:after {
top: 0;
}
@@ -101,15 +101,15 @@
.navItem {
margin-right: 0.5rem;
}
- .menu {
+ .navMenu {
clear: none;
float: right;
max-height: none;
}
- .menu li {
+ .navMenu li {
float: left;
}
- .menuIcn {
+ .navMenuIcn {
display: none;
}
}
diff --git a/renderer/styles/components/player.module.css b/renderer/styles/components/player.module.css
new file mode 100644
index 0000000..2279d2e
--- /dev/null
+++ b/renderer/styles/components/player.module.css
@@ -0,0 +1,8 @@
+.player {
+ cursor: pointer;
+ left: 50%;
+ position: absolute;
+ top: 100%;
+ transform: translate(-50%, -50%);
+ width: 144px;
+}
diff --git a/renderer/styles/components/subnav.module.css b/renderer/styles/components/subnav.module.css
index 91ac3b4..98ff38b 100644
--- a/renderer/styles/components/subnav.module.css
+++ b/renderer/styles/components/subnav.module.css
@@ -2,31 +2,52 @@
padding: 1.5rem;
}
-.sidebar {
+.subnavSidebar {
border-bottom: 1px solid #333;
margin: 0 0 2rem 0;
min-width: 200px;
padding-bottom: 1rem;
}
-.sidebar h4 {
+.subnavSidebar h4 {
margin: 0 0 0.5rem;
}
-.sidebar ul {
+.subnavSidebar ul {
margin: 0 0 1rem;
padding: 0 0 0 1.25rem;
}
-.sidebar a {
+.subnavSidebar a {
border-bottom: 2px solid transparent;
margin: 0.5rem;
}
-.sidebar a:hover {
+.subnavSidebar a:hover {
border-bottom: 2px solid #fff;
}
+.subnavContent {
+ overflow: auto;
+ width: 100%;
+}
+
+.subnavContent h1 {
+ font-size: 42px;
+}
+
+.subnavContent h2 {
+ font-size: 32px;
+}
+
+.subnavContent h3 {
+ font-size: 28px;
+}
+
+.subnavContent h4 {
+ font-size: 24px;
+}
+
@media (min-width: 48rem) {
.subnav {
display: flex;
@@ -34,7 +55,7 @@
max-width: 1024px;
}
- .sidebar {
+ .subnavSidebar {
border: 0;
margin-right: 6rem;
}
diff --git a/renderer/styles/docs.module.css b/renderer/styles/docs.module.css
deleted file mode 100644
index 8d18403..0000000
--- a/renderer/styles/docs.module.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.content {
- overflow: auto;
- width: 100%;
-}
-
-.content h1 {
- font-size: 42px;
-}
-
-.content h2 {
- font-size: 32px;
-}
-
-.content h3 {
- font-size: 28px;
-}
-
-.content h4 {
- font-size: 24px;
-}
diff --git a/renderer/tsconfig.json b/renderer/tsconfig.json
index 2851f6c..5cc1367 100644
--- a/renderer/tsconfig.json
+++ b/renderer/tsconfig.json
@@ -1,20 +1,21 @@
{
"compilerOptions": {
- "target": "es2020",
- "lib": ["dom", "dom.iterable", "es2020"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": false,
- "forceConsistentCasingInFileNames": true,
- "noEmit": true,
+ "lib": ["es2023", "dom", "dom.iterable"],
+ "module": "esnext",
+ "target": "es2022",
+ "strict": true,
"esModuleInterop": true,
- "module": "es2020",
+ "skipLibCheck": true,
"moduleResolution": "node",
+ "outDir": "build",
+ "declaration": true,
+ "allowJs": true,
+ "noEmit": true,
+ "incremental": true,
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
- "incremental": true
+ "jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "exclude": ["tests/**/*.ts", "node_modules"]
}