Commit 3c5aa41a authored by Steven's avatar Steven

feat: implement week start day setting

parent 06c460b4
...@@ -101,12 +101,12 @@ var ( ...@@ -101,12 +101,12 @@ var (
) )
func init() { func init() {
viper.SetDefault("mode", "demo") viper.SetDefault("mode", "dev")
viper.SetDefault("driver", "sqlite") viper.SetDefault("driver", "sqlite")
viper.SetDefault("port", 8081) viper.SetDefault("port", 8081)
viper.SetDefault("password-auth", true) viper.SetDefault("password-auth", true)
rootCmd.PersistentFlags().String("mode", "demo", `mode of server, can be "prod" or "dev" or "demo"`) rootCmd.PersistentFlags().String("mode", "dev", `mode of server, can be "prod" or "dev" or "demo"`)
rootCmd.PersistentFlags().String("addr", "", "address of server") rootCmd.PersistentFlags().String("addr", "", "address of server")
rootCmd.PersistentFlags().Int("port", 8081, "port of server") rootCmd.PersistentFlags().Int("port", 8081, "port of server")
rootCmd.PersistentFlags().String("data", "", "data directory") rootCmd.PersistentFlags().String("data", "", "data directory")
......
...@@ -2075,6 +2075,13 @@ definitions: ...@@ -2075,6 +2075,13 @@ definitions:
customProfile: customProfile:
$ref: '#/definitions/apiv1WorkspaceCustomProfile' $ref: '#/definitions/apiv1WorkspaceCustomProfile'
description: custom_profile is the custom profile. description: custom_profile is the custom profile.
weekStartDayOffset:
type: integer
format: int32
description: |-
week_start_day_offset is the week start day offset from Sunday.
0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
Default is Sunday.
apiv1WorkspaceMemoRelatedSetting: apiv1WorkspaceMemoRelatedSetting:
type: object type: object
properties: properties:
......
...@@ -42,6 +42,10 @@ message WorkspaceGeneralSetting { ...@@ -42,6 +42,10 @@ message WorkspaceGeneralSetting {
string additional_style = 4; string additional_style = 4;
// custom_profile is the custom profile. // custom_profile is the custom profile.
WorkspaceCustomProfile custom_profile = 5; WorkspaceCustomProfile custom_profile = 5;
// week_start_day_offset is the week start day offset from Sunday.
// 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
// Default is Sunday.
int32 week_start_day_offset = 6;
} }
message WorkspaceCustomProfile { message WorkspaceCustomProfile {
......
This diff is collapsed.
...@@ -32,11 +32,15 @@ message WorkspaceBasicSetting { ...@@ -32,11 +32,15 @@ message WorkspaceBasicSetting {
message WorkspaceGeneralSetting { message WorkspaceGeneralSetting {
// additional_script is the additional script. // additional_script is the additional script.
string additional_script = 3; string additional_script = 1;
// additional_style is the additional style. // additional_style is the additional style.
string additional_style = 4; string additional_style = 2;
// custom_profile is the custom profile. // custom_profile is the custom profile.
WorkspaceCustomProfile custom_profile = 5; WorkspaceCustomProfile custom_profile = 3;
// week_start_day_offset is the week start day offset from Sunday.
// 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
// Default is Sunday.
int32 week_start_day_offset = 4;
} }
message WorkspaceCustomProfile { message WorkspaceCustomProfile {
......
...@@ -19,6 +19,7 @@ func (s *APIV1Service) GetWorkspaceProfile(ctx context.Context, _ *v1pb.GetWorks ...@@ -19,6 +19,7 @@ func (s *APIV1Service) GetWorkspaceProfile(ctx context.Context, _ *v1pb.GetWorks
PasswordAuth: s.Profile.PasswordAuth, PasswordAuth: s.Profile.PasswordAuth,
InstanceUrl: s.Profile.InstanceURL, InstanceUrl: s.Profile.InstanceURL,
} }
println("workspaceProfile: ", workspaceProfile.Mode)
owner, err := s.GetInstanceOwner(ctx) owner, err := s.GetInstanceOwner(ctx)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get instance owner: %v", err) return nil, status.Errorf(codes.Internal, "failed to get instance owner: %v", err)
......
...@@ -132,8 +132,9 @@ func convertWorkspaceGeneralSettingFromStore(setting *storepb.WorkspaceGeneralSe ...@@ -132,8 +132,9 @@ func convertWorkspaceGeneralSettingFromStore(setting *storepb.WorkspaceGeneralSe
return nil return nil
} }
generalSetting := &v1pb.WorkspaceGeneralSetting{ generalSetting := &v1pb.WorkspaceGeneralSetting{
AdditionalScript: setting.AdditionalScript, AdditionalScript: setting.AdditionalScript,
AdditionalStyle: setting.AdditionalStyle, AdditionalStyle: setting.AdditionalStyle,
WeekStartDayOffset: setting.WeekStartDayOffset,
} }
if setting.CustomProfile != nil { if setting.CustomProfile != nil {
generalSetting.CustomProfile = &v1pb.WorkspaceCustomProfile{ generalSetting.CustomProfile = &v1pb.WorkspaceCustomProfile{
...@@ -152,8 +153,9 @@ func convertWorkspaceGeneralSettingToStore(setting *v1pb.WorkspaceGeneralSetting ...@@ -152,8 +153,9 @@ func convertWorkspaceGeneralSettingToStore(setting *v1pb.WorkspaceGeneralSetting
return nil return nil
} }
generalSetting := &storepb.WorkspaceGeneralSetting{ generalSetting := &storepb.WorkspaceGeneralSetting{
AdditionalScript: setting.AdditionalScript, AdditionalScript: setting.AdditionalScript,
AdditionalStyle: setting.AdditionalStyle, AdditionalStyle: setting.AdditionalStyle,
WeekStartDayOffset: setting.WeekStartDayOffset,
} }
if setting.CustomProfile != nil { if setting.CustomProfile != nil {
generalSetting.CustomProfile = &storepb.WorkspaceCustomProfile{ generalSetting.CustomProfile = &storepb.WorkspaceCustomProfile{
......
import { Tooltip } from "@mui/joy"; import { Tooltip } from "@mui/joy";
import clsx from "clsx"; import clsx from "clsx";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useWorkspaceSettingStore } from "@/store/v1";
import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_setting_service";
import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const WEEK_DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
interface Props { interface Props {
// Format: 2021-1 // Format: 2021-1
month: string; month: string;
...@@ -29,11 +34,16 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => { ...@@ -29,11 +34,16 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
const ActivityCalendar = (props: Props) => { const ActivityCalendar = (props: Props) => {
const t = useTranslate(); const t = useTranslate();
const { month: monthStr, data, onClick } = props; const { month: monthStr, data, onClick } = props;
const workspaceSettingStore = useWorkspaceSettingStore();
const weekStartDayOffset = (
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL).generalSetting || WorkspaceGeneralSetting.fromPartial({})
).weekStartDayOffset;
const year = dayjs(monthStr).toDate().getFullYear(); const year = dayjs(monthStr).toDate().getFullYear();
const month = dayjs(monthStr).toDate().getMonth() + 1; const month = dayjs(monthStr).toDate().getMonth() + 1;
const dayInMonth = new Date(year, month, 0).getDate(); const dayInMonth = new Date(year, month, 0).getDate();
const firstDay = new Date(year, month - 1, 1).getDay(); const firstDay = new Date(year, month - 1, 1).getDay() - weekStartDayOffset;
const lastDay = new Date(year, month - 1, dayInMonth).getDay(); const lastDay = new Date(year, month - 1, dayInMonth).getDay() - weekStartDayOffset;
const weekDays = WEEK_DAYS.slice(weekStartDayOffset).concat(WEEK_DAYS.slice(0, weekStartDayOffset));
const maxCount = Math.max(...Object.values(data)); const maxCount = Math.max(...Object.values(data));
const days = []; const days = [];
...@@ -49,13 +59,13 @@ const ActivityCalendar = (props: Props) => { ...@@ -49,13 +59,13 @@ const ActivityCalendar = (props: Props) => {
return ( return (
<div className={clsx("w-full h-auto shrink-0 grid grid-cols-7 grid-flow-row gap-1")}> <div className={clsx("w-full h-auto shrink-0 grid grid-cols-7 grid-flow-row gap-1")}>
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Su</div> {weekDays.map((day, index) => {
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Mo</div> return (
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Tu</div> <div key={index} className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>We</div> {day}
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Th</div> </div>
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Fr</div> );
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Sa</div> })}
{days.map((day, index) => { {days.map((day, index) => {
const date = dayjs(`${year}-${month}-${day}`).format("YYYY-MM-DD"); const date = dayjs(`${year}-${month}-${day}`).format("YYYY-MM-DD");
const count = data[date] || 0; const count = data[date] || 0;
......
...@@ -60,8 +60,8 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => { ...@@ -60,8 +60,8 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
toast.success(t("message.password-changed")); toast.success(t("message.password-changed"));
handleCloseBtnClick(); handleCloseBtnClick();
} catch (error: any) { } catch (error: any) {
toast.error(error.details);
console.error(error); console.error(error);
toast.error(error.response.data.message);
} }
}; };
......
...@@ -77,8 +77,8 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => { ...@@ -77,8 +77,8 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
onConfirm(); onConfirm();
destroy(); destroy();
} catch (error: any) { } catch (error: any) {
toast.error(error.details);
console.error(error); console.error(error);
toast.error(error.response.data.message);
} }
}; };
......
...@@ -224,8 +224,8 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => { ...@@ -224,8 +224,8 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
toast.success(t("setting.sso-section.sso-updated", { name: basicInfo.title })); toast.success(t("setting.sso-section.sso-updated", { name: basicInfo.title }));
} }
} catch (error: any) { } catch (error: any) {
toast.error(error.details);
console.error(error); console.error(error);
toast.error(error.response.data.message);
} }
if (confirmCallback) { if (confirmCallback) {
confirmCallback(); confirmCallback();
......
...@@ -78,8 +78,8 @@ const MemoActionMenu = (props: Props) => { ...@@ -78,8 +78,8 @@ const MemoActionMenu = (props: Props) => {
toast.success(t("message.archived-successfully")); toast.success(t("message.archived-successfully"));
} }
} catch (error: any) { } catch (error: any) {
toast.error(error.details);
console.error(error); console.error(error);
toast.error(error.response.data.message);
return; return;
} }
......
...@@ -51,8 +51,8 @@ const AddMemoRelationPopover = (props: Props) => { ...@@ -51,8 +51,8 @@ const AddMemoRelationPopover = (props: Props) => {
}); });
setFetchedMemos(memos); setFetchedMemos(memos);
} catch (error: any) { } catch (error: any) {
toast.error(error.details);
console.error(error); console.error(error);
toast.error(error.response.data.message);
} }
setIsFetching(false); setIsFetching(false);
}, },
......
import { Button, Textarea } from "@mui/joy"; import { Button, Select, Textarea, Option, Divider } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
...@@ -25,27 +25,15 @@ const WorkspaceSection = () => { ...@@ -25,27 +25,15 @@ const WorkspaceSection = () => {
setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, additionalStyle: value }); setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, additionalStyle: value });
}; };
const handleSaveAdditionalStyle = async () => {
try {
await workspaceSettingServiceClient.setWorkspaceSetting({
setting: {
name: `${workspaceSettingNamePrefix}${WorkspaceSettingKey.GENERAL}`,
generalSetting: workspaceGeneralSetting,
},
});
} catch (error: any) {
toast.error(error.response.data.message);
console.error(error);
return;
}
toast.success(t("message.update-succeed"));
};
const handleAdditionalScriptChanged = (value: string) => { const handleAdditionalScriptChanged = (value: string) => {
setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, additionalScript: value }); setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, additionalScript: value });
}; };
const handleSaveAdditionalScript = async () => { const handleWeekStartDayOffsetChanged = (value: number) => {
setWorkspaceGeneralSetting({ ...workspaceGeneralSetting, weekStartDayOffset: value });
};
const handleSaveGeneralSetting = async () => {
try { try {
await workspaceSettingServiceClient.setWorkspaceSetting({ await workspaceSettingServiceClient.setWorkspaceSetting({
setting: { setting: {
...@@ -54,7 +42,7 @@ const WorkspaceSection = () => { ...@@ -54,7 +42,7 @@ const WorkspaceSection = () => {
}, },
}); });
} catch (error: any) { } catch (error: any) {
toast.error(error.response.data.message); toast.error(error.details);
console.error(error); console.error(error);
return; return;
} }
...@@ -69,57 +57,70 @@ const WorkspaceSection = () => { ...@@ -69,57 +57,70 @@ const WorkspaceSection = () => {
{t("setting.system-section.server-name")}:{" "} {t("setting.system-section.server-name")}:{" "}
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span> <span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
</div> </div>
<Button onClick={handleUpdateCustomizedProfileButtonClick}>{t("common.edit")}</Button> <Button variant="outlined" color="neutral" onClick={handleUpdateCustomizedProfileButtonClick}>
{t("common.edit")}
</Button>
</div> </div>
<Divider />
<p className="font-medium text-gray-700 dark:text-gray-500">General</p> <p className="font-medium text-gray-700 dark:text-gray-500">General</p>
<div className="space-y-2 border rounded-md py-2 px-3 dark:border-zinc-700"> <div className="w-full flex flex-row justify-between items-center">
<div className="w-full flex flex-row justify-between items-center"> <span>{t("setting.system-section.additional-style")}</span>
<span>{t("setting.system-section.additional-style")}</span> </div>
<Button variant="outlined" color="neutral" onClick={handleSaveAdditionalStyle}> <Textarea
{t("common.save")} className="w-full"
</Button> sx={{
</div> fontFamily: "monospace",
<Textarea fontSize: "14px",
className="w-full" }}
sx={{ minRows={2}
fontFamily: "monospace", maxRows={4}
fontSize: "14px", placeholder={t("setting.system-section.additional-style-placeholder")}
}} value={workspaceGeneralSetting.additionalStyle}
minRows={2} onChange={(event) => handleAdditionalStyleChanged(event.target.value)}
maxRows={4} />
placeholder={t("setting.system-section.additional-style-placeholder")} <div className="w-full flex flex-row justify-between items-center">
value={workspaceGeneralSetting.additionalStyle} <span>{t("setting.system-section.additional-script")}</span>
onChange={(event) => handleAdditionalStyleChanged(event.target.value)} </div>
/> <Textarea
<div className="w-full flex flex-row justify-between items-center"> className="w-full"
<span>{t("setting.system-section.additional-script")}</span> color="neutral"
<Button variant="outlined" color="neutral" onClick={handleSaveAdditionalScript}> sx={{
{t("common.save")} fontFamily: "monospace",
</Button> fontSize: "14px",
</div> }}
<Textarea minRows={2}
className="w-full" maxRows={4}
color="neutral" placeholder={t("setting.system-section.additional-script-placeholder")}
sx={{ value={workspaceGeneralSetting.additionalScript}
fontFamily: "monospace", onChange={(event) => handleAdditionalScriptChanged(event.target.value)}
fontSize: "14px", />
<div className="w-full">
<Link
className="text-gray-500 text-sm flex flex-row justify-start items-center hover:underline hover:text-blue-600"
to="https://usememos.com/docs/advanced-settings/custom-style-and-script"
target="_blank"
>
{t("common.learn-more")}
<Icon.ExternalLink className="inline w-4 h-auto ml-1" />
</Link>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
<Select
className="!min-w-fit"
value={workspaceGeneralSetting.weekStartDayOffset}
onChange={(_, weekStartDayOffset) => {
handleWeekStartDayOffsetChanged(weekStartDayOffset || 0);
}} }}
minRows={2} >
maxRows={4} <Option value={0}>Sunday</Option>
placeholder={t("setting.system-section.additional-script-placeholder")} <Option value={1}>Monday</Option>
value={workspaceGeneralSetting.additionalScript} </Select>
onChange={(event) => handleAdditionalScriptChanged(event.target.value)} </div>
/> <div>
<div className="w-full"> <Button variant="outlined" color="neutral" onClick={handleSaveGeneralSetting}>
<Link {t("common.save")}
className="text-gray-500 text-sm flex flex-row justify-start items-center hover:underline hover:text-blue-600" </Button>
to="https://usememos.com/docs/advanced-settings/custom-style-and-script"
target="_blank"
>
{t("common.learn-more")}
<Icon.ExternalLink className="inline w-4 h-auto ml-1" />
</Link>
</div>
</div> </div>
</div> </div>
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment