TopBar.tsx 5.4 KB

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