Yuki Takei 3 лет назад
Родитель
Сommit
f9f93b223b

+ 3 - 1
packages/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx

@@ -1,6 +1,6 @@
 import React, { useMemo } from 'react';
 
-import { GrowiThemeColorSummary, GrowiThemeSchemeType } from '@growi/core';
+import { GrowiThemeColorSummary, GrowiThemeSchemeType, isCustomTheme } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 import { ThemeColorBox } from './ThemeColorBox';
@@ -35,6 +35,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
               <ThemeColorBox
                 key={theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
+                isCustomTheme={isCustomTheme(theme)}
                 onSelected={() => onSelected?.(theme.name)}
                 {...theme}
               />
@@ -51,6 +52,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
               <ThemeColorBox
                 key={theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
+                isCustomTheme={isCustomTheme(theme)}
                 onSelected={() => onSelected?.(theme.name)}
                 {...theme}
               />

+ 14 - 6
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
-import { useSWRxGrowiTheme } from '~/stores/admin/customize';
+import { useSWRxGrowiThemeSetting } from '~/stores/admin/customize';
 
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
@@ -20,12 +20,12 @@ type Props = {
 const CustomizeThemeSetting = (props: Props): JSX.Element => {
   const { t } = useTranslation();
 
-  const { data: currentTheme, error } = useSWRxGrowiTheme();
-  const [selectedTheme, setSelectedTheme] = useState(currentTheme);
+  const { data, error } = useSWRxGrowiThemeSetting();
+  const [selectedTheme, setSelectedTheme] = useState(data?.currentTheme);
 
   useEffect(() => {
-    setSelectedTheme(currentTheme);
-  }, [currentTheme]);
+    setSelectedTheme(data?.currentTheme);
+  }, [data?.currentTheme]);
 
   const selectedHandler = useCallback((themeName: string) => {
     setSelectedTheme(themeName);
@@ -49,11 +49,19 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     }
   }, [selectedTheme, t]);
 
+  const availableThemes = data?.customThemeSummaries == null
+    ? PresetThemesSummaries
+    : PresetThemesSummaries.concat(data.customThemeSummaries);
+
   return (
     <div className="row">
       <div className="col-12">
         <h2 className="admin-setting-header">{t('admin:customize_settings.theme')}</h2>
-        <CustomizeThemeOptions onSelected={selectedHandler} availableThemes={PresetThemesSummaries} selectedTheme={selectedTheme} />
+        <CustomizeThemeOptions
+          onSelected={selectedHandler}
+          availableThemes={availableThemes}
+          selectedTheme={selectedTheme}
+        />
         <AdminUpdateButtonRow onClick={submitHandler} disabled={error != null} />
       </div>
     </div>

+ 4 - 2
packages/app/src/components/Admin/Customize/ThemeColorBox.tsx

@@ -8,19 +8,20 @@ type Props = {
   topbar: string,
   sidebar: string,
   theme: string,
+  isCustomTheme?: boolean,
   onSelected?: () => void,
 };
 
 export const ThemeColorBox = (props: Props): JSX.Element => {
 
   const {
-    isSelected, onSelected, name, bg, topbar, sidebar, theme,
+    isSelected, onSelected, name, bg, topbar, sidebar, theme, isCustomTheme,
   } = props;
 
   return (
     <div
       id={`theme-option-${name}`}
-      className={`theme-option-container d-flex flex-column align-items-center ${isSelected && 'active'}`}
+      className={`theme-option-container d-flex flex-column align-items-center ${isSelected ? 'active' : ''}`}
       onClick={onSelected}
     >
       <a id={name} role="button" className={`m-0 ${name} theme-button`}>
@@ -34,6 +35,7 @@ export const ThemeColorBox = (props: Props): JSX.Element => {
         </svg>
       </a>
       <span className="theme-option-name"><b>{ name }</b></span>
+      { isCustomTheme && <span className='theme-option-badge badge badge-primary mt-1'>Plugin</span> }
     </div>
   );
 

+ 2 - 2
packages/app/src/components/Layout/Admin.module.scss

@@ -267,12 +267,12 @@ $slack-work-space-name-card-border: #efc1f6;
       background-color: $gray-50;
       border: 1px solid $border-color;
     }
-    .theme-option-name {
+    .theme-option-name, .theme-option-badge {
       opacity: 0.3;
     }
     // style (active)
     .theme-option-container.active {
-      .theme-option-name {
+      .theme-option-name, .theme-option-badge {
         opacity: 1;
       }
     }

+ 10 - 4
packages/app/src/stores/admin/customize.tsx

@@ -27,22 +27,28 @@ export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> &
   };
 };
 
-export const useSWRxGrowiTheme = (): SWRResponse<string, Error> => {
+export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> => {
 
   const fetcher = useCallback(async() => {
     const res = await apiv3Get<IResGrowiTheme>('/customize-setting/theme');
-    return res.data.theme;
+    return res.data;
   }, []);
 
   const swrResponse = useSWRImmutable('/customize-setting/theme', fetcher);
 
   const update = async(theme: string) => {
     await apiv3Put('/customize-setting/layout', { theme });
-    await swrResponse.mutate();
+
+    if (swrResponse.data == null) {
+      swrResponse.mutate();
+      return;
+    }
+
+    const newData = { ...swrResponse.data, currentTheme: theme };
     // The updateFn should be a promise or asynchronous function to handle the remote mutation
     // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
     // Moreover, `async() => false` does not work since it's too fast to be calculated.
-    await swrResponse.mutate(new Promise(r => setTimeout(() => r(theme), 10)), { optimisticData: () => theme });
+    await swrResponse.mutate(new Promise(r => setTimeout(() => r(newData), 10)), { optimisticData: () => newData });
   };
 
   return Object.assign(

+ 4 - 0
packages/core/src/interfaces/growi-custom-theme-summary.ts

@@ -17,3 +17,7 @@ export type GrowiThemeColorSummary = {
 export type GrowiCustomThemeSummary = GrowiThemeColorSummary & {
   manifestKey: string,
 };
+
+export const isCustomTheme = (summary: GrowiThemeColorSummary): summary is GrowiCustomThemeSummary => {
+  return 'manifestKey' in summary;
+};