mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
refactor!: next generation AFFiNE code structure (#1176)
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
export const LogoIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 50 50"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M21.1996 0L4 50H14.0741L25.0146 15.4186L35.96 50H46L28.7978 0H21.1996Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const DocIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 50 50"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 40.5353V9.46462C2 6.95444 2.99716 4.54708 4.77212 2.77212C6.54708 0.997163 8.95444 0 11.4646 0H37.7552C39.0224 0 40.0497 1.02726 40.0497 2.29445V33.3652C40.0497 33.4357 40.0465 33.5055 40.0403 33.5744C39.9882 34.1502 39.7234 34.6646 39.3251 35.0385C38.9147 35.4237 38.3625 35.6597 37.7552 35.6597H11.4646C11.0129 35.6597 10.5676 35.7224 10.1404 35.8429C8.60419 36.2781 7.37011 37.4505 6.85245 38.9541C6.67955 39.4584 6.58891 39.9922 6.58891 40.5354C6.58891 41.8285 7.1026 43.0687 8.01697 43.983C8.93134 44.8974 10.1715 45.4111 11.4646 45.4111H42.6309V4.68456C42.6309 3.41736 43.6582 2.3901 44.9254 2.3901C46.1926 2.3901 47.2198 3.41736 47.2198 4.68456V47.7055C47.2198 48.9727 46.1926 50 44.9254 50H11.4646C8.95445 50 6.54708 49.0028 4.77212 47.2279C2.99716 45.4529 2 43.0456 2 40.5353ZM12.6596 38.2409C11.3925 38.2409 10.3652 39.2682 10.3652 40.5354C10.3652 41.8026 11.3925 42.8298 12.6596 42.8298H36.5602C37.8274 42.8298 38.8546 41.8026 38.8546 40.5354C38.8546 39.2682 37.8274 38.2409 36.5602 38.2409H12.6596Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const TwitterIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M21.6725 4.5259C20.9071 4.86507 20.0848 5.09423 19.2204 5.19782C20.1123 4.66413 20.7796 3.82416 21.0977 2.83465C20.2598 3.33239 19.3426 3.68274 18.3862 3.87048C17.7431 3.18378 16.8912 2.72861 15.9629 2.57567C15.0345 2.42272 14.0816 2.58054 13.2522 3.02463C12.4227 3.46872 11.7631 4.17423 11.3757 5.03163C10.9883 5.88902 10.8948 6.85034 11.1097 7.76632C9.41177 7.68106 7.75072 7.23974 6.23436 6.47098C4.71801 5.70222 3.38025 4.62321 2.30789 3.30398C1.94123 3.93648 1.73039 4.66982 1.73039 5.45082C1.72998 6.1539 1.90312 6.8462 2.23445 7.46632C2.56577 8.08644 3.04504 8.61518 3.62973 9.00565C2.95165 8.98407 2.28853 8.80085 1.69556 8.47123V8.52623C1.69549 9.51233 2.03659 10.4681 2.66098 11.2313C3.28536 11.9945 4.15458 12.5183 5.12114 12.7136C4.49211 12.8838 3.83262 12.9089 3.19248 12.7869C3.46518 13.6354 3.99639 14.3773 4.71173 14.9089C5.42707 15.4405 6.29073 15.7351 7.18181 15.7514C5.66916 16.9389 3.80104 17.583 1.87798 17.5801C1.53733 17.5802 1.19696 17.5603 0.858643 17.5206C2.81066 18.7756 5.08295 19.4417 7.40364 19.4391C15.2595 19.4391 19.5541 12.9326 19.5541 7.28965C19.5541 7.10632 19.5495 6.92115 19.5412 6.73782C20.3766 6.13371 21.0976 5.38564 21.6706 4.52865L21.6725 4.5259V4.5259Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const GithubIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_3073_4801)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.9999 0.000812531C4.92238 0.000812531 -0.00012207 4.92331 -0.00012207 11.0008C-0.00012207 15.8683 3.14863 19.9796 7.52113 21.4371C8.07113 21.5333 8.27738 21.2033 8.27738 20.9146C8.27738 20.6533 8.26363 19.7871 8.26363 18.8658C5.49988 19.3746 4.78488 18.1921 4.56488 17.5733C4.44113 17.2571 3.90488 16.2808 3.43738 16.0196C3.05238 15.8133 2.50238 15.3046 3.42363 15.2908C4.28988 15.2771 4.90863 16.0883 5.11488 16.4183C6.10488 18.0821 7.68613 17.6146 8.31863 17.3258C8.41488 16.6108 8.70363 16.1296 9.01988 15.8546C6.57238 15.5796 4.01488 14.6308 4.01488 10.4233C4.01488 9.22706 4.44113 8.23706 5.14238 7.46706C5.03238 7.19206 4.64738 6.06456 5.25238 4.55206C5.25238 4.55206 6.17363 4.26331 8.27738 5.67956C9.15738 5.43206 10.0924 5.30831 11.0274 5.30831C11.9624 5.30831 12.8974 5.43206 13.7774 5.67956C15.8811 4.24956 16.8024 4.55206 16.8024 4.55206C17.4074 6.06456 17.0224 7.19206 16.9124 7.46706C17.6136 8.23706 18.0399 9.21331 18.0399 10.4233C18.0399 14.6446 15.4686 15.5796 13.0211 15.8546C13.4199 16.1983 13.7636 16.8583 13.7636 17.8896C13.7636 19.3608 13.7499 20.5433 13.7499 20.9146C13.7499 21.2033 13.9561 21.5471 14.5061 21.4371C16.6898 20.6998 18.5873 19.2964 19.9316 17.4242C21.2758 15.5521 21.9992 13.3056 21.9999 11.0008C21.9999 4.92331 17.0774 0.000812531 10.9999 0.000812531Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3073_4801">
|
||||
<rect width="22" height="22" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const DiscordIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_3073_4817)">
|
||||
<path d="M18.6239 4.11773C17.2214 3.48523 15.718 3.01773 14.146 2.7519C14.132 2.74921 14.1175 2.75094 14.1045 2.75685C14.0915 2.76276 14.0807 2.77256 14.0735 2.7849C13.881 3.12315 13.6665 3.56406 13.5162 3.9124C11.8489 3.66328 10.1538 3.66328 8.48645 3.9124C8.31901 3.52635 8.13019 3.14993 7.92087 2.7849C7.91377 2.77241 7.90304 2.76239 7.8901 2.75617C7.87716 2.74994 7.86263 2.74781 7.84845 2.75006C6.27729 3.0159 4.77395 3.4834 3.37054 4.11681C3.35846 4.12188 3.34823 4.13051 3.3412 4.14156C0.488536 8.33531 -0.293381 12.4255 0.0907023 16.4643C0.0917711 16.4742 0.0948415 16.4838 0.0997268 16.4924C0.104612 16.5011 0.11121 16.5087 0.119119 16.5147C1.78415 17.727 3.64126 18.6504 5.6127 19.2464C5.62645 19.2506 5.64114 19.2506 5.65488 19.2464C5.66862 19.2421 5.68075 19.2338 5.6897 19.2226C6.11393 18.6552 6.48986 18.0533 6.81354 17.4231C6.81803 17.4145 6.82062 17.4051 6.82113 17.3953C6.82164 17.3856 6.82006 17.3759 6.81649 17.3669C6.81293 17.3579 6.80747 17.3497 6.80047 17.343C6.79348 17.3362 6.78512 17.331 6.77595 17.3278C6.18379 17.1048 5.61003 16.8357 5.05995 16.523C5.05007 16.5173 5.04174 16.5093 5.03571 16.4997C5.02969 16.49 5.02615 16.479 5.02542 16.4677C5.0247 16.4563 5.0268 16.4449 5.03155 16.4346C5.03629 16.4243 5.04353 16.4153 5.05262 16.4084C5.16812 16.3231 5.28362 16.2342 5.39362 16.1453C5.40352 16.1373 5.41545 16.1322 5.42807 16.1306C5.4407 16.129 5.45352 16.1309 5.46512 16.1361C9.06487 17.7531 12.9635 17.7531 16.521 16.1361C16.5327 16.1306 16.5456 16.1284 16.5584 16.1299C16.5712 16.1313 16.5834 16.1364 16.5935 16.1444C16.7035 16.2342 16.818 16.3231 16.9345 16.4084C16.9436 16.4151 16.951 16.424 16.9559 16.4342C16.9608 16.4445 16.9631 16.4558 16.9625 16.4672C16.962 16.4785 16.9586 16.4896 16.9528 16.4993C16.9469 16.509 16.9387 16.5172 16.929 16.523C16.3808 16.8383 15.8106 17.1051 15.212 17.3269C15.2028 17.3302 15.1945 17.3355 15.1875 17.3423C15.1805 17.3492 15.175 17.3574 15.1715 17.3665C15.1679 17.3756 15.1663 17.3854 15.1668 17.3952C15.1674 17.4049 15.17 17.4145 15.1745 17.4231C15.5045 18.0529 15.8821 18.6524 16.2974 19.2216C16.306 19.2333 16.318 19.2421 16.3318 19.2467C16.3456 19.2512 16.3605 19.2515 16.3744 19.2473C18.3492 18.653 20.2093 17.7291 21.8762 16.5147C21.8843 16.509 21.8912 16.5017 21.8962 16.4931C21.9013 16.4846 21.9045 16.4751 21.9055 16.4652C22.3639 11.7957 21.1374 7.73856 18.6523 4.1434C18.6462 4.13171 18.6361 4.1226 18.6239 4.11773ZM7.35162 14.0049C6.26812 14.0049 5.37437 13.025 5.37437 11.8232C5.37437 10.6206 6.2507 9.64156 7.35162 9.64156C8.46079 9.64156 9.34629 10.6288 9.32887 11.8232C9.32887 13.0259 8.45254 14.0049 7.35162 14.0049V14.0049ZM14.662 14.0049C13.5776 14.0049 12.6848 13.025 12.6848 11.8232C12.6848 10.6206 13.5602 9.64156 14.662 9.64156C15.7712 9.64156 16.6567 10.6288 16.6393 11.8232C16.6393 13.0259 15.7721 14.0049 14.662 14.0049V14.0049Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3073_4817">
|
||||
<rect width="22" height="22" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const TelegramIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM14.64 6.8C14.49 8.38 13.84 12.22 13.51 13.99C13.37 14.74 13.09 14.99 12.83 15.02C12.25 15.07 11.81 14.64 11.25 14.27C10.37 13.69 9.87 13.33 9.02 12.77C8.03 12.12 8.67 11.76 9.24 11.18C9.39 11.03 11.95 8.7 12 8.49C12.0069 8.45819 12.006 8.42517 11.9973 8.3938C11.9886 8.36244 11.9724 8.33367 11.95 8.31C11.89 8.26 11.81 8.28 11.74 8.29C11.65 8.31 10.25 9.24 7.52 11.08C7.12 11.35 6.76 11.49 6.44 11.48C6.08 11.47 5.4 11.28 4.89 11.11C4.26 10.91 3.77 10.8 3.81 10.45C3.83 10.27 4.08 10.09 4.55 9.9C7.47 8.63 9.41 7.79 10.38 7.39C13.16 6.23 13.73 6.03 14.11 6.03C14.19 6.03 14.38 6.05 14.5 6.15C14.6 6.23 14.63 6.34 14.64 6.42C14.63 6.48 14.65 6.66 14.64 6.8Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const RedditIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M10.75 13.04C10.75 12.47 10.28 12 9.71 12C9.14 12 8.67 12.47 8.67 13.04C8.67 13.3158 8.77957 13.5804 8.97461 13.7754C9.16965 13.9704 9.43418 14.08 9.71 14.08C9.98583 14.08 10.2504 13.9704 10.4454 13.7754C10.6404 13.5804 10.75 13.3158 10.75 13.04ZM14.09 15.41C13.64 15.86 12.68 16.02 12 16.02C11.32 16.02 10.36 15.86 9.91 15.41C9.88567 15.384 9.85626 15.3632 9.82357 15.349C9.79089 15.3348 9.75563 15.3275 9.72 15.3275C9.68437 15.3275 9.64911 15.3348 9.61643 15.349C9.58374 15.3632 9.55432 15.384 9.53 15.41C9.50396 15.4343 9.4832 15.4637 9.469 15.4964C9.45481 15.5291 9.44748 15.5644 9.44748 15.6C9.44748 15.6356 9.45481 15.6709 9.469 15.7036C9.4832 15.7363 9.50396 15.7657 9.53 15.79C10.24 16.5 11.6 16.56 12 16.56C12.4 16.56 13.76 16.5 14.47 15.79C14.496 15.7657 14.5168 15.7363 14.531 15.7036C14.5452 15.6709 14.5525 15.6356 14.5525 15.6C14.5525 15.5644 14.5452 15.5291 14.531 15.4964C14.5168 15.4637 14.496 15.4343 14.47 15.41C14.37 15.31 14.2 15.31 14.09 15.41ZM14.29 12C13.72 12 13.25 12.47 13.25 13.04C13.25 13.61 13.72 14.08 14.29 14.08C14.86 14.08 15.33 13.61 15.33 13.04C15.33 12.47 14.87 12 14.29 12Z" />
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM17.8 13.33C17.82 13.47 17.83 13.62 17.83 13.77C17.83 16.01 15.22 17.83 12 17.83C8.78 17.83 6.17 16.01 6.17 13.77C6.17 13.62 6.18 13.47 6.2 13.33C5.69 13.1 5.34 12.59 5.34 12C5.33852 11.7132 5.4218 11.4324 5.57939 11.1929C5.73698 10.9533 5.96185 10.7656 6.22576 10.6534C6.48966 10.5413 6.78083 10.5096 7.06269 10.5623C7.34456 10.6151 7.60454 10.75 7.81 10.95C8.82 10.22 10.22 9.76 11.77 9.71L12.51 6.22C12.52 6.15 12.56 6.09 12.62 6.06C12.68 6.02 12.75 6.01 12.82 6.02L15.24 6.54C15.3221 6.37358 15.4472 6.23215 15.6023 6.13038C15.7575 6.02861 15.9371 5.9702 16.1224 5.96122C16.3077 5.95224 16.4921 5.99301 16.6564 6.07931C16.8207 6.1656 16.9589 6.29428 17.0566 6.45199C17.1544 6.60969 17.2082 6.79069 17.2125 6.9762C17.2167 7.16171 17.1712 7.34498 17.0808 7.507C16.9903 7.66901 16.8582 7.80388 16.698 7.8976C16.5379 7.99132 16.3556 8.04049 16.17 8.04C15.61 8.04 15.16 7.6 15.13 7.05L12.96 6.59L12.3 9.71C13.83 9.76 15.2 10.23 16.2 10.95C16.3533 10.8036 16.5367 10.6925 16.7375 10.6244C16.9382 10.5563 17.1514 10.5329 17.3621 10.5558C17.5728 10.5787 17.776 10.6474 17.9574 10.757C18.1388 10.8667 18.2941 11.0146 18.4123 11.1905C18.5306 11.3664 18.609 11.5661 18.642 11.7754C18.6751 11.9848 18.662 12.1989 18.6037 12.4027C18.5454 12.6064 18.4432 12.795 18.3044 12.9552C18.1656 13.1154 17.9934 13.2433 17.8 13.33Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const LinkIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.2917 1.33334C10.2917 0.988166 10.5715 0.708344 10.9167 0.708344H14.6667C15.0118 0.708344 15.2917 0.988166 15.2917 1.33334V5.08334C15.2917 5.42852 15.0118 5.70834 14.6667 5.70834C14.3215 5.70834 14.0417 5.42852 14.0417 5.08334V2.84223L8.44194 8.44195C8.19787 8.68603 7.80214 8.68603 7.55806 8.44195C7.31398 8.19787 7.31398 7.80215 7.55806 7.55807L13.1578 1.95834H10.9167C10.5715 1.95834 10.2917 1.67852 10.2917 1.33334ZM3.97464 1.54168L7.58334 1.54168C7.92851 1.54168 8.20834 1.8215 8.20834 2.16668C8.20834 2.51185 7.92851 2.79168 7.58334 2.79168H4C3.52298 2.79168 3.2028 2.79216 2.95623 2.81231C2.71697 2.83186 2.60256 2.86676 2.5271 2.90521C2.33109 3.00508 2.17174 3.16443 2.07187 3.36044C2.03342 3.4359 1.99852 3.55031 1.97897 3.78957C1.95882 4.03614 1.95834 4.35632 1.95834 4.83334V12C1.95834 12.477 1.95882 12.7972 1.97897 13.0438C1.99852 13.283 2.03342 13.3974 2.07187 13.4729C2.17174 13.6689 2.33109 13.8283 2.5271 13.9281C2.60256 13.9666 2.71697 14.0015 2.95623 14.021C3.2028 14.0412 3.52298 14.0417 4 14.0417H11.1667C11.6437 14.0417 11.9639 14.0412 12.2104 14.021C12.4497 14.0015 12.5641 13.9666 12.6396 13.9281C12.8356 13.8283 12.9949 13.6689 13.0948 13.4729C13.1333 13.3974 13.1682 13.283 13.1877 13.0438C13.2079 12.7972 13.2083 12.477 13.2083 12V8.41668C13.2083 8.0715 13.4882 7.79168 13.8333 7.79168C14.1785 7.79168 14.4583 8.0715 14.4583 8.41668V12.0254C14.4583 12.4705 14.4584 12.842 14.4336 13.1456C14.4077 13.4621 14.3518 13.7594 14.2086 14.0404C13.9888 14.4716 13.6383 14.8222 13.2071 15.0419C12.926 15.1851 12.6288 15.241 12.3122 15.2669C12.0087 15.2917 11.6372 15.2917 11.192 15.2917H3.97463C3.5295 15.2917 3.15797 15.2917 2.85444 15.2669C2.53787 15.241 2.24066 15.1851 1.95961 15.0419C1.5284 14.8222 1.17782 14.4716 0.958113 14.0404C0.81491 13.7594 0.758984 13.4621 0.733119 13.1456C0.70832 12.842 0.708327 12.4705 0.708336 12.0254V4.80798C0.708327 4.36285 0.70832 3.99131 0.733119 3.68779C0.758984 3.37121 0.81491 3.074 0.958113 2.79295C1.17782 2.36174 1.5284 2.01116 1.95961 1.79145C2.24066 1.64825 2.53787 1.59232 2.85444 1.56646C3.15797 1.54166 3.52951 1.54167 3.97464 1.54168Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
FlexWrapper,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalWrapper,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
|
||||
import logo from './affine-text-logo.png';
|
||||
import {
|
||||
DiscordIcon,
|
||||
DocIcon,
|
||||
GithubIcon,
|
||||
LinkIcon,
|
||||
LogoIcon,
|
||||
RedditIcon,
|
||||
TelegramIcon,
|
||||
TwitterIcon,
|
||||
} from './Icons';
|
||||
import {
|
||||
StyledBigLink,
|
||||
StyledLogo,
|
||||
StyledModalFooter,
|
||||
StyledModalHeader,
|
||||
StyledPrivacyContainer,
|
||||
StyledSmallLink,
|
||||
StyledSubTitle,
|
||||
} from './style';
|
||||
const linkList = [
|
||||
{
|
||||
icon: <GithubIcon />,
|
||||
title: 'GitHub',
|
||||
link: 'https://github.com/toeverything/AFFiNE',
|
||||
},
|
||||
{
|
||||
icon: <RedditIcon />,
|
||||
title: 'Reddit',
|
||||
link: 'https://www.reddit.com/r/Affine/',
|
||||
},
|
||||
{
|
||||
icon: <TwitterIcon />,
|
||||
title: 'Twitter',
|
||||
link: 'https://twitter.com/AffineOfficial',
|
||||
},
|
||||
{
|
||||
icon: <TelegramIcon />,
|
||||
title: 'Telegram',
|
||||
link: 'https://t.me/affineworkos',
|
||||
},
|
||||
{
|
||||
icon: <DiscordIcon />,
|
||||
title: 'Discord',
|
||||
link: 'https://discord.gg/Arn7TqJBvG',
|
||||
},
|
||||
];
|
||||
|
||||
type TransitionsModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const ContactModal = ({
|
||||
open,
|
||||
onClose,
|
||||
}: TransitionsModalProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const topLinkList = [
|
||||
{
|
||||
icon: <LogoIcon />,
|
||||
title: t('Official Website'),
|
||||
subTitle: 'AFFiNE.pro',
|
||||
link: 'https://affine.pro',
|
||||
},
|
||||
{
|
||||
icon: <DocIcon />,
|
||||
title: t('Check Our Docs'),
|
||||
subTitle: 'Open Source',
|
||||
link: 'https://github.com/toeverything/AFFiNE',
|
||||
},
|
||||
];
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
|
||||
<ModalWrapper width={720} height={436} style={{ letterSpacing: '1px' }}>
|
||||
<StyledModalHeader>
|
||||
<StyledLogo src={logo.src} alt="" />
|
||||
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</StyledModalHeader>
|
||||
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
{topLinkList.map(({ icon, title, subTitle, link }) => {
|
||||
return (
|
||||
<StyledBigLink key={title} href={link} target="_blank">
|
||||
{icon}
|
||||
<p>{title}</p>
|
||||
<p>
|
||||
{subTitle}
|
||||
<LinkIcon />
|
||||
</p>
|
||||
</StyledBigLink>
|
||||
);
|
||||
})}
|
||||
</FlexWrapper>
|
||||
<StyledSubTitle>
|
||||
{t('Get in touch! Join our communities.')}
|
||||
</StyledSubTitle>
|
||||
<FlexWrapper justifyContent="center">
|
||||
{linkList.map(({ icon, title, link }) => {
|
||||
return (
|
||||
<StyledSmallLink key={title} href={link} target="_blank">
|
||||
{icon}
|
||||
<p>{title}</p>
|
||||
</StyledSmallLink>
|
||||
);
|
||||
})}
|
||||
</FlexWrapper>
|
||||
|
||||
<StyledModalFooter>
|
||||
<p>Copyright © {year} Toeverything</p>
|
||||
<StyledPrivacyContainer>
|
||||
<a href="https://affine.pro/terms">Terms</a>
|
||||
<a href="https://affine.pro/privacy">Privacy</a>
|
||||
</StyledPrivacyContainer>
|
||||
</StyledModalFooter>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactModal;
|
||||
@@ -0,0 +1,144 @@
|
||||
import { absoluteCenter, displayFlex, styled } from '@affine/component';
|
||||
|
||||
export const StyledBigLink = styled('a')(({ theme }) => {
|
||||
return {
|
||||
width: '268px',
|
||||
height: '76px',
|
||||
paddingLeft: '96px',
|
||||
fontSize: '24px',
|
||||
lineHeight: '36px',
|
||||
color: theme.colors.textColor,
|
||||
borderRadius: '10px',
|
||||
flexDirection: 'column',
|
||||
...displayFlex('center'),
|
||||
position: 'relative',
|
||||
transition: 'background .15s',
|
||||
letterSpacing: '1px',
|
||||
|
||||
':visited': {
|
||||
color: theme.colors.textColor,
|
||||
},
|
||||
':hover': {
|
||||
background: 'rgba(68, 97, 242, 0.1)',
|
||||
},
|
||||
':not(:last-of-type)': {
|
||||
marginRight: '48px',
|
||||
},
|
||||
svg: {
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
marginRight: '24px',
|
||||
color: theme.colors.primaryColor,
|
||||
...absoluteCenter({ vertical: true, position: { left: '26px' } }),
|
||||
},
|
||||
p: {
|
||||
width: '100%',
|
||||
height: '24px',
|
||||
lineHeight: '24px',
|
||||
|
||||
...displayFlex('flex-start', 'center'),
|
||||
':first-of-type': {
|
||||
marginBottom: '4px',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
},
|
||||
':last-of-type': {
|
||||
fontSize: '16px',
|
||||
color: theme.colors.primaryColor,
|
||||
fontWeight: '500',
|
||||
},
|
||||
svg: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
position: 'static',
|
||||
transform: 'translate(0,0)',
|
||||
marginLeft: '4px',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledSmallLink = styled('a')(({ theme }) => {
|
||||
return {
|
||||
width: '124px',
|
||||
height: '76px',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.textColor,
|
||||
transition: 'background .15s, color .15s',
|
||||
|
||||
...displayFlex('center', 'center'),
|
||||
flexWrap: 'wrap',
|
||||
':visited': {
|
||||
color: theme.colors.textColor,
|
||||
},
|
||||
':hover': {
|
||||
color: theme.colors.primaryColor,
|
||||
background: theme.colors.hoverBackground,
|
||||
},
|
||||
svg: {
|
||||
width: '22px',
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
p: {
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledSubTitle = styled('div')(({ theme }) => {
|
||||
return {
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: theme.colors.textColor,
|
||||
marginTop: '48px',
|
||||
marginBottom: '8px',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLogo = styled('img')({
|
||||
height: '18px',
|
||||
width: 'auto',
|
||||
marginTop: '24px',
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
height: '72px',
|
||||
padding: '0 40px',
|
||||
marginBottom: '24px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(({ theme }) => {
|
||||
return {
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
textAlign: 'center',
|
||||
color: theme.colors.textColor,
|
||||
marginTop: '40px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPrivacyContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
marginTop: '4px',
|
||||
position: 'relative',
|
||||
a: {
|
||||
height: '16px',
|
||||
lineHeight: '16px',
|
||||
color: theme.colors.iconColor,
|
||||
padding: '0 8px',
|
||||
':visited': {
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
':first-of-type': {
|
||||
borderRight: `1px solid ${theme.colors.borderColor}`,
|
||||
},
|
||||
':hover': {
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { styled } from '@affine/component';
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
import { Input } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { KeyboardEvent } from 'react';
|
||||
|
||||
interface ModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onCreate: (name: string) => void;
|
||||
}
|
||||
|
||||
export const CreateWorkspaceModal = ({
|
||||
open,
|
||||
onClose,
|
||||
onCreate,
|
||||
}: ModalProps) => {
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const isComposition = useRef(false);
|
||||
const handleCreateWorkspace = useCallback(() => {
|
||||
onCreate(workspaceName);
|
||||
}, [onCreate, workspaceName]);
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter' && workspaceName && !isComposition.current) {
|
||||
handleCreateWorkspace();
|
||||
}
|
||||
},
|
||||
[handleCreateWorkspace, workspaceName]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper width={560} height={342} style={{ padding: '10px' }}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<Input
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={t('Set a Workspace name')}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
onChange={value => {
|
||||
setWorkspaceName(value);
|
||||
}}
|
||||
onCompositionStart={() => {
|
||||
isComposition.current = true;
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
isComposition.current = false;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={!workspaceName}
|
||||
style={{
|
||||
width: '260px',
|
||||
textAlign: 'center',
|
||||
marginTop: '16px',
|
||||
opacity: !workspaceName ? 0.5 : 1,
|
||||
}}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
handleCreateWorkspace();
|
||||
}}
|
||||
>
|
||||
{t('Create')}
|
||||
</Button>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
});
|
||||
|
||||
const Content = styled('div')(({ theme }) => {
|
||||
return {
|
||||
padding: '0 84px',
|
||||
textAlign: 'center',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
color: theme.colors.inputColor,
|
||||
p: {
|
||||
marginTop: '12px',
|
||||
marginBottom: '16px',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ContentTitle = styled('div')(() => {
|
||||
return {
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'center',
|
||||
paddingBottom: '16px',
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Button } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import React, { ChangeEvent, useRef } from 'react';
|
||||
|
||||
export type UploadProps = React.PropsWithChildren<{
|
||||
uploadType?: string;
|
||||
accept?: string;
|
||||
fileChange: (file: File) => void;
|
||||
}>;
|
||||
|
||||
export const Upload: React.FC<UploadProps> = ({
|
||||
fileChange,
|
||||
accept,
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const input_ref = useRef<HTMLInputElement>(null);
|
||||
const _chooseFile = () => {
|
||||
if (input_ref.current) {
|
||||
input_ref.current.click();
|
||||
}
|
||||
};
|
||||
const _handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
fileChange(file);
|
||||
if (input_ref.current) {
|
||||
input_ref.current.value = '';
|
||||
}
|
||||
};
|
||||
return (
|
||||
<UploadStyle onClick={_chooseFile}>
|
||||
{children ?? <Button>{t('Upload')}</Button>}
|
||||
<input
|
||||
ref={input_ref}
|
||||
type="file"
|
||||
style={{ display: 'none' }}
|
||||
onChange={_handleInputChange}
|
||||
accept={accept}
|
||||
/>
|
||||
</UploadStyle>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadStyle = styled('div')(() => {
|
||||
return {
|
||||
display: 'inline-block',
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import { FlexWrapper } from '@affine/component';
|
||||
import { IconButton } from '@affine/component';
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { AccessTokenMessage } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { CloudWorkspaceIcon, SignOutIcon } from '@blocksuite/icons';
|
||||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { stringToColour } from '../../../utils';
|
||||
import { StyledFooter, StyledSignInButton, StyleUserInfo } from './styles';
|
||||
|
||||
export type FooterProps = {
|
||||
user: AccessTokenMessage | null;
|
||||
onLogin: () => void;
|
||||
onLogout: () => void;
|
||||
};
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledFooter data-testid="workspace-list-modal-footer">
|
||||
{user && (
|
||||
<>
|
||||
<FlexWrapper>
|
||||
<WorkspaceAvatar
|
||||
size={40}
|
||||
name={user.name}
|
||||
avatar={user.avatar_url}
|
||||
></WorkspaceAvatar>
|
||||
<StyleUserInfo>
|
||||
<p>{user.name}</p>
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
</FlexWrapper>
|
||||
<Tooltip content={t('Sign out')} disablePortal={true}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onLogout();
|
||||
}}
|
||||
>
|
||||
<SignOutIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!user && (
|
||||
<StyledSignInButton
|
||||
noBorder
|
||||
bold
|
||||
icon={
|
||||
<div className="circle">
|
||||
<CloudWorkspaceIcon fontSize={16} />
|
||||
</div>
|
||||
}
|
||||
onClick={async () => {
|
||||
onLogin();
|
||||
}}
|
||||
>
|
||||
{t('Sign in')}
|
||||
</StyledSignInButton>
|
||||
)}
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
|
||||
interface WorkspaceAvatarProps {
|
||||
size: number;
|
||||
name: string;
|
||||
avatar: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const WorkspaceAvatar: React.FC<WorkspaceAvatarProps> = props => {
|
||||
const size = props.size || 20;
|
||||
const sizeStr = size + 'px';
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.avatar ? (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
color: '#fff',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
>
|
||||
<picture>
|
||||
<img
|
||||
style={{ width: sizeStr, height: sizeStr }}
|
||||
src={props.avatar}
|
||||
alt=""
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
border: '1px solid #fff',
|
||||
color: '#fff',
|
||||
fontSize: Math.ceil(0.5 * size) + 'px',
|
||||
background: stringToColour(props.name || 'AFFiNE'),
|
||||
borderRadius: '50%',
|
||||
textAlign: 'center',
|
||||
lineHeight: size + 'px',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
>
|
||||
{(props.name || 'AFFiNE').substring(0, 1)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
displayFlex,
|
||||
displayInlineFlex,
|
||||
styled,
|
||||
textEllipsis,
|
||||
} from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
|
||||
export const StyledSplitLine = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
background: theme.colors.borderColor,
|
||||
marginRight: '24px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
width: '202px',
|
||||
p: {
|
||||
color: theme.colors.popoverColor,
|
||||
height: '20px',
|
||||
fontSize: theme.font.sm,
|
||||
...displayFlex('flex-start', 'center'),
|
||||
},
|
||||
svg: {
|
||||
marginRight: '10px',
|
||||
fontSize: '16px',
|
||||
flexShrink: 0,
|
||||
},
|
||||
span: {
|
||||
flexGrow: 1,
|
||||
...textEllipsis(1),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '10px',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCard = styled.div<{
|
||||
active?: boolean;
|
||||
}>(({ theme, active }) => {
|
||||
const borderColor = active ? theme.colors.primaryColor : 'transparent';
|
||||
return {
|
||||
width: '310px',
|
||||
height: '124px',
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${borderColor}`,
|
||||
...displayFlex('flex-start', 'flex-start'),
|
||||
marginBottom: '24px',
|
||||
transition: 'background .2s',
|
||||
background: theme.mode === 'light' ? '#FFF' : '#2C2C2C',
|
||||
':hover': {
|
||||
background: theme.colors.cardHoverBackground,
|
||||
'.add-icon': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledFooter = styled('div')({
|
||||
height: '84px',
|
||||
padding: '0 40px',
|
||||
flexShrink: 0,
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
|
||||
export const StyleUserInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
flex: 1,
|
||||
p: {
|
||||
lineHeight: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'p:first-of-type': {
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeaderLeft = styled.div(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
marginLeft: '15px',
|
||||
fontWeight: 400,
|
||||
fontSize: theme.font.h6,
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalContent = styled('div')({
|
||||
height: '534px',
|
||||
padding: '8px 40px',
|
||||
marginTop: '72px',
|
||||
overflow: 'auto',
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
});
|
||||
export const StyledOperationWrapper = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('flex-end', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceAdd = styled.div(() => {
|
||||
return {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
background: '#f4f5fa',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
transition: 'background .2s',
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '72px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
padding: '0 40px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 700,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
export const HelpIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.0041 3.8002C7.47536 3.8002 3.8041 7.47146 3.8041 12.0002C3.8041 13.311 4.1111 14.5479 4.65639 15.6449C4.92133 16.1779 4.91348 16.8471 4.85228 17.3998C4.7869 17.9901 4.63749 18.6238 4.47572 19.1908C4.3722 19.5537 4.26086 19.8987 4.1573 20.2002H12.0041C16.5328 20.2002 20.2041 16.5289 20.2041 12.0002C20.2041 7.47146 16.5328 3.8002 12.0041 3.8002ZM2.26631 20.6926L2.2668 20.6914L2.26948 20.685L2.28104 20.6567C2.29139 20.6312 2.30687 20.5928 2.32657 20.5429C2.36599 20.4431 2.42218 20.2979 2.48795 20.1192C2.61992 19.7607 2.78833 19.2734 2.93712 18.7519C3.08715 18.226 3.21054 17.6884 3.262 17.2236C3.31765 16.7212 3.2707 16.4518 3.22364 16.3571C2.57081 15.0438 2.2041 13.5637 2.2041 12.0002C2.2041 6.58781 6.59171 2.2002 12.0041 2.2002C17.4165 2.2002 21.8041 6.5878 21.8041 12.0002C21.8041 17.4126 17.4165 21.8002 12.0041 21.8002H3.0049C2.73745 21.8002 2.4876 21.6665 2.33922 21.444C2.19087 21.2215 2.16356 20.9395 2.26631 20.6926ZM11.9672 9.0502C11.4091 9.0502 10.9382 9.43186 10.8049 9.9496C10.6948 10.3775 10.2587 10.6351 9.83079 10.5249C9.40291 10.4148 9.14532 9.97867 9.25545 9.55079C9.56623 8.3433 10.6614 7.4502 11.9672 7.4502C13.5136 7.4502 14.7672 8.7038 14.7672 10.2502C14.7672 11.1058 14.3536 11.6751 13.8978 12.115C13.7108 12.2955 13.4978 12.4721 13.2997 12.6362C13.2705 12.6604 13.2416 12.6844 13.2132 12.7081C12.982 12.9004 12.7556 13.0932 12.5329 13.3159C12.2205 13.6283 11.7139 13.6283 11.4015 13.3159C11.0891 13.0035 11.0891 12.4969 11.4015 12.1845C11.6788 11.9072 11.9525 11.6756 12.19 11.478C12.2213 11.4519 12.2517 11.4267 12.2812 11.4022C12.4849 11.2332 12.6465 11.0991 12.7866 10.9638C13.0808 10.6799 13.1672 10.4992 13.1672 10.2502C13.1672 9.58745 12.6299 9.0502 11.9672 9.0502ZM11.9772 16.5502H11.9672C11.5254 16.5502 11.1672 16.192 11.1672 15.7502C11.1672 15.3084 11.5254 14.9502 11.9672 14.9502H11.9772C12.419 14.9502 12.7772 15.3084 12.7772 15.7502C12.7772 16.192 12.419 16.5502 11.9772 16.5502Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContactIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.16957 3.25H14.8304C15.3646 3.24999 15.8104 3.24998 16.1747 3.27974C16.5546 3.31078 16.9112 3.37789 17.2485 3.54973C17.7659 3.81338 18.1866 4.23408 18.4503 4.75153C18.6221 5.08879 18.6892 5.44545 18.7203 5.82533C18.75 6.18956 18.75 6.6354 18.75 7.16955V8.36732C18.9209 8.40912 19.0869 8.46741 19.2485 8.54973C19.7659 8.81339 20.1866 9.23408 20.4503 9.75153C20.6221 10.0888 20.6892 10.4454 20.7203 10.8253C20.75 11.1896 20.75 11.6354 20.75 12.1695V20C20.75 20.2766 20.5978 20.5307 20.3539 20.6613C20.11 20.7918 19.8141 20.7775 19.584 20.624L17.3901 19.1615C17.069 18.9474 16.9819 18.8925 16.8945 18.8544C16.8046 18.8151 16.7104 18.7866 16.6138 18.7694C16.52 18.7527 16.4171 18.75 16.0311 18.75H11.1695C10.6354 18.75 10.1896 18.75 9.82533 18.7203C9.44545 18.6892 9.08879 18.6221 8.75153 18.4503C8.23408 18.1866 7.81339 17.7659 7.54973 17.2485C7.52446 17.1989 7.50144 17.1488 7.48046 17.0983L4.33541 18.6708C4.10292 18.7871 3.82681 18.7746 3.6057 18.638C3.38459 18.5013 3.25 18.2599 3.25 18L3.25 7.16957C3.24999 6.63541 3.24998 6.18956 3.27974 5.82533C3.31078 5.44545 3.37789 5.08879 3.54973 4.75153C3.81338 4.23408 4.23408 3.81338 4.75153 3.54973C5.08879 3.37789 5.44545 3.31078 5.82533 3.27974C6.18956 3.24998 6.63541 3.24999 7.16957 3.25ZM7.25322 15.5349C7.25 15.318 7.25 15.0803 7.25 14.8211L7.25 12.1696C7.24999 11.6354 7.24998 11.1896 7.27974 10.8253C7.31078 10.4454 7.37789 10.0888 7.54973 9.75153C7.81339 9.23408 8.23408 8.81339 8.75153 8.54973C9.08879 8.37789 9.44545 8.31078 9.82533 8.27974C10.1896 8.24998 10.6354 8.24999 11.1696 8.25H16.8304C16.9769 8.25 17.1167 8.24999 17.25 8.25061V7.2C17.25 6.62757 17.2494 6.24336 17.2252 5.94748C17.2018 5.66036 17.1599 5.52307 17.1138 5.43251C16.9939 5.19731 16.8027 5.00608 16.5675 4.88624C16.4769 4.8401 16.3396 4.79822 16.0525 4.77476C15.7566 4.75058 15.3724 4.75 14.8 4.75H7.2C6.62757 4.75 6.24336 4.75058 5.94748 4.77476C5.66036 4.79822 5.52307 4.8401 5.43251 4.88624C5.19731 5.00608 5.00608 5.19731 4.88624 5.43251C4.8401 5.52307 4.79822 5.66036 4.77476 5.94748C4.75058 6.24336 4.75 6.62757 4.75 7.2V16.7865L7.25322 15.5349ZM9.94748 9.77476C9.66036 9.79822 9.52307 9.8401 9.43251 9.88624C9.19731 10.0061 9.00608 10.1973 8.88624 10.4325C8.8401 10.5231 8.79822 10.6604 8.77476 10.9475C8.75058 11.2434 8.75 11.6276 8.75 12.2V14.8C8.75 15.3018 8.7503 15.6608 8.7673 15.9461C8.78877 16.3064 8.83405 16.465 8.88624 16.5675C9.00608 16.8027 9.19731 16.9939 9.43251 17.1138C9.52307 17.1599 9.66036 17.2018 9.94748 17.2252C10.2434 17.2494 10.6276 17.25 11.2 17.25H16.0311C16.049 17.25 16.0666 17.25 16.084 17.25C16.3929 17.2499 16.6362 17.2497 16.877 17.2927C17.0895 17.3306 17.2968 17.3933 17.4947 17.4797C17.7189 17.5775 17.9213 17.7126 18.1782 17.884C18.1927 17.8937 18.2073 17.9035 18.2222 17.9134L19.25 18.5986V12.2C19.25 11.6276 19.2494 11.2434 19.2252 10.9475C19.2018 10.6604 19.1599 10.5231 19.1138 10.4325C18.9939 10.1973 18.8027 10.0061 18.5675 9.88624C18.4769 9.8401 18.3396 9.79822 18.0525 9.77476C17.7566 9.75058 17.3724 9.75 16.8 9.75H11.2C10.6276 9.75 10.2434 9.75058 9.94748 9.77476Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const KeyboardIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M19.745 5C20.3417 5 20.914 5.23705 21.336 5.65901C21.7579 6.08097 21.995 6.65326 21.995 7.25V16.755C21.995 17.3517 21.7579 17.924 21.336 18.346C20.914 18.7679 20.3417 19.005 19.745 19.005H4.25C3.95453 19.005 3.66194 18.9468 3.38896 18.8337C3.11598 18.7207 2.86794 18.5549 2.65901 18.346C2.45008 18.1371 2.28434 17.889 2.17127 17.616C2.0582 17.3431 2 17.0505 2 16.755V7.25C2 6.65326 2.23705 6.08097 2.65901 5.65901C3.08097 5.23705 3.65326 5 4.25 5H19.745ZM19.745 6.5H4.25C4.05109 6.5 3.86032 6.57902 3.71967 6.71967C3.57902 6.86032 3.5 7.05109 3.5 7.25V16.755C3.5 17.169 3.836 17.505 4.25 17.505H19.745C19.9439 17.505 20.1347 17.426 20.2753 17.2853C20.416 17.1447 20.495 16.9539 20.495 16.755V7.25C20.495 7.05109 20.416 6.86032 20.2753 6.71967C20.1347 6.57902 19.9439 6.5 19.745 6.5ZM6.75 14.5H17.25C17.44 14.5001 17.6229 14.5722 17.7618 14.702C17.9006 14.8317 17.9851 15.0093 17.998 15.1989C18.011 15.3885 17.9515 15.5759 17.8316 15.7233C17.7117 15.8707 17.5402 15.9671 17.352 15.993L17.25 16H6.75C6.55998 15.9999 6.37706 15.9278 6.23821 15.798C6.09936 15.6683 6.01493 15.4907 6.00197 15.3011C5.98902 15.1115 6.04852 14.9241 6.16843 14.7767C6.28835 14.6293 6.45975 14.5329 6.648 14.507L6.75 14.5H17.25H6.75ZM16.5 11C16.7652 11 17.0196 11.1054 17.2071 11.2929C17.3946 11.4804 17.5 11.7348 17.5 12C17.5 12.2652 17.3946 12.5196 17.2071 12.7071C17.0196 12.8946 16.7652 13 16.5 13C16.2348 13 15.9804 12.8946 15.7929 12.7071C15.6054 12.5196 15.5 12.2652 15.5 12C15.5 11.7348 15.6054 11.4804 15.7929 11.2929C15.9804 11.1054 16.2348 11 16.5 11ZM10.505 11C10.7702 11 11.0246 11.1054 11.2121 11.2929C11.3996 11.4804 11.505 11.7348 11.505 12C11.505 12.2652 11.3996 12.5196 11.2121 12.7071C11.0246 12.8946 10.7702 13 10.505 13C10.2398 13 9.98543 12.8946 9.79789 12.7071C9.61036 12.5196 9.505 12.2652 9.505 12C9.505 11.7348 9.61036 11.4804 9.79789 11.2929C9.98543 11.1054 10.2398 11 10.505 11ZM7.505 11C7.77022 11 8.02457 11.1054 8.21211 11.2929C8.39964 11.4804 8.505 11.7348 8.505 12C8.505 12.2652 8.39964 12.5196 8.21211 12.7071C8.02457 12.8946 7.77022 13 7.505 13C7.23978 13 6.98543 12.8946 6.79789 12.7071C6.61036 12.5196 6.505 12.2652 6.505 12C6.505 11.7348 6.61036 11.4804 6.79789 11.2929C6.98543 11.1054 7.23978 11 7.505 11ZM13.505 11C13.7702 11 14.0246 11.1054 14.2121 11.2929C14.3996 11.4804 14.505 11.7348 14.505 12C14.505 12.2652 14.3996 12.5196 14.2121 12.7071C14.0246 12.8946 13.7702 13 13.505 13C13.2398 13 12.9854 12.8946 12.7979 12.7071C12.6104 12.5196 12.505 12.2652 12.505 12C12.505 11.7348 12.6104 11.4804 12.7979 11.2929C12.9854 11.1054 13.2398 11 13.505 11ZM6 8C6.26522 8 6.51957 8.10536 6.70711 8.29289C6.89464 8.48043 7 8.73478 7 9C7 9.26522 6.89464 9.51957 6.70711 9.70711C6.51957 9.89464 6.26522 10 6 10C5.73478 10 5.48043 9.89464 5.29289 9.70711C5.10536 9.51957 5 9.26522 5 9C5 8.73478 5.10536 8.48043 5.29289 8.29289C5.48043 8.10536 5.73478 8 6 8ZM8.995 8C9.26022 8 9.51457 8.10536 9.70211 8.29289C9.88964 8.48043 9.995 8.73478 9.995 9C9.995 9.26522 9.88964 9.51957 9.70211 9.70711C9.51457 9.89464 9.26022 10 8.995 10C8.72978 10 8.47543 9.89464 8.28789 9.70711C8.10036 9.51957 7.995 9.26522 7.995 9C7.995 8.73478 8.10036 8.48043 8.28789 8.29289C8.47543 8.10536 8.72978 8 8.995 8ZM11.995 8C12.2602 8 12.5146 8.10536 12.7021 8.29289C12.8896 8.48043 12.995 8.73478 12.995 9C12.995 9.26522 12.8896 9.51957 12.7021 9.70711C12.5146 9.89464 12.2602 10 11.995 10C11.7298 10 11.4754 9.89464 11.2879 9.70711C11.1004 9.51957 10.995 9.26522 10.995 9C10.995 8.73478 11.1004 8.48043 11.2879 8.29289C11.4754 8.10536 11.7298 8 11.995 8ZM14.995 8C15.2602 8 15.5146 8.10536 15.7021 8.29289C15.8896 8.48043 15.995 8.73478 15.995 9C15.995 9.26522 15.8896 9.51957 15.7021 9.70711C15.5146 9.89464 15.2602 10 14.995 10C14.7298 10 14.4754 9.89464 14.2879 9.70711C14.1004 9.51957 13.995 9.26522 13.995 9C13.995 8.73478 14.1004 8.48043 14.2879 8.29289C14.4754 8.10536 14.7298 8 14.995 8ZM17.995 8C18.2602 8 18.5146 8.10536 18.7021 8.29289C18.8896 8.48043 18.995 8.73478 18.995 9C18.995 9.26522 18.8896 9.51957 18.7021 9.70711C18.5146 9.89464 18.2602 10 17.995 10C17.7298 10 17.4754 9.89464 17.2879 9.70711C17.1004 9.51957 16.995 9.26522 16.995 9C16.995 8.73478 17.1004 8.48043 17.2879 8.29289C17.4754 8.10536 17.7298 8 17.995 8Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { MuiFade } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import { useState } from 'react';
|
||||
|
||||
import ContactModal from '../contact-modal';
|
||||
import { ShortcutsModal } from '../shortcuts-modal';
|
||||
import { ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import {
|
||||
StyledAnimateWrapper,
|
||||
StyledIconWrapper,
|
||||
StyledIsland,
|
||||
StyledTriggerWrapper,
|
||||
} from './style';
|
||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||
export const HelpIsland = ({
|
||||
showList = ['contact', 'shortcuts'],
|
||||
}: {
|
||||
showList?: IslandItemNames[];
|
||||
}) => {
|
||||
const [spread, setShowSpread] = useState(false);
|
||||
// const { triggerShortcutsModal, triggerContactModal } = useModal();
|
||||
// const blockHub = useGlobalState(store => store.blockHub);
|
||||
const { t } = useTranslation();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// blockHub?.blockHubStatusUpdated.on(status => {
|
||||
// if (status) {
|
||||
// setShowSpread(false);
|
||||
// }
|
||||
// });
|
||||
// return () => {
|
||||
// blockHub?.blockHubStatusUpdated.dispose();
|
||||
// };
|
||||
// }, [blockHub]);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// spread && blockHub?.toggleMenu(false);
|
||||
// }, [blockHub, spread]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openShortCut, setOpenShortCut] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<StyledIsland
|
||||
spread={spread}
|
||||
data-testid="help-island"
|
||||
onClick={() => {
|
||||
setShowSpread(!spread);
|
||||
}}
|
||||
>
|
||||
<StyledAnimateWrapper
|
||||
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
|
||||
>
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip content={t('Contact Us')} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-contact-us-icon"
|
||||
onClick={() => {
|
||||
setShowSpread(false);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<ContactIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('shortcuts') && (
|
||||
<Tooltip content={t('Keyboard Shortcuts')} placement="left-end">
|
||||
<StyledIconWrapper
|
||||
data-testid="shortcuts-icon"
|
||||
onClick={() => {
|
||||
setShowSpread(false);
|
||||
setOpenShortCut(true);
|
||||
}}
|
||||
>
|
||||
<KeyboardIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
</StyledAnimateWrapper>
|
||||
|
||||
<Tooltip content={t('Help and Feedback')} placement="left-end">
|
||||
<MuiFade in={!spread} data-testid="faq-icon">
|
||||
<StyledTriggerWrapper>
|
||||
<HelpIcon />
|
||||
</StyledTriggerWrapper>
|
||||
</MuiFade>
|
||||
</Tooltip>
|
||||
<MuiFade in={spread}>
|
||||
<StyledTriggerWrapper>
|
||||
<CloseIcon />
|
||||
</StyledTriggerWrapper>
|
||||
</MuiFade>
|
||||
</StyledIsland>
|
||||
<ContactModal open={open} onClose={() => setOpen(false)} />
|
||||
<ShortcutsModal
|
||||
open={openShortCut}
|
||||
onClose={() => setOpenShortCut(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { displayFlex, positionAbsolute, styled } from '@affine/component';
|
||||
|
||||
export const StyledIsland = styled('div')<{
|
||||
spread: boolean;
|
||||
}>(({ theme, spread }) => {
|
||||
return {
|
||||
transition: 'box-shadow 0.2s',
|
||||
width: '44px',
|
||||
position: 'relative',
|
||||
boxShadow: spread
|
||||
? '4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06)'
|
||||
: 'unset',
|
||||
padding: '0 4px 44px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
':hover': {
|
||||
boxShadow:
|
||||
'4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06)',
|
||||
},
|
||||
'::after': {
|
||||
content: '""',
|
||||
width: '36px',
|
||||
height: '1px',
|
||||
background: spread ? theme.colors.borderColor : 'transparent',
|
||||
...positionAbsolute({
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: '44px',
|
||||
}),
|
||||
margin: 'auto',
|
||||
transition: 'background 0.15s',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledIconWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
...displayFlex('center', 'center'),
|
||||
cursor: 'pointer',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
borderRadius: '5px',
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
margin: '4px auto 4px',
|
||||
transition: 'background-color 0.2s',
|
||||
position: 'relative',
|
||||
':hover': {
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledAnimateWrapper = styled('div')(() => ({
|
||||
transition: 'height 0.2s cubic-bezier(0, 0, 0.55, 1.6)',
|
||||
overflow: 'hidden',
|
||||
}));
|
||||
|
||||
export const StyledTriggerWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
color: theme.colors.iconColor,
|
||||
borderRadius: '5px',
|
||||
fontSize: '24px',
|
||||
...displayFlex('center', 'center'),
|
||||
...positionAbsolute({ left: '4px', bottom: '4px' }),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
CloudWorkspaceIcon as DefaultCloudWorkspaceIcon,
|
||||
JoinedWorkspaceIcon as DefaultJoinedWorkspaceIcon,
|
||||
LocalDataIcon as DefaultLocalDataIcon,
|
||||
LocalWorkspaceIcon as DefaultLocalWorkspaceIcon,
|
||||
PublishIcon as DefaultPublishIcon,
|
||||
} from '@blocksuite/icons';
|
||||
|
||||
// Here are some icons with special color or size
|
||||
|
||||
export const JoinedWorkspaceIcon = () => {
|
||||
return <DefaultJoinedWorkspaceIcon style={{ color: '#FF646B' }} />;
|
||||
};
|
||||
export const LocalWorkspaceIcon = () => {
|
||||
return <DefaultLocalWorkspaceIcon style={{ color: '#FDBD32' }} />;
|
||||
};
|
||||
|
||||
export const CloudWorkspaceIcon = () => {
|
||||
return <DefaultCloudWorkspaceIcon style={{ color: '#60A5FA' }} />;
|
||||
};
|
||||
|
||||
export const LocalDataIcon = () => {
|
||||
return <DefaultLocalDataIcon style={{ color: '#62CD80' }} />;
|
||||
};
|
||||
|
||||
export const PublishIcon = () => {
|
||||
return <DefaultPublishIcon style={{ color: '#8699FF' }} />;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
StyledLoading,
|
||||
StyledLoadingItem,
|
||||
StyledLoadingWrapper,
|
||||
} from './styled';
|
||||
|
||||
export const Loading = ({ size = 40 }: { size?: number }) => {
|
||||
return (
|
||||
<StyledLoadingWrapper size={size}>
|
||||
<StyledLoading>
|
||||
<StyledLoadingItem size={size} />
|
||||
<StyledLoadingItem size={size} />
|
||||
<StyledLoadingItem size={size} />
|
||||
<StyledLoadingItem size={size} />
|
||||
</StyledLoading>
|
||||
</StyledLoadingWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { styled } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
|
||||
import Loading from './Loading';
|
||||
|
||||
// Used for the full page loading
|
||||
const StyledLoadingContainer = styled('div')(() => {
|
||||
return {
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: '#6880FF',
|
||||
h1: {
|
||||
fontSize: '2em',
|
||||
marginTop: '15px',
|
||||
fontWeight: '600',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const PageLoading = ({ text }: { text?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledLoadingContainer>
|
||||
<div className="wrapper">
|
||||
<Loading />
|
||||
<h1>{text ? text : t('Loading')}</h1>
|
||||
</div>
|
||||
</StyledLoadingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageLoading;
|
||||
@@ -0,0 +1,3 @@
|
||||
import Loading from './Loading';
|
||||
export * from './PageLoading';
|
||||
export default Loading;
|
||||
@@ -0,0 +1,94 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
// Inspired by https://codepen.io/graphilla/pen/rNvBMYY
|
||||
export const StyledLoadingWrapper = styled('div', {
|
||||
shouldForwardProp: prop => {
|
||||
return !['size'].includes(prop);
|
||||
},
|
||||
})<{ size?: number }>(({ size = 40 }) => {
|
||||
return {
|
||||
width: size * 4,
|
||||
height: size * 4,
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
export const StyledLoading = styled.div`
|
||||
position: absolute;
|
||||
left: 25%;
|
||||
top: 50%;
|
||||
transform: rotateX(55deg) rotateZ(-45deg);
|
||||
@keyframes slide {
|
||||
0% {
|
||||
transform: translate(var(--sx), var(--sy));
|
||||
}
|
||||
65% {
|
||||
transform: translate(var(--ex), var(--sy));
|
||||
}
|
||||
95%,
|
||||
100% {
|
||||
transform: translate(var(--ex), var(--ey));
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledLoadingItem = styled.div<{ size: number }>(
|
||||
({ size = 40 }) => {
|
||||
return `
|
||||
position: absolute;
|
||||
width: ${size}px;
|
||||
height: ${size}px;
|
||||
background: #9dacf9;
|
||||
animation: slide 0.9s cubic-bezier(0.65, 0.53, 0.59, 0.93) infinite;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: #5260b9;
|
||||
transform: skew(0deg, -45deg);
|
||||
right: 100%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #6880ff;
|
||||
transform: skew(-45deg, 0deg);
|
||||
top: 100%;
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
&:nth-of-type(1) {
|
||||
--sx: 50%;
|
||||
--sy: -50%;
|
||||
--ex: 150%;
|
||||
--ey: 50%;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
--sx: -50%;
|
||||
--sy: -50%;
|
||||
--ex: 50%;
|
||||
--ey: -50%;
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
--sx: 150%;
|
||||
--sy: 50%;
|
||||
--ex: 50%;
|
||||
--ey: 50%;
|
||||
}
|
||||
|
||||
&:nth-of-type(4) {
|
||||
--sx: 50%;
|
||||
--sy: 50%;
|
||||
--ex: -50%;
|
||||
--ey: -50%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import { assertEquals, uuidv4 } from '@blocksuite/store';
|
||||
import { Command } from 'cmdk';
|
||||
import { NextRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import { useBlockSuiteWorkspaceHelper } from '../../../hooks/use-blocksuite-workspace-helper';
|
||||
import { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { StyledModalFooterContent } from './style';
|
||||
|
||||
export type FooterProps = {
|
||||
query: string;
|
||||
onClose: () => void;
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({
|
||||
query,
|
||||
onClose,
|
||||
blockSuiteWorkspace,
|
||||
router,
|
||||
}) => {
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Command.Item
|
||||
data-testid="quick-search-add-new-page"
|
||||
onSelect={async () => {
|
||||
onClose();
|
||||
const id = uuidv4();
|
||||
const pageId = await createPage(id, query);
|
||||
assertEquals(pageId, id);
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: blockSuiteWorkspace.room,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<StyledModalFooterContent>
|
||||
<PlusIcon />
|
||||
{query ? (
|
||||
<span>{t('New Keyword Page', { query: query })}</span>
|
||||
) : (
|
||||
<span>{t('New Page')}</span>
|
||||
)}
|
||||
</StyledModalFooterContent>
|
||||
</Command.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { Command } from 'cmdk';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { StyledInputContent, StyledLabel } from './style';
|
||||
export const Input = (props: {
|
||||
open: boolean;
|
||||
query: string;
|
||||
setQuery: (query: string) => void;
|
||||
isPublic: boolean;
|
||||
publishWorkspaceName: string | undefined;
|
||||
}) => {
|
||||
const { open, query, setQuery, isPublic, publishWorkspaceName } = props;
|
||||
const [isComposition, setIsComposition] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const inputElement = inputRef.current;
|
||||
return inputElement?.focus();
|
||||
}
|
||||
}, [open]);
|
||||
useEffect(() => {
|
||||
const inputElement = inputRef.current;
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
const handleFocus = () => {
|
||||
inputElement?.focus();
|
||||
};
|
||||
inputElement?.addEventListener('blur', handleFocus, true);
|
||||
return () => inputElement?.removeEventListener('blur', handleFocus, true);
|
||||
}, [inputRef, open]);
|
||||
useEffect(() => {
|
||||
setInputValue(query);
|
||||
}, [query]);
|
||||
return (
|
||||
<StyledInputContent>
|
||||
<StyledLabel htmlFor=":r5:">
|
||||
<SearchIcon />
|
||||
</StyledLabel>
|
||||
<Command.Input
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onCompositionStart={() => {
|
||||
setIsComposition(true);
|
||||
}}
|
||||
onCompositionEnd={e => {
|
||||
setQuery(e.data);
|
||||
setIsComposition(false);
|
||||
}}
|
||||
onValueChange={str => {
|
||||
setInputValue(str);
|
||||
if (!isComposition) {
|
||||
setQuery(str);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'a' && e.metaKey) {
|
||||
e.stopPropagation();
|
||||
inputRef.current?.select();
|
||||
return;
|
||||
}
|
||||
if (isComposition) {
|
||||
if (
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
isPublic
|
||||
? t('Quick search placeholder2', {
|
||||
workspace: publishWorkspaceName,
|
||||
})
|
||||
: t('Quick search placeholder')
|
||||
}
|
||||
/>
|
||||
</StyledInputContent>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
export const NoResultSVG = memo(function NoResultSVG() {
|
||||
return (
|
||||
<svg
|
||||
width="156"
|
||||
height="100"
|
||||
viewBox="0 0 156 100"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_10846_21787)">
|
||||
<path
|
||||
d="M39.4187 37.5349C22.2581 37.5349 7.0595 50.1896 8.29792 65.6334C9.1854 76.6959 25.1286 93.4946 39.4187 93.732C56.5764 94.0171 70.5394 81.1271 70.5394 65.6334C70.5394 50.1397 56.5792 37.5349 39.4187 37.5349Z"
|
||||
fill="#6166F0"
|
||||
/>
|
||||
<path
|
||||
d="M39.4187 47.5859C34.3599 47.5859 30.2447 51.7124 30.2447 56.7849C30.2447 61.8575 34.3599 65.9847 39.4187 65.9847C44.4774 65.9847 48.5926 61.8575 48.5926 56.7849C48.5926 51.7124 44.4774 47.5859 39.4187 47.5859Z"
|
||||
fill="#FCF1D5"
|
||||
/>
|
||||
<path
|
||||
d="M39.708 61.191C42.1823 61.191 44.1881 59.1796 44.1881 56.6986C44.1881 54.2175 42.1823 52.2062 39.708 52.2062C37.2336 52.2062 35.2278 54.2175 35.2278 56.6986C35.2278 59.1796 37.2336 61.191 39.708 61.191Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M27.5711 39.8472C27.5424 39.8191 27.5172 39.7868 27.4884 39.7587C27.4632 39.7341 27.5781 39.9048 27.4919 39.7601C27.4219 39.6428 27.3469 39.5304 27.2825 39.4096C27.1767 39.2116 27.0804 39.0086 26.9939 38.8014C27.064 38.9643 26.933 38.6476 26.9148 38.6019C26.554 37.6762 26.2822 36.7153 26.0602 35.7474C25.4858 33.2491 25.254 30.6468 25.5012 28.0908C25.7576 25.4422 26.6416 22.7507 28.5216 20.8086C29.4798 19.8183 30.6601 18.9705 31.9174 18.4072C32.8729 17.9787 33.7786 17.7884 34.7991 17.9015C34.8222 17.9043 34.9364 17.9219 34.8131 17.9029C35.0842 17.9441 35.351 18.0099 35.6103 18.0996C35.6614 18.1171 35.943 18.2309 35.833 18.1824C36.2092 18.3496 36.6589 18.6369 36.8578 18.8061C37.58 19.4214 38.003 20.0058 38.4261 20.8746C39.2267 22.5189 39.4649 24.664 39.1301 26.4768C38.763 28.461 37.7775 30.2717 36.1055 31.3471C35.0758 32.0087 33.6455 31.9448 32.6032 31.3309C32.1374 31.057 31.7654 30.6545 31.6786 30.1116C31.5322 29.1999 31.8649 28.2552 32.3237 27.4826C34.2311 24.2713 38.5886 22.6741 42.1133 23.7881C43.6964 24.2882 45.1631 25.313 46.106 26.6917C47.102 28.1484 47.6918 30.1572 46.5241 31.6842C46.3189 31.9525 46.0681 32.2187 45.7802 32.3985C45.6745 32.4645 45.5512 32.4849 45.4503 32.5453C45.458 32.5404 45.5764 32.454 45.402 32.5425C45.4349 32.5256 45.5568 32.6148 45.4048 32.5214L45.7515 33.1276C45.7473 32.9871 45.7466 32.9702 45.8202 32.8642C45.8825 32.7743 45.755 32.9386 45.8258 32.8494C45.8538 32.8143 45.8853 32.7813 45.9175 32.7504C46.0352 32.6373 46.0128 32.6549 46.1165 32.5882C46.522 32.3262 46.9549 32.1892 47.4312 32.0831C48.5071 31.8429 49.7021 31.8436 50.7605 32.1653C53.2598 32.926 54.494 36.1843 53.3879 38.4902C52.0283 41.325 48.6479 42.3744 45.727 42.1348C44.0116 41.9944 42.3319 41.6706 40.6031 41.6952C39.1364 41.7162 37.6808 41.896 36.2232 42.0435C34.7725 42.1903 33.2693 42.3252 31.8236 42.0611C30.1943 41.7626 28.7822 40.9724 27.5711 39.8472C26.9106 39.2333 25.9173 40.2244 26.5806 40.8404C27.6565 41.8398 28.8802 42.6476 30.2777 43.114C31.4825 43.5157 32.7727 43.6323 34.0363 43.6021C36.7611 43.5368 39.4418 42.9517 42.1778 43.1392C43.8505 43.2537 45.5064 43.6485 47.1903 43.5761C48.7211 43.5126 50.2149 43.0842 51.5478 42.3266C54.0001 40.9225 55.6308 38.3294 55.0936 35.4489C54.8491 34.1362 54.1493 32.8677 53.1554 31.9764C52.2147 31.1335 51.1206 30.7121 49.88 30.5695C48.7677 30.4417 47.5741 30.5323 46.5059 30.8779C45.6331 31.1609 44.6679 31.6765 44.4066 32.6324C44.3415 32.8691 44.3163 33.1683 44.4129 33.3973C44.5607 33.7492 44.9705 33.9114 45.3291 33.9388C45.9161 33.9838 46.4415 33.6775 46.8821 33.3207C47.4258 32.8792 47.8614 32.3187 48.1557 31.6822C48.45 31.0457 48.5952 30.3501 48.58 29.6487C48.5015 26.6334 46.1585 23.9876 43.5045 22.8223C40.6753 21.5812 37.4567 21.8559 34.7711 23.3456C33.408 24.1014 32.1787 25.164 31.3101 26.4655C30.5088 27.6659 29.9253 29.3621 30.4115 30.7964C31.1708 33.0391 34.2829 33.7099 36.2617 32.8537C37.837 32.1716 39.0909 30.6173 39.7787 29.0798C41.1285 26.0631 41.0024 22.1916 39.1966 19.3786C38.3729 18.0953 37.1646 17.0474 35.6719 16.6506C33.9004 16.18 32.0589 16.6857 30.4955 17.5552C28.481 18.6762 26.7522 20.3338 25.6819 22.3883C24.6355 24.3985 24.164 26.6538 24.0387 28.9049C23.921 31.0198 24.1171 33.155 24.5213 35.2319C24.8225 36.7792 25.2351 38.3364 25.9005 39.7699C26.0749 40.1464 26.2794 40.5489 26.5806 40.8404C27.2293 41.469 28.2211 40.4765 27.5711 39.8472ZM69.6848 71.5538C65.7272 71.5538 62.3566 69.7375 61.2982 67.0334C61.2142 66.8187 61.1732 66.5895 61.1775 66.3589C61.1818 66.1283 61.2313 65.9008 61.3233 65.6895C61.4153 65.4781 61.5479 65.2869 61.7135 65.1269C61.8791 64.9669 62.0746 64.8412 62.2887 64.757C62.5028 64.6727 62.7313 64.6315 62.9613 64.6359C63.1912 64.6402 63.4181 64.6899 63.6289 64.7821C63.8397 64.8743 64.0303 65.0073 64.1899 65.1734C64.3495 65.3395 64.4748 65.5354 64.5589 65.7501C65.0702 67.0565 67.2739 68.042 69.6855 68.042C74.4998 68.042 76.9416 65.5183 77.3696 60.1002C77.6351 56.7464 77.0446 52.3909 75.6626 47.5031C75.542 47.057 75.6017 46.5813 75.8287 46.1791C76.0556 45.7768 76.4316 45.4806 76.875 45.3545C77.3184 45.2284 77.7935 45.2827 78.1973 45.5056C78.601 45.7284 78.9009 46.1019 79.0318 46.5451C80.5476 51.9049 81.1626 56.5588 80.8607 60.3776C80.2905 67.5847 76.3217 71.5538 69.6848 71.5538Z"
|
||||
fill="#6166F0"
|
||||
/>
|
||||
<path
|
||||
d="M39.2358 69.3196C35.4863 69.3196 31.6933 67.9204 28.5328 65.3167C28.4618 65.2582 28.4029 65.1862 28.3596 65.1049C28.3163 65.0235 28.2893 64.9345 28.2804 64.8427C28.2714 64.7509 28.2805 64.6583 28.3073 64.5701C28.334 64.4818 28.3778 64.3997 28.4361 64.3285C28.4945 64.2572 28.5663 64.1982 28.6474 64.1548C28.7285 64.1113 28.8174 64.0844 28.9089 64.0754C29.0004 64.0664 29.0928 64.0755 29.1808 64.1023C29.2688 64.1291 29.3506 64.173 29.4217 64.2316C32.9611 67.1471 37.3474 68.4233 41.4556 67.7301C45.4678 67.0537 48.9036 64.5448 51.393 60.4732C51.4403 60.393 51.5031 60.3231 51.5776 60.2675C51.6521 60.2118 51.7369 60.1717 51.8271 60.1492C51.9173 60.1268 52.011 60.1226 52.1028 60.1368C52.1946 60.151 52.2826 60.1834 52.3618 60.2321C52.441 60.2807 52.5098 60.3447 52.5641 60.4203C52.6184 60.4959 52.6572 60.5815 52.6782 60.6723C52.6992 60.763 52.7019 60.857 52.6863 60.9489C52.6707 61.0407 52.6371 61.1285 52.5873 61.2071C49.88 65.6349 46.1116 68.3693 41.6882 69.1152C40.8778 69.2517 40.0575 69.3201 39.2358 69.3196ZM43.4771 85.0387C43.2474 84.5056 40.6396 77.432 40.608 77.2508C40.6291 77.2262 41.6713 75.8446 42.1161 75.2996C42.4411 74.895 42.8404 74.4329 42.6576 73.8745C42.5126 73.4327 42.044 73.229 41.6209 73.163C40.3993 72.9719 39.1119 72.9768 37.8868 73.0583C37.3607 73.0934 36.8655 73.1876 36.6518 73.7558C36.4704 74.2397 36.6708 74.7159 36.9685 75.1022C37.339 75.5834 37.7551 76.0329 38.1501 76.4943L38.7728 77.222L38.8156 77.2705C38.7413 77.5058 38.6573 77.7389 38.576 77.9672C37.7534 80.2666 36.8658 82.542 35.9143 84.7908C35.908 84.8055 35.9094 84.8196 35.9052 84.8343C35.8975 84.8484 35.8855 84.8582 35.8792 84.8744C35.6453 85.4524 35.8589 85.8605 36.1685 86.3585C36.4886 86.8726 38.6748 89.784 39.5139 90.6627C39.58 90.7276 39.6689 90.7641 39.7615 90.7641C39.8541 90.7641 39.943 90.7276 40.0092 90.6627C40.6963 89.9378 43.3945 86.5292 43.5731 86.0923C43.7244 85.7179 43.6305 85.3948 43.4771 85.0387Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M76.6425 48.8123C76.3904 48.8123 74.165 48.7681 73.7048 47.2257C73.6515 47.0472 73.671 46.8548 73.7592 46.6908C73.8473 46.5268 73.9968 46.4047 74.1748 46.3512C74.3528 46.2977 74.5446 46.3174 74.7082 46.4057C74.8717 46.4941 74.9936 46.644 75.0469 46.8225C75.1575 47.1941 76.0618 47.4041 76.6439 47.4069C76.8298 47.4071 77.008 47.4813 77.1393 47.6132C77.2706 47.7451 77.3442 47.9239 77.344 48.1103C77.3438 48.2967 77.2698 48.4753 77.1383 48.607C77.0067 48.7387 76.8284 48.8125 76.6425 48.8123Z"
|
||||
fill="#6166F0"
|
||||
/>
|
||||
<path
|
||||
d="M76.2439 47.6098C76.1001 47.6098 75.9597 47.5654 75.8419 47.4826C75.7241 47.3997 75.6346 47.2826 75.5855 47.1469L74.628 44.4983C74.567 44.3237 74.5771 44.1321 74.6561 43.965C74.7352 43.7979 74.8768 43.6688 75.0502 43.6057C75.2235 43.5426 75.4148 43.5507 75.5823 43.6281C75.7498 43.7055 75.8801 43.8461 75.9449 44.0193L76.9024 46.6679C76.9408 46.774 76.9531 46.8878 76.9383 46.9997C76.9235 47.1115 76.8821 47.2182 76.8175 47.3106C76.7529 47.403 76.667 47.4784 76.5672 47.5305C76.4674 47.5826 76.3565 47.6098 76.2439 47.6098ZM77.6253 46.9643C77.4541 46.9642 77.289 46.9013 77.161 46.7874C77.0329 46.6735 76.9509 46.5166 76.9304 46.3462L76.611 43.7032C76.5887 43.5182 76.6406 43.3319 76.7553 43.1853C76.87 43.0387 77.038 42.9438 77.2225 42.9215C77.407 42.8991 77.5927 42.9511 77.7389 43.0661C77.8851 43.1811 77.9798 43.3497 78.0021 43.5346L78.3215 46.1777C78.3334 46.2764 78.3243 46.3765 78.2947 46.4714C78.2651 46.5663 78.2158 46.6538 78.15 46.7282C78.0842 46.8025 78.0034 46.862 77.9129 46.9028C77.8225 46.9435 77.7244 46.9645 77.6253 46.9643Z"
|
||||
fill="#6166F0"
|
||||
/>
|
||||
<path
|
||||
d="M78.559 47.3711C78.3971 47.3711 78.2401 47.3149 78.1149 47.2119C77.9896 47.109 77.9038 46.9658 77.872 46.8066C77.8402 46.6474 77.8644 46.482 77.9405 46.3387C78.0166 46.1953 78.1398 46.0829 78.2893 46.0204C78.3601 45.9874 78.8868 45.7099 78.8868 44.7624C78.8868 44.5762 78.9606 44.3975 79.092 44.2658C79.2233 44.1341 79.4015 44.0601 79.5873 44.0601C79.773 44.0601 79.9512 44.1341 80.0826 44.2658C80.2139 44.3975 80.2877 44.5762 80.2877 44.7624C80.2877 46.3259 79.3617 47.107 78.814 47.3226C78.7327 47.3546 78.6463 47.3711 78.559 47.3711Z"
|
||||
fill="#6166F0"
|
||||
/>
|
||||
<path
|
||||
d="M153.975 95.8553C152.352 99.2028 148.33 100.596 144.991 98.9689L88.0537 74.6765L93.932 62.5549L150.87 86.8473C151.663 87.2342 152.373 87.7742 152.959 88.4363C153.545 89.0985 153.995 89.8698 154.283 90.7063C154.571 91.5428 154.692 92.4281 154.64 93.3115C154.587 94.195 154.361 95.0594 153.975 95.8553Z"
|
||||
fill="#F9C8B7"
|
||||
/>
|
||||
<path
|
||||
d="M147.925 100.002C146.868 100.002 145.816 99.7612 144.839 99.2843L87.5781 74.8543L93.7632 62.0999L151.007 86.5236C152.722 87.3587 153.997 88.8007 154.614 90.591C155.231 92.3814 155.116 94.3052 154.29 96.0084L153.975 95.8546L154.29 96.0084C153.886 96.8473 153.32 97.5975 152.625 98.2155C151.93 98.8336 151.119 99.3071 150.241 99.6088C149.483 99.8722 148.702 100.002 147.925 100.002ZM88.53 74.4975L145.129 98.6452C146.675 99.3988 148.403 99.5027 150.011 98.9451C151.62 98.3874 152.916 97.2355 153.659 95.7008C154.403 94.1661 154.508 92.4326 153.952 90.82C153.395 89.2073 152.247 87.9079 150.716 87.162L94.1008 63.0073L88.53 74.4975Z"
|
||||
fill="#F9C8B7"
|
||||
/>
|
||||
<path
|
||||
d="M24.3413 7.24359C1.83337 20.8865 -5.38417 50.2416 8.22087 72.8117C21.8259 95.3819 51.1016 102.618 73.6095 88.9755C96.1174 75.3333 103.335 45.9775 89.7292 23.4081C76.1235 0.838624 46.8491 -6.39865 24.3413 7.24359ZM70.2144 83.3432C50.8088 95.1051 25.5685 88.8659 13.8379 69.4073C2.10865 49.948 8.33084 24.6386 27.7364 12.8766C47.1419 1.11466 72.3823 7.35386 84.1122 26.8132C95.8428 46.2711 89.6199 71.5812 70.2144 83.3432Z"
|
||||
fill="#F9C8B7"
|
||||
/>
|
||||
<path
|
||||
d="M49.0549 96.2008C32.8 96.2008 16.9262 87.931 7.92107 72.9929C1.29262 61.9972 -0.668668 49.0707 2.39865 36.5958C5.46596 24.1209 13.1941 13.5895 24.1598 6.94293C46.7966 -6.77727 76.3462 0.526733 90.0297 23.2261C103.713 45.9255 96.4284 75.5552 73.7909 89.2761C66.3322 93.8091 57.7766 96.2042 49.0549 96.2008ZM24.3413 7.24355L24.5227 7.54417C13.7167 14.0938 6.10128 24.4707 3.07879 36.7637C0.0563084 49.0567 1.98958 61.7943 8.52067 72.6298C22.0045 94.9977 51.1219 102.196 73.4288 88.6749C95.7356 75.1541 102.914 45.9571 89.4301 23.5892C75.947 1.22208 46.8288 -5.97656 24.5227 7.54417L24.3413 7.24355ZM49.0437 89.621C35.0128 89.621 21.3111 82.4827 13.5388 69.5885C1.72761 49.995 8.01564 24.4194 27.555 12.576C37.0203 6.83898 48.1478 5.14134 58.8865 7.79562C69.6253 10.4506 78.6907 17.1393 84.412 26.6312C90.1334 36.1224 91.8264 47.2797 89.1793 58.0485C86.5316 68.8166 79.8611 77.9067 70.3958 83.6437C63.9574 87.5566 56.5722 89.624 49.0437 89.621ZM48.9022 7.27516C41.5698 7.27516 34.3536 9.27622 27.9178 13.1772C8.70839 24.8198 2.52683 49.9627 14.1384 69.2254C25.7499 88.4873 50.8243 94.6858 70.0337 83.0425C79.3393 77.4024 85.897 68.4661 88.4999 57.8799C91.1028 47.2937 89.4378 36.3254 83.8131 26.9944C78.1884 17.6633 69.2765 11.0877 58.7191 8.47762C55.5072 7.68087 52.2109 7.27711 48.9022 7.27516Z"
|
||||
fill="#F9C8B7"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_10846_21787">
|
||||
<rect
|
||||
width="154.006"
|
||||
height="100"
|
||||
fill="white"
|
||||
transform="translate(0.997025)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EdgelessIcon, PaperIcon } from '@blocksuite/icons';
|
||||
import { Command } from 'cmdk';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
|
||||
export type PublishedResultsProps = {
|
||||
query: string;
|
||||
loading: boolean;
|
||||
setPublishWorkspaceName: (name: string) => void;
|
||||
onClose: () => void;
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
};
|
||||
|
||||
export const PublishedResults: React.FC<PublishedResultsProps> = ({
|
||||
query,
|
||||
loading,
|
||||
onClose,
|
||||
setPublishWorkspaceName,
|
||||
blockSuiteWorkspace,
|
||||
}) => {
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const router = useRouter();
|
||||
const pageList = usePageMeta(blockSuiteWorkspace);
|
||||
// useEffect(() => {
|
||||
// dataCenter
|
||||
// .loadPublicWorkspace(router.query.workspaceId as string)
|
||||
// .then(data => {
|
||||
// setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
// if (data.blocksuiteWorkspace) {
|
||||
// setWorkspace(data.blocksuiteWorkspace);
|
||||
// setPublishWorkspaceName(data.blocksuiteWorkspace.meta.name);
|
||||
// }
|
||||
// })
|
||||
// .catch(() => {
|
||||
// router.push('/404');
|
||||
// });
|
||||
// }, [router, dataCenter, setPublishWorkspaceName]);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
setResults(blockSuiteWorkspace.search(query));
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
}, [blockSuiteWorkspace, query, setResults]);
|
||||
const pageIds = useMemo(() => [...results.values()], [results]);
|
||||
const resultsPageMeta = useMemo(
|
||||
() => pageList.filter(page => pageIds.indexOf(page.id) > -1 && !page.trash),
|
||||
[pageIds, pageList]
|
||||
);
|
||||
|
||||
return loading ? null : (
|
||||
<>
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EdgelessIcon, PaperIcon } from '@blocksuite/icons';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { Command } from 'cmdk';
|
||||
import { NextRouter } from 'next/router';
|
||||
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
|
||||
import { useBlockSuiteWorkspaceHelper } from '../../../hooks/use-blocksuite-workspace-helper';
|
||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { useSwitchToConfig } from './config';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
|
||||
export type ResultsProps = {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
query: string;
|
||||
loading: boolean;
|
||||
onClose: () => void;
|
||||
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
|
||||
router: NextRouter;
|
||||
};
|
||||
export const Results: React.FC<ResultsProps> = ({
|
||||
query,
|
||||
loading,
|
||||
blockSuiteWorkspace,
|
||||
setShowCreatePage,
|
||||
router,
|
||||
onClose,
|
||||
}) => {
|
||||
useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const pageList = usePageMeta(blockSuiteWorkspace);
|
||||
assertExists(blockSuiteWorkspace.room);
|
||||
const List = useSwitchToConfig(blockSuiteWorkspace.room);
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
setResults(blockSuiteWorkspace.search(query));
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query, setResults]);
|
||||
const pageIds = [...results.values()];
|
||||
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowCreatePage(!resultsPageMeta.length);
|
||||
//Determine whether to display the ‘+ New page’
|
||||
}, [resultsPageMeta.length, setShowCreatePage]);
|
||||
return loading ? null : (
|
||||
<>
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
assertExists(blockSuiteWorkspace.room);
|
||||
// fixme: refactor to `useRouterHelper`
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: blockSuiteWorkspace.room,
|
||||
pageId: result.id,
|
||||
},
|
||||
});
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<Command.Group heading={t('Jump to')}>
|
||||
{List.map(link => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={link.title}
|
||||
value={link.title}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
router.push(link.href);
|
||||
}}
|
||||
>
|
||||
<StyledListItem>
|
||||
<link.icon />
|
||||
<span>{link.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import {
|
||||
DeleteTemporarilyIcon,
|
||||
FavoriteIcon,
|
||||
FolderIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { FC, SVGProps, useMemo } from 'react';
|
||||
|
||||
import { pathGenerator } from '../../../shared';
|
||||
export const useSwitchToConfig = (
|
||||
workspaceId: string
|
||||
): {
|
||||
title: string;
|
||||
href: string;
|
||||
icon: FC<SVGProps<SVGSVGElement>>;
|
||||
}[] => {
|
||||
const { t } = useTranslation();
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('All pages'),
|
||||
href: pathGenerator.all(workspaceId),
|
||||
icon: FolderIcon,
|
||||
},
|
||||
{
|
||||
title: t('Favorites'),
|
||||
href: pathGenerator.favorite(workspaceId),
|
||||
icon: FavoriteIcon,
|
||||
},
|
||||
{
|
||||
title: t('Workspace Settings'),
|
||||
href: pathGenerator.setting(workspaceId),
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: t('Trash'),
|
||||
href: pathGenerator.trash(workspaceId),
|
||||
icon: DeleteTemporarilyIcon,
|
||||
},
|
||||
],
|
||||
[workspaceId, t]
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
import { Modal, ModalWrapper } from '@affine/component';
|
||||
import { Command } from 'cmdk';
|
||||
import { NextRouter } from 'next/router';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
|
||||
import { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { getUaHelper } from '../../../utils/useragent';
|
||||
import { Footer } from './Footer';
|
||||
import { Input } from './Input';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
import { Results } from './Results';
|
||||
import {
|
||||
StyledContent,
|
||||
StyledModalDivider,
|
||||
StyledModalFooter,
|
||||
StyledModalHeader,
|
||||
StyledShortcut,
|
||||
} from './style';
|
||||
|
||||
const isMac = () => {
|
||||
return getUaHelper().isMacOs;
|
||||
};
|
||||
|
||||
export type QuickSearchModalProps = {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
enableShortCut: boolean;
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
export const QuickSearchModal: React.FC<QuickSearchModalProps> = ({
|
||||
open,
|
||||
setOpen,
|
||||
router,
|
||||
enableShortCut,
|
||||
blockSuiteWorkspace,
|
||||
}) => {
|
||||
const [loading, startTransition] = useTransition();
|
||||
const [query, _setQuery] = useState('');
|
||||
const setQuery = useCallback((query: string) => {
|
||||
startTransition(() => {
|
||||
_setQuery(query);
|
||||
});
|
||||
}, []);
|
||||
const isPublicWorkspace = useMemo(
|
||||
() => router.pathname.startsWith('/public-workspace'),
|
||||
[router]
|
||||
);
|
||||
const [publishWorkspaceName, setPublishWorkspaceName] = useState('');
|
||||
const [showCreatePage, setShowCreatePage] = useState(true);
|
||||
const isPublicAndNoQuery = useCallback(() => {
|
||||
return isPublicWorkspace && query.length === 0;
|
||||
}, [isPublicWorkspace, query.length]);
|
||||
const handleClose = useCallback(() => {
|
||||
setQuery('');
|
||||
setOpen(false);
|
||||
}, [setOpen, setQuery]);
|
||||
// Add ‘⌘+K’ shortcut keys as switches
|
||||
useEffect(() => {
|
||||
if (!enableShortCut) {
|
||||
return;
|
||||
}
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) {
|
||||
const selection = window.getSelection();
|
||||
// prevent search bar focus in firefox
|
||||
e.preventDefault();
|
||||
setQuery('');
|
||||
if (selection?.toString()) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
if (selection?.isCollapsed) {
|
||||
setOpen(!open);
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', keydown, { capture: true });
|
||||
return () =>
|
||||
document.removeEventListener('keydown', keydown, { capture: true });
|
||||
}, [enableShortCut, open, router, setOpen, setQuery]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['top', 'center']}
|
||||
data-testid="quickSearch"
|
||||
>
|
||||
<ModalWrapper
|
||||
width={620}
|
||||
style={{
|
||||
maxHeight: '80vh',
|
||||
minHeight: isPublicAndNoQuery() ? '72px' : '350px',
|
||||
top: '12vh',
|
||||
}}
|
||||
>
|
||||
<Command
|
||||
shouldFilter={false}
|
||||
//Handle KeyboardEvent conflicts with blocksuite
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if (
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'ArrowLeft' ||
|
||||
e.key === 'ArrowRight'
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<Input
|
||||
open={open}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
isPublic={isPublicWorkspace}
|
||||
publishWorkspaceName={publishWorkspaceName}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
/>
|
||||
<Command.List>
|
||||
<StyledContent
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
>
|
||||
{!isPublicWorkspace ? (
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
onClose={handleClose}
|
||||
router={router}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
) : (
|
||||
<PublishedResults
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
query={query}
|
||||
loading={loading}
|
||||
onClose={handleClose}
|
||||
setPublishWorkspaceName={setPublishWorkspaceName}
|
||||
data-testid="published-search-results"
|
||||
/>
|
||||
)}
|
||||
</StyledContent>
|
||||
{!isPublicWorkspace ? (
|
||||
showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer
|
||||
query={query}
|
||||
onClose={handleClose}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
router={router}
|
||||
/>
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickSearchModal;
|
||||
@@ -0,0 +1,158 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
|
||||
export const StyledContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
minHeight: '220px',
|
||||
maxHeight: '55vh',
|
||||
width: '100%',
|
||||
overflow: 'auto',
|
||||
marginBottom: '10px',
|
||||
...displayFlex('center', 'flex-start'),
|
||||
color: theme.colors.popoverColor,
|
||||
transition: 'all 0.15s',
|
||||
letterSpacing: '0.06em',
|
||||
'[cmdk-group-heading]': {
|
||||
margin: '5px 16px',
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: '500',
|
||||
},
|
||||
'[aria-selected="true"]': {
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledJumpTo = styled('div')(({ theme }) => {
|
||||
return {
|
||||
...displayFlex('center', 'start'),
|
||||
flexDirection: 'column',
|
||||
padding: '10px 10px 10px 0',
|
||||
fontSize: theme.font.base,
|
||||
strong: {
|
||||
fontWeight: '500',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledNotFound = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '612px',
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
padding: '5px 16px',
|
||||
fontSize: theme.font.base,
|
||||
span: {
|
||||
width: '100%',
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
'>svg': {
|
||||
marginTop: '10px',
|
||||
height: '200px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledInputContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
margin: '13px 0',
|
||||
...displayFlex('space-between', 'center'),
|
||||
input: {
|
||||
width: '492px',
|
||||
height: '22px',
|
||||
padding: '0 12px',
|
||||
fontSize: theme.font.base,
|
||||
...displayFlex('space-between', 'center'),
|
||||
letterSpacing: '0.06em',
|
||||
color: theme.colors.popoverColor,
|
||||
|
||||
'::placeholder': {
|
||||
color: theme.colors.placeHolderColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledShortcut = styled('div')(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.placeHolderColor,
|
||||
fontSize: theme.font.base,
|
||||
whiteSpace: 'nowrap',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLabel = styled('label')(({ theme }) => {
|
||||
return {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(({ theme }) => {
|
||||
return {
|
||||
height: '48px',
|
||||
margin: '12px 24px 0px 24px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
export const StyledModalDivider = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: 'auto',
|
||||
height: '0',
|
||||
margin: '6px 16px 6.5px 16px',
|
||||
position: 'relative',
|
||||
borderTop: `0.5px solid ${theme.colors.borderColor}`,
|
||||
transition: 'all 0.15s',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: '22px',
|
||||
marginBottom: '8px',
|
||||
textAlign: 'center',
|
||||
...displayFlex('center', 'center'),
|
||||
color: theme.colors.popoverColor,
|
||||
'[aria-selected="true"]': {
|
||||
transition: 'background .15s, color .15s',
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledModalFooterContent = styled.button(({ theme }) => {
|
||||
return {
|
||||
width: '612px',
|
||||
height: '32px',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: '22px',
|
||||
textAlign: 'center',
|
||||
...displayFlex('center', 'center'),
|
||||
color: 'inherit',
|
||||
borderRadius: '5px',
|
||||
transition: 'background .15s, color .15s',
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledListItem = styled.button(({ theme }) => {
|
||||
return {
|
||||
width: '612px',
|
||||
height: '32px',
|
||||
fontSize: theme.font.base,
|
||||
color: 'inherit',
|
||||
paddingLeft: '12px',
|
||||
borderRadius: '5px',
|
||||
transition: 'background .15s, color .15s',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
export const CloseIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M7.94 7.00014L13.4667 1.47348C13.5759 1.34594 13.633 1.18189 13.6265 1.01411C13.62 0.846324 13.5504 0.687165 13.4317 0.568435C13.313 0.449706 13.1538 0.38015 12.986 0.37367C12.8183 0.367189 12.6542 0.42426 12.5267 0.533477L7 6.06014L1.47334 0.526811C1.3478 0.401275 1.17754 0.33075 1 0.33075C0.822468 0.33075 0.652205 0.401275 0.526669 0.526811C0.401133 0.652346 0.330608 0.82261 0.330608 1.00014C0.330608 1.17768 0.401133 1.34794 0.526669 1.47348L6.06 7.00014L0.526669 12.5268C0.456881 12.5866 0.400201 12.6601 0.360186 12.7428C0.32017 12.8255 0.297683 12.9156 0.294137 13.0074C0.290591 13.0993 0.306061 13.1908 0.339577 13.2764C0.373094 13.3619 0.423932 13.4396 0.488902 13.5046C0.553872 13.5695 0.63157 13.6204 0.71712 13.6539C0.80267 13.6874 0.894225 13.7029 0.986038 13.6993C1.07785 13.6958 1.16794 13.6733 1.25065 13.6333C1.33336 13.5933 1.4069 13.5366 1.46667 13.4668L7 7.94014L12.5267 13.4668C12.6542 13.576 12.8183 13.6331 12.986 13.6266C13.1538 13.6201 13.313 13.5506 13.4317 13.4319C13.5504 13.3131 13.62 13.154 13.6265 12.9862C13.633 12.8184 13.5759 12.6543 13.4667 12.5268L7.94 7.00014Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const KeyboardIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="15"
|
||||
viewBox="0 0 20 15"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17.745 0C18.3417 0 18.914 0.237053 19.336 0.65901C19.7579 1.08097 19.995 1.65326 19.995 2.25V11.755C19.995 12.3517 19.7579 12.924 19.336 13.346C18.914 13.7679 18.3417 14.005 17.745 14.005H2.25C1.95453 14.005 1.66194 13.9468 1.38896 13.8337C1.11598 13.7207 0.867941 13.5549 0.65901 13.346C0.450078 13.1371 0.284344 12.889 0.171271 12.616C0.058198 12.3431 0 12.0505 0 11.755V2.25C0 1.65326 0.237053 1.08097 0.65901 0.65901C1.08097 0.237053 1.65326 0 2.25 0H17.745ZM17.745 1.5H2.25C2.05109 1.5 1.86032 1.57902 1.71967 1.71967C1.57902 1.86032 1.5 2.05109 1.5 2.25V11.755C1.5 12.169 1.836 12.505 2.25 12.505H17.745C17.9439 12.505 18.1347 12.426 18.2753 12.2853C18.416 12.1447 18.495 11.9539 18.495 11.755V2.25C18.495 2.05109 18.416 1.86032 18.2753 1.71967C18.1347 1.57902 17.9439 1.5 17.745 1.5ZM4.75 9.5H15.25C15.44 9.50006 15.6229 9.57224 15.7618 9.70197C15.9006 9.8317 15.9851 10.0093 15.998 10.1989C16.011 10.3885 15.9515 10.5759 15.8316 10.7233C15.7117 10.8707 15.5402 10.9671 15.352 10.993L15.25 11H4.75C4.55998 10.9999 4.37706 10.9278 4.23821 10.798C4.09936 10.6683 4.01493 10.4907 4.00197 10.3011C3.98902 10.1115 4.04852 9.92411 4.16843 9.7767C4.28835 9.62929 4.45975 9.5329 4.648 9.507L4.75 9.5H15.25H4.75ZM14.5 6C14.7652 6 15.0196 6.10536 15.2071 6.29289C15.3946 6.48043 15.5 6.73478 15.5 7C15.5 7.26522 15.3946 7.51957 15.2071 7.70711C15.0196 7.89464 14.7652 8 14.5 8C14.2348 8 13.9804 7.89464 13.7929 7.70711C13.6054 7.51957 13.5 7.26522 13.5 7C13.5 6.73478 13.6054 6.48043 13.7929 6.29289C13.9804 6.10536 14.2348 6 14.5 6ZM8.505 6C8.77022 6 9.02457 6.10536 9.21211 6.29289C9.39964 6.48043 9.505 6.73478 9.505 7C9.505 7.26522 9.39964 7.51957 9.21211 7.70711C9.02457 7.89464 8.77022 8 8.505 8C8.23978 8 7.98543 7.89464 7.79789 7.70711C7.61036 7.51957 7.505 7.26522 7.505 7C7.505 6.73478 7.61036 6.48043 7.79789 6.29289C7.98543 6.10536 8.23978 6 8.505 6ZM5.505 6C5.77022 6 6.02457 6.10536 6.21211 6.29289C6.39964 6.48043 6.505 6.73478 6.505 7C6.505 7.26522 6.39964 7.51957 6.21211 7.70711C6.02457 7.89464 5.77022 8 5.505 8C5.23978 8 4.98543 7.89464 4.79789 7.70711C4.61036 7.51957 4.505 7.26522 4.505 7C4.505 6.73478 4.61036 6.48043 4.79789 6.29289C4.98543 6.10536 5.23978 6 5.505 6ZM11.505 6C11.7702 6 12.0246 6.10536 12.2121 6.29289C12.3996 6.48043 12.505 6.73478 12.505 7C12.505 7.26522 12.3996 7.51957 12.2121 7.70711C12.0246 7.89464 11.7702 8 11.505 8C11.2398 8 10.9854 7.89464 10.7979 7.70711C10.6104 7.51957 10.505 7.26522 10.505 7C10.505 6.73478 10.6104 6.48043 10.7979 6.29289C10.9854 6.10536 11.2398 6 11.505 6ZM4 3C4.26522 3 4.51957 3.10536 4.70711 3.29289C4.89464 3.48043 5 3.73478 5 4C5 4.26522 4.89464 4.51957 4.70711 4.70711C4.51957 4.89464 4.26522 5 4 5C3.73478 5 3.48043 4.89464 3.29289 4.70711C3.10536 4.51957 3 4.26522 3 4C3 3.73478 3.10536 3.48043 3.29289 3.29289C3.48043 3.10536 3.73478 3 4 3ZM6.995 3C7.26022 3 7.51457 3.10536 7.70211 3.29289C7.88964 3.48043 7.995 3.73478 7.995 4C7.995 4.26522 7.88964 4.51957 7.70211 4.70711C7.51457 4.89464 7.26022 5 6.995 5C6.72978 5 6.47543 4.89464 6.28789 4.70711C6.10036 4.51957 5.995 4.26522 5.995 4C5.995 3.73478 6.10036 3.48043 6.28789 3.29289C6.47543 3.10536 6.72978 3 6.995 3ZM9.995 3C10.2602 3 10.5146 3.10536 10.7021 3.29289C10.8896 3.48043 10.995 3.73478 10.995 4C10.995 4.26522 10.8896 4.51957 10.7021 4.70711C10.5146 4.89464 10.2602 5 9.995 5C9.72978 5 9.47543 4.89464 9.28789 4.70711C9.10036 4.51957 8.995 4.26522 8.995 4C8.995 3.73478 9.10036 3.48043 9.28789 3.29289C9.47543 3.10536 9.72978 3 9.995 3ZM12.995 3C13.2602 3 13.5146 3.10536 13.7021 3.29289C13.8896 3.48043 13.995 3.73478 13.995 4C13.995 4.26522 13.8896 4.51957 13.7021 4.70711C13.5146 4.89464 13.2602 5 12.995 5C12.7298 5 12.4754 4.89464 12.2879 4.70711C12.1004 4.51957 11.995 4.26522 11.995 4C11.995 3.73478 12.1004 3.48043 12.2879 3.29289C12.4754 3.10536 12.7298 3 12.995 3ZM15.995 3C16.2602 3 16.5146 3.10536 16.7021 3.29289C16.8896 3.48043 16.995 3.73478 16.995 4C16.995 4.26522 16.8896 4.51957 16.7021 4.70711C16.5146 4.89464 16.2602 5 15.995 5C15.7298 5 15.4754 4.89464 15.2879 4.70711C15.1004 4.51957 14.995 4.26522 14.995 4C14.995 3.73478 15.1004 3.48043 15.2879 3.29289C15.4754 3.10536 15.7298 3 15.995 3Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
interface ShortcutTip {
|
||||
[x: string]: string;
|
||||
}
|
||||
export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
[t('Undo')]: '⌘+Z',
|
||||
[t('Redo')]: '⌘+⇧+Z',
|
||||
[t('Bold')]: '⌘+B',
|
||||
[t('Italic')]: '⌘+I',
|
||||
[t('Underline')]: '⌘+U',
|
||||
[t('Strikethrough')]: '⌘+⇧+S',
|
||||
[t('Inline code')]: ' ⌘+E',
|
||||
[t('Code block')]: '⌘+⌥+C',
|
||||
[t('Link')]: '⌘+K',
|
||||
[t('Quick search')]: '⌘+K',
|
||||
[t('Body text')]: '⌘+⌥+0',
|
||||
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
||||
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
||||
[t('Heading', { number: '3' })]: '⌘+⌥+3',
|
||||
[t('Heading', { number: '4' })]: '⌘+⌥+4',
|
||||
[t('Heading', { number: '5' })]: '⌘+⌥+5',
|
||||
[t('Heading', { number: '6' })]: '⌘+⌥+6',
|
||||
[t('Increase indent')]: 'Tab',
|
||||
[t('Reduce indent')]: '⇧+Tab',
|
||||
};
|
||||
};
|
||||
|
||||
export const useMacMarkdownShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
[t('Bold')]: '**Text** ',
|
||||
[t('Italic')]: '*Text* ',
|
||||
[t('Underline')]: '~Text~ ',
|
||||
[t('Strikethrough')]: '~~Text~~ ',
|
||||
[t('Divider')]: '***',
|
||||
[t('Inline code')]: '`Text` ',
|
||||
[t('Code block')]: '``` Space',
|
||||
[t('Heading', { number: '1' })]: '# Text',
|
||||
[t('Heading', { number: '2' })]: '## Text',
|
||||
[t('Heading', { number: '3' })]: '### Text',
|
||||
[t('Heading', { number: '4' })]: '#### Text',
|
||||
[t('Heading', { number: '5' })]: '##### Text',
|
||||
[t('Heading', { number: '6' })]: '###### Text',
|
||||
};
|
||||
};
|
||||
|
||||
export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
[t('Undo')]: 'Ctrl+Z',
|
||||
[t('Redo')]: 'Ctrl+Y',
|
||||
[t('Bold')]: 'Ctrl+B',
|
||||
[t('Italic')]: 'Ctrl+I',
|
||||
[t('Underline')]: 'Ctrl+U',
|
||||
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
||||
[t('Inline code')]: ' Ctrl+E',
|
||||
[t('Code block')]: 'Ctrl+Alt+C',
|
||||
[t('Link')]: 'Ctrl+K',
|
||||
[t('Quick search')]: 'Ctrl+K',
|
||||
[t('Body text')]: 'Ctrl+Shift+0',
|
||||
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
||||
[t('Heading', { number: '3' })]: 'Ctrl+Shift+3',
|
||||
[t('Heading', { number: '4' })]: 'Ctrl+Shift+4',
|
||||
[t('Heading', { number: '5' })]: 'Ctrl+Shift+5',
|
||||
[t('Heading', { number: '6' })]: 'Ctrl+Shift+6',
|
||||
[t('Increase indent')]: 'Tab',
|
||||
[t('Reduce indent')]: 'Shift+Tab',
|
||||
};
|
||||
};
|
||||
export const useWinMarkdownShortcuts = (): ShortcutTip => {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
[t('Bold')]: '**Text** ',
|
||||
[t('Italic')]: '*Text* ',
|
||||
[t('Underline')]: '~Text~ ',
|
||||
[t('Strikethrough')]: '~~Text~~ ',
|
||||
[t('Divider')]: '***',
|
||||
[t('Inline code')]: '`Text` ',
|
||||
[t('Code block')]: '``` Text',
|
||||
[t('Heading', { number: '1' })]: '# Text',
|
||||
[t('Heading', { number: '2' })]: '## Text',
|
||||
[t('Heading', { number: '3' })]: '### Text',
|
||||
[t('Heading', { number: '4' })]: '#### Text',
|
||||
[t('Heading', { number: '5' })]: '##### Text',
|
||||
[t('Heading', { number: '6' })]: '###### Text',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
ModalCloseButton,
|
||||
MuiClickAwayListener,
|
||||
MuiSlide,
|
||||
} from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getUaHelper } from '../../../utils/useragent';
|
||||
import {
|
||||
useMacKeyboardShortcuts,
|
||||
useMacMarkdownShortcuts,
|
||||
useWindowsKeyboardShortcuts,
|
||||
useWinMarkdownShortcuts,
|
||||
} from './config';
|
||||
import { KeyboardIcon } from './Icons';
|
||||
import {
|
||||
StyledListItem,
|
||||
StyledModalHeader,
|
||||
StyledShortcutsModal,
|
||||
StyledSubTitle,
|
||||
StyledTitle,
|
||||
} from './style';
|
||||
type ModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const checkIsMac = () => {
|
||||
return getUaHelper().isMacOs;
|
||||
};
|
||||
|
||||
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const macMarkdownShortcuts = useMacMarkdownShortcuts();
|
||||
const winMarkdownShortcuts = useWinMarkdownShortcuts();
|
||||
const macKeyboardShortcuts = useMacKeyboardShortcuts();
|
||||
const windowsKeyboardShortcuts = useWindowsKeyboardShortcuts();
|
||||
const [isMac, setIsMac] = useState(false);
|
||||
const markdownShortcuts = isMac ? macMarkdownShortcuts : winMarkdownShortcuts;
|
||||
const keyboardShortcuts = isMac
|
||||
? macKeyboardShortcuts
|
||||
: windowsKeyboardShortcuts;
|
||||
|
||||
useEffect(() => {
|
||||
setIsMac(checkIsMac());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<StyledShortcutsModal data-testid="shortcuts-modal">
|
||||
<MuiClickAwayListener
|
||||
onClickAway={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<StyledModalHeader>
|
||||
<StyledTitle>
|
||||
<KeyboardIcon />
|
||||
{t('Shortcuts')}
|
||||
</StyledTitle>
|
||||
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
size={[24, 24]}
|
||||
iconSize={[15, 15]}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</StyledModalHeader>
|
||||
<StyledSubTitle style={{ marginTop: 0 }}>
|
||||
{t('Keyboard Shortcuts')}
|
||||
</StyledSubTitle>
|
||||
{Object.entries(keyboardShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
<StyledSubTitle>{t('Markdown Syntax')}</StyledSubTitle>
|
||||
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</MuiClickAwayListener>
|
||||
</StyledShortcutsModal>
|
||||
</MuiSlide>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
|
||||
export const StyledShortcutsModal = styled.div(({ theme }) => ({
|
||||
width: '288px',
|
||||
height: '74vh',
|
||||
paddingBottom: '28px',
|
||||
backgroundColor: theme.colors.popoverBackground,
|
||||
boxShadow: theme.shadow.popover,
|
||||
borderRadius: `${theme.radius.popover} 0 ${theme.radius.popover} ${theme.radius.popover}`,
|
||||
color: theme.colors.popoverColor,
|
||||
overflow: 'auto',
|
||||
boxRadius: '10px',
|
||||
position: 'fixed',
|
||||
right: '12px',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
zIndex: theme.zIndex.modal,
|
||||
}));
|
||||
export const StyledTitle = styled.div(({ theme }) => ({
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: '500',
|
||||
fontSize: theme.font.sm,
|
||||
height: '44px',
|
||||
...displayFlex('center', 'center'),
|
||||
svg: {
|
||||
width: '20px',
|
||||
marginRight: '14px',
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
}));
|
||||
export const StyledSubTitle = styled.div(({ theme }) => ({
|
||||
color: theme.colors.popoverColor,
|
||||
fontWeight: '500',
|
||||
fontSize: theme.font.sm,
|
||||
height: '34px',
|
||||
lineHeight: '36px',
|
||||
marginTop: '28px',
|
||||
padding: '0 16px',
|
||||
}));
|
||||
export const StyledModalHeader = styled.div(() => ({
|
||||
...displayFlex('space-between', 'center'),
|
||||
paddingTop: '8px 4px 0 4px',
|
||||
width: '100%',
|
||||
padding: '8px 16px 0 16px',
|
||||
position: 'sticky',
|
||||
left: '0',
|
||||
top: '0',
|
||||
background: 'var(--affine-popover-background)',
|
||||
|
||||
transition: 'background-color 0.5s',
|
||||
}));
|
||||
|
||||
export const StyledListItem = styled.div(({ theme }) => ({
|
||||
height: '34px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
fontSize: theme.font.sm,
|
||||
padding: '0 16px',
|
||||
}));
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useWorkspaceBlobImage } from '../../../hooks/use-workspace-blob';
|
||||
import { BlockSuiteWorkspace, RemWorkspace } from '../../../shared';
|
||||
import { stringToColour } from '../../../utils';
|
||||
|
||||
interface AvatarProps {
|
||||
size: number;
|
||||
name: string;
|
||||
avatar_url: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<AvatarProps> = React.memo<AvatarProps>(
|
||||
function Avatar(props) {
|
||||
const size = props.size || 20;
|
||||
const sizeStr = size + 'px';
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.avatar_url ? (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
color: '#fff',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
>
|
||||
<picture>
|
||||
<img
|
||||
style={{ width: sizeStr, height: sizeStr }}
|
||||
src={props.avatar_url}
|
||||
alt=""
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
border: '1px solid #fff',
|
||||
color: '#fff',
|
||||
fontSize: Math.ceil(0.5 * size) + 'px',
|
||||
background: stringToColour(props.name || 'AFFiNE'),
|
||||
borderRadius: '50%',
|
||||
textAlign: 'center',
|
||||
lineHeight: size + 'px',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
>
|
||||
{(props.name || 'AFFiNE').substring(0, 1)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type WorkspaceUnitAvatarProps = {
|
||||
size?: number;
|
||||
workspace: RemWorkspace | null;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export type BlockSuiteWorkspaceAvatar = Omit<
|
||||
WorkspaceUnitAvatarProps,
|
||||
'workspace'
|
||||
> & {
|
||||
workspace: BlockSuiteWorkspace;
|
||||
};
|
||||
|
||||
export const BlockSuiteWorkspaceAvatar: React.FC<BlockSuiteWorkspaceAvatar> = ({
|
||||
size = 20,
|
||||
workspace,
|
||||
style,
|
||||
}) => {
|
||||
const avatarURL = useWorkspaceBlobImage(workspace.meta.avatar, workspace);
|
||||
return (
|
||||
<Avatar
|
||||
size={size}
|
||||
name={workspace.meta.name}
|
||||
avatar_url={avatarURL ?? ''}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceAvatar: React.FC<WorkspaceUnitAvatarProps> = ({
|
||||
size = 20,
|
||||
workspace,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
if (workspace && 'blockSuiteWorkspace' in workspace) {
|
||||
return (
|
||||
<BlockSuiteWorkspaceAvatar
|
||||
{...props}
|
||||
size={size}
|
||||
workspace={workspace.blockSuiteWorkspace}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Avatar size={size} name="UNKNOWN" avatar_url="" style={style} />;
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
import { PermissionType } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { RemWorkspace, RemWorkspaceFlavour } from '../../../shared';
|
||||
import {
|
||||
CloudWorkspaceIcon,
|
||||
JoinedWorkspaceIcon,
|
||||
LocalDataIcon,
|
||||
LocalWorkspaceIcon,
|
||||
PublishIcon,
|
||||
} from '../icons';
|
||||
import { WorkspaceAvatar } from '../workspace-avatar';
|
||||
import { StyledCard, StyleWorkspaceInfo, StyleWorkspaceTitle } from './styles';
|
||||
|
||||
export type WorkspaceTypeProps = {
|
||||
workspace: RemWorkspace;
|
||||
};
|
||||
|
||||
const WorkspaceType: React.FC<WorkspaceTypeProps> = ({ workspace }) => {
|
||||
const { t } = useTranslation();
|
||||
let isOwner = true;
|
||||
if (workspace.flavour === RemWorkspaceFlavour.AFFINE) {
|
||||
isOwner = workspace.permission === PermissionType.Owner;
|
||||
} else if (workspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
isOwner = true;
|
||||
}
|
||||
|
||||
if (workspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
return (
|
||||
<p title={t('Local Workspace')}>
|
||||
<LocalWorkspaceIcon />
|
||||
<span>{t('Local Workspace')}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return isOwner ? (
|
||||
<p title={t('Cloud Workspace')}>
|
||||
<CloudWorkspaceIcon />
|
||||
<span>{t('Cloud Workspace')}</span>
|
||||
</p>
|
||||
) : (
|
||||
<p title={t('Joined Workspace')}>
|
||||
<JoinedWorkspaceIcon />
|
||||
<span>{t('Joined Workspace')}</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export type WorkspaceCardProps = {
|
||||
currentWorkspaceId: string | null;
|
||||
workspace: RemWorkspace;
|
||||
onClick: (workspace: RemWorkspace) => void;
|
||||
};
|
||||
|
||||
export const WorkspaceCard: React.FC<WorkspaceCardProps> = ({
|
||||
workspace,
|
||||
onClick,
|
||||
currentWorkspaceId,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
let name = 'UNKNOWN';
|
||||
if (workspace.flavour === RemWorkspaceFlavour.LOCAL) {
|
||||
name = workspace.blockSuiteWorkspace.meta.name;
|
||||
} else if (workspace.flavour === RemWorkspaceFlavour.AFFINE) {
|
||||
if (workspace.firstBinarySynced) {
|
||||
name = workspace.blockSuiteWorkspace.meta.name;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledCard
|
||||
data-testid="workspace-card"
|
||||
onClick={useCallback(() => {
|
||||
onClick(workspace);
|
||||
}, [onClick, workspace])}
|
||||
active={workspace.id === currentWorkspaceId}
|
||||
>
|
||||
<WorkspaceAvatar size={58} workspace={workspace} />
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{name}</StyleWorkspaceTitle>
|
||||
<WorkspaceType workspace={workspace} />
|
||||
{workspace.flavour === RemWorkspaceFlavour.LOCAL && (
|
||||
<p title={t('Available Offline')}>
|
||||
<LocalDataIcon />
|
||||
<span>{t('Available Offline')}</span>
|
||||
</p>
|
||||
)}
|
||||
{workspace.flavour === RemWorkspaceFlavour.AFFINE &&
|
||||
workspace.public && (
|
||||
<p title={t('Published to Web')}>
|
||||
<PublishIcon />
|
||||
<span>{t('Published to Web')}</span>
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
displayFlex,
|
||||
displayInlineFlex,
|
||||
styled,
|
||||
textEllipsis,
|
||||
} from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
|
||||
export const StyledSplitLine = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
background: theme.colors.borderColor,
|
||||
marginRight: '24px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
width: '202px',
|
||||
p: {
|
||||
color: theme.colors.popoverColor,
|
||||
height: '20px',
|
||||
fontSize: theme.font.sm,
|
||||
...displayFlex('flex-start', 'center'),
|
||||
},
|
||||
svg: {
|
||||
marginRight: '10px',
|
||||
fontSize: '16px',
|
||||
flexShrink: 0,
|
||||
},
|
||||
span: {
|
||||
flexGrow: 1,
|
||||
...textEllipsis(1),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '10px',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCard = styled.div<{
|
||||
active?: boolean;
|
||||
}>(({ theme, active }) => {
|
||||
const borderColor = active ? theme.colors.primaryColor : 'transparent';
|
||||
return {
|
||||
width: '310px',
|
||||
height: '124px',
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${borderColor}`,
|
||||
...displayFlex('flex-start', 'flex-start'),
|
||||
marginBottom: '24px',
|
||||
transition: 'background .2s',
|
||||
background: theme.mode === 'light' ? '#FFF' : '#2C2C2C',
|
||||
':hover': {
|
||||
background: theme.colors.cardHoverBackground,
|
||||
'.add-icon': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledFooter = styled('div')({
|
||||
height: '84px',
|
||||
padding: '0 40px',
|
||||
flexShrink: 0,
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
|
||||
export const StyleUserInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
flex: 1,
|
||||
p: {
|
||||
lineHeight: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'p:first-of-type': {
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeaderLeft = styled.div(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
marginLeft: '15px',
|
||||
fontWeight: 400,
|
||||
fontSize: theme.font.h6,
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalContent = styled('div')({
|
||||
height: '534px',
|
||||
padding: '8px 40px',
|
||||
marginTop: '72px',
|
||||
overflow: 'auto',
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
});
|
||||
export const StyledOperationWrapper = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('flex-end', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceAdd = styled.div(() => {
|
||||
return {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
background: '#f4f5fa',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
transition: 'background .2s',
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '72px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
padding: '0 40px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 700,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalWrapper,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { AccessTokenMessage } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { HelpIcon, PlusIcon } from '@blocksuite/icons';
|
||||
|
||||
import { RemWorkspace } from '../../../shared';
|
||||
import { Footer } from '../footer';
|
||||
import { WorkspaceCard } from '../workspace-card';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
import {
|
||||
StyledCard,
|
||||
StyledHelperContainer,
|
||||
StyledModalContent,
|
||||
StyledModalHeader,
|
||||
StyledModalHeaderLeft,
|
||||
StyledModalTitle,
|
||||
StyledOperationWrapper,
|
||||
StyledSplitLine,
|
||||
StyleWorkspaceAdd,
|
||||
StyleWorkspaceInfo,
|
||||
StyleWorkspaceTitle,
|
||||
} from './styles';
|
||||
|
||||
interface WorkspaceModalProps {
|
||||
user: AccessTokenMessage | null;
|
||||
workspaces: RemWorkspace[];
|
||||
currentWorkspaceId: RemWorkspace['id'] | null;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onClickWorkspace: (workspace: RemWorkspace) => void;
|
||||
onClickLogin: () => void;
|
||||
onClickLogout: () => void;
|
||||
onCreateWorkspace: () => void;
|
||||
}
|
||||
|
||||
export const WorkspaceListModal = ({
|
||||
open,
|
||||
onClose,
|
||||
workspaces,
|
||||
user,
|
||||
onClickLogin,
|
||||
onClickLogout,
|
||||
onClickWorkspace,
|
||||
onCreateWorkspace,
|
||||
currentWorkspaceId,
|
||||
}: WorkspaceModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={720}
|
||||
height={690}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t('Workspace description')}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledHelperContainer>
|
||||
<HelpIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<StyledOperationWrapper>
|
||||
<LanguageMenu />
|
||||
<StyledSplitLine />
|
||||
<ModalCloseButton
|
||||
data-testid="close-workspace-modal"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
absolute={false}
|
||||
/>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
|
||||
<StyledModalContent>
|
||||
{workspaces.map(workspace => {
|
||||
return (
|
||||
<WorkspaceCard
|
||||
workspace={workspace}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
key={workspace.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<StyledCard onClick={onCreateWorkspace}>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<PlusIcon />
|
||||
</StyleWorkspaceAdd>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
|
||||
<p>{t('Create Or Import')}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
</StyledModalContent>
|
||||
|
||||
<Footer user={user} onLogin={onClickLogin} onLogout={onClickLogout} />
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Button, Menu, MenuItem, styled } from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
const LanguageMenuContent: React.FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: React.FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={<LanguageMenuContent />}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button
|
||||
icon={<ArrowDownSmallIcon />}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
{currentLanguage?.originalName}
|
||||
</Button>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItem = styled(MenuItem)(({ theme }) => ({
|
||||
height: '38px',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.base,
|
||||
textTransform: 'capitalize',
|
||||
padding: '0 24px',
|
||||
}));
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
displayFlex,
|
||||
displayInlineFlex,
|
||||
styled,
|
||||
textEllipsis,
|
||||
} from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
|
||||
export const StyledSplitLine = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
background: theme.colors.borderColor,
|
||||
marginRight: '24px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
width: '202px',
|
||||
p: {
|
||||
color: theme.colors.popoverColor,
|
||||
height: '20px',
|
||||
fontSize: theme.font.sm,
|
||||
...displayFlex('flex-start', 'center'),
|
||||
},
|
||||
svg: {
|
||||
marginRight: '10px',
|
||||
fontSize: '16px',
|
||||
flexShrink: 0,
|
||||
},
|
||||
span: {
|
||||
flexGrow: 1,
|
||||
...textEllipsis(1),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '10px',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCard = styled.div<{
|
||||
active?: boolean;
|
||||
}>(({ theme, active }) => {
|
||||
const borderColor = active ? theme.colors.primaryColor : 'transparent';
|
||||
return {
|
||||
width: '310px',
|
||||
height: '124px',
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${borderColor}`,
|
||||
...displayFlex('flex-start', 'flex-start'),
|
||||
marginBottom: '24px',
|
||||
transition: 'background .2s',
|
||||
background: theme.mode === 'light' ? '#FFF' : '#2C2C2C',
|
||||
':hover': {
|
||||
background: theme.colors.cardHoverBackground,
|
||||
'.add-icon': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledFooter = styled('div')({
|
||||
height: '84px',
|
||||
padding: '0 40px',
|
||||
flexShrink: 0,
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
|
||||
export const StyleUserInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
flex: 1,
|
||||
p: {
|
||||
lineHeight: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'p:first-of-type': {
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeaderLeft = styled.div(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
marginLeft: '15px',
|
||||
fontWeight: 400,
|
||||
fontSize: theme.font.h6,
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalContent = styled('div')({
|
||||
height: '534px',
|
||||
padding: '8px 40px',
|
||||
marginTop: '72px',
|
||||
overflow: 'auto',
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
});
|
||||
export const StyledOperationWrapper = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('flex-end', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceAdd = styled.div(() => {
|
||||
return {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
background: '#f4f5fa',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
transition: 'background .2s',
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '72px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
padding: '0 40px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 700,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
import { RemWorkspace } from '../../../../shared';
|
||||
import { WorkspaceAvatar } from '../../workspace-avatar';
|
||||
import { SelectorWrapper, WorkspaceName } from './styles';
|
||||
|
||||
export type WorkspaceSelectorProps = {
|
||||
currentWorkspace: RemWorkspace | null;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const WorkspaceSelector: React.FC<WorkspaceSelectorProps> = ({
|
||||
currentWorkspace,
|
||||
onClick,
|
||||
}) => {
|
||||
let name = 'Untitled Workspace';
|
||||
if (currentWorkspace) {
|
||||
if (currentWorkspace.flavour === 'affine') {
|
||||
if (currentWorkspace.firstBinarySynced) {
|
||||
name = currentWorkspace.blockSuiteWorkspace.meta.name;
|
||||
}
|
||||
} else if (currentWorkspace.flavour === 'local') {
|
||||
name = currentWorkspace.blockSuiteWorkspace.meta.name;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<SelectorWrapper onClick={onClick} data-testid="current-workspace">
|
||||
<WorkspaceAvatar
|
||||
data-testid="workspace-avatar"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
}}
|
||||
size={32}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
<WorkspaceName data-testid="workspace-name">{name}</WorkspaceName>
|
||||
</SelectorWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WorkspaceSelector';
|
||||
@@ -0,0 +1,32 @@
|
||||
import { MuiAvatar, textEllipsis } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
export const SelectorWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
height: '42px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 12px',
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.textColor,
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const Avatar = styled(MuiAvatar)({
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
});
|
||||
|
||||
export const WorkspaceName = styled('span')(({ theme }) => {
|
||||
return {
|
||||
marginLeft: '12px',
|
||||
fontSize: theme.font.h6,
|
||||
fontWeight: 500,
|
||||
marginTop: '4px',
|
||||
flexGrow: 1,
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
export const Arrow = () => {
|
||||
return (
|
||||
<svg
|
||||
width="6"
|
||||
height="10"
|
||||
viewBox="0 0 6 10"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0.354 9.22997C0.201333 9.0773 0.125 8.91764 0.125 8.75097C0.125 8.5843 0.201333 8.42464 0.354 8.27197L3.625 5.00097L0.354 1.72997C0.201333 1.5773 0.125 1.41764 0.125 1.25097C0.125 1.0843 0.201333 0.924636 0.354 0.771969C0.506667 0.619302 0.666333 0.542969 0.833 0.542969C0.999667 0.542969 1.15933 0.619302 1.312 0.771969L4.979 4.43897C5.06233 4.52164 5.125 4.61164 5.167 4.70897C5.20833 4.8063 5.229 4.90364 5.229 5.00097C5.229 5.0983 5.20833 5.19564 5.167 5.29297C5.125 5.3903 5.06233 5.4803 4.979 5.56297L1.312 9.22997C1.15933 9.38264 0.999667 9.45897 0.833 9.45897C0.666333 9.45897 0.506667 9.38264 0.354 9.22997Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
export const AffineIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="39"
|
||||
height="39"
|
||||
rx="19.5"
|
||||
stroke="#6880FF"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18.6303 8.79688L11.2559 29.8393H15.5752L20.2661 15.2858L24.959 29.8393H29.2637L21.8881 8.79688H18.6303Z"
|
||||
fill="#6880FF"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,273 @@
|
||||
import { MuiCollapse } from '@affine/component';
|
||||
import { Tooltip } from '@affine/component';
|
||||
import { IconButton } from '@affine/component';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
DeleteTemporarilyIcon,
|
||||
FavoriteIcon,
|
||||
FolderIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { PageMeta } from '@blocksuite/store';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import { RemWorkspace } from '../../../shared';
|
||||
import { Arrow } from './icons';
|
||||
import {
|
||||
StyledArrowButton,
|
||||
StyledLink,
|
||||
StyledListItem,
|
||||
StyledNewPageButton,
|
||||
StyledSliderBar,
|
||||
StyledSliderBarWrapper,
|
||||
StyledSubListItem,
|
||||
} from './style';
|
||||
import { WorkspaceSelector } from './WorkspaceSelector';
|
||||
|
||||
export type FavoriteListProps = {
|
||||
currentPageId: string | null;
|
||||
openPage: (pageId: string) => void;
|
||||
showList: boolean;
|
||||
pageMeta: PageMeta[];
|
||||
};
|
||||
|
||||
const FavoriteList: React.FC<FavoriteListProps> = ({
|
||||
pageMeta,
|
||||
openPage,
|
||||
showList,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const favoriteList = useMemo(
|
||||
() => pageMeta.filter(p => p.favorite && !p.trash),
|
||||
[pageMeta]
|
||||
);
|
||||
return (
|
||||
<MuiCollapse in={showList}>
|
||||
{favoriteList.map((pageMeta, index) => {
|
||||
const active = router.query.pageId === pageMeta.id;
|
||||
return (
|
||||
<StyledSubListItem
|
||||
data-testid={`favorite-list-item-${pageMeta.id}`}
|
||||
active={active}
|
||||
key={`${pageMeta}-${index}`}
|
||||
onClick={() => {
|
||||
if (active) {
|
||||
return;
|
||||
}
|
||||
openPage(pageMeta.id);
|
||||
}}
|
||||
>
|
||||
{pageMeta.title || 'Untitled'}
|
||||
</StyledSubListItem>
|
||||
);
|
||||
})}
|
||||
{favoriteList.length === 0 && (
|
||||
<StyledSubListItem disable={true}>{t('No item')}</StyledSubListItem>
|
||||
)}
|
||||
</MuiCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export type WorkSpaceSliderBarProps = {
|
||||
isPublicWorkspace: boolean;
|
||||
onOpenQuickSearchModal: () => void;
|
||||
onOpenWorkspaceListModal: () => void;
|
||||
currentWorkspace: RemWorkspace | null;
|
||||
currentPageId: string | null;
|
||||
openPage: (pageId: string) => void;
|
||||
createPage: () => Promise<string>;
|
||||
show: boolean;
|
||||
setShow: (show: boolean) => void;
|
||||
currentPath: string;
|
||||
paths: {
|
||||
all: (workspaceId: string) => string;
|
||||
favorite: (workspaceId: string) => string;
|
||||
trash: (workspaceId: string) => string;
|
||||
setting: (workspaceId: string) => string;
|
||||
};
|
||||
};
|
||||
|
||||
export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
isPublicWorkspace,
|
||||
currentWorkspace,
|
||||
currentPageId,
|
||||
openPage,
|
||||
createPage,
|
||||
show,
|
||||
setShow,
|
||||
currentPath,
|
||||
paths,
|
||||
onOpenQuickSearchModal,
|
||||
onOpenWorkspaceListModal,
|
||||
}) => {
|
||||
const currentWorkspaceId = currentWorkspace?.id || null;
|
||||
const [showSubFavorite, setShowSubFavorite] = useState(true);
|
||||
const [showTip, setShowTip] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
|
||||
const onClickNewPage = useCallback(async () => {
|
||||
const pageId = await createPage();
|
||||
if (pageId) {
|
||||
openPage(pageId);
|
||||
}
|
||||
}, [createPage, openPage]);
|
||||
return (
|
||||
<>
|
||||
<StyledSliderBar show={isPublicWorkspace ? false : show}>
|
||||
<Tooltip
|
||||
content={show ? t('Collapse sidebar') : t('Expand sidebar')}
|
||||
placement="right"
|
||||
visible={showTip}
|
||||
>
|
||||
<StyledArrowButton
|
||||
data-testid="sliderBar-arrowButton"
|
||||
isShow={show}
|
||||
style={{
|
||||
visibility: isPublicWorkspace ? 'hidden' : 'visible',
|
||||
}}
|
||||
onClick={useCallback(() => {
|
||||
setShow(!show);
|
||||
setShowTip(false);
|
||||
}, [setShow, show])}
|
||||
onMouseEnter={useCallback(() => {
|
||||
setShowTip(true);
|
||||
}, [])}
|
||||
onMouseLeave={useCallback(() => {
|
||||
setShowTip(false);
|
||||
}, [])}
|
||||
>
|
||||
<Arrow />
|
||||
</StyledArrowButton>
|
||||
</Tooltip>
|
||||
|
||||
<StyledSliderBarWrapper data-testid="sliderBar">
|
||||
<WorkspaceSelector
|
||||
currentWorkspace={currentWorkspace}
|
||||
onClick={onOpenWorkspaceListModal}
|
||||
/>
|
||||
|
||||
<StyledListItem
|
||||
data-testid="slider-bar-quick-search-button"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={useCallback(() => {
|
||||
onOpenQuickSearchModal();
|
||||
}, [onOpenQuickSearchModal])}
|
||||
>
|
||||
<SearchIcon />
|
||||
{t('Quick search')}
|
||||
</StyledListItem>
|
||||
<Link
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.all(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<FolderIcon />
|
||||
<span data-testid="all-pages">{t('All pages')}</span>
|
||||
</StyledListItem>
|
||||
</Link>
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.favorite(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname:
|
||||
currentWorkspaceId && paths.favorite(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<FavoriteIcon />
|
||||
{t('Favorites')}
|
||||
</StyledLink>
|
||||
<IconButton
|
||||
darker={true}
|
||||
onClick={useCallback(() => {
|
||||
setShowSubFavorite(!showSubFavorite);
|
||||
}, [showSubFavorite])}
|
||||
>
|
||||
<ArrowDownSmallIcon
|
||||
style={{
|
||||
transform: `rotate(${showSubFavorite ? '180' : '0'}deg)`,
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</StyledListItem>
|
||||
<FavoriteList
|
||||
currentPageId={currentPageId}
|
||||
showList={showSubFavorite}
|
||||
openPage={openPage}
|
||||
pageMeta={pageMeta}
|
||||
/>
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.setting(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname:
|
||||
currentWorkspaceId && paths.setting(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<SettingsIcon />
|
||||
{t('Workspace Settings')}
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
|
||||
{/* <WorkspaceSetting
|
||||
isShow={showWorkspaceSetting}
|
||||
onClose={() => {
|
||||
setShowWorkspaceSetting(false);
|
||||
}}
|
||||
/> */}
|
||||
{/* TODO: will finish the feature next version */}
|
||||
{/* <StyledListItem
|
||||
onClick={() => {
|
||||
triggerImportModal();
|
||||
}}
|
||||
>
|
||||
<ImportIcon /> {t('Import')}
|
||||
</StyledListItem> */}
|
||||
|
||||
<Link
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.trash(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<DeleteTemporarilyIcon /> {t('Trash')}
|
||||
</StyledListItem>
|
||||
</Link>
|
||||
<StyledNewPageButton
|
||||
data-testid="new-page-button"
|
||||
onClick={onClickNewPage}
|
||||
>
|
||||
<PlusIcon /> {t('New Page')}
|
||||
</StyledNewPageButton>
|
||||
</StyledSliderBarWrapper>
|
||||
</StyledSliderBar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkSpaceSliderBar;
|
||||
@@ -0,0 +1,139 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const StyledSliderBar = styled.div<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
width: show ? '256px' : '0',
|
||||
height: '100vh',
|
||||
minHeight: '450px',
|
||||
background: theme.colors.hubBackground,
|
||||
boxShadow: theme.shadow.modal,
|
||||
transition: 'width .15s, padding .15s',
|
||||
position: 'relative',
|
||||
zIndex: theme.zIndex.modal,
|
||||
padding: show ? '24px 12px' : '24px 0',
|
||||
flexShrink: 0,
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledSliderBarWrapper = styled.div(() => {
|
||||
return {
|
||||
height: '100%',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledArrowButton = styled.button<{ isShow: boolean }>(
|
||||
({ theme, isShow }) => {
|
||||
return {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
...displayFlex('center', 'center'),
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
borderRadius: '50%',
|
||||
transition: 'all .15s',
|
||||
position: 'absolute',
|
||||
top: '34px',
|
||||
right: '-20px',
|
||||
zIndex: theme.zIndex.modal,
|
||||
svg: {
|
||||
transform: isShow ? 'rotate(180deg)' : 'unset',
|
||||
},
|
||||
':hover': {
|
||||
color: '#fff',
|
||||
backgroundColor: theme.colors.primaryColor,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledListItem = styled.div<{
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
}>(({ theme, active, disabled }) => {
|
||||
return {
|
||||
height: '32px',
|
||||
marginTop: '12px',
|
||||
color: active ? theme.colors.primaryColor : theme.colors.textColor,
|
||||
paddingLeft: '12px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
...(disabled
|
||||
? {
|
||||
cursor: 'not-allowed',
|
||||
color: theme.colors.borderColor,
|
||||
}
|
||||
: {}),
|
||||
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
':hover:not([disabled])': {
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLink = styled(Link)(() => {
|
||||
return {
|
||||
flexGrow: 1,
|
||||
textAlign: 'left',
|
||||
color: 'inherit',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
':visited': {
|
||||
color: 'inherit',
|
||||
},
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledNewPageButton = styled(StyledListItem)(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: '24px',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: 'auto',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSubListItem = styled.button<{
|
||||
disable?: boolean;
|
||||
active?: boolean;
|
||||
}>(({ theme, disable, active }) => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
marginTop: '4px',
|
||||
color: disable
|
||||
? theme.colors.disableColor
|
||||
: active
|
||||
? theme.colors.primaryColor
|
||||
: theme.colors.popoverColor,
|
||||
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
|
||||
|
||||
cursor: disable ? 'not-allowed' : 'pointer',
|
||||
paddingLeft: '45px',
|
||||
lineHeight: '32px',
|
||||
textAlign: 'start',
|
||||
...textEllipsis(1),
|
||||
':hover': disable
|
||||
? {}
|
||||
: {
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import Header from '../../blocksuite/header/header';
|
||||
import { StyledPageListTittleWrapper } from '../../blocksuite/header/styles';
|
||||
|
||||
export type WorkspaceTitleProps = React.PropsWithChildren<{
|
||||
icon?: ReactNode;
|
||||
}>;
|
||||
|
||||
export const WorkspaceTitle: React.FC<WorkspaceTitleProps> = ({
|
||||
icon,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<Header>
|
||||
<StyledPageListTittleWrapper>
|
||||
{icon}
|
||||
{children}
|
||||
{/* fixme(himself65): todo *;/}
|
||||
{/*<QuickSearchButton />*/}
|
||||
</StyledPageListTittleWrapper>
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user