From 3ebaac6752cb2b13ee5bfb6274cd7ae60b0d6bcb Mon Sep 17 00:00:00 2001
From: EMERALD <hudson.samuels@gmail.com>
Date: Thu, 19 Jan 2023 20:00:42 -0600
Subject: Store and plugin installation visual improvements (#343)

* Redesign store, add comments for filtering

* Improve installation/uninstallation modals

* Fix store comment to be easier to fix

* Add source code info to about page
---
 frontend/src/components/store/Store.tsx | 224 +++++++++++++++++++++++++++++---
 1 file changed, 206 insertions(+), 18 deletions(-)

(limited to 'frontend/src/components/store/Store.tsx')

diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx
index 2ffd8efb..7a9c0e33 100644
--- a/frontend/src/components/store/Store.tsx
+++ b/frontend/src/components/store/Store.tsx
@@ -1,6 +1,16 @@
-import { SteamSpinner } from 'decky-frontend-lib';
-import { FC, useEffect, useState } from 'react';
+import {
+  Dropdown,
+  DropdownOption,
+  Focusable,
+  PanelSectionRow,
+  SteamSpinner,
+  Tabs,
+  TextField,
+  findModule,
+} from 'decky-frontend-lib';
+import { FC, useEffect, useMemo, useState } from 'react';
 
+import logo from '../../../assets/plugin_store.png';
 import Logger from '../../logger';
 import { StorePlugin, getPluginList } from '../../store';
 import PluginCard from './PluginCard';
@@ -8,7 +18,12 @@ import PluginCard from './PluginCard';
 const logger = new Logger('FilePicker');
 
 const StorePage: FC<{}> = () => {
+  const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse');
   const [data, setData] = useState<StorePlugin[] | null>(null);
+  const { TabCount } = findModule((m) => {
+    if (m?.TabCount && m?.TabTitle) return true;
+    return false;
+  });
 
   useEffect(() => {
     (async () => {
@@ -19,19 +34,12 @@ const StorePage: FC<{}> = () => {
   }, []);
 
   return (
-    <div
-      style={{
-        marginTop: '40px',
-        height: 'calc( 100% - 40px )',
-        overflowY: 'scroll',
-      }}
-    >
+    <>
       <div
         style={{
-          display: 'flex',
-          flexWrap: 'nowrap',
-          flexDirection: 'column',
-          height: '100%',
+          marginTop: '40px',
+          height: 'calc( 100% - 40px )',
+          background: '#0005',
         }}
       >
         {!data ? (
@@ -39,13 +47,193 @@ const StorePage: FC<{}> = () => {
             <SteamSpinner />
           </div>
         ) : (
-          <div>
-            {data.map((plugin: StorePlugin) => (
-              <PluginCard plugin={plugin} />
-            ))}
-          </div>
+          <Tabs
+            activeTab={currentTabRoute}
+            onShowTab={(tabId: string) => {
+              setCurrentTabRoute(tabId);
+            }}
+            tabs={[
+              {
+                title: 'Browse',
+                content: <BrowseTab children={{ data: data }} />,
+                id: 'browse',
+                renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
+              },
+              {
+                title: 'About',
+                content: <AboutTab />,
+                id: 'about',
+              },
+            ]}
+          />
         )}
       </div>
+    </>
+  );
+};
+
+const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
+  const sortOptions = useMemo(
+    (): DropdownOption[] => [
+      { data: 1, label: 'Alphabetical (A to Z)' },
+      { data: 2, label: 'Alphabetical (Z to A)' },
+    ],
+    [],
+  );
+
+  // const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []);
+
+  const [selectedSort, setSort] = useState<number>(sortOptions[0].data);
+  // const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data);
+  const [searchFieldValue, setSearchValue] = useState<string>('');
+
+  return (
+    <>
+      <style>{`
+              .deckyStoreCardInstallContainer > .Panel {
+                padding: 0;
+              }
+            `}</style>
+      {/* This should be used once filtering is added
+
+      <PanelSectionRow>
+        <Focusable style={{ display: 'flex', maxWidth: '100%' }}>
+          <div
+            style={{
+              display: 'flex',
+              flexDirection: 'column',
+              width: '47.5%',
+            }}
+          >
+            <span className="DialogLabel">Sort</span>
+            <Dropdown
+              menuLabel="Sort"
+              rgOptions={sortOptions}
+              strDefaultLabel="Last Updated (Newest)"
+              selectedOption={selectedSort}
+              onChange={(e) => setSort(e.data)}
+            />
+          </div>
+          <div
+            style={{
+              display: 'flex',
+              flexDirection: 'column',
+              width: '47.5%',
+              marginLeft: 'auto',
+            }}
+          >
+            <span className="DialogLabel">Filter</span>
+            <Dropdown
+              menuLabel="Filter"
+              rgOptions={filterOptions}
+              strDefaultLabel="All"
+              selectedOption={selectedFilter}
+              onChange={(e) => setFilter(e.data)}
+            />
+          </div>
+        </Focusable>
+      </PanelSectionRow>
+      <div style={{ justifyContent: 'center', display: 'flex' }}>
+        <Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
+          <div style={{ width: '100%' }}>
+            <TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
+          </div>
+        </Focusable>
+      </div>
+      */}
+      <PanelSectionRow>
+        <Focusable style={{ display: 'flex', maxWidth: '100%' }}>
+          <div
+            style={{
+              display: 'flex',
+              flexDirection: 'column',
+              minWidth: '100%',
+              maxWidth: '100%',
+            }}
+          >
+            <span className="DialogLabel">Sort</span>
+            <Dropdown
+              menuLabel="Sort"
+              rgOptions={sortOptions}
+              strDefaultLabel="Last Updated (Newest)"
+              selectedOption={selectedSort}
+              onChange={(e) => setSort(e.data)}
+            />
+          </div>
+        </Focusable>
+      </PanelSectionRow>
+      <div style={{ justifyContent: 'center', display: 'flex' }}>
+        <Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
+          <div style={{ width: '100%' }}>
+            <TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
+          </div>
+        </Focusable>
+      </div>
+      <div>
+        {data.children.data
+          .filter((plugin: StorePlugin) => {
+            return (
+              plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
+              plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
+              plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
+              plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase()))
+            );
+          })
+          .sort((a, b) => {
+            if (selectedSort % 2 === 1) return a.name.localeCompare(b.name);
+            else return b.name.localeCompare(a.name);
+          })
+          .map((plugin: StorePlugin) => (
+            <PluginCard plugin={plugin} />
+          ))}
+      </div>
+    </>
+  );
+};
+
+const AboutTab: FC<{}> = () => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        flexDirection: 'column',
+      }}
+    >
+      <style>{`
+              .deckyStoreAboutHeader {
+                font-size: 24px;
+                font-weight: 600;
+                margin-top: 20px;
+              }
+            `}</style>
+      <img
+        src={logo}
+        style={{
+          width: '256px',
+          height: 'auto',
+          alignSelf: 'center',
+        }}
+      />
+      <span className="deckyStoreAboutHeader">Testing</span>
+      <span>
+        Please consider testing new plugins to help the Decky Loader team!{' '}
+        <a
+          href="https://deckbrew.xyz/testing"
+          target="_blank"
+          style={{
+            textDecoration: 'none',
+          }}
+        >
+          deckbrew.xyz/testing
+        </a>
+      </span>
+      <span className="deckyStoreAboutHeader">Contributing</span>
+      <span>
+        If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template
+        repository on GitHub. Information on development and distribution is available in the README.
+      </span>
+      <span className="deckyStoreAboutHeader">Source Code</span>
+      <span>All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.</span>
     </div>
   );
 };
-- 
cgit v1.2.3

