diff --git a/packages/frontend/component/src/components/setting-components/index.tsx b/packages/frontend/component/src/components/setting-components/index.tsx
index 3cf81c0fbe..d26bc5d939 100644
--- a/packages/frontend/component/src/components/setting-components/index.tsx
+++ b/packages/frontend/component/src/components/setting-components/index.tsx
@@ -1,6 +1,5 @@
export { SettingHeader } from './setting-header';
export { SettingRow } from './setting-row';
-export * from './storage-progess';
export * from './workspace-detail-skeleton';
export * from './workspace-list-skeleton';
export { SettingWrapper } from './wrapper';
diff --git a/packages/frontend/component/src/components/setting-components/share.css.ts b/packages/frontend/component/src/components/setting-components/share.css.ts
index 2b06a4361f..223d5b7933 100644
--- a/packages/frontend/component/src/components/setting-components/share.css.ts
+++ b/packages/frontend/component/src/components/setting-components/share.css.ts
@@ -85,39 +85,3 @@ globalStyle(`${settingRow} .right-col`, {
paddingLeft: '15px',
flexShrink: 0,
});
-export const storageProgressContainer = style({
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
-});
-export const storageProgressWrapper = style({
- flexGrow: 1,
- marginRight: '20px',
-});
-globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
- fontSize: cssVar('fontXs'),
- color: cssVar('textSecondaryColor'),
- height: '20px',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- marginBottom: 2,
-});
-globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
- height: '8px',
- borderRadius: '4px',
- backgroundColor: cssVar('black10'),
- overflow: 'hidden',
-});
-export const storageProgressBar = style({
- height: '100%',
- backgroundColor: cssVar('processingColor'),
- selectors: {
- '&.danger': {
- backgroundColor: cssVar('errorColor'),
- },
- },
-});
-export const storageButton = style({
- padding: '4px 12px',
-});
diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx
index 1f95dcec7b..5276e5b0af 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx
@@ -3,25 +3,20 @@ import { pushNotificationAtom } from '@affine/component/notification-center';
import {
SettingHeader,
SettingRow,
- StorageProgress,
} from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
-import { useUserQuota } from '@affine/core/hooks/use-quota';
import {
- allBlobSizesQuery,
removeAvatarMutation,
- SubscriptionPlan,
updateUserProfileMutation,
uploadAvatarMutation,
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
-import bytes from 'bytes';
import { useSetAtom } from 'jotai';
import type { FC, MouseEvent } from 'react';
-import { Suspense, useCallback, useMemo, useState } from 'react';
+import { Suspense, useCallback, useState } from 'react';
import {
authAtom,
@@ -31,11 +26,10 @@ import {
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
import { useServerFeatures } from '../../../../hooks/affine/use-server-config';
import { useMutation } from '../../../../hooks/use-mutation';
-import { useQuery } from '../../../../hooks/use-query';
-import { useUserSubscription } from '../../../../hooks/use-subscription';
import { mixpanel } from '../../../../utils';
import { validateAndReduceImage } from '../../../../utils/reduce-image';
import { Upload } from '../../../pure/file-upload';
+import { StorageProgress } from './storage-progress';
import * as styles from './style.css';
export const UserAvatar = () => {
@@ -190,21 +184,6 @@ const StoragePanel = () => {
const t = useAFFiNEI18N();
const { payment: hasPaymentFeature } = useServerFeatures();
- const { data } = useQuery({
- query: allBlobSizesQuery,
- });
-
- const [subscription] = useUserSubscription();
- const plan = subscription?.plan ?? SubscriptionPlan.Free;
-
- const quota = useUserQuota();
- const maxLimit = useMemo(() => {
- if (quota) {
- return quota.storageQuota;
- }
- return bytes.parse(plan === SubscriptionPlan.Free ? '10GB' : '100GB');
- }, [plan, quota]);
-
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
const onUpgrade = useCallback(() => {
mixpanel.track('Button', {
@@ -222,13 +201,7 @@ const StoragePanel = () => {
desc=""
spreadCol={false}
>
-
+
);
};
diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.css.ts b/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.css.ts
new file mode 100644
index 0000000000..a6eb95b317
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.css.ts
@@ -0,0 +1,33 @@
+import { cssVar } from '@toeverything/theme';
+import { globalStyle, style } from '@vanilla-extract/css';
+
+export const storageProgressContainer = style({
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+});
+export const storageProgressWrapper = style({
+ flexGrow: 1,
+ marginRight: '20px',
+});
+globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
+ fontSize: cssVar('fontXs'),
+ color: cssVar('textSecondaryColor'),
+ height: '20px',
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 2,
+});
+globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
+ height: '8px',
+ borderRadius: '4px',
+ backgroundColor: cssVar('black10'),
+ overflow: 'hidden',
+});
+export const storageProgressBar = style({
+ height: '100%',
+});
+export const storageButton = style({
+ padding: '4px 12px',
+});
diff --git a/packages/frontend/component/src/components/setting-components/storage-progess.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.tsx
similarity index 70%
rename from packages/frontend/component/src/components/setting-components/storage-progess.tsx
rename to packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.tsx
index e5cb1c7053..2594e96335 100644
--- a/packages/frontend/component/src/components/setting-components/storage-progess.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.tsx
@@ -1,19 +1,14 @@
+import { Button, Tooltip } from '@affine/component';
+import { useCloudStorageUsage } from '@affine/core/hooks/affine/use-cloud-storage-usage';
import { SubscriptionPlan } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
-import bytes from 'bytes';
-import clsx from 'clsx';
import { useMemo } from 'react';
-import { Button } from '../../ui/button';
-import { Tooltip } from '../../ui/tooltip';
-import * as styles from './share.css';
+import * as styles from './storage-progress.css';
export interface StorageProgressProgress {
- max: number;
- value: number;
upgradable?: boolean;
onUpgrade: () => void;
- plan: SubscriptionPlan;
}
enum ButtonType {
@@ -22,20 +17,12 @@ enum ButtonType {
}
export const StorageProgress = ({
- max: upperLimit,
- value,
upgradable = true,
onUpgrade,
- plan,
}: StorageProgressProgress) => {
const t = useAFFiNEI18N();
- const percent = useMemo(
- () => Math.round((value / upperLimit) * 100),
- [upperLimit, value]
- );
-
- const used = useMemo(() => bytes.format(value), [value]);
- const max = useMemo(() => bytes.format(upperLimit), [upperLimit]);
+ const { plan, usedText, color, percent, maxLimitText } =
+ useCloudStorageUsage();
const buttonType = useMemo(() => {
if (plan === SubscriptionPlan.Free) {
@@ -50,17 +37,15 @@ export const StorageProgress = ({
{t['com.affine.storage.used.hint']()}
- {used}/{max}
+ {usedText}/{maxLimitText}
{` (${plan} ${t['com.affine.storage.plan']()})`}
80,
- })}
- style={{ width: `${percent > 100 ? '100' : percent}%` }}
+ className={styles.storageProgressBar}
+ style={{ width: `${percent}%`, backgroundColor: color }}
>
diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.css.ts b/packages/frontend/core/src/components/root-app-sidebar/index.css.ts
index 684f679db5..3c1d6d782a 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/index.css.ts
+++ b/packages/frontend/core/src/components/root-app-sidebar/index.css.ts
@@ -1,4 +1,7 @@
-import { globalStyle, style } from '@vanilla-extract/css';
+import { cssVar } from '@toeverything/theme';
+import { createVar, globalStyle, style } from '@vanilla-extract/css';
+
+export const progressColorVar = createVar();
export const workspaceAndUserWrapper = style({
display: 'flex',
@@ -23,3 +26,46 @@ export const userInfoWrapper = style({
globalStyle(`button.${userInfoWrapper} > span`, {
lineHeight: 0,
});
+
+export const operationMenu = style({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 4,
+});
+
+export const cloudUsage = style({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 4,
+ padding: '4px 12px',
+});
+export const cloudUsageLabel = style({
+ fontWeight: 500,
+ lineHeight: '20px',
+ fontSize: cssVar('fontXs'),
+ color: cssVar('textSecondaryColor'),
+});
+export const cloudUsageLabelUsed = style({
+ color: progressColorVar,
+});
+
+export const cloudUsageBar = style({
+ height: 10,
+ borderRadius: 5,
+ overflow: 'hidden',
+ position: 'relative',
+ minWidth: 160,
+
+ '::before': {
+ position: 'absolute',
+ inset: 0,
+ content: '""',
+ backgroundColor: cssVar('black'),
+ opacity: 0.04,
+ },
+});
+export const cloudUsageBarInner = style({
+ height: '100%',
+ borderRadius: 'inherit',
+ backgroundColor: progressColorVar,
+});
diff --git a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
index f1b8b127a6..54c53eb45b 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
+++ b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
@@ -12,12 +12,18 @@ import {
openSettingModalAtom,
openSignOutModalAtom,
} from '@affine/core/atoms';
+import { useCloudStorageUsage } from '@affine/core/hooks/affine/use-cloud-storage-usage';
import {
useCurrentUser,
useSession,
} from '@affine/core/hooks/affine/use-current-user';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
-import { AccountIcon, SignOutIcon } from '@blocksuite/icons';
+import {
+ AccountIcon,
+ ArrowRightSmallIcon,
+ SignOutIcon,
+} from '@blocksuite/icons';
+import { assignInlineVars } from '@vanilla-extract/dynamic';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
@@ -92,6 +98,11 @@ const AccountMenu = () => {
}
+ endFix={
+
+
+
+ }
data-testid="workspace-modal-account-settings-option"
onClick={onOpenAccountSetting}
>
@@ -104,6 +115,11 @@ const AccountMenu = () => {
}
+ endFix={
+
+
+
+ }
data-testid="workspace-modal-sign-out-option"
onClick={onOpenSignOutModal}
>
@@ -113,13 +129,37 @@ const AccountMenu = () => {
);
};
-const OperationMenu = () => {
- // TODO: display usage progress bar
- const StorageUsage = null;
+const CloudUsage = () => {
+ const { color, usedText, maxLimitText, percent } = useCloudStorageUsage();
+ return (
+
+
+ {usedText}
+ /
+ {maxLimitText}
+
+
+
+
+ );
+};
+
+const OperationMenu = () => {
return (
<>
- {StorageUsage}
+
+
>
);
diff --git a/packages/frontend/core/src/hooks/affine/use-cloud-storage-usage.ts b/packages/frontend/core/src/hooks/affine/use-cloud-storage-usage.ts
new file mode 100644
index 0000000000..f0439c0830
--- /dev/null
+++ b/packages/frontend/core/src/hooks/affine/use-cloud-storage-usage.ts
@@ -0,0 +1,56 @@
+import { allBlobSizesQuery, SubscriptionPlan } from '@affine/graphql';
+import { cssVar } from '@toeverything/theme';
+import bytes from 'bytes';
+import { useMemo } from 'react';
+
+import { useQuery } from '../use-query';
+import { useUserQuota } from '../use-quota';
+import { useUserSubscription } from '../use-subscription';
+
+/**
+ * Hook to get currentUser's cloud storage usage information.
+ */
+export const useCloudStorageUsage = () => {
+ const { data } = useQuery({
+ query: allBlobSizesQuery,
+ });
+
+ const quota = useUserQuota();
+ const [subscription] = useUserSubscription();
+
+ const plan = subscription?.plan ?? SubscriptionPlan.Free;
+
+ const maxLimit = useMemo(() => {
+ if (quota) {
+ return quota.storageQuota;
+ }
+ return bytes.parse(plan === SubscriptionPlan.Free ? '10GB' : '100GB');
+ }, [plan, quota]);
+
+ const used = data?.collectAllBlobSizes?.size ?? 0;
+ const percent = Math.min(
+ 100,
+ Math.max(0.5, Number(((used / maxLimit) * 100).toFixed(4)))
+ );
+ const color = percent > 80 ? cssVar('errorColor') : cssVar('processingColor');
+
+ const usedText = bytes.format(used);
+ const maxLimitText = bytes.format(maxLimit);
+
+ return {
+ /** Current subscription plan of logged in user */
+ plan,
+ /** Used storage in bytes */
+ used,
+ /** Formatted used storage */
+ usedText,
+ /** CSS variable name for progress bar color */
+ color,
+ /** Percentage of storage used */
+ percent,
+ /** Maximum storage limit in bytes */
+ maxLimit,
+ /** Formatted maximum storage limit */
+ maxLimitText,
+ };
+};