mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: impl resources list page
This commit is contained in:
@ -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";
|
||||
|
@ -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"
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user