TopBar.tsx 5.5 KB

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