SentEdit.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { Affix } from "antd";
  2. import { useEffect, useRef, useState } from "react";
  3. import { IStudio } from "../auth/Studio";
  4. import type { IUser } from "../auth/User";
  5. import { IChannel } from "../channel/Channel";
  6. import { TContentType } from "../discussion/DiscussionCreate";
  7. import { ITocPathNode } from "../corpus/TocPath";
  8. import SentContent from "./SentEdit/SentContent";
  9. import SentTab from "./SentEdit/SentTab";
  10. import { IWbw } from "./Wbw/WbwWord";
  11. import { ArticleMode } from "../article/Article";
  12. import { TChannelType } from "../api/Channel";
  13. import { useAppSelector } from "../../hooks";
  14. import { currFocus } from "../../reducers/focus";
  15. import { ISentenceData } from "../api/Corpus";
  16. import "./SentEdit/style.css";
  17. import SentCell from "./SentEdit/SentCell";
  18. import { settingInfo } from "../../reducers/setting";
  19. import { GetUserSetting } from "../auth/setting/default";
  20. import { SENTENCE_FIX_WIDTH } from "../../types/article";
  21. import { useSetting } from "../../hooks/useSetting";
  22. export interface IResNumber {
  23. translation?: number;
  24. nissaya?: number;
  25. commentary?: number;
  26. origin?: number;
  27. sim?: number;
  28. }
  29. export interface ISuggestionCount {
  30. suggestion?: number;
  31. discussion?: number;
  32. }
  33. export interface ISentence {
  34. id?: string;
  35. uid?: string;
  36. content: string | null;
  37. contentType?: TContentType;
  38. html: string;
  39. book: number;
  40. para: number;
  41. wordStart: number;
  42. wordEnd: number;
  43. editor: IUser;
  44. acceptor?: IUser;
  45. prEditAt?: string;
  46. channel: IChannel;
  47. studio?: IStudio;
  48. forkAt?: string | null;
  49. updateAt: string;
  50. createdAt?: string;
  51. suggestionCount?: ISuggestionCount;
  52. openInEditMode?: boolean;
  53. translationChannels?: string[];
  54. }
  55. export interface ISentenceId {
  56. book: number;
  57. para: number;
  58. wordStart: number;
  59. wordEnd: number;
  60. }
  61. export const toISentence = (apiData: ISentenceData): ISentence => ({
  62. id: apiData.id,
  63. content: apiData.content,
  64. contentType: apiData.content_type,
  65. html: apiData.html,
  66. book: apiData.book,
  67. para: apiData.paragraph,
  68. wordStart: apiData.word_start,
  69. wordEnd: apiData.word_end,
  70. editor: apiData.editor,
  71. studio: apiData.studio,
  72. channel: apiData.channel,
  73. updateAt: apiData.updated_at,
  74. acceptor: apiData.acceptor,
  75. prEditAt: apiData.pr_edit_at,
  76. forkAt: apiData.fork_at,
  77. suggestionCount: apiData.suggestionCount,
  78. });
  79. export interface IWidgetSentEditInner {
  80. id: string;
  81. book: number;
  82. para: number;
  83. wordStart: number;
  84. wordEnd: number;
  85. channels?: string[];
  86. origin?: ISentence[];
  87. translation?: ISentence[];
  88. commentaries?: ISentence[];
  89. answer?: ISentence;
  90. path?: ITocPathNode[];
  91. layout?: "row" | "column";
  92. tranNum?: number;
  93. nissayaNum?: number;
  94. commNum?: number;
  95. originNum: number;
  96. simNum?: number;
  97. compact?: boolean;
  98. mode?: ArticleMode;
  99. showWbwProgress?: boolean;
  100. readonly?: boolean;
  101. wbwProgress?: number;
  102. wbwScore?: number;
  103. onTranslationChange?: (data: ISentence) => void;
  104. }
  105. export const SentEditInner = ({
  106. id,
  107. book,
  108. para,
  109. wordStart,
  110. wordEnd,
  111. channels,
  112. origin,
  113. translation,
  114. answer,
  115. path,
  116. layout = "column",
  117. tranNum,
  118. nissayaNum,
  119. commNum,
  120. originNum,
  121. simNum,
  122. compact = false,
  123. mode,
  124. showWbwProgress = false,
  125. readonly = false,
  126. commentaries,
  127. onTranslationChange,
  128. }: IWidgetSentEditInner) => {
  129. const [wbwData, setWbwData] = useState<IWbw[]>();
  130. const [magicDict, setMagicDict] = useState<string>();
  131. const [magicDictLoading, setMagicDictLoading] = useState(false);
  132. const [isCompact, setIsCompact] = useState(compact);
  133. const [articleMode, setArticleMode] = useState<ArticleMode | undefined>(mode);
  134. const [loadedRes, setLoadedRes] = useState<IResNumber>();
  135. const [isFocus, setIsFocus] = useState(false);
  136. const focus = useAppSelector(currFocus);
  137. const divRef = useRef<HTMLDivElement>(null);
  138. const [affix, setAffix] = useState<boolean>(false);
  139. const settings = useAppSelector(settingInfo);
  140. const [commentaryLayout, setCommentaryLayout] = useState("column");
  141. const rootFixed = useSetting("setting.layout.root.fixed");
  142. useEffect(() => {
  143. const layoutCommentary = GetUserSetting(
  144. "setting.layout.commentary",
  145. settings
  146. );
  147. layoutCommentary &&
  148. typeof layoutCommentary === "string" &&
  149. setCommentaryLayout(layoutCommentary);
  150. }, [settings]);
  151. useEffect(() => {
  152. if (focus) {
  153. if (focus.focus?.type === "sentence") {
  154. if (focus.focus.id === id) {
  155. setIsFocus(true);
  156. divRef.current?.scrollIntoView({
  157. behavior: "smooth",
  158. block: "nearest",
  159. inline: "nearest",
  160. });
  161. } else {
  162. setIsFocus(false);
  163. }
  164. }
  165. } else {
  166. setIsFocus(false);
  167. }
  168. }, [focus, id]);
  169. useEffect(() => {
  170. const validRes = (value: ISentence, type: TChannelType) =>
  171. value.channel.type === type &&
  172. value.content &&
  173. value.content.trim().length > 0;
  174. if (translation) {
  175. const res = {
  176. translation: translation.filter((value) =>
  177. validRes(value, "translation")
  178. ).length,
  179. nissaya: translation.filter((value) => validRes(value, "nissaya"))
  180. .length,
  181. commentary: translation.filter((value) => validRes(value, "commentary"))
  182. .length,
  183. };
  184. setLoadedRes(res);
  185. }
  186. }, [translation]);
  187. useEffect(() => {
  188. const content = origin?.find(
  189. (value) => value.contentType === "json"
  190. )?.content;
  191. if (content) {
  192. setWbwData(JSON.parse(content));
  193. }
  194. }, []);
  195. const channelsId = translation?.map((item) => item.channel.id);
  196. const content = (
  197. <SentContent
  198. sid={id}
  199. book={book}
  200. para={para}
  201. wordStart={wordStart}
  202. wordEnd={wordEnd}
  203. origin={origin}
  204. translation={translation}
  205. answer={answer}
  206. layout={layout}
  207. magicDict={magicDict}
  208. compact={isCompact}
  209. mode={articleMode}
  210. wbwProgress={showWbwProgress}
  211. readonly={readonly}
  212. onWbwChange={(data: IWbw[]) => {
  213. setWbwData(data);
  214. }}
  215. onMagicDictDone={() => {
  216. setMagicDictLoading(false);
  217. setMagicDict(undefined);
  218. }}
  219. onTranslationChange={onTranslationChange}
  220. />
  221. );
  222. return (
  223. <div
  224. ref={divRef}
  225. className={`sent-edit-inner` + (isFocus ? " sent-focus" : "")}
  226. style={{
  227. display: commentaryLayout === "column" ? "block" : "flex",
  228. width: commentaryLayout === "column" ? "100%" : SENTENCE_FIX_WIDTH,
  229. }}
  230. >
  231. <div>
  232. {affix || rootFixed === true ? (
  233. <Affix offsetTop={44}>
  234. <div className="affix">{content}</div>
  235. </Affix>
  236. ) : (
  237. content
  238. )}
  239. <div
  240. style={{
  241. width: commentaryLayout === "column" ? "unset" : SENTENCE_FIX_WIDTH,
  242. }}
  243. >
  244. <SentTab
  245. id={id}
  246. book={book}
  247. para={para}
  248. wordStart={wordStart}
  249. wordEnd={wordEnd}
  250. channelsId={channelsId}
  251. path={path}
  252. tranNum={tranNum}
  253. nissayaNum={nissayaNum}
  254. commNum={commNum}
  255. originNum={originNum}
  256. simNum={simNum}
  257. loadedRes={loadedRes}
  258. wbwData={wbwData}
  259. origin={origin}
  260. magicDictLoading={magicDictLoading}
  261. compact={isCompact}
  262. mode={articleMode}
  263. onMagicDict={(type: string) => {
  264. setMagicDict(type);
  265. setMagicDictLoading(true);
  266. }}
  267. onCompact={(value: boolean) => setIsCompact(value)}
  268. onModeChange={(value: ArticleMode | undefined) =>
  269. setArticleMode(value)
  270. }
  271. onAffix={() => setAffix(!affix)}
  272. />
  273. </div>
  274. </div>
  275. <div className="pcd_sent_commentary">
  276. {commentaries?.map((item, id) => {
  277. return (
  278. <SentCell
  279. value={item}
  280. key={id}
  281. isPr={false}
  282. editMode={item.openInEditMode}
  283. />
  284. );
  285. })}
  286. </div>
  287. </div>
  288. );
  289. };
  290. interface IWidgetSentEdit {
  291. props: string;
  292. }
  293. const Widget = ({ props }: IWidgetSentEdit) => {
  294. const prop = JSON.parse(atob(props)) as IWidgetSentEditInner;
  295. //console.log("sent data", prop);
  296. return <SentEditInner {...prop} />;
  297. };
  298. export default Widget;