TopBar.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { useEffect, useState } from "preact/hooks";
  2. import { EditorMode } from "./Editor.tsx";
  3. import Button from "../components/form/Button.tsx";
  4. import Input from "../components/form/Input.tsx";
  5. import Checkbox from "../components/form/Checkbox.tsx";
  6. interface TopBarProps {
  7. allowMode: EditorMode;
  8. isLogined: boolean;
  9. shared: boolean;
  10. title: string;
  11. id: string;
  12. }
  13. const settingsData: { [key: string]: string } = {};
  14. const shareData: { [key: string]: boolean } = {};
  15. export default function TopBar(props: TopBarProps) {
  16. const [mode, setMode] = useState(props.allowMode);
  17. const doLogout = async () => {
  18. const resp = await fetch("/api/user/logout");
  19. const respJson = await resp.json();
  20. if (respJson.success) {
  21. location.href = "/login";
  22. return true;
  23. }
  24. return false;
  25. };
  26. const goHome = () => {
  27. location.href = "/";
  28. };
  29. const showShare = () => {
  30. shareData["shared"] = shareData["submittedShared"] !== undefined
  31. ? shareData["submittedShared"]
  32. : props.shared;
  33. globalThis.$modal?.show(
  34. "Share options",
  35. <div style="display: flex; align-items: center">
  36. <Checkbox
  37. label="Share to friends"
  38. checked={shareData["shared"]}
  39. onChange={(e) => {
  40. shareData["shared"] = (e.target as HTMLInputElement).checked;
  41. (
  42. document.querySelector("#shared-button") as HTMLButtonElement
  43. ).style.visibility = shareData["shared"] ? "visible" : "hidden";
  44. }}
  45. />
  46. <Button
  47. type="button"
  48. id="shared-button"
  49. className="ml-2"
  50. style={`${!shareData["shared"] ? ";visibility: hidden" : ""}`}
  51. onClick={async () => {
  52. await navigator.clipboard.writeText(location.href.split("?")[0]);
  53. }}
  54. >
  55. Copy Link
  56. </Button>
  57. </div>,
  58. [
  59. {
  60. text: "Confirm",
  61. onClick: async () => {
  62. const resp = await fetch("/api/share", {
  63. method: "POST",
  64. headers: { "Content-Type": "application/json" },
  65. body: JSON.stringify({
  66. id: props.id,
  67. shared: shareData["shared"],
  68. }),
  69. });
  70. const respJson = await resp.json();
  71. if (respJson.success) {
  72. shareData["submittedShared"] = shareData["shared"];
  73. globalThis.$modal?.hide();
  74. }
  75. },
  76. },
  77. ],
  78. );
  79. };
  80. const showSetting = () => {
  81. settingsData["title"] = settingsData["submittedTitle"] || props.title;
  82. globalThis.$modal?.show(
  83. "Post Settings",
  84. <div style="display: flex; align-items: center">
  85. <span style="margin-right: 8px">Title</span>
  86. <Input
  87. placeholder="Post title here"
  88. value={settingsData["title"]}
  89. onInput={(e) => {
  90. settingsData["title"] = (e.target as HTMLInputElement).value;
  91. }}
  92. />
  93. </div>,
  94. [
  95. {
  96. text: "Confirm",
  97. onClick: async () => {
  98. const resp = await fetch("/api/post", {
  99. method: "PUT",
  100. headers: { "Content-Type": "application/json" },
  101. body: JSON.stringify({
  102. id: props.id,
  103. title: settingsData["title"],
  104. }),
  105. });
  106. const respJson = await resp.json();
  107. if (respJson.success) {
  108. settingsData["submittedTitle"] = settingsData["title"];
  109. document.title = settingsData["title"];
  110. globalThis.$modal?.hide();
  111. }
  112. },
  113. },
  114. ],
  115. );
  116. };
  117. // Event listener
  118. const modeChangeListener = (e: Event) => {
  119. const { detail } = e as CustomEvent;
  120. if (
  121. detail &&
  122. (props.allowMode === detail || props.allowMode === EditorMode.Both)
  123. ) {
  124. setMode(detail);
  125. }
  126. };
  127. // Event dispatcher
  128. const modeChangeDispatcher = (mode: EditorMode) => {
  129. dispatchEvent(new CustomEvent("ModeChange", { detail: mode }));
  130. };
  131. // Init event listeners
  132. useEffect(() => {
  133. addEventListener("ModeChange", modeChangeListener);
  134. return () => {
  135. removeEventListener("ModeChange", modeChangeListener);
  136. };
  137. }, []);
  138. return (
  139. <div className="w-full flex mb-3 justify-between box-border flex-shrink-0">
  140. <div
  141. className={`border border-gray-300 rounded box-border text-sm ${
  142. props.allowMode !== EditorMode.Both ? "hidden" : ""
  143. }`}
  144. >
  145. <Button
  146. variant={mode === EditorMode.Edit ? "primary" : "default"}
  147. className="rounded-tr-none rounded-br-none"
  148. id="edit"
  149. type="button"
  150. onClick={() => {
  151. modeChangeDispatcher(EditorMode.Edit);
  152. }}
  153. >
  154. Edit
  155. </Button>
  156. <Button
  157. variant={mode === EditorMode.Read ? "primary" : "default"}
  158. className="rounded-none"
  159. id="read"
  160. type="button"
  161. onClick={() => {
  162. modeChangeDispatcher(EditorMode.Read);
  163. }}
  164. >
  165. Read
  166. </Button>
  167. <Button
  168. variant={mode === EditorMode.Both ? "primary" : "default"}
  169. className="rounded-tl-none rounded-bl-none"
  170. id="both"
  171. type="button"
  172. onClick={() => {
  173. modeChangeDispatcher(EditorMode.Both);
  174. }}
  175. >
  176. Both
  177. </Button>
  178. </div>
  179. {!props.isLogined
  180. ? (
  181. <span className="leading-[30px] text-2xl font-medium text-center w-full">
  182. {props.title}
  183. </span>
  184. )
  185. : null}
  186. {props.isLogined
  187. ? (
  188. <div className="h-[30px] leading-[28px] box-border px-2 border border-gray-300 rounded">
  189. <i
  190. className="bi bi-box-arrow-left mr-4 text-base cursor-pointer h-4 w-4 hover:text-blue-600"
  191. onClick={doLogout}
  192. />
  193. <i
  194. className="bi bi-house-door mr-4 text-base cursor-pointer h-4 w-4 hover:text-blue-600"
  195. onClick={goHome}
  196. />
  197. <i
  198. className="bi bi-share mr-4 text-base cursor-pointer h-4 w-4 hover:text-blue-600"
  199. onClick={showShare}
  200. />
  201. <i
  202. className="bi bi-gear text-base cursor-pointer h-4 w-4 hover:text-blue-600"
  203. onClick={showSetting}
  204. />
  205. </div>
  206. )
  207. : null}
  208. </div>
  209. );
  210. }