feat: impl resources list page

This commit is contained in:
Steven
2023-09-16 11:48:53 +08:00
parent 4424c8a231
commit fb1490c183
24 changed files with 234 additions and 191 deletions

View File

@ -1,6 +1,7 @@
import { Autocomplete, Button, Input, List, ListItem, Option, Select, Typography } from "@mui/joy";
import React, { useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import { useTranslate } from "@/utils/i18n";
import { useResourceStore } from "../store/module";
import { generateDialog } from "./Dialog";

View File

@ -1,3 +1,4 @@
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import Icon from "../Icon";
import ResourceIcon from "../ResourceIcon";
@ -23,7 +24,7 @@ const ResourceListView = (props: Props) => {
key={resource.id}
className="max-w-full flex flex-row justify-start items-center flex-nowrap gap-x-1 bg-gray-100 dark:bg-zinc-800 px-2 py-1 rounded text-gray-500"
>
<ResourceIcon resource={resource} className="!w-4 !h-auto !opacity-100" />
<ResourceIcon resource={resource} className="!w-4 !h-4 !opacity-100" />
<span className="text-sm max-w-[8rem] truncate">{resource.filename}</span>
<Icon.X
className="w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"

View File

@ -8,6 +8,7 @@ import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts";
import { clearContentQueryParam } from "@/helpers/utils";
import { getMatchedNodes } from "@/labs/marked";
import { useFilterStore, useGlobalStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "@/store/module";
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import { useTranslate } from "@/utils/i18n";
import showCreateResourceDialog from "../CreateResourceDialog";
import Icon from "../Icon";

View File

@ -1,5 +1,6 @@
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import { getResourceUrl } from "@/utils/resource";
import Icon from "./Icon";
import ResourceIcon from "./ResourceIcon";
interface Props {
resource: Resource;
@ -16,7 +17,7 @@ const MemoResource: React.FC<Props> = (props: Props) => {
return (
<>
<div className={`w-auto flex flex-row justify-start items-center hover:opacity-80 ${className}`}>
<div className={`w-auto flex flex-row justify-start items-center text-gray-500 dark:text-gray-400 hover:opacity-80 ${className}`}>
{resource.type.startsWith("audio") ? (
<>
<audio controls>
@ -25,8 +26,8 @@ const MemoResource: React.FC<Props> = (props: Props) => {
</>
) : (
<>
<Icon.FileText className="w-4 h-auto mr-1 text-gray-500" />
<span className="text-gray-500 text-sm max-w-[256px] truncate font-mono cursor-pointer" onClick={handlePreviewBtnClick}>
<ResourceIcon className="!w-4 !h-4 mr-1" resource={resource} />
<span className="text-sm max-w-[256px] truncate cursor-pointer" onClick={handlePreviewBtnClick}>
{resource.filename}
</span>
</>

View File

@ -1,5 +1,6 @@
import classNames from "classnames";
import { absolutifyLink } from "@/helpers/utils";
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import { getResourceType, getResourceUrl } from "@/utils/resource";
import MemoResource from "./MemoResource";
import showPreviewImageDialog from "./PreviewImageDialog";
@ -42,7 +43,7 @@ const MemoResourceListView: React.FC<Props> = (props: Props) => {
<>
{imageResourceList.length > 0 &&
(imageResourceList.length === 1 ? (
<div className="mt-2 max-w-[90%] max-h-64 flex justify-center items-center shadow rounded overflow-hidden hide-scrollbar hover:shadow-md">
<div className="mt-2 max-w-[90%] max-h-64 flex justify-center items-center border rounded overflow-hidden hide-scrollbar hover:shadow-md">
<img
className="cursor-pointer min-h-full w-auto min-w-full object-cover"
src={getResourceUrl(imageResourceList[0])}
@ -63,7 +64,7 @@ const MemoResourceListView: React.FC<Props> = (props: Props) => {
return (
<SquareDiv
key={resource.id}
className="flex justify-center items-center shadow rounded overflow-hidden hide-scrollbar hover:shadow-md"
className="flex justify-center items-center border dark:border-zinc-900 rounded overflow-hidden hide-scrollbar hover:shadow-md"
>
<img
className="cursor-pointer min-h-full w-auto min-w-full object-cover"

View File

@ -1,4 +1,5 @@
import { getDateTimeString } from "@/helpers/datetime";
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import ResourceIcon from "./ResourceIcon";
interface Props {

View File

@ -1,5 +1,6 @@
import classNames from "classnames";
import React from "react";
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
import { getResourceType, getResourceUrl } from "@/utils/resource";
import Icon from "./Icon";
import showPreviewImageDialog from "./PreviewImageDialog";
@ -18,38 +19,52 @@ const ResourceIcon = (props: Props) => {
const className = classNames("w-full h-auto", props.className);
const strokeWidth = props.strokeWidth;
switch (resourceType) {
case "image/*":
return (
<SquareDiv className={classNames(className, "flex items-center justify-center overflow-clip")}>
<img
className="max-w-full max-h-full object-cover shadow"
src={resource.externalLink ? resourceUrl : resourceUrl + "?thumbnail=1"}
onClick={() => showPreviewImageDialog(resourceUrl)}
/>
</SquareDiv>
);
case "video/*":
return <Icon.FileVideo2 strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "audio/*":
return <Icon.FileAudio strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "text/*":
return <Icon.FileText strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/epub+zip":
return <Icon.Book strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/pdf":
return <Icon.Book strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/msword":
return <Icon.FileEdit strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/msexcel":
return <Icon.SheetIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/zip":
return <Icon.FileArchiveIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
case "application/x-java-archive":
return <Icon.BinaryIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
default:
return <Icon.File strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
const previewResource = () => {
window.open(resourceUrl);
};
if (resourceType === "image/*") {
return (
<SquareDiv className={classNames(className, "flex items-center justify-center overflow-clip")}>
<img
className="min-w-full min-h-full object-cover shadow"
src={resource.externalLink ? resourceUrl : resourceUrl + "?thumbnail=1"}
onClick={() => showPreviewImageDialog(resourceUrl)}
/>
</SquareDiv>
);
}
const getResourceIcon = () => {
switch (resourceType) {
case "video/*":
return <Icon.FileVideo2 strokeWidth={strokeWidth} className="w-full h-auto" />;
case "audio/*":
return <Icon.FileAudio strokeWidth={strokeWidth} className="w-full h-auto" />;
case "text/*":
return <Icon.FileText strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/epub+zip":
return <Icon.Book strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/pdf":
return <Icon.Book strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/msword":
return <Icon.FileEdit strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/msexcel":
return <Icon.SheetIcon strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/zip":
return <Icon.FileArchiveIcon onClick={previewResource} strokeWidth={strokeWidth} className="w-full h-auto" />;
case "application/x-java-archive":
return <Icon.BinaryIcon strokeWidth={strokeWidth} className="w-full h-auto" />;
default:
return <Icon.File strokeWidth={strokeWidth} className="w-full h-auto" />;
}
};
return (
<div onClick={previewResource} className={classNames(className, "max-w-[4rem] opacity-50")}>
{getResourceIcon()}
</div>
);
};
export default React.memo(ResourceIcon);

View File

@ -64,7 +64,7 @@ const AccessTokenSection = () => {
Access Tokens
<LearnMore className="ml-2" url="https://usememos.com/docs/local-storage" />
</p>
<p className="text-sm text-gray-700">A list of all access tokens for your account.</p>
<p className="text-sm text-gray-700 dark:text-gray-500">A list of all access tokens for your account.</p>
</div>
<div className="mt-4 sm:mt-0">
<Button
@ -81,7 +81,7 @@ const AccessTokenSection = () => {
<div className="mt-2 flow-root">
<div className="overflow-x-auto">
<div className="inline-block min-w-full py-2 align-middle">
<table className="min-w-full divide-y divide-gray-300">
<table className="min-w-full divide-y divide-gray-300 dark:divide-gray-400">
<thead>
<tr>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
@ -101,7 +101,7 @@ const AccessTokenSection = () => {
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tbody className="divide-y divide-gray-200 dark:divide-gray-500">
{userAccessTokens.map((userAccessToken) => (
<tr key={userAccessToken.accessToken}>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">