mirror of
https://github.com/webstudio-is/webstudio.git
synced 2025-03-14 09:57:02 +00:00
refactor: replace date-fns with use-relative-time (#4738)
See https://github.com/nkzw-tech/use-relative-time We use only single function from date-fns while the package is like 22MB. Here's small alternative for react based on intl. Added jsx support to ours toasts inside of builder and RelativeTime component to use it inside of callbacks.
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
import { formatDistance } from "date-fns/formatDistance";
|
||||
import {
|
||||
AutogrowTextArea,
|
||||
Box,
|
||||
@ -41,6 +40,7 @@ import { AiApiException, RateLimitException } from "./api-exceptions";
|
||||
import { getSetting, setSetting } from "~/builder/shared/client-settings";
|
||||
import { flushSync } from "react-dom";
|
||||
import { $selectedPage } from "~/shared/awareness";
|
||||
import { RelativeTime } from "~/builder/shared/relative-time";
|
||||
|
||||
type PartialButtonProps<T = ComponentPropsWithoutRef<typeof Button>> = {
|
||||
[key in keyof T]?: T[key];
|
||||
@ -117,13 +117,10 @@ export const AiCommandBar = () => {
|
||||
} catch (error) {
|
||||
if (error instanceof RateLimitException) {
|
||||
toast.info(
|
||||
`Temporary AI rate limit reached. Please wait ${formatDistance(
|
||||
Date.now(),
|
||||
new Date(error.meta.reset),
|
||||
{
|
||||
includeSeconds: true,
|
||||
}
|
||||
)} and try again.`
|
||||
<>
|
||||
Temporary AI rate limit reached. Please wait{" "}
|
||||
<RelativeTime time={new Date(error.meta.reset)} /> and try again.
|
||||
</>
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -219,13 +216,10 @@ export const AiCommandBar = () => {
|
||||
|
||||
if (error instanceof RateLimitException) {
|
||||
toast.info(
|
||||
`Temporary AI rate limit reached. Please wait ${formatDistance(
|
||||
Date.now(),
|
||||
new Date(error.meta.reset),
|
||||
{
|
||||
includeSeconds: true,
|
||||
}
|
||||
)} and try again.`
|
||||
<>
|
||||
Temporary AI rate limit reached. Please wait{" "}
|
||||
<RelativeTime time={new Date(error.meta.reset)} /> and try again.
|
||||
</>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import {
|
||||
useOptimistic,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { formatDistance } from "date-fns/formatDistance";
|
||||
import { Entri } from "./entri";
|
||||
import { nativeClient } from "~/shared/trpc/trpc-client";
|
||||
import { useStore } from "@nanostores/react";
|
||||
@ -36,6 +36,7 @@ import { extractCname } from "./cname";
|
||||
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
|
||||
import DomainCheckbox from "./domain-checkbox";
|
||||
import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard";
|
||||
import { RelativeTime } from "~/builder/shared/relative-time";
|
||||
|
||||
export type Domain = Project["domainsVirtual"][number];
|
||||
type DomainStatus = Project["domainsVirtual"][number]["status"];
|
||||
@ -77,13 +78,12 @@ export const getPublishStatusAndText = ({
|
||||
? "Publish failed"
|
||||
: "Publishing started";
|
||||
|
||||
const statusText = `${textStart} ${formatDistance(
|
||||
new Date(createdAt),
|
||||
new Date(),
|
||||
{
|
||||
addSuffix: true,
|
||||
}
|
||||
)}`;
|
||||
const statusText = (
|
||||
<>
|
||||
{textStart}
|
||||
<RelativeTime time={new Date(createdAt)} />
|
||||
</>
|
||||
);
|
||||
|
||||
return { statusText, status };
|
||||
};
|
||||
@ -95,7 +95,7 @@ const getStatusText = (props: {
|
||||
const status = getStatus(props.projectDomain);
|
||||
|
||||
let isVerifiedActive = false;
|
||||
let text = "Something went wrong";
|
||||
let text: ReactNode = "Something went wrong";
|
||||
|
||||
switch (status) {
|
||||
case "UNVERIFIED":
|
||||
|
@ -58,10 +58,10 @@ import { humanizeString } from "~/shared/string-utils";
|
||||
import { trpcClient, nativeClient } from "~/shared/trpc/trpc-client";
|
||||
import { isFeatureEnabled } from "@webstudio-is/feature-flags";
|
||||
import type { Templates } from "@webstudio-is/sdk";
|
||||
import { formatDistance } from "date-fns/formatDistance";
|
||||
import DomainCheckbox, { domainToPublishName } from "./domain-checkbox";
|
||||
import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard";
|
||||
import { $openProjectSettings } from "~/shared/nano-states/project-settings";
|
||||
import { RelativeTime } from "~/builder/shared/relative-time";
|
||||
|
||||
type ChangeProjectDomainProps = {
|
||||
project: Project;
|
||||
@ -206,7 +206,9 @@ const Publish = ({
|
||||
|
||||
refresh: () => Promise<void>;
|
||||
}) => {
|
||||
const [publishError, setPublishError] = useState<undefined | string>();
|
||||
const [publishError, setPublishError] = useState<
|
||||
undefined | JSX.Element | string
|
||||
>();
|
||||
const [isPublishing, setIsPublishing] = useOptimistic(false);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [hasSelectedDomains, setHasSelectedDomains] = useState(false);
|
||||
@ -276,12 +278,17 @@ const Publish = ({
|
||||
if (publishResult.success === false) {
|
||||
console.error(publishResult.error);
|
||||
|
||||
let error = publishResult.error;
|
||||
let error: JSX.Element | string = publishResult.error;
|
||||
if (publishResult.error === "NOT_IMPLEMENTED") {
|
||||
error =
|
||||
`Build data for publishing has been successfully created.\n\n` +
|
||||
`Use Webstudio CLI to generate the code.\n\n` +
|
||||
`https://docs.webstudio.is/university/self-hosting/cli`;
|
||||
error = (
|
||||
<>
|
||||
Build data for publishing has been successfully created. Use{" "}
|
||||
<Link href="https://docs.webstudio.is/university/self-hosting/cli">
|
||||
Webstudio CLI
|
||||
</Link>{" "}
|
||||
to generate the code.
|
||||
</>
|
||||
);
|
||||
}
|
||||
setPublishError(error);
|
||||
if (publishResult.error === "NOT_IMPLEMENTED") {
|
||||
@ -391,13 +398,11 @@ const getStaticPublishStatusAndText = ({
|
||||
? "Download failed"
|
||||
: "Download started";
|
||||
|
||||
const statusText = `${textStart} ${formatDistance(
|
||||
new Date(updatedAt),
|
||||
new Date(),
|
||||
{
|
||||
addSuffix: true,
|
||||
}
|
||||
)}`;
|
||||
const statusText = (
|
||||
<>
|
||||
{textStart} <RelativeTime time={new Date(updatedAt)} />
|
||||
</>
|
||||
);
|
||||
|
||||
return { statusText, status };
|
||||
};
|
||||
|
5
apps/builder/app/builder/shared/relative-time.tsx
Normal file
5
apps/builder/app/builder/shared/relative-time.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import useRelativeTime from "@nkzw/use-relative-time";
|
||||
|
||||
export const RelativeTime = ({ time }: { time: Date }) => {
|
||||
return useRelativeTime(time.getTime());
|
||||
};
|
@ -5,9 +5,16 @@ import { uploadAssets } from "~/builder/shared/assets/use-assets";
|
||||
|
||||
const apiWindowNamespace = "__webstudio__$__builderApi";
|
||||
|
||||
type ToastHandler = (message: string) => void;
|
||||
|
||||
const _builderApi = {
|
||||
isInitialized: () => true,
|
||||
toast,
|
||||
toast: {
|
||||
info: toast.info as ToastHandler,
|
||||
warn: toast.warn as ToastHandler,
|
||||
error: toast.error as ToastHandler,
|
||||
success: toast.success as ToastHandler,
|
||||
},
|
||||
uploadImages: async (srcs: string[]) => {
|
||||
const urlToIds = await uploadAssets(
|
||||
"image",
|
||||
|
@ -41,6 +41,7 @@
|
||||
"@lezer/css": "^1.1.9",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@nanostores/react": "^0.8.0",
|
||||
"@nkzw/use-relative-time": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.4",
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@react-aria/interactions": "^3.22.5",
|
||||
@ -81,7 +82,6 @@
|
||||
"colord": "^2.9.3",
|
||||
"cookie": "^1.0.1",
|
||||
"css-tree": "^2.3.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"debug": "^4.3.7",
|
||||
"downshift": "^6.1.7",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "react";
|
||||
import * as ToastPrimitive from "@radix-ui/react-toast";
|
||||
import hotToast, {
|
||||
resolveValue,
|
||||
@ -9,7 +10,6 @@ import { keyframes, rawTheme, styled, type CSS } from "../stitches.config";
|
||||
import { Box } from "./box";
|
||||
import { theme } from "../stitches.config";
|
||||
import { Grid } from "./grid";
|
||||
|
||||
import { Text } from "./text";
|
||||
import { Flex } from "./flex";
|
||||
import { Tooltip } from "./tooltip";
|
||||
@ -383,9 +383,12 @@ export const Toaster = () => {
|
||||
type Options = Pick<ToastOptions, "duration" | "id" | "icon">;
|
||||
|
||||
export const toast = {
|
||||
info: (value: string, options?: Options) => hotToast(value, options),
|
||||
error: (value: string, options?: Options) => hotToast.error(value, options),
|
||||
warn: (value: string, options?: Options) => hotToast.custom(value, options),
|
||||
success: (value: string, options?: Options) =>
|
||||
info: (value: JSX.Element | string, options?: Options) =>
|
||||
hotToast(value, options),
|
||||
error: (value: JSX.Element | string, options?: Options) =>
|
||||
hotToast.error(value, options),
|
||||
warn: (value: JSX.Element | string, options?: Options) =>
|
||||
hotToast.custom(value, options),
|
||||
success: (value: JSX.Element | string, options?: Options) =>
|
||||
hotToast.success(value, options),
|
||||
};
|
||||
|
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -190,6 +190,9 @@ importers:
|
||||
'@nanostores/react':
|
||||
specifier: ^0.8.0
|
||||
version: 0.8.0(nanostores@0.11.3)(react@18.3.0-canary-14898b6a9-20240318)
|
||||
'@nkzw/use-relative-time':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(react@18.3.0-canary-14898b6a9-20240318)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^2.1.4
|
||||
version: 2.1.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)
|
||||
@ -310,9 +313,6 @@ importers:
|
||||
css-tree:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(patch_hash=epgcmebti7rfrc2ej4odb3t4jy)
|
||||
date-fns:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
debug:
|
||||
specifier: ^4.3.7
|
||||
version: 4.3.7
|
||||
@ -3483,6 +3483,11 @@ packages:
|
||||
resolution: {integrity: sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@nkzw/use-relative-time@1.1.0':
|
||||
resolution: {integrity: sha512-ogCL62FvScpRpsZUuaN6Jt0xPGRv62atQUNGyMcX+nZs4H5Fs5K1iA3MbSmkJ1y0n/N0RIRLc3VAp8o46lq2CA==}
|
||||
peerDependencies:
|
||||
react: 18.3.0-canary-14898b6a9-20240318
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -9918,6 +9923,10 @@ snapshots:
|
||||
'@netlify/node-cookies': 0.1.0
|
||||
urlpattern-polyfill: 8.0.2
|
||||
|
||||
'@nkzw/use-relative-time@1.1.0(react@18.3.0-canary-14898b6a9-20240318)':
|
||||
dependencies:
|
||||
react: 18.3.0-canary-14898b6a9-20240318
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
Reference in New Issue
Block a user