mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: add usage into heatmap (#1443)
This commit is contained in:
@ -474,7 +474,6 @@ const MemoEditor = () => {
|
|||||||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
||||||
onClick={handleSaveBtnClick}
|
onClick={handleSaveBtnClick}
|
||||||
>
|
>
|
||||||
<img className="w-5 -ml-0.5 mr-0.5 h-auto" src="/logo.webp" />
|
|
||||||
{t("editor.save")}
|
{t("editor.save")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,14 +14,12 @@ interface Props extends DialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
angle: number;
|
|
||||||
scale: number;
|
scale: number;
|
||||||
originX: number;
|
originX: number;
|
||||||
originY: number;
|
originY: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: State = {
|
const defaultState: State = {
|
||||||
angle: 0,
|
|
||||||
scale: 1,
|
scale: 1,
|
||||||
originX: -1,
|
originX: -1,
|
||||||
originY: -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 handleImgContainerScroll = (event: React.WheelEvent) => {
|
||||||
const offsetX = event.nativeEvent.offsetX;
|
const offsetX = event.nativeEvent.offsetX;
|
||||||
const offsetY = event.nativeEvent.offsetY;
|
const offsetY = event.nativeEvent.offsetY;
|
||||||
const sign = event.deltaY < 0 ? 1 : -1;
|
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({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
originX: offsetX,
|
originX: offsetX,
|
||||||
originY: offsetY,
|
originY: offsetY,
|
||||||
scale: curAngle,
|
scale: scale,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageComputedStyle = () => {
|
const imageComputedStyle = {
|
||||||
return {
|
transform: `scale(${state.scale})`,
|
||||||
transform: `scale(${state.scale}) rotate(${state.angle}deg)`,
|
transformOrigin: `${state.originX === -1 ? "center" : `${state.originX}px`} ${state.originY === -1 ? "center" : `${state.originY}px`}`,
|
||||||
transformOrigin: `${state.originX === -1 ? "center" : `${state.originX}px`} ${
|
|
||||||
state.originY === -1 ? "center" : `${state.originY}px`
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -145,22 +129,16 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, initialIndex }:
|
|||||||
<button className="btn" onClick={handleDownloadBtnClick}>
|
<button className="btn" onClick={handleDownloadBtnClick}>
|
||||||
<Icon.Download className="icon-img" />
|
<Icon.Download className="icon-img" />
|
||||||
</button>
|
</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>
|
||||||
<div className="img-container" onClick={handleImgContainerClick}>
|
<div className="img-container" onClick={handleImgContainerClick}>
|
||||||
<img
|
<img
|
||||||
|
style={imageComputedStyle}
|
||||||
|
src={imgUrls[currentIndex]}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onTouchStart={handleTouchStart}
|
onTouchStart={handleTouchStart}
|
||||||
onTouchMove={handleTouchMove}
|
onTouchMove={handleTouchMove}
|
||||||
onTouchEnd={handleTouchEnd}
|
onTouchEnd={handleTouchEnd}
|
||||||
src={imgUrls[currentIndex]}
|
|
||||||
onWheel={handleImgContainerScroll}
|
onWheel={handleImgContainerScroll}
|
||||||
style={getImageComputedStyle()}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -131,7 +131,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="watermark-container">
|
<div className="watermark-container">
|
||||||
<div className="logo-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>
|
||||||
<div className="userinfo-container">
|
<div className="userinfo-container">
|
||||||
<span className="name-text">{user.nickname || user.username}</span>
|
<span className="name-text">{user.nickname || user.username}</span>
|
||||||
@ -141,10 +141,9 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={`${window.location.origin}/m/${memo.id}`}
|
value={`${window.location.origin}/m/${memo.id}`}
|
||||||
size={64}
|
size={40}
|
||||||
bgColor={"#F3F4F6"}
|
bgColor={"#F3F4F6"}
|
||||||
fgColor={"#4B5563"}
|
fgColor={"#4B5563"}
|
||||||
level={"L"}
|
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -166,17 +165,17 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="flex flex-row justify-end items-center">
|
<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 ? (
|
{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>
|
||||||
<button className="btn-normal" onClick={handleCopyLinkBtnClick}>
|
<button className="btn-normal h-8" onClick={handleCopyLinkBtnClick}>
|
||||||
<Icon.Link className="w-4 h-auto mr-1" />
|
<Icon.Link className="w-4 h-auto sm:mr-1" />
|
||||||
<span>{t("common.link")}</span>
|
<span className="hidden sm:block">{t("common.link")}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@ const ShortcutList = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
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">
|
<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>
|
<span className="text-sm leading-6 font-mono text-gray-400">{t("common.shortcuts")}</span>
|
||||||
<button
|
<button
|
||||||
|
@ -68,7 +68,7 @@ const TagList = () => {
|
|||||||
}, [tagsText]);
|
}, [tagsText]);
|
||||||
|
|
||||||
return (
|
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">
|
<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>
|
<span className="text-sm leading-6 font-mono text-gray-400">{t("common.tags")}</span>
|
||||||
<button
|
<button
|
||||||
|
@ -38,13 +38,23 @@ const UsageHeatMap = () => {
|
|||||||
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
||||||
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
||||||
const memos = memoStore.state.memos;
|
const memos = memoStore.state.memos;
|
||||||
|
const [memoAmount, setMemoAmount] = useState(0);
|
||||||
|
const [createdDays, setCreatedDays] = useState(0);
|
||||||
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
||||||
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
||||||
const containerElRef = useRef<HTMLDivElement>(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(() => {
|
useEffect(() => {
|
||||||
getMemoStats(userStore.getCurrentUserId())
|
getMemoStats(userStore.getCurrentUserId())
|
||||||
.then(({ data: { data } }) => {
|
.then(({ data: { data } }) => {
|
||||||
|
setMemoAmount(data.length);
|
||||||
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
||||||
for (const record of data) {
|
for (const record of data) {
|
||||||
const index = (utils.getDateStampByDate(record * 1000) - beginDayTimestamp) / (1000 * 3600 * 24) - 1;
|
const index = (utils.getDateStampByDate(record * 1000) - beginDayTimestamp) / (1000 * 3600 * 24) - 1;
|
||||||
@ -97,6 +107,7 @@ const UsageHeatMap = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="usage-heat-map-wrapper" ref={containerElRef}>
|
<div className="usage-heat-map-wrapper" ref={containerElRef}>
|
||||||
<div className="usage-heat-map">
|
<div className="usage-heat-map">
|
||||||
{allStat.map((v, i) => {
|
{allStat.map((v, i) => {
|
||||||
@ -144,6 +155,11 @@ const UsageHeatMap = () => {
|
|||||||
<span className="tip-text">{t("days.sat")}</span>
|
<span className="tip-text">{t("days.sat")}</span>
|
||||||
</div>
|
</div>
|
||||||
</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 {
|
> .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 {
|
> .userinfo-container {
|
||||||
@apply w-auto grow truncate flex mr-2 flex-col justify-center items-start;
|
@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;
|
@apply mr-2;
|
||||||
|
|
||||||
> .logo-img {
|
> .logo-img {
|
||||||
|
@ -53,7 +53,7 @@ const MemoDetail = () => {
|
|||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<div className="title-container">
|
<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>
|
<p className="logo-text">{customizedProfile.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="action-button-container">
|
<div className="action-button-container">
|
||||||
|
@ -346,20 +346,12 @@ const ResourcesDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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">
|
<p className="text-sm text-gray-400 italic">
|
||||||
{isComplete ? (
|
{!isComplete && (
|
||||||
resources.length === 0 ? (
|
<span className="cursor-pointer my-6 hover:text-green-600" onClick={handleFetchMoreResourceBtnClick}>
|
||||||
t("message.no-resource")
|
|
||||||
) : (
|
|
||||||
t("message.resource-ready")
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="cursor-pointer hover:text-green-600" onClick={handleFetchMoreResourceBtnClick}>
|
|
||||||
{t("memo-list.fetch-more")}
|
{t("memo-list.fetch-more")}
|
||||||
</span>
|
</span>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user