a tool for shared writing and social publishing
1"use client"; 2 3import React, { JSX, useState } from "react"; 4import { useUIState } from "src/useUIState"; 5import { useEntitySetContext } from "../EntitySetProvider"; 6 7import { useReplicache } from "src/replicache"; 8 9import { Media } from "../Media"; 10import { MenuItem, Menu } from "../Layout"; 11import { PageThemeSetter } from "../ThemeManager/PageThemeSetter"; 12import { PageShareMenu } from "./PageShareMenu"; 13import { useUndoState } from "src/undoManager"; 14import { CloseTiny } from "components/Icons/CloseTiny"; 15import { MoreOptionsTiny } from "components/Icons/MoreOptionsTiny"; 16import { PaintSmall } from "components/Icons/PaintSmall"; 17import { ShareSmall } from "components/Icons/ShareSmall"; 18import { useCardBorderHidden } from "./useCardBorderHidden"; 19import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 20 21export const PageOptionButton = ({ 22 children, 23 secondary, 24 cardBorderHidden, 25 className, 26 disabled, 27 ...props 28}: { 29 children: React.ReactNode; 30 secondary?: boolean; 31 cardBorderHidden: boolean | undefined; 32 className?: string; 33 disabled?: boolean; 34} & Omit<JSX.IntrinsicElements["button"], "content">) => { 35 return ( 36 <button 37 className={` 38 pageOptionsTrigger 39 shrink-0 40 pt-[2px] h-5 w-5 p-0.5 mx-auto 41 border border-border 42 ${secondary ? "bg-border text-bg-page" : "bg-bg-page text-border"} 43 ${disabled && "opacity-50"} 44 ${cardBorderHidden ? "rounded-md" : `rounded-b-md sm:rounded-l-none sm:rounded-r-md`} 45 flex items-center justify-center 46 ${className} 47 48 `} 49 {...props} 50 > 51 {children} 52 </button> 53 ); 54}; 55 56export const PageOptions = (props: { 57 entityID: string; 58 first: boolean | undefined; 59 isFocused: boolean; 60}) => { 61 let cardBorderHidden = useCardBorderHidden(props.entityID); 62 63 return ( 64 <div 65 className={`pageOptions w-fit z-10 66 ${props.isFocused ? "block" : "sm:hidden block"} 67 absolute sm:-right-[20px] right-3 sm:top-3 top-0 68 flex sm:flex-col flex-row-reverse gap-1 items-start`} 69 > 70 {!props.first && ( 71 <PageOptionButton 72 cardBorderHidden={cardBorderHidden} 73 secondary 74 onClick={() => { 75 useUIState.getState().closePage(props.entityID); 76 }} 77 > 78 <CloseTiny /> 79 </PageOptionButton> 80 )} 81 <OptionsMenu 82 entityID={props.entityID} 83 first={!!props.first} 84 cardBorderHidden={cardBorderHidden} 85 /> 86 <UndoButtons cardBorderHidden={cardBorderHidden} /> 87 </div> 88 ); 89}; 90 91export const UndoButtons = (props: { 92 cardBorderHidden: boolean | undefined; 93}) => { 94 let undoState = useUndoState(); 95 let { undoManager } = useReplicache(); 96 return ( 97 <Media mobile> 98 {undoState.canUndo && ( 99 <div className="gap-1 flex sm:flex-col"> 100 <PageOptionButton 101 secondary 102 cardBorderHidden={props.cardBorderHidden} 103 onClick={() => undoManager.undo()} 104 > 105 <UndoTiny /> 106 </PageOptionButton> 107 108 <PageOptionButton 109 secondary 110 cardBorderHidden={props.cardBorderHidden} 111 onClick={() => undoManager.undo()} 112 disabled={!undoState.canRedo} 113 > 114 <RedoTiny /> 115 </PageOptionButton> 116 </div> 117 )} 118 </Media> 119 ); 120}; 121 122export const OptionsMenu = (props: { 123 entityID: string; 124 first: boolean; 125 cardBorderHidden: boolean | undefined; 126}) => { 127 let [state, setState] = useState<"normal" | "theme" | "share">("normal"); 128 let { permissions } = useEntitySetContext(); 129 if (!permissions.write) return null; 130 131 let { data: pub, mutate } = useLeafletPublicationData(); 132 if (pub && props.first) return; 133 return ( 134 <Menu 135 align="end" 136 asChild 137 onOpenChange={(open) => { 138 if (!open) setState("normal"); 139 }} 140 trigger={ 141 <PageOptionButton 142 cardBorderHidden={props.cardBorderHidden} 143 className="!w-8 !h-5 sm:!w-5 sm:!h-8" 144 > 145 <MoreOptionsTiny className="sm:rotate-90" /> 146 </PageOptionButton> 147 } 148 > 149 {state === "normal" ? ( 150 <> 151 {!props.first && ( 152 <MenuItem 153 onSelect={(e) => { 154 e.preventDefault(); 155 setState("share"); 156 }} 157 > 158 <ShareSmall /> Share Page 159 </MenuItem> 160 )} 161 {!pub && ( 162 <MenuItem 163 onSelect={(e) => { 164 e.preventDefault(); 165 setState("theme"); 166 }} 167 > 168 <PaintSmall /> Theme Page 169 </MenuItem> 170 )} 171 </> 172 ) : state === "theme" ? ( 173 <PageThemeSetter entityID={props.entityID} /> 174 ) : state === "share" ? ( 175 <PageShareMenu entityID={props.entityID} /> 176 ) : null} 177 </Menu> 178 ); 179}; 180 181const UndoTiny = () => { 182 return ( 183 <svg 184 width="16" 185 height="16" 186 viewBox="0 0 16 16" 187 fill="none" 188 xmlns="http://www.w3.org/2000/svg" 189 > 190 <path 191 fillRule="evenodd" 192 clipRule="evenodd" 193 d="M5.98775 3.14543C6.37828 2.75491 6.37828 2.12174 5.98775 1.73122C5.59723 1.34069 4.96407 1.34069 4.57354 1.73122L1.20732 5.09744C0.816798 5.48796 0.816798 6.12113 1.20732 6.51165L4.57354 9.87787C4.96407 10.2684 5.59723 10.2684 5.98775 9.87787C6.37828 9.48735 6.37828 8.85418 5.98775 8.46366L4.32865 6.80456H9.6299C12.1732 6.80456 13.0856 8.27148 13.0856 9.21676C13.0856 9.84525 12.8932 10.5028 12.5318 10.9786C12.1942 11.4232 11.6948 11.7367 10.9386 11.7367H9.43173C8.87944 11.7367 8.43173 12.1844 8.43173 12.7367C8.43173 13.2889 8.87944 13.7367 9.43173 13.7367H10.9386C12.3587 13.7367 13.4328 13.0991 14.1246 12.1883C14.7926 11.3086 15.0856 10.2062 15.0856 9.21676C15.0856 6.92612 13.0205 4.80456 9.6299 4.80456L4.32863 4.80456L5.98775 3.14543Z" 194 fill="currentColor" 195 /> 196 </svg> 197 ); 198}; 199 200const RedoTiny = () => { 201 return ( 202 <svg 203 width="16" 204 height="16" 205 viewBox="0 0 16 16" 206 fill="none" 207 xmlns="http://www.w3.org/2000/svg" 208 > 209 <path 210 fillRule="evenodd" 211 clipRule="evenodd" 212 d="M10.0122 3.14543C9.62172 2.75491 9.62172 2.12174 10.0122 1.73122C10.4028 1.34069 11.0359 1.34069 11.4265 1.73122L14.7927 5.09744C15.1832 5.48796 15.1832 6.12113 14.7927 6.51165L11.4265 9.87787C11.0359 10.2684 10.4028 10.2684 10.0122 9.87787C9.62172 9.48735 9.62172 8.85418 10.0122 8.46366L11.6713 6.80456H6.3701C3.82678 6.80456 2.91443 8.27148 2.91443 9.21676C2.91443 9.84525 3.10681 10.5028 3.46817 10.9786C3.8058 11.4232 4.30523 11.7367 5.06143 11.7367H6.56827C7.12056 11.7367 7.56827 12.1844 7.56827 12.7367C7.56827 13.2889 7.12056 13.7367 6.56827 13.7367H5.06143C3.6413 13.7367 2.56723 13.0991 1.87544 12.1883C1.20738 11.3086 0.914429 10.2062 0.914429 9.21676C0.914429 6.92612 2.97946 4.80456 6.3701 4.80456L11.6714 4.80456L10.0122 3.14543Z" 213 fill="currentColor" 214 /> 215 </svg> 216 ); 217};