mirror of
https://github.com/usememos/memos.git
synced 2024-09-21 14:55:59 +08:00
chore: add usage into heatmap (#1443)
This commit is contained in:
parent
1ea65c0b60
commit
d71bfce1a0
|
@ -474,7 +474,6 @@ const MemoEditor = () => {
|
|||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
||||
onClick={handleSaveBtnClick}
|
||||
>
|
||||
<img className="w-5 -ml-0.5 mr-0.5 h-auto" src="/logo.webp" />
|
||||
{t("editor.save")}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -14,14 +14,12 @@ interface Props extends DialogProps {
|
|||
}
|
||||
|
||||
interface State {
|
||||
angle: number;
|
||||
scale: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
}
|
||||
|
||||
const defaultState: State = {
|
||||
angle: 0,
|
||||
scale: 1,
|
||||
originX: -1,
|
||||
originY: -1,
|
||||
|
@ -104,36 +102,22 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, initialIndex }:
|
|||
}
|
||||
};
|
||||
|
||||
const handleImgRotate = (event: React.MouseEvent, angle: number) => {
|
||||
const curImgAngle = (state.angle + angle + 360) % 360;
|
||||
setState({
|
||||
...state,
|
||||
originX: -1,
|
||||
originY: -1,
|
||||
angle: curImgAngle,
|
||||
});
|
||||
};
|
||||
|
||||
const handleImgContainerScroll = (event: React.WheelEvent) => {
|
||||
const offsetX = event.nativeEvent.offsetX;
|
||||
const offsetY = event.nativeEvent.offsetY;
|
||||
const sign = event.deltaY < 0 ? 1 : -1;
|
||||
const curAngle = Math.max(MIN_SCALE, Math.min(MAX_SCALE, state.scale + sign * SCALE_UNIT));
|
||||
const scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, state.scale + sign * SCALE_UNIT));
|
||||
setState({
|
||||
...state,
|
||||
originX: offsetX,
|
||||
originY: offsetY,
|
||||
scale: curAngle,
|
||||
scale: scale,
|
||||
});
|
||||
};
|
||||
|
||||
const getImageComputedStyle = () => {
|
||||
return {
|
||||
transform: `scale(${state.scale}) rotate(${state.angle}deg)`,
|
||||
transformOrigin: `${state.originX === -1 ? "center" : `${state.originX}px`} ${
|
||||
state.originY === -1 ? "center" : `${state.originY}px`
|
||||
}`,
|
||||
};
|
||||
const imageComputedStyle = {
|
||||
transform: `scale(${state.scale})`,
|
||||
transformOrigin: `${state.originX === -1 ? "center" : `${state.originX}px`} ${state.originY === -1 ? "center" : `${state.originY}px`}`,
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -145,22 +129,16 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, initialIndex }:
|
|||
<button className="btn" onClick={handleDownloadBtnClick}>
|
||||
<Icon.Download className="icon-img" />
|
||||
</button>
|
||||
<button className="btn" onClick={(e) => handleImgRotate(e, -90)}>
|
||||
<Icon.RotateCcw className="icon-img" />
|
||||
</button>
|
||||
<button className="btn" onClick={(e) => handleImgRotate(e, 90)}>
|
||||
<Icon.RotateCw className="icon-img" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="img-container" onClick={handleImgContainerClick}>
|
||||
<img
|
||||
style={imageComputedStyle}
|
||||
src={imgUrls[currentIndex]}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
src={imgUrls[currentIndex]}
|
||||
onWheel={handleImgContainerScroll}
|
||||
style={getImageComputedStyle()}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -131,7 +131,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||
</div>
|
||||
<div className="watermark-container">
|
||||
<div className="logo-container">
|
||||
<img className="logo-img" src={`${systemStatus.customizedProfile.logoUrl || "/logo.webp"}`} alt="" />
|
||||
<img className="h-10 w-auto rounded-lg" src={`${systemStatus.customizedProfile.logoUrl || "/logo.webp"}`} alt="" />
|
||||
</div>
|
||||
<div className="userinfo-container">
|
||||
<span className="name-text">{user.nickname || user.username}</span>
|
||||
|
@ -141,10 +141,9 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||
</div>
|
||||
<QRCodeSVG
|
||||
value={`${window.location.origin}/m/${memo.id}`}
|
||||
size={64}
|
||||
size={40}
|
||||
bgColor={"#F3F4F6"}
|
||||
fgColor={"#4B5563"}
|
||||
level={"L"}
|
||||
includeMargin={false}
|
||||
/>
|
||||
</div>
|
||||
|
@ -166,17 +165,17 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||
))}
|
||||
</Select>
|
||||
<div className="flex flex-row justify-end items-center">
|
||||
<button disabled={createLoadingState.isLoading} className="btn-normal mr-2" onClick={handleDownloadBtnClick}>
|
||||
<button disabled={createLoadingState.isLoading} className="btn-normal h-8 mr-2" onClick={handleDownloadBtnClick}>
|
||||
{createLoadingState.isLoading ? (
|
||||
<Icon.Loader className="w-4 h-auto mr-1 animate-spin" />
|
||||
<Icon.Loader className="w-4 h-auto sm:mr-1 animate-spin" />
|
||||
) : (
|
||||
<Icon.Download className="w-4 h-auto mr-1" />
|
||||
<Icon.Download className="w-4 h-auto sm:mr-1" />
|
||||
)}
|
||||
<span>{t("common.image")}</span>
|
||||
<span className="hidden sm:block">{t("common.image")}</span>
|
||||
</button>
|
||||
<button className="btn-normal" onClick={handleCopyLinkBtnClick}>
|
||||
<Icon.Link className="w-4 h-auto mr-1" />
|
||||
<span>{t("common.link")}</span>
|
||||
<button className="btn-normal h-8" onClick={handleCopyLinkBtnClick}>
|
||||
<Icon.Link className="w-4 h-auto sm:mr-1" />
|
||||
<span className="hidden sm:block">{t("common.link")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -36,7 +36,7 @@ const ShortcutList = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start items-start w-full py-0 px-1 mt-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||
<div className="flex flex-col justify-start items-start w-full mt-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||
<div className="flex flex-row justify-start items-center w-full px-4">
|
||||
<span className="text-sm leading-6 font-mono text-gray-400">{t("common.shortcuts")}</span>
|
||||
<button
|
||||
|
|
|
@ -68,7 +68,7 @@ const TagList = () => {
|
|||
}, [tagsText]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start items-start w-full py-0 px-1 mt-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||
<div className="flex flex-col justify-start items-start w-full mt-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||
<div className="flex flex-row justify-start items-center w-full px-4">
|
||||
<span className="text-sm leading-6 font-mono text-gray-400">{t("common.tags")}</span>
|
||||
<button
|
||||
|
|
|
@ -38,13 +38,23 @@ const UsageHeatMap = () => {
|
|||
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
||||
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
||||
const memos = memoStore.state.memos;
|
||||
const [memoAmount, setMemoAmount] = useState(0);
|
||||
const [createdDays, setCreatedDays] = useState(0);
|
||||
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
||||
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
||||
const containerElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userStore.state.user) {
|
||||
return;
|
||||
}
|
||||
setCreatedDays(Math.ceil((Date.now() - utils.getTimeStampByDate(userStore.state.user.createdTs)) / 1000 / 3600 / 24));
|
||||
}, [userStore.state.user]);
|
||||
|
||||
useEffect(() => {
|
||||
getMemoStats(userStore.getCurrentUserId())
|
||||
.then(({ data: { data } }) => {
|
||||
setMemoAmount(data.length);
|
||||
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
||||
for (const record of data) {
|
||||
const index = (utils.getDateStampByDate(record * 1000) - beginDayTimestamp) / (1000 * 3600 * 24) - 1;
|
||||
|
@ -97,53 +107,59 @@ const UsageHeatMap = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className="usage-heat-map-wrapper" ref={containerElRef}>
|
||||
<div className="usage-heat-map">
|
||||
{allStat.map((v, i) => {
|
||||
const count = v.count;
|
||||
const colorLevel =
|
||||
count <= 0
|
||||
? ""
|
||||
: count <= 1
|
||||
? "stat-day-l1-bg"
|
||||
: count <= 2
|
||||
? "stat-day-l2-bg"
|
||||
: count <= 4
|
||||
? "stat-day-l3-bg"
|
||||
: "stat-day-l4-bg";
|
||||
<>
|
||||
<div className="usage-heat-map-wrapper" ref={containerElRef}>
|
||||
<div className="usage-heat-map">
|
||||
{allStat.map((v, i) => {
|
||||
const count = v.count;
|
||||
const colorLevel =
|
||||
count <= 0
|
||||
? ""
|
||||
: count <= 1
|
||||
? "stat-day-l1-bg"
|
||||
: count <= 2
|
||||
? "stat-day-l2-bg"
|
||||
: count <= 4
|
||||
? "stat-day-l3-bg"
|
||||
: "stat-day-l4-bg";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="stat-wrapper"
|
||||
key={i}
|
||||
onMouseEnter={(e) => handleUsageStatItemMouseEnter(e, v)}
|
||||
onMouseLeave={handleUsageStatItemMouseLeave}
|
||||
onClick={() => handleUsageStatItemClick(v)}
|
||||
>
|
||||
<span
|
||||
className={`stat-container ${colorLevel} ${currentStat === v ? "current" : ""} ${
|
||||
todayTimeStamp === v.timestamp ? "today" : ""
|
||||
}`}
|
||||
></span>
|
||||
return (
|
||||
<div
|
||||
className="stat-wrapper"
|
||||
key={i}
|
||||
onMouseEnter={(e) => handleUsageStatItemMouseEnter(e, v)}
|
||||
onMouseLeave={handleUsageStatItemMouseLeave}
|
||||
onClick={() => handleUsageStatItemClick(v)}
|
||||
>
|
||||
<span
|
||||
className={`stat-container ${colorLevel} ${currentStat === v ? "current" : ""} ${
|
||||
todayTimeStamp === v.timestamp ? "today" : ""
|
||||
}`}
|
||||
></span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{nullCell.map((_, i) => (
|
||||
<div className="stat-wrapper" key={i}>
|
||||
<span className="stat-container null"></span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{nullCell.map((_, i) => (
|
||||
<div className="stat-wrapper" key={i}>
|
||||
<span className="stat-container null"></span>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
<div className="day-tip-text-container">
|
||||
<span className="tip-text">{t("days.sun")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.tue")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.thu")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.sat")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="day-tip-text-container">
|
||||
<span className="tip-text">{t("days.sun")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.tue")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.thu")}</span>
|
||||
<span className="tip-text"></span>
|
||||
<span className="tip-text">{t("days.sat")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="w-full pl-4 text-xs -mt-2 mb-3 text-gray-400 dark:text-zinc-400">
|
||||
<span className="font-medium text-gray-500 dark:text-zinc-300">{memoAmount}</span> memos in{" "}
|
||||
<span className="font-medium text-gray-500 dark:text-zinc-300">{createdDays}</span> days
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
}
|
||||
|
||||
> .watermark-container {
|
||||
@apply flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-700 py-2 px-6;
|
||||
@apply flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-700 py-3 px-6;
|
||||
|
||||
> .userinfo-container {
|
||||
@apply w-auto grow truncate flex mr-2 flex-col justify-center items-start;
|
||||
|
@ -70,7 +70,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
> .logo-container{
|
||||
> .logo-container {
|
||||
@apply mr-2;
|
||||
|
||||
> .logo-img {
|
||||
|
|
|
@ -53,7 +53,7 @@ const MemoDetail = () => {
|
|||
<div className="page-container">
|
||||
<div className="page-header">
|
||||
<div className="title-container">
|
||||
<img className="logo-img" src={customizedProfile.logoUrl} alt="" />
|
||||
<img className="h-10 w-auto rounded-lg mr-2" src={customizedProfile.logoUrl} alt="" />
|
||||
<p className="logo-text">{customizedProfile.name}</p>
|
||||
</div>
|
||||
<div className="action-button-container">
|
||||
|
|
|
@ -346,20 +346,12 @@ const ResourcesDashboard = () => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-start items-center w-full my-6">
|
||||
<div className="flex flex-col justify-start items-center w-full">
|
||||
<p className="text-sm text-gray-400 italic">
|
||||
{isComplete ? (
|
||||
resources.length === 0 ? (
|
||||
t("message.no-resource")
|
||||
) : (
|
||||
t("message.resource-ready")
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<span className="cursor-pointer hover:text-green-600" onClick={handleFetchMoreResourceBtnClick}>
|
||||
{t("memo-list.fetch-more")}
|
||||
</span>
|
||||
</>
|
||||
{!isComplete && (
|
||||
<span className="cursor-pointer my-6 hover:text-green-600" onClick={handleFetchMoreResourceBtnClick}>
|
||||
{t("memo-list.fetch-more")}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue