From b6271938b32dc6643b44df7ccc111bd6ff813447 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 29 Jul 2024 23:05:30 +0800 Subject: [PATCH] feat: implement custom branding --- frontend/web/src/components/Header.tsx | 3 +- frontend/web/src/components/Logo.tsx | 23 ++++++++++ .../components/setting/WorkspaceSection.tsx | 46 ++++++++++++++++++- frontend/web/src/pages/SignIn.tsx | 4 +- frontend/web/src/pages/SignUp.tsx | 4 +- server/route/api/v1/workspace_service.go | 1 + 6 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 frontend/web/src/components/Logo.tsx diff --git a/frontend/web/src/components/Header.tsx b/frontend/web/src/components/Header.tsx index 7978468..3207f79 100644 --- a/frontend/web/src/components/Header.tsx +++ b/frontend/web/src/components/Header.tsx @@ -8,6 +8,7 @@ import { PlanType } from "@/types/proto/api/v1/subscription_service"; import { Role } from "@/types/proto/api/v1/user_service"; import AboutDialog from "./AboutDialog"; import Icon from "./Icon"; +import Logo from "./Logo"; import Dropdown from "./common/Dropdown"; const Header: React.FC = () => { @@ -32,7 +33,7 @@ const Header: React.FC = () => {
- + Slash {profile.plan === PlanType.PRO && ( diff --git a/frontend/web/src/components/Logo.tsx b/frontend/web/src/components/Logo.tsx new file mode 100644 index 0000000..b2bff10 --- /dev/null +++ b/frontend/web/src/components/Logo.tsx @@ -0,0 +1,23 @@ +import classNames from "classnames"; +import { useWorkspaceStore } from "@/stores"; +import Icon from "./Icon"; + +interface Props { + className?: string; +} + +const Logo = ({ className }: Props) => { + const workspaceStore = useWorkspaceStore(); + const branding = workspaceStore.setting.branding ? new TextDecoder().decode(workspaceStore.setting.branding) : ""; + return ( +
+ {branding ? ( + branding + ) : ( + + )} +
+ ); +}; + +export default Logo; diff --git a/frontend/web/src/components/setting/WorkspaceSection.tsx b/frontend/web/src/components/setting/WorkspaceSection.tsx index 86a9a4c..0a7887e 100644 --- a/frontend/web/src/components/setting/WorkspaceSection.tsx +++ b/frontend/web/src/components/setting/WorkspaceSection.tsx @@ -1,5 +1,5 @@ import { Button, Option, Select, Textarea } from "@mui/joy"; -import { isEqual } from "lodash-es"; +import { head, isEqual } from "lodash-es"; import { useRef, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -7,6 +7,7 @@ import { workspaceServiceClient } from "@/grpcweb"; import { useWorkspaceStore } from "@/stores"; import { Visibility } from "@/types/proto/api/v1/common"; import { WorkspaceSetting } from "@/types/proto/api/v1/workspace_service"; +import Icon from "../Icon"; const getDefaultVisibility = (visibility?: Visibility) => { if (!visibility || [Visibility.VISIBILITY_UNSPECIFIED, Visibility.UNRECOGNIZED].includes(visibility)) { @@ -16,12 +17,32 @@ const getDefaultVisibility = (visibility?: Visibility) => { return visibility; }; +const convertFileToBase64 = (file: File) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + }); + const WorkspaceSection = () => { const { t } = useTranslation(); const workspaceStore = useWorkspaceStore(); const [workspaceSetting, setWorkspaceSetting] = useState(workspaceStore.setting); const originalWorkspaceSetting = useRef(workspaceStore.setting); const allowSave = !isEqual(originalWorkspaceSetting.current, workspaceSetting); + const branding = workspaceSetting.branding ? new TextDecoder().decode(workspaceSetting.branding) : ""; + + const onBrandingChange = async (event: React.ChangeEvent) => { + const files: File[] = Array.from(event.target.files || []); + const file = head(files); + if (!file) { + return; + } + + const base64 = await convertFileToBase64(file); + setWorkspaceSetting({ ...workspaceSetting, branding: new TextEncoder().encode(base64) }); + }; const handleCustomStyleChange = async (value: string) => { setWorkspaceSetting({ @@ -39,6 +60,9 @@ const WorkspaceSection = () => { const handleSaveWorkspaceSetting = async () => { const updateMask: string[] = []; + if (!isEqual(originalWorkspaceSetting.current.branding, workspaceSetting.branding)) { + updateMask.push("branding"); + } if (!isEqual(originalWorkspaceSetting.current.customStyle, workspaceSetting.customStyle)) { updateMask.push("custom_style"); } @@ -70,6 +94,26 @@ const WorkspaceSection = () => {

{t("settings.workspace.self")}

+
+
+

Custom branding

+

Recommand logo ratio: 1:1

+
+
+ {branding ? ( +
+ branding + setWorkspaceSetting({ ...workspaceSetting, branding: new TextEncoder().encode("") })} + /> +
+ ) : ( + + )} + +
+

{t("settings.workspace.default-visibility")}

diff --git a/frontend/web/src/pages/SignIn.tsx b/frontend/web/src/pages/SignIn.tsx index d864135..27f882d 100644 --- a/frontend/web/src/pages/SignIn.tsx +++ b/frontend/web/src/pages/SignIn.tsx @@ -3,7 +3,7 @@ import React, { FormEvent, useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import Icon from "@/components/Icon"; +import Logo from "@/components/Logo"; import { authServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; @@ -64,7 +64,7 @@ const SignIn: React.FC = () => {
- + Slash
diff --git a/frontend/web/src/pages/SignUp.tsx b/frontend/web/src/pages/SignUp.tsx index 65bb607..2b32f4f 100644 --- a/frontend/web/src/pages/SignUp.tsx +++ b/frontend/web/src/pages/SignUp.tsx @@ -3,7 +3,7 @@ import React, { FormEvent, useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import Icon from "@/components/Icon"; +import Logo from "@/components/Logo"; import { authServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useNavigateTo from "@/hooks/useNavigateTo"; @@ -75,7 +75,7 @@ const SignUp: React.FC = () => {
- + Slash

{t("auth.create-your-account")}

diff --git a/server/route/api/v1/workspace_service.go b/server/route/api/v1/workspace_service.go index acfebbd..ff7244d 100644 --- a/server/route/api/v1/workspace_service.go +++ b/server/route/api/v1/workspace_service.go @@ -61,6 +61,7 @@ func (s *APIV1Service) GetWorkspaceSetting(ctx context.Context, _ *v1pb.GetWorks for _, v := range workspaceSettings { if v.Key == storepb.WorkspaceSettingKey_WORKSPACE_SETTING_GENERAL { generalSetting := v.GetGeneral() + workspaceSetting.Branding = generalSetting.GetBranding() workspaceSetting.CustomStyle = generalSetting.GetCustomStyle() } else if v.Key == storepb.WorkspaceSettingKey_WORKSPACE_SETTING_SHORTCUT_RELATED { shortcutRelatedSetting := v.GetShortcutRelated()