Merge branch 'main' into fix-sync-status-icon

This commit is contained in:
Oleg Isonen authored and GitHub committed 2022-07-27 02:23:45 +02:00
commit 04ce1b09f1
17 files changed
+189 -61

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@
## Before merging
- [ ] tested locally and on preview environment
- [ ] tested locally and on preview environment (preview dev login: 5de6e)
- [ ] updated [test cases](https://github.com/webstudio-is/webstudio-designer/blob/main/apps/designer/docs/test-cases.md) document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example` and the `designer/env-check.js` if mandatory
+2 -1
View File
@@ -11,9 +11,10 @@ FILE_UPLOAD_PATH="uploads"
# S3 envs
S3_BUCKET=
S3_REGION=
S3_ACL="public-read"
S3_ENDPOINT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
# if using r2 this your worker url
WORKER_URL=
ASSET_CDN_URL=
@@ -1,44 +1,49 @@
import { ImageIcon } from "~/shared/icons";
import { Button, Flex, Grid, Heading } from "~/shared/design-system";
import { useRef } from "react";
import { Form, useSubmit } from "@remix-run/react";
import type { Asset } from "@webstudio-is/prisma-client";
import { Flex, Grid, Heading } from "~/shared/design-system";
import { useEffect, useState } from "react";
import { useActionData } from "@remix-run/react";
import { Asset } from "@webstudio-is/prisma-client";
import { AssetManagerImage } from "./components/image";
export const TabContent = ({ assets }: { assets: Array<Asset> }) => {
const inputRef = useRef<HTMLInputElement | null>(null);
const submit = useSubmit();
import { AddAnAssetForm } from "./components/add-an-asset-form";
import { UploadingAsset } from "../types";
export const TabContent = ({
assets: baseAssets,
}: {
assets: Array<Asset | UploadingAsset>;
}) => {
const newImages = useActionData();
const [assets, setAssets] = useState(baseAssets);
useEffect(() => {
if (newImages?.length) {
setAssets((currentAssets) => [
...newImages,
...currentAssets.filter((asset) => asset.status !== "uploading"),
]);
}
}, [newImages]);
return (
<Flex gap="3" direction="column" css={{ padding: "$1" }}>
<Flex justify="between">
<Heading>Assets</Heading>
<Form
method="post"
encType="multipart/form-data"
onChange={(event) => {
if (inputRef.current?.files) {
submit(event.currentTarget);
event.currentTarget.reset();
}
}}
>
<input
accept="image/*"
type="file"
name="image"
multiple
ref={inputRef}
style={{ display: "none" }}
/>
<Button type="button" onClick={() => inputRef?.current?.click()}>
Upload Image
</Button>
</Form>
<AddAnAssetForm
onSubmit={(uploadedAssets: Array<UploadingAsset>) =>
setAssets((assets) => [...uploadedAssets, ...assets])
}
/>
</Flex>
<Grid columns={2} gap={2}>
{assets.map((asset) => (
<AssetManagerImage key={asset.id} asset={asset} />
<AssetManagerImage
key={asset.id}
path={asset.path}
alt={asset.alt || asset.name}
status={asset.status}
/>
))}
</Grid>
</Flex>
@@ -0,0 +1,66 @@
import { Form, useSubmit } from "@remix-run/react";
import { Asset } from "@webstudio-is/prisma-client";
import ObjectID from "bson-objectid";
import { ChangeEvent, useRef } from "react";
import { Button } from "~/shared/design-system";
import { UploadingAsset } from "../../types";
const readImages = async (fileList: FileList) => {
const images = [];
for (const file of fileList) {
const path = await new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener("load", (event) => {
const dataUri = event?.target?.result;
resolve(dataUri);
});
reader.readAsDataURL(file);
});
images.push({
path: path as string,
name: file.name,
id: ObjectID().toString(),
status: "uploading" as Asset["status"],
alt: file.name,
});
}
return images;
};
export const AddAnAssetForm = ({
onSubmit,
}: {
onSubmit: (uploadedAssets: Array<UploadingAsset>) => void;
}) => {
const submit = useSubmit();
const inputRef = useRef<HTMLInputElement | null>(null);
const onFormChange = async (event: ChangeEvent<HTMLFormElement>) => {
const newFiles = inputRef?.current?.files;
if (newFiles) {
submit(event.currentTarget);
const parsedFiles = await readImages(newFiles);
onSubmit(parsedFiles);
event.currentTarget.reset();
}
};
return (
<Form method="post" encType="multipart/form-data" onChange={onFormChange}>
<input
accept="image/*"
type="file"
name="image"
multiple
ref={inputRef}
style={{ display: "none" }}
/>
<Button type="button" onClick={() => inputRef?.current?.click()}>
Upload Image
</Button>
</Form>
);
};
@@ -1,11 +1,12 @@
import { Asset } from "@webstudio-is/prisma-client";
import { useEffect, useState } from "react";
import { Box } from "~/shared/design-system";
import placholderImage from "~/shared/images/image-placeholder.svg";
import { Box, ProgressBar } from "~/shared/design-system";
import { useInterval } from "react-use";
import placeholderImage from "~/shared/images/image-placeholder.svg";
import brokenImage from "~/shared/images/broken-image-placeholder.svg";
import { Asset } from "@webstudio-is/prisma-client";
const useImageWithFallback = ({ path }: { path: string }) => {
const [src, setSrc] = useState(placholderImage);
const [src, setSrc] = useState(placeholderImage);
useEffect(() => {
const newImage = new Image();
@@ -18,21 +19,61 @@ const useImageWithFallback = ({ path }: { path: string }) => {
};
export const AssetManagerImage = ({
asset: { path, alt },
path,
alt,
status,
}: {
asset: Asset;
path: string;
alt?: string;
status?: Asset["status"];
}) => {
const isUploading = status === "uploading";
const src = useImageWithFallback({ path });
const [progressBarPercentage, setProgressBarPercentage] = useState(0);
// @todo rewrite this fake indication to show real progress
useInterval(
() => {
setProgressBarPercentage((percentage) =>
percentage < 60 ? percentage + 1 : percentage
);
},
isUploading ? 100 : null
);
return (
<Box
title={alt || ""}
css={{
aspectRatio: "1/1",
backgroundImage: `url(${src})`,
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "0 $2",
position: "relative",
}}
></Box>
>
<Box
css={{
backgroundImage: `url("${src}")`,
width: "100%",
height: "100%",
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
position: "absolute",
left: 0,
top: 0,
...(isUploading ? { filter: "blur(1px)", opacity: 0.7 } : {}),
}}
></Box>
{isUploading && (
<ProgressBar
value={progressBarPercentage}
max={60}
css={{ width: "100%", height: "$1" }}
/>
)}
</Box>
);
};
@@ -1,3 +1,9 @@
import { Asset } from "@webstudio-is/prisma-client";
import { panels } from "./panels";
export type TabName = keyof typeof panels | "none";
export type UploadingAsset = Pick<
Asset,
"id" | "status" | "name" | "path" | "alt"
>;
+4 -3
View File
@@ -42,9 +42,10 @@ export const action: ActionFunction = async ({ request, params }) => {
projectId: params.id,
dirname: __dirname,
});
return {
assets,
};
return assets.map((asset: Asset) => ({
...asset,
status: "uploaded",
}));
} catch (error) {
if (error instanceof Error) {
return {
+1 -1
View File
@@ -55,7 +55,7 @@ if (process.env.DEV_LOGIN === "true") {
new FormStrategy(async ({ form }) => {
const secret = form.get("secret");
if (secret === process.env.AUTH_SECRET) {
if (secret === process.env.AUTH_SECRET?.slice(0, 4)) {
try {
const user = await db.user.createOrLoginWithDev();
return user;
@@ -39,6 +39,7 @@ export { Tooltip } from "./components/tooltip";
export { Button } from "./components/button";
export { IconButton } from "./components/icon-button";
export { Box } from "./components/box";
export { ProgressBar } from "./components/progress-bar";
export {
Popover,
PopoverContent,
+1 -1
View File
@@ -64,7 +64,7 @@
- Open the login page when logged in to make sure you are redirected to the dashboard
- Ensure you can view a built site in an incognito tab
- Ensure you can view a forked site in an incognito tab
- Click on dev login, add the AUTH_SECRET value in the `.env` value and make sure user is authenticated and redirected to the dashboard
- Click on dev login, add the first four characters in AUTH_SECRET value in the `.env` value and make sure user is authenticated and redirected to the dashboard
- Click on dev login and write a wrong value to make sure you get an error message and stay in the login page.
- Click on login with GitHub and make sure that you are redirected to the dashboard
- Click on login with Google and make sure that you are redirected to the dashboard
+5 -1
View File
@@ -1,5 +1,6 @@
import { type Location, prisma, Project } from "@webstudio-is/prisma-client";
import sharp from "sharp";
import { getAssetPath } from "../helpers/get-asset-path";
export const create = async (
projectId: Project["id"],
@@ -24,5 +25,8 @@ export const create = async (
},
});
return newAsset;
return {
...newAsset,
path: getAssetPath(newAsset),
};
};
@@ -22,7 +22,7 @@ describe("getAssetPath", () => {
...OLD_ENV,
S3_ACCESS_KEY_ID: "testid",
S3_SECRET_ACCESS_KEY: "testkey",
WORKER_URL: "",
ASSET_CDN_URL: "",
};
});
@@ -87,7 +87,7 @@ describe("getAssetPath", () => {
process.env.S3_ENDPOINT = "https://userid.r2.cloudflarestorage.com";
process.env.S3_BUCKET = "bucket";
process.env.S3_REGION = "auto";
process.env.WORKER_URL = "https://worker-test.workers.dev";
process.env.ASSET_CDN_URL = "https://worker-test.workers.dev";
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
@@ -13,10 +13,10 @@ export const getAssetPath = (asset: DbAsset) => {
if (asset.location === Location.REMOTE && s3Envs.success) {
const s3Url = new URL(s3Envs.data.S3_ENDPOINT);
if (s3Envs.data.WORKER_URL) {
const r2Url = new URL(s3Envs.data.WORKER_URL);
r2Url.pathname = asset.name;
return r2Url.toString();
if (s3Envs.data.ASSET_CDN_URL) {
const cndUrl = new URL(s3Envs.data.ASSET_CDN_URL);
cndUrl.pathname = asset.name;
return cndUrl.toString();
}
s3Url.hostname = `${s3Envs.data.S3_BUCKET}.${s3Url.hostname}`;
s3Url.pathname = asset.name;
+2 -2
View File
@@ -20,8 +20,8 @@ export const s3EnvVariables = z.object({
S3_ACCESS_KEY_ID: z.string(),
S3_SECRET_ACCESS_KEY: z.string(),
S3_BUCKET: z.string(),
S3_ACL: z.string().optional().default("public-read"),
WORKER_URL: z.string().optional(),
S3_ACL: z.string().optional(),
ASSET_CDN_URL: z.string().optional(),
});
export const fsEnvVariables = z.object({
@@ -29,5 +29,5 @@ export const uploadToDisk = async ({
return newAsset;
});
await Promise.all(allInfo);
return await Promise.all(allInfo);
};
@@ -43,8 +43,8 @@ export const s3UploadHandler: S3UploadHandler = async ({
const [name, extension] = getFilenameAndExtension(filename);
const key = encodeURI(`${name}_${Date.now()}.${extension}`);
// r2 does not have ACL support so in that case we pass nothing there
const ACL = s3Envs.WORKER_URL ? {} : { ACL: s3Envs.S3_ACL };
// if there is no ACL passed we do not default since some providers do not support it
const ACL = s3Envs.S3_ACL ? { ACL: s3Envs.S3_ACL } : {};
const params: PutObjectCommandInput = {
Bucket: s3Envs.S3_BUCKET,
+4 -1
View File
@@ -2,5 +2,8 @@ import type { Asset as DbAsset } from "@prisma/client";
export { Location } from "@prisma/client";
export type { InstanceProps, Project, User, Breakpoints } from "@prisma/client";
export type Asset = DbAsset & { path: string };
export type Asset = DbAsset & {
path: string;
status?: "uploading" | "uploaded";
};
export type { DbAsset };