From 2d4d726120cfa126c9497c6d4a8236d60da73843 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Tue, 21 Nov 2023 08:58:08 +0000 Subject: [PATCH 1/9] load_data_from_json --- src/pages/movies2.tsx | 51 +++++-------------------------------------- src/pages/qanda.tsx | 24 +++++--------------- src/services/urls.ts | 12 +--------- 3 files changed, 12 insertions(+), 75 deletions(-) diff --git a/src/pages/movies2.tsx b/src/pages/movies2.tsx index ae4ede2..13fddda 100644 --- a/src/pages/movies2.tsx +++ b/src/pages/movies2.tsx @@ -1,11 +1,9 @@ -import type { NextPage, GetServerSideProps } from "next"; +import type { NextPage, GetStaticProps } from "next"; import Head from "next/head"; import Image from "next/image"; import { BackButton } from "@components/buttons/BackButton"; -import { ensureWithoutBom, parseCSV } from "@libs/csv"; import { Locale } from "@services/i18n/i18n"; -import { moviesDataUrl } from "@services/urls"; type PageProps = { data: MovieData[]; @@ -13,57 +11,18 @@ type PageProps = { type MovieData = { title: string; - video_url: string; thumbnail_url: string; thumbnail_width: number; thumbnail_height: number; thumbnail_url_with_play_button: string; upload_date: string; uri: string; -}; -const getOEmbedData = async (movieUrl: string) => { - const base = `https://vimeo.com/api/oembed.json`; - const params = new URLSearchParams({ - url: movieUrl, - autoplay: "true", - responsive: "true", - }); - const url = `${base}?${params.toString()}`; - console.log(url); - const res = await fetch(url).then(res => res.json()); - console.log(res); - if (!res) return null; - return res as MovieData; -}; - -const loadCsv = async (url: string) => { - const text = await fetch(url).then(res => res.text()); - if (!text) return null; - const res = parseCSV(ensureWithoutBom(text)); - const [header, ...body] = res.ok ? (res.value as string[][]) : ([] as string[][]); - const data = body.map(row => { - const zipped = header.map((key, i) => [key, row[i]]); - return Object.fromEntries(zipped) as Record; - }); - console.log(data); - return data; + player_url: string; }; -export const getServerSideProps: GetServerSideProps = async ({ locale }) => { - const notNull = (x: unknown): x is NonNullable => x !== null && x !== undefined; - const url = moviesDataUrl(locale as Locale); - const table = await loadCsv(url); - const data = table - ? (( - await Promise.all( - table.map(async row => { - const movieUrl = row["url"]; - const oEmbedData = await getOEmbedData(movieUrl); - return oEmbedData ? oEmbedData : null; - }), - ) - ).filter(notNull) as MovieData[]) - : []; +export const getStaticProps: GetStaticProps = async ({ locale }) => { + locale = locale as Locale; + const data = (await import("json_in_repo/movies.json")).default as MovieData[]; return { props: { data }, diff --git a/src/pages/qanda.tsx b/src/pages/qanda.tsx index 6c9a7c7..df12d99 100755 --- a/src/pages/qanda.tsx +++ b/src/pages/qanda.tsx @@ -1,29 +1,17 @@ -import type { NextPage, GetServerSideProps } from "next"; +import type { NextPage, GetStaticProps } from "next"; import Head from "next/head"; import { BackButton } from "@components/buttons/BackButton"; - -import { ensureWithoutBom, parseCSV } from "@libs/csv"; import { Locale } from "@services/i18n/i18n"; -import { qAndAUrl } from "@services/urls"; type PageProps = { data: { question: string; answer: string }[]; }; -export const getServerSideProps: GetServerSideProps = async ({ locale }) => { - const url = qAndAUrl(locale as Locale); - const text = await fetch(url).then(res => res.text()); - if (!text) { - return { - props: { data: [] }, - }; - } - const res = parseCSV(ensureWithoutBom(text)); - const [header, ...body] = res.ok ? (res.value as string[][]) : ([] as string[][]); - const data = body.map(row => { - const zipped = header.map((key, i) => [key, row[i]]); - return Object.fromEntries(zipped) as { question: string; answer: string }; - }); +type QandA = { question: string; answer: string }[]; + +export const getStaticProps: GetStaticProps = async ({ locale }) => { + locale = locale as Locale; + const data = (await import("json_in_repo/q-and-a.json")).default as QandA; return { props: { data }, }; diff --git a/src/services/urls.ts b/src/services/urls.ts index b6e7f7f..116a9a2 100755 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -1,6 +1,6 @@ import { useRouter } from "next/router"; import { isValidShortId } from "@libs/uuid_translator"; -import { Locale, locales } from "./i18n/i18n"; +import { locales } from "./i18n/i18n"; const origin = typeof window !== "undefined" && window?.location @@ -12,16 +12,6 @@ const useFullUrl = () => { return origin + router.asPath; }; -const qAndAUrl = (locale: Locale) => { - // eslint-disable-next-line max-len - return "https://docs.google.com/spreadsheets/d/1xGi_y12VTi2KxLwqqq7ad7mDljNRAfKXI-JkGl0K04Y/export?format=csv&gid=0"; -}; - -const moviesDataUrl = (locale: Locale) => { - // eslint-disable-next-line max-len - return `https://docs.google.com/spreadsheets/d/1MNa7Zh2h5vGnSYUHU3vFjWPN4GveYsF_xXZnC0wXUOI/export?format=csv&gid=0`; -}; - const itemUrlToId = (url: string) => { const params = url.replace(`${origin}/x`, ""); const id = locales.reduce((acc, locale) => { From f07c8804f0f715f99ddd516c4a1d7c839f658ee7 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Tue, 21 Nov 2023 10:04:40 +0000 Subject: [PATCH 2/9] adated to new json types --- src/pages/movies3/[id].tsx | 76 ++++++++++++++++++++ src/pages/{movies2.tsx => movies3/index.tsx} | 40 ++++++++--- src/pages/qanda.tsx | 5 +- 3 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 src/pages/movies3/[id].tsx rename src/pages/{movies2.tsx => movies3/index.tsx} (64%) diff --git a/src/pages/movies3/[id].tsx b/src/pages/movies3/[id].tsx new file mode 100644 index 0000000..196099b --- /dev/null +++ b/src/pages/movies3/[id].tsx @@ -0,0 +1,76 @@ +import type { NextPage, GetStaticProps, GetStaticPaths } from "next"; +import Image from "next/image"; +import { BackButton } from "@components/buttons/BackButton"; + +import { Locale, Locales } from "@services/i18n/i18n"; +import { MovieData } from "."; + +type PageProps = { + data: MovieData[]; + id: string; +}; + +type PathParams = { + id: string; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + const data = { + ja: (await import("json_in_repo/movies/ja.json")).default, + en: (await import("json_in_repo/movies/en.json")).default, + }; + const paths = (["en", "ja"] as Locales) + .map(locale => data[locale].map(({ id }) => ({ params: { id }, locale }))) + .flat(); + return { + paths, + fallback: false, + }; +}; + +export const getStaticProps: GetStaticProps = async ({ locale, params }) => { + const id = (params?.id as string) || ""; + locale = locale as Locale; + const data = + locale === "ja" + ? (await import(`json_in_repo/movies/ja.json`)).default + : (await import(`json_in_repo/movies/en.json`)).default; + + return { + props: { data, id }, + }; +}; + +const HeaderBar = () => { + return ( +
+
+ +
+
+ ); +}; + +const Card = ({ data }: { data: MovieData["data"] }) => { + return ( +
+ {data.title} +
{data.title}
+
+ ); +}; + +const QandAPage: NextPage = ({ data, id }: PageProps) => { + return <>{id}; +}; + +export default QandAPage; diff --git a/src/pages/movies2.tsx b/src/pages/movies3/index.tsx similarity index 64% rename from src/pages/movies2.tsx rename to src/pages/movies3/index.tsx index 13fddda..24a3d20 100644 --- a/src/pages/movies2.tsx +++ b/src/pages/movies3/index.tsx @@ -11,18 +11,35 @@ type PageProps = { type MovieData = { title: string; - thumbnail_url: string; - thumbnail_width: number; - thumbnail_height: number; - thumbnail_url_with_play_button: string; - upload_date: string; - uri: string; - player_url: string; + category: string; + url: string; + id: string; + data: { + type: string; + version: string; + provider_name: string; + provider_url: string; + title: string; + author_name: string; + author_url: string; + html: string; + player_url: string; + thumbnail_url: string; + thumbnail_width: number; + thumbnail_height: number; + thumbnail_url_with_play_button: string; + upload_date: string; + uri: string; + id: string; + }; }; export const getStaticProps: GetStaticProps = async ({ locale }) => { locale = locale as Locale; - const data = (await import("json_in_repo/movies.json")).default as MovieData[]; + const data = + locale === "ja" + ? (await import(`json_in_repo/movies/ja.json`)).default + : (await import(`json_in_repo/movies/en.json`)).default; return { props: { data }, @@ -39,7 +56,7 @@ const HeaderBar = () => { ); }; -const Card = ({ data }: { data: MovieData }) => { +const Card = ({ data }: { data: MovieData["data"] }) => { return (
= ({ data }: PageProps) => { return ( <> - QandA + Movies
{data.map((movieData, i) => { - return ; + return ; })}
@@ -74,3 +91,4 @@ const QandAPage: NextPage = ({ data }: PageProps) => { }; export default QandAPage; +export { type MovieData }; diff --git a/src/pages/qanda.tsx b/src/pages/qanda.tsx index df12d99..58ceb43 100755 --- a/src/pages/qanda.tsx +++ b/src/pages/qanda.tsx @@ -11,7 +11,10 @@ type QandA = { question: string; answer: string }[]; export const getStaticProps: GetStaticProps = async ({ locale }) => { locale = locale as Locale; - const data = (await import("json_in_repo/q-and-a.json")).default as QandA; + const data = + locale === "ja" + ? (await import(`json_in_repo/q-and-a/ja.json`)).default + : (await import(`json_in_repo/q-and-a/en.json`)).default; return { props: { data }, }; From 00cf4b5c51e4f0489884e1c2eccb8340fc2e0723 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Tue, 21 Nov 2023 10:16:35 +0000 Subject: [PATCH 3/9] implement new movie page --- src/components/buttons/BackButton.tsx | 9 ++++- src/pages/movies.tsx | 50 ------------------------- src/pages/{movies3 => movies}/[id].tsx | 31 +++++++-------- src/pages/{movies3 => movies}/index.tsx | 17 +++++---- src/services/urls.ts | 11 +----- 5 files changed, 32 insertions(+), 86 deletions(-) delete mode 100644 src/pages/movies.tsx rename src/pages/{movies3 => movies}/[id].tsx (76%) rename src/pages/{movies3 => movies}/index.tsx (85%) diff --git a/src/components/buttons/BackButton.tsx b/src/components/buttons/BackButton.tsx index 66bcf35..18fed3c 100755 --- a/src/components/buttons/BackButton.tsx +++ b/src/components/buttons/BackButton.tsx @@ -2,10 +2,15 @@ import Link from "next/link"; import { MdArrowBack } from "react-icons/md"; import { useLocaleText } from "@services/i18n/i18n"; -const BackButton = () => { +type BackButtonProps = { + href?: string; +}; + +const BackButton = ({ href }: BackButtonProps) => { const { t } = useLocaleText("@components/buttons/BackButton"); + href = href || "/"; return ( - + { - return ( -
-
- -
-
- ); -}; - -const CiteAsPage: NextPage = () => { - const { t } = useTranslation("@pages/movies"); - return ( - <> - - {t("title")} - -
- -
-

{t("h1")}

-
{t("discription")}
- -
- - {t("linkToListPage")} - - -
-
-
- -
-
-
- - ); -}; - -export default CiteAsPage; diff --git a/src/pages/movies3/[id].tsx b/src/pages/movies/[id].tsx similarity index 76% rename from src/pages/movies3/[id].tsx rename to src/pages/movies/[id].tsx index 196099b..5031906 100644 --- a/src/pages/movies3/[id].tsx +++ b/src/pages/movies/[id].tsx @@ -1,12 +1,11 @@ import type { NextPage, GetStaticProps, GetStaticPaths } from "next"; -import Image from "next/image"; import { BackButton } from "@components/buttons/BackButton"; import { Locale, Locales } from "@services/i18n/i18n"; import { MovieData } from "."; type PageProps = { - data: MovieData[]; + data: MovieData | undefined; id: string; }; @@ -31,11 +30,11 @@ export const getStaticPaths: GetStaticPaths = async () => { export const getStaticProps: GetStaticProps = async ({ locale, params }) => { const id = (params?.id as string) || ""; locale = locale as Locale; - const data = + const allData = locale === "ja" ? (await import(`json_in_repo/movies/ja.json`)).default : (await import(`json_in_repo/movies/en.json`)).default; - + const data = allData.find(({ id: _id }) => _id === id); return { props: { data, id }, }; @@ -45,7 +44,7 @@ const HeaderBar = () => { return (
- +
); @@ -53,24 +52,22 @@ const HeaderBar = () => { const Card = ({ data }: { data: MovieData["data"] }) => { return ( -
- {data.title} +
+ +
+ +
+
{data.title}
); }; const QandAPage: NextPage = ({ data, id }: PageProps) => { - return <>{id}; + return <>{data ? : `not found ${id}`}; }; export default QandAPage; diff --git a/src/pages/movies3/index.tsx b/src/pages/movies/index.tsx similarity index 85% rename from src/pages/movies3/index.tsx rename to src/pages/movies/index.tsx index 24a3d20..2e484ea 100644 --- a/src/pages/movies3/index.tsx +++ b/src/pages/movies/index.tsx @@ -1,6 +1,7 @@ import type { NextPage, GetStaticProps } from "next"; import Head from "next/head"; import Image from "next/image"; +import Link from "next/link"; import { BackButton } from "@components/buttons/BackButton"; import { Locale } from "@services/i18n/i18n"; @@ -63,13 +64,15 @@ const Card = ({ data }: { data: MovieData["data"] }) => { className="w-[fit-content] rounded-md drop-shadow-md transition hover:opacity-60 hover:drop-shadow-xl" > - {data.title} -
{data.title}
+ + {data.title} +
{data.title}
+
); }; diff --git a/src/services/urls.ts b/src/services/urls.ts index 116a9a2..2e47621 100755 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -33,13 +33,4 @@ const isValidItemUrlOrId = (urlOrId: string) => { return isValidShortId(id); }; -export { - origin, - useFullUrl, - itemUrlToId, - isValidItemUrlOrId, - itemIdToUrl, - objectiveIdToUrl, - qAndAUrl, - moviesDataUrl, -}; +export { origin, useFullUrl, itemUrlToId, isValidItemUrlOrId, itemIdToUrl, objectiveIdToUrl }; From e992428bda027f9987358433f9e823d67272c4b6 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Tue, 21 Nov 2023 10:18:00 +0000 Subject: [PATCH 4/9] remove unused file --- bin/make-movies-data.ts | 62 ----------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 bin/make-movies-data.ts diff --git a/bin/make-movies-data.ts b/bin/make-movies-data.ts deleted file mode 100644 index 63ccbc8..0000000 --- a/bin/make-movies-data.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { ensureWithoutBom, parseCSV } from "@libs/csv"; -import { Locale } from "@services/i18n/i18n"; -import { moviesDataUrl } from "@services/urls"; - -type MovieData = { - title: string; - video_url: string; - thumbnail_url: string; - thumbnail_width: number; - thumbnail_height: number; - thumbnail_url_with_play_button: string; - upload_date: string; - uri: string; -}; -const getOEmbedData = async (movieUrl: string) => { - const base = `https://vimeo.com/api/oembed.json`; - const params = new URLSearchParams({ - url: movieUrl, - autoplay: "true", - responsive: "true", - }); - const url = `${base}?${params.toString()}`; - console.log(url); - const res = await fetch(url).then(res => res.json()); - console.log(res); - if (!res) return null; - return res as MovieData; -}; - -const loadCsv = async (url: string) => { - const text = await fetch(url).then(res => res.text()); - if (!text) return null; - const res = parseCSV(ensureWithoutBom(text)); - const [header, ...body] = res.ok ? (res.value as string[][]) : ([] as string[][]); - const data = body.map(row => { - const zipped = header.map((key, i) => [key, row[i]]); - return Object.fromEntries(zipped) as Record; - }); - console.log(data); - return data; -}; - -const makeData = async (locale: Locale) => { - const notNull = (x: unknown): x is NonNullable => x !== null && x !== undefined; - const url = moviesDataUrl(locale as Locale); - const table = await loadCsv(url); - const data = table - ? (( - await Promise.all( - table.map(async row => { - const movieUrl = row["url"]; - const oEmbedData = await getOEmbedData(movieUrl); - return oEmbedData ? oEmbedData : null; - }), - ) - ).filter(notNull) as MovieData[]) - : []; - - console.log(data); -}; - -makeData("ja"); From 89993259b2b8c29fd8431f197d1d14b457b75b1d Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Tue, 21 Nov 2023 11:03:34 +0000 Subject: [PATCH 5/9] add format in description --- src/pages/movies/[id].tsx | 35 ++++++++++++++++++++++++++++++++++- src/pages/movies/index.tsx | 27 +++++++++++++++++++++------ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/pages/movies/[id].tsx b/src/pages/movies/[id].tsx index 5031906..595739b 100644 --- a/src/pages/movies/[id].tsx +++ b/src/pages/movies/[id].tsx @@ -1,4 +1,5 @@ import type { NextPage, GetStaticProps, GetStaticPaths } from "next"; +import Link from "next/link"; import { BackButton } from "@components/buttons/BackButton"; import { Locale, Locales } from "@services/i18n/i18n"; @@ -50,6 +51,35 @@ const HeaderBar = () => { ); }; +const textToTextWithLink = (text: string) => { + const urlRegex = /(https?:\/\/[^\s]+)/g; + const texts = text.split(urlRegex); + return texts.map((text, i) => { + return ( + + {text.match(urlRegex) ? ( + + {text} + + ) : ( + text + )} + + ); + }); +}; + +const Desctiption = ({ text }: { text: string }) => { + const texts = text.split("\n"); + return ( +
+ {texts.map((text, i) => { + return

{textToTextWithLink(text)}

; + })} +
+ ); +}; + const Card = ({ data }: { data: MovieData["data"] }) => { return (
@@ -61,7 +91,10 @@ const Card = ({ data }: { data: MovieData["data"] }) => { >
-
{data.title}
+
{data.title}
+
+ +
); }; diff --git a/src/pages/movies/index.tsx b/src/pages/movies/index.tsx index 2e484ea..f66f000 100644 --- a/src/pages/movies/index.tsx +++ b/src/pages/movies/index.tsx @@ -32,6 +32,7 @@ type MovieData = { upload_date: string; uri: string; id: string; + description: string; }; }; @@ -77,21 +78,35 @@ const Card = ({ data }: { data: MovieData["data"] }) => { ); }; -const QandAPage: NextPage = ({ data }: PageProps) => { +const MoviesPage: NextPage = ({ data }: PageProps) => { + const categories = [...new Set(data.map(({ category }) => category))]; + const categoryData = categories.map(category => { + return { + category, + data: data.filter(({ category: _category }) => _category === category), + }; + }); return ( <> Movies -
- {data.map((movieData, i) => { - return ; - })} +
+ {categoryData.map((data, i) => ( +
+

{data.category}

+
+ {data.data.map((movieData, i) => { + return ; + })} +
+
+ ))}
); }; -export default QandAPage; +export default MoviesPage; export { type MovieData }; From 98dcf2a09256a0afaf9273abb54cc5068aeb2d15 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Wed, 29 Nov 2023 06:52:34 +0000 Subject: [PATCH 6/9] add link for tables --- src/components/ContextMenu.tsx | 4 +- src/components/OutcomesTOC.tsx | 53 ++++++++++++++++++-------- src/components/StyledText.tsx | 14 +++++-- src/components/Table.tsx | 4 +- src/components/buttons/BackButton.tsx | 4 +- src/pages/index.tsx | 9 ++++- src/pages/tables/index.tsx | 54 +++++++++++++++++++++++++++ src/services/i18n/text.ts | 6 +++ src/services/tables.ts | 6 ++- 9 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 src/pages/tables/index.tsx diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 938758b..93405a8 100755 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -60,7 +60,9 @@ const ContextMenu = ({ ); return name in links && links[name] ? ( - {content} + + {content} + ) : ( content ); diff --git a/src/components/OutcomesTOC.tsx b/src/components/OutcomesTOC.tsx index ccc3c0d..e0a0de1 100755 --- a/src/components/OutcomesTOC.tsx +++ b/src/components/OutcomesTOC.tsx @@ -1,3 +1,4 @@ +import Link from "next/link"; import { ReactNode, useCallback, useState } from "react"; import { useScrollObserver } from "@hooks/IntersecterObserver"; import type { Tree } from "@libs/treeUtils"; @@ -43,7 +44,7 @@ const OutcomesTOC = ({ outcomesTree }: OutcomesTreeProps) => { case "l3": return ; } - return <>; + return ; })} ); @@ -63,8 +64,30 @@ const ChildList = ({ active, childnodes }: { active: boolean; childnodes: ReactN ); }; -const ItemText = ({ children }: { targeted: boolean; children: ReactNode }) => { - return {children}; +type MenuItemProps = { + children: ReactNode; + padding?: string; + size?: string; + href: string; + targeted?: boolean; +}; +const MenuItem = ({ children, padding, size, href, targeted }: MenuItemProps) => { + padding ??= "pl-2"; + size ??= ""; + const linkProps = { + className: `block w-full truncate border-l-4 py-3 ${padding} ${size} + hover:bg-info/20 hover:underline ${targeted ? " border-info " : "border-transparent"}`, + href, + }; + return ( +
+ {href.startsWith("#") ? ( + {children} + ) : ( + {children} + )} +
+ ); }; const ItemList = ({ item, childnodes, active, targeted }: PropType) => { @@ -80,6 +103,12 @@ const ItemList = ({ item, childnodes, active, targeted }: l2: "text-sm", l3: "text-sm", }[item.layer]; + const menuItemProps = { + padding, + size, + href: id, + targeted, + }; return (
  • ({ item, childnodes, active, targeted }: targeted ? "text-info" : "text-base-content" } transition-all duration-500 ease-in-out`} > - + + {item.index.slice(-2)} + {item.text} + {item.layer !== "l3" ? : ""}
  • ); }; -export { OutcomesTOC }; +export { OutcomesTOC, MenuItem }; diff --git a/src/components/StyledText.tsx b/src/components/StyledText.tsx index 62e5e42..00ffaf6 100755 --- a/src/components/StyledText.tsx +++ b/src/components/StyledText.tsx @@ -20,11 +20,19 @@ const StyledText = ({ text, map }: { text: string; map: MappedInfo[] } ); case "sub": - return {text}; + return ( + + {text} + + ); case "italic": - return {text}; + return ( + + {text} + + ); } - return <>{"unknown:" + text + ":" + attr.type}; + return {"unknown:" + text + ":" + attr.type}; })} ); diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 3a2b5d7..1120eab 100755 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -27,8 +27,8 @@ const Table = ({ {rowList.map((row, i) => ( - {header.map(key => ( - + {header.map((key, i) => ( + {row.id in attrInfo && key in attrInfo[row.id] ? ( ) : ( diff --git a/src/components/buttons/BackButton.tsx b/src/components/buttons/BackButton.tsx index 18fed3c..3106350 100755 --- a/src/components/buttons/BackButton.tsx +++ b/src/components/buttons/BackButton.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { useRouter } from "next/router"; import { MdArrowBack } from "react-icons/md"; import { useLocaleText } from "@services/i18n/i18n"; @@ -8,7 +9,8 @@ type BackButtonProps = { const BackButton = ({ href }: BackButtonProps) => { const { t } = useLocaleText("@components/buttons/BackButton"); - href = href || "/"; + const router = useRouter(); + href = href || (router.query.referer as string) || "/"; return ( = ({ outcomesTree }: PageProps) => { } - menu={} + menu={ + + + {t("tables")} + + } /> ); diff --git a/src/pages/tables/index.tsx b/src/pages/tables/index.tsx new file mode 100644 index 0000000..67dd185 --- /dev/null +++ b/src/pages/tables/index.tsx @@ -0,0 +1,54 @@ +import type { NextPage, GetStaticProps } from "next"; +import Head from "next/head"; +import Link from "next/link"; +import { BackButton } from "@components/buttons/BackButton"; +import { Locale, useLocaleText } from "@services/i18n/i18n"; +import { getTalbleInfoList, TableInfo } from "@services/tables"; + +type PageProps = { tables: TableInfo[] }; + +type PathParams = { + id: string; +}; + +export const getStaticProps: GetStaticProps = async ({ locale }) => { + return { + props: { tables: getTalbleInfoList(locale as Locale) }, + }; +}; + +const TableList: NextPage = ({ tables }: PageProps) => { + const { t } = useLocaleText("@pages/list/tables"); + return ( + <> + + {t("title")} + +
    +
    + +
    +
    +

    {t("h1")}

    +
    +
    +
    + {tables.map(({ link, item, index, number }) => { + const name = `${t("table") + number}. ${item}`; + return ( +
    + + {name} + +
    + ); + })} +
    + + ); +}; + +export default TableList; diff --git a/src/services/i18n/text.ts b/src/services/i18n/text.ts index a2ee940..553c1c3 100755 --- a/src/services/i18n/text.ts +++ b/src/services/i18n/text.ts @@ -187,6 +187,11 @@ const text = { title: { ja: "{name} | モデルコアカリキュラム", en: "{name} | Model Core Curriculum" }, table: { ja: "表", en: "Table" }, }, + "@pages/list/tables": { + title: { ja: "別表一覧 | モデルコアカリキュラム", en: "Tables | Model Core Curriculum" }, + h1: { ja: "別表一覧", en: "Tables" }, + table: { ja: "表", en: "Table" }, + }, "@pages/x/[id]": { title: { ja: "{name} | モデルコアカリキュラム", en: "{name} | Model Core Curriculum" }, table: { ja: "表", en: "Table" }, @@ -290,6 +295,7 @@ const text = { }, "@pages/index": { outcomesTitle: { ja: "資質・能力", en: "Outcomes" }, + tables: { ja: "別表一覧", en: "Tables" }, discription: { // eslint-disable-next-line max-len ja: "モデル・コア・カリキュラムでは、医学生が卒業するまでに身に付けるべき能力を10の資質・能力(第1層)として提示し、詳細を第2層から4層までの項目と、関連づけられた別表で示しています。", diff --git a/src/services/tables.ts b/src/services/tables.ts index 0b0108e..2964763 100755 --- a/src/services/tables.ts +++ b/src/services/tables.ts @@ -42,6 +42,10 @@ const getTableFiles = (locale: Locale) => { return Object.keys(loadTableInfoDict(locale)); }; +const getTalbleInfoList = (locale: Locale) => { + return Object.values(loadTableInfoDict(locale)); +}; + const getTable = (file: string, locale: Locale): TableInfoSet => { const tableInfo = loadTableInfoDict(locale)[file]; const rawTable = renameColumns(loadTable(file, locale), tableInfo.columns); @@ -76,5 +80,5 @@ type TableInfo = Expand; type TableAttrInfo = { [id: string]: { [key: string]: MappedInfo[] } }; type TableInfoSet = { table: HeaderedTable; tableInfo: TableInfo; attrInfo: TableAttrInfo }; -export { loadTableInfoDict, getTableFiles, getTable, getAllTables }; +export { loadTableInfoDict, getTableFiles, getTable, getAllTables, getTalbleInfoList }; export type { TableInfo, TableInfoDict, TableAttrInfo, TableInfoSet }; From 9751e8fb06ad97f669822eec25bcceb307e7de41 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Wed, 29 Nov 2023 10:45:18 +0000 Subject: [PATCH 7/9] load data from json in outcomes and tables --- src/components/Outcomes.tsx | 2 +- src/components/StyledText.tsx | 2 +- src/libs/textMapper.ts | 1 + src/libs/treeUtils.ts | 1 + src/pages/forms/outcomes.tsx | 35 ----- src/pages/forms/skills.tsx | 77 ---------- src/pages/forms/symptoms.tsx | 68 --------- src/pages/index.tsx | 7 +- src/pages/list.tsx | 10 +- src/pages/search.tsx | 10 +- src/pages/tables/[id].tsx | 18 ++- src/pages/tables/index.tsx | 6 +- src/pages/x/[id].tsx | 10 +- .../__test__/curriculumMapTable.spec.ts | 8 +- src/services/attrInfo.ts | 37 +++++ src/services/outcomes.ts | 141 +++++------------- src/services/replaceMap.ts | 98 ------------ src/services/tables.ts | 121 +++++++-------- 18 files changed, 165 insertions(+), 487 deletions(-) delete mode 100755 src/pages/forms/outcomes.tsx delete mode 100755 src/pages/forms/skills.tsx delete mode 100755 src/pages/forms/symptoms.tsx create mode 100755 src/services/attrInfo.ts delete mode 100755 src/services/replaceMap.ts diff --git a/src/components/Outcomes.tsx b/src/components/Outcomes.tsx index df8c086..c60f056 100755 --- a/src/components/Outcomes.tsx +++ b/src/components/Outcomes.tsx @@ -6,6 +6,7 @@ import { applyMappedInfo } from "@libs/textMapper"; import type { MappedInfo } from "@libs/textMapper"; import type { Tree } from "@libs/treeUtils"; import { reduceTree } from "@libs/treeUtils"; +import { AttrInfo } from "@services/attrInfo"; import type { OutcomeInfo, Outcomel1 as L1, @@ -13,7 +14,6 @@ import type { Outcomel3 as L3, Outcomel4 as L4, } from "@services/outcomes"; -import { AttrInfo } from "@services/replaceMap"; type PropType = { item: T; diff --git a/src/components/StyledText.tsx b/src/components/StyledText.tsx index 00ffaf6..7a1d6ce 100755 --- a/src/components/StyledText.tsx +++ b/src/components/StyledText.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { applyMappedInfo, MappedInfo } from "@libs/textMapper"; -import { AttrInfo } from "@services/replaceMap"; +import { AttrInfo } from "@services/attrInfo"; const StyledText = ({ text, map }: { text: string; map: MappedInfo[] }) => { if (map.length === 0) return <>{text}; diff --git a/src/libs/textMapper.ts b/src/libs/textMapper.ts index 923b5d5..bab9c14 100755 --- a/src/libs/textMapper.ts +++ b/src/libs/textMapper.ts @@ -16,6 +16,7 @@ type MappedText = { }; type TextMapper = (res: RegExpExecArray) => MapperResult; type ReplaceMap = { reg: RegExp; mapper: TextMapper }[]; + const mapText = (text: string, replaceMap: ReplaceMap): MappedText => { const seek: (str: string) => (string | MapperResultWithPos | null)[] = (str: string) => { const res = replaceMap.reduce( diff --git a/src/libs/treeUtils.ts b/src/libs/treeUtils.ts index 70fc71c..54e5d28 100755 --- a/src/libs/treeUtils.ts +++ b/src/libs/treeUtils.ts @@ -125,6 +125,7 @@ const searchMap = (tree: Tree, searchMapper: TreeSearchMapper) => }; type TreeMapper = (item: T, parents: T[], hasChildren: boolean) => R; + const mapTree = (tree: Tree, mapper: TreeMapper) => { const walkTree: (currTree: Tree, currParents?: T[]) => Tree = ( currTree: Tree, diff --git a/src/pages/forms/outcomes.tsx b/src/pages/forms/outcomes.tsx deleted file mode 100755 index a8e7045..0000000 --- a/src/pages/forms/outcomes.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { NextPage, GetStaticProps } from "next"; -import Head from "next/head"; -import { OutcomesChecklists } from "@components/OutcomesChecklists"; -import type { Tree } from "@libs/treeUtils"; -import { Locale } from "@services/i18n/i18n"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; -import type { OutcomeInfo } from "@services/outcomes"; -import { loadTableInfoDict } from "@services/tables"; - -type PageProps = { outcomesTree: Tree }; - -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadFullOutcomesTable(locale as Locale); - const tableInfoDict = loadTableInfoDict(locale as Locale); - const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); - - return { - props: { outcomesTree }, - }; -}; - -const Home: NextPage = ({ outcomesTree }: PageProps) => { - return ( - <> - - Outcomes Checklists - - - - - - ); -}; - -export default Home; diff --git a/src/pages/forms/skills.tsx b/src/pages/forms/skills.tsx deleted file mode 100755 index b7bc3de..0000000 --- a/src/pages/forms/skills.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import type { NextPage, GetStaticProps } from "next"; -import Head from "next/head"; -import { getNameColumnValues, mapRow } from "@libs/tableUtils"; -import type { HeaderedTable } from "@libs/tableUtils"; -import type { Locale } from "@services/i18n/i18n"; -import { loadTable } from "@services/loadCsv"; - -type PageProps = { table: HeaderedTable }; - -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadTable("TBL0300", locale as Locale); - - return { - props: { table }, - }; -}; - -type RatingCheckProp = { name: string }; -const RatingCheck = ({ name }: RatingCheckProp) => ( -
    - - - - - -
    -); - -const Home: NextPage = ({ table }: PageProps) => { - const skillTable = mapRow(table, row => ({ - skill: { - name: row["item"], - id: row["id"], - index: row["index"], - }, - })); - const skillList = getNameColumnValues(skillTable, "skill"); - return ( - <> - - Skills Checklists - - - -

    基本的臨床手技

    - - - - - - - - - - {skillList.map((skill, i) => ( - - - - - - ))} - -
    基本的臨床手技 -
    自己評価
    -
    1-5
    -
    -
    指導者評価
    1-5
    -
    {skill.name} - - - -
    - - ); -}; - -export default Home; diff --git a/src/pages/forms/symptoms.tsx b/src/pages/forms/symptoms.tsx deleted file mode 100755 index 0f0279b..0000000 --- a/src/pages/forms/symptoms.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type { NextPage, GetStaticProps } from "next"; -import Head from "next/head"; -import { getNameColumnValues, mapRow } from "@libs/tableUtils"; -import type { HeaderedTable } from "@libs/tableUtils"; -import type { Locale } from "@services/i18n/i18n"; -import { loadTable } from "@services/loadCsv"; - -type PageProps = { table: HeaderedTable }; - -export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadTable("TBL0500", locale as Locale); - - return { - props: { table }, - }; -}; - -const Home: NextPage = ({ table }: PageProps) => { - const skillTable = mapRow< - string, - { name: string; id: string; index: string; diseases: string[] } - >(table, row => ({ - main: { - name: row["item"], - diseases: row["ddx"].split(",").filter(v => v.trim() !== ""), - id: row["id"], - index: row["index"], - }, - })); - const list = getNameColumnValues(skillTable, "main"); - return ( - <> - - Symptoms Checklists - - - -

    経験した症候

    - - - - - - - - - {list.map((unit, j) => ( - - - - - ))} - -
    症候鑑別を考慮した疾患
    {unit.name} - {unit.diseases.map((disease, i) => ( - - - - - ))} -
    - - ); -}; - -export default Home; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d650ebb..a5f179e 100755 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,16 +6,13 @@ import { OutcomesTree } from "@components/Outcomes"; import { MenuItem, OutcomesTOC } from "@components/OutcomesTOC"; import type { Tree } from "@libs/treeUtils"; import { Locale, useLocaleText } from "@services/i18n/i18n"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; +import { loadOutcomesTree } from "@services/outcomes"; import type { OutcomeInfo } from "@services/outcomes"; -import { loadTableInfoDict } from "@services/tables"; type PageProps = { outcomesTree: Tree }; export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadFullOutcomesTable(locale as Locale); - const tableInfoDict = loadTableInfoDict(locale as Locale); - const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); + const outcomesTree = await loadOutcomesTree(locale as Locale); return { props: { outcomesTree }, diff --git a/src/pages/list.tsx b/src/pages/list.tsx index d3726a6..3b64ed5 100755 --- a/src/pages/list.tsx +++ b/src/pages/list.tsx @@ -23,10 +23,10 @@ import { schemaItemsWithValue, useClearItemList, } from "@services/itemList/local"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; +import { loadOutcomesTree } from "@services/outcomes"; import type { OutcomeInfo } from "@services/outcomes"; import { searchOutcomes, searchTables } from "@services/search"; -import { getAllTables, loadTableInfoDict, TableInfoSet } from "@services/tables"; +import { getAllTables, TableInfoSet } from "@services/tables"; import { itemIdToUrl, objectiveIdToUrl } from "@services/urls"; type PageProps = { @@ -35,10 +35,8 @@ type PageProps = { }; export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadFullOutcomesTable(locale as Locale); - const tableInfoDict = loadTableInfoDict(locale as Locale); - const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); - const allTables = getAllTables(locale as Locale); + const outcomesTree = await loadOutcomesTree(locale as Locale); + const allTables = await getAllTables(locale as Locale); return { props: { outcomesTree, allTables }, diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 60f0b2b..549e2de 100755 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -9,10 +9,10 @@ import { BackButton } from "@components/buttons/BackButton"; import { HeaderedTable } from "@libs/tableUtils"; import { Tree } from "@libs/treeUtils"; import { Locale, useLocaleText } from "@services/i18n/i18n"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; +import { loadOutcomesTree } from "@services/outcomes"; import type { OutcomeInfo } from "@services/outcomes"; import { searchOutcomes, searchTables } from "@services/search"; -import { getAllTables, loadTableInfoDict, TableInfoSet } from "@services/tables"; +import { getAllTables, TableInfoSet } from "@services/tables"; type PageProps = { outcomesTree: Tree; @@ -20,10 +20,8 @@ type PageProps = { }; export const getStaticProps: GetStaticProps = async ({ locale }) => { - const table = loadFullOutcomesTable(locale as Locale); - const tableInfoDict = loadTableInfoDict(locale as Locale); - const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); - const allTables = getAllTables(locale as Locale); + const outcomesTree = await loadOutcomesTree(locale as Locale); + const allTables = await getAllTables(locale as Locale); return { props: { outcomesTree, allTables }, diff --git a/src/pages/tables/[id].tsx b/src/pages/tables/[id].tsx index f5e86fb..a37ad8e 100755 --- a/src/pages/tables/[id].tsx +++ b/src/pages/tables/[id].tsx @@ -2,10 +2,9 @@ import type { NextPage, GetStaticProps, GetStaticPaths } from "next"; import Head from "next/head"; import { Table } from "@components/Table"; import { BackButton } from "@components/buttons/BackButton"; -import { dropColumnsByNames } from "@libs/tableUtils"; import { fmt } from "@libs/utils"; import { Locale, Locales, useLocaleText } from "@services/i18n/i18n"; -import { getTable, getTableFiles, TableInfoSet } from "@services/tables"; +import { getTable, getTableFiles, TableFile, TableInfoSet } from "@services/tables"; type PageProps = TableInfoSet; @@ -13,10 +12,14 @@ type PathParams = { id: string; }; -export const getStaticPaths: GetStaticPaths = () => { - const paths = (["en", "ja"] as Locales) - .map(locale => getTableFiles(locale as Locale).map(file => ({ params: { id: file }, locale }))) - .flat(); +export const getStaticPaths: GetStaticPaths = async () => { + const paths = ( + await Promise.all( + (["en", "ja"] as Locales).map(async locale => + (await getTableFiles(locale as Locale)).map(file => ({ params: { id: file }, locale })), + ), + ) + ).flat(); return { paths, fallback: false, @@ -27,14 +30,13 @@ export const getStaticProps: GetStaticProps = async ({ locale, params const { id } = params as PathParams; return { - props: getTable(id, locale as Locale), + props: await getTable(id as TableFile, locale as Locale), }; }; const TableOfId: NextPage = ({ table, tableInfo, attrInfo }: PageProps) => { const { t } = useLocaleText("@pages/list/table/[id]"); const name = `${t("table") + tableInfo.number}. ${tableInfo.item}`; - const [header, ...body] = dropColumnsByNames(table, ["id", "index", "H28ID"]); return ( <> diff --git a/src/pages/tables/index.tsx b/src/pages/tables/index.tsx index 67dd185..ac13ac9 100644 --- a/src/pages/tables/index.tsx +++ b/src/pages/tables/index.tsx @@ -7,13 +7,9 @@ import { getTalbleInfoList, TableInfo } from "@services/tables"; type PageProps = { tables: TableInfo[] }; -type PathParams = { - id: string; -}; - export const getStaticProps: GetStaticProps = async ({ locale }) => { return { - props: { tables: getTalbleInfoList(locale as Locale) }, + props: { tables: await getTalbleInfoList(locale as Locale) }, }; }; diff --git a/src/pages/x/[id].tsx b/src/pages/x/[id].tsx index 449fa0d..5a5da91 100755 --- a/src/pages/x/[id].tsx +++ b/src/pages/x/[id].tsx @@ -23,10 +23,10 @@ import { schemaItemsWithValue, } from "@services/itemList/local"; import { getItemListFromId, getItemListFromIds } from "@services/itemList/server"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; +import { loadOutcomesTree } from "@services/outcomes"; import type { OutcomeInfo } from "@services/outcomes"; import { searchOutcomes, searchTables } from "@services/search"; -import { getAllTables, loadTableInfoDict, TableInfoSet } from "@services/tables"; +import { getAllTables, TableInfoSet } from "@services/tables"; import { itemIdToUrl } from "@services/urls"; type PageProps = { @@ -65,10 +65,8 @@ const getServerItems = async (ids: readonly string[]) => { }; export const getStaticProps: GetStaticProps = async ({ locale, params }) => { - const table = loadFullOutcomesTable(locale as Locale); - const tableInfoDict = loadTableInfoDict(locale as Locale); - const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); - const allTables = getAllTables(locale as Locale); + const outcomesTree = await loadOutcomesTree(locale as Locale); + const allTables = await getAllTables(locale as Locale); const id = (params?.id as string) ?? ""; const itemList = await getServerItem(id); const children = diff --git a/src/services/__test__/curriculumMapTable.spec.ts b/src/services/__test__/curriculumMapTable.spec.ts index 0e71df3..7bd1328 100755 --- a/src/services/__test__/curriculumMapTable.spec.ts +++ b/src/services/__test__/curriculumMapTable.spec.ts @@ -4,13 +4,11 @@ import { parseCSV } from "@libs/csv"; import { makeOutcomeTableData } from "@services/curriculumMapTable"; import { Locale } from "@services/i18n/i18n"; import { ServerItemList } from "@services/itemList/server"; -import { loadFullOutcomesTable, makeOutcomesTree } from "@services/outcomes"; -import { getAllTables, loadTableInfoDict } from "@services/tables"; +import { loadOutcomesTree } from "@services/outcomes"; +import { getAllTables } from "@services/tables"; const locale: Locale = "ja"; -const table = loadFullOutcomesTable(locale as Locale); -const tableInfoDict = loadTableInfoDict(locale as Locale); -const outcomesTree = makeOutcomesTree(table, tableInfoDict, locale as Locale); +const outcomesTree = await loadOutcomesTree(locale as Locale); const allTables = getAllTables(locale as Locale); const dummyItems: ServerItemList[] = [ diff --git a/src/services/attrInfo.ts b/src/services/attrInfo.ts new file mode 100755 index 0000000..40f62d8 --- /dev/null +++ b/src/services/attrInfo.ts @@ -0,0 +1,37 @@ +type LinkInfo = { + type: "link"; + url: string; +}; +type TableLinkInfo = { + type: "tableLink"; + url: string; + id: string; + index: string; + title: string; +}; +type AbbrInfo = { + type: "abbr"; + def: string; +}; +type SubTagInfo = { + type: "sub"; +}; +type SupTagInfo = { + type: "sup"; +}; +type TextInfo = { + type: "text"; +}; +type ItalicInfo = { + type: "italic"; +}; +type AttrInfo = + | LinkInfo + | AbbrInfo + | TableLinkInfo + | SubTagInfo + | SupTagInfo + | TextInfo + | ItalicInfo; + +export type { AttrInfo }; diff --git a/src/services/outcomes.ts b/src/services/outcomes.ts index e5c7216..8198b34 100755 --- a/src/services/outcomes.ts +++ b/src/services/outcomes.ts @@ -1,52 +1,6 @@ -import { - HeaderedTable, - prefixColumns, - join, - selectColumnsByNames, - renameColumns, - mapRow, -} from "@libs/tableUtils"; -import { mapText } from "@libs/textMapper"; -import { tableToTree } from "@libs/treeUtils"; -import type { Tree } from "@libs/treeUtils"; -import { loadOutcomes } from "@services/loadCsv"; -import type { TableInfoDict } from "@services/tables"; +import { mapTree, type Tree } from "@libs/treeUtils"; +import { AttrInfo } from "./attrInfo"; import { Locale } from "./i18n/i18n"; -import { AttrInfo, getReplaceMap } from "./replaceMap"; - -const loadFullOutcomesTable = (locale: Locale) => { - const header = [ - "l1_item", - "l2_item", - "l3_item", - "l4_item", - "l1_id", - "l2_id", - "l3_id", - "l4_id", - "l1_index", - "l2_index", - "l3_index", - "l4_index", - "l1_description", - "l2_description", - "l1_spell", - ] as const; - const renameDict = { - l2_parent: "l1_id", - l3_parent: "l2_id", - l4_parent: "l3_id", - }; - const [l1, l2, l3, l4] = (["1", "2", "3", "4"] as const).map(layer => { - const raw = loadOutcomes(layer, locale); - const data = prefixColumns(raw, `l${layer}_`); - return renameColumns(data, renameDict); - }); - const l12 = join(l1, l2, { on: "l1_id", nan: "", how: "right" }); - const l123 = join(l12, l3, { on: "l2_id", nan: "", how: "right" }); - const l1234 = join(l123, l4, { on: "l3_id", nan: "", how: "right" }); - return selectColumnsByNames(l1234, header); -}; type OutcomeBasicInfo = { text: string; @@ -71,66 +25,47 @@ type Outcomel4 = { attrInfo?: { gap: number; length: number; info: AttrInfo }[]; } & OutcomeBasicInfo; type OutcomeInfo = Outcomel4 | Outcomel3 | Outcomel2 | Outcomel1; -type OutcomeRow = { - l1: Outcomel1; - l2: Outcomel2; - l3: Outcomel3; - l4: Outcomel4; + +const loadIdTree = async (locale: Locale) => { + return (locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/outcome_tree.json`)).default + : (await import(`json_in_repo/outcomes/en/outcome_tree.json`)) + .default) as unknown as Tree; }; -const makeMappedOutcomesTable = ( - fullOutcomesTable: HeaderedTable, - infoDict: TableInfoDict, - locale: Locale, -) => { - const map = getReplaceMap(locale, infoDict); +const loadAttrInfo = async (locale: Locale) => { + return ( + locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/attr_info.json`)).default + : (await import(`json_in_repo/outcomes/en/attr_info.json`)).default + ) as { + [key: string]: { [item: string]: { info: AttrInfo; gap: number; length: number }[] }; + }; +}; - return mapRow(fullOutcomesTable, row => { - const { text: l4text, infoList: l4AttrInfo } = mapText(row["l4_item"], map); - const { text: l3text, infoList: l3AttrInfo } = mapText(row["l3_item"], map); - const newRow: OutcomeRow = { - l1: { - layer: "l1", - text: row["l1_item"], - id: row["l1_id"], - desc: row["l1_description"], - index: row["l1_index"], - spell: row["l1_spell"], - }, - l2: { - layer: "l2", - text: row["l2_item"], - id: row["l2_id"], - desc: row["l2_description"], - index: row["l2_index"], - }, - l3: { - layer: "l3", - text: l3text, - id: row["l3_id"], - index: row["l3_index"], - }, - l4: { - layer: "l4", - text: l4text, - id: row["l4_id"], - index: row["l4_index"], - }, - }; - if (l4AttrInfo?.length > 0) newRow.l4.attrInfo = l4AttrInfo; - if (l3AttrInfo?.length > 0) newRow.l3.attrInfo = l3AttrInfo; - return newRow; - }); +const loadIdDict = async (locale: Locale) => { + return Object.fromEntries( + locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/id_list.json`)).default + : (await import(`json_in_repo/outcomes/en/id_list.json`)).default, + ); }; -const makeOutcomesTree = ( - fullOutcomesTable: HeaderedTable, - infoDict: TableInfoDict, - locale: Locale, -) => { - const [, ...table] = makeMappedOutcomesTable(fullOutcomesTable, infoDict, locale); - return tableToTree(table, (u1, u2) => u1.id === u2.id) as Tree; +const loadOutcomesTree = async (locale: Locale) => { + const idTree = await loadIdTree(locale); + const attrInfo = await loadAttrInfo(locale); + const idDict = await loadIdDict(locale); + return mapTree(idTree, id => { + const { type, ...item } = idDict[id]; + const info = attrInfo[id]?.item; + const layer = type as "l1" | "l2" | "l3" | "l4"; + const outcomeInfo: OutcomeInfo = { layer, id, ...item }; + if (info && (outcomeInfo.layer === "l4" || outcomeInfo.layer === "l3")) { + outcomeInfo.attrInfo = info; + } + return outcomeInfo; + }); }; -export { loadFullOutcomesTable, makeMappedOutcomesTable, makeOutcomesTree }; +export { loadOutcomesTree }; export type { Outcomel1, Outcomel2, Outcomel3, Outcomel4, OutcomeInfo }; diff --git a/src/services/replaceMap.ts b/src/services/replaceMap.ts deleted file mode 100755 index 9f0824c..0000000 --- a/src/services/replaceMap.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ReplaceMap } from "@libs/textMapper"; -import { Locale } from "./i18n/i18n"; -import { TableInfoDict } from "./tables"; - -type LinkInfo = { - type: "link"; - url: string; -}; -type TableLinkInfo = { - type: "tableLink"; - url: string; - id: string; - index: string; - title: string; -}; -type AbbrInfo = { - type: "abbr"; - def: string; -}; -type SubTagInfo = { - type: "sub"; -}; -type SupTagInfo = { - type: "sup"; -}; -type TextInfo = { - type: "text"; -}; -type ItalicInfo = { - type: "italic"; -}; -type AttrInfo = - | LinkInfo - | AbbrInfo - | TableLinkInfo - | SubTagInfo - | SupTagInfo - | TextInfo - | ItalicInfo; - -const getReplaceMap = (locale: Locale, infoDict?: TableInfoDict): ReplaceMap => { - const tableLabel = locale === "ja" ? "表" : "Table"; - return [ - { - reg: /\[\@tbl:(.+?)\]/, - mapper: ([, id]: RegExpExecArray) => { - if (!infoDict) return { text: id, attr: { type: "text" } }; - const target = infoDict[id]; - return { - text: `${tableLabel + target.number}`, - attr: { - type: "tableLink", - url: target.link, - index: target.number, - title: `${tableLabel + target.number}. ${target.item}`, - id, - }, - } as const; - }, - }, - { - reg: /(.+?)<\/sub>/i, - mapper: ([, text]: RegExpExecArray) => { - return { - text: text, - attr: { - type: "sub", - }, - } as const; - }, - }, - { - reg: /(.+?)<\/sup>/i, - mapper: ([, text]: RegExpExecArray) => { - return { - text: text, - attr: { - type: "sup", - }, - } as const; - }, - }, - { - reg: /\*(.+?)\*/, - mapper: ([, text]: RegExpExecArray) => { - return { - text: text, - attr: { - type: "italic", - }, - } as const; - }, - }, - ]; -}; - -export type { AttrInfo }; -export { getReplaceMap }; diff --git a/src/services/tables.ts b/src/services/tables.ts index 2964763..4cd205f 100755 --- a/src/services/tables.ts +++ b/src/services/tables.ts @@ -1,84 +1,79 @@ -import { - HeaderedTable, - mapTable, - reduceTable, - renameColumns, - toObjectList, -} from "@libs/tableUtils"; -import { MappedText, mapText, MappedInfo } from "@libs/textMapper"; -import { loadTable, loadTableIndex } from "@services/loadCsv"; +import { HeaderedTable } from "@libs/tableUtils"; +import { MappedInfo } from "@libs/textMapper"; +import { AttrInfo } from "./attrInfo"; import { Locale } from "./i18n/i18n"; -import { AttrInfo, getReplaceMap } from "./replaceMap"; - -const fileToTableLink = (file: string) => { - return `/tables/${file}`; -}; type Expand = { [K in keyof T]: T[K] }; -const loadTableInfoDict = (locale: Locale) => { - const trimExt = (filename: string) => filename.replace(/\.[^\.]+$/, ""); - const makeIndexed = ( - source: T[], - key: K, - ): { [key in T[K] extends string ? T[K] : never]: T } => { - return source.reduce((indexed, item) => { - return { ...indexed, [item[key] as string]: item }; - }, {} as { [key in T[K] extends string ? T[K] : never]: T }); +const loadAttrInfo = async (locale: Locale) => { + return ( + locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/attr_info.json`)).default + : (await import(`json_in_repo/outcomes/en/attr_info.json`)).default + ) as { + [key: string]: { [item: string]: { info: AttrInfo; gap: number; length: number }[] }; }; - const tableIndex = loadTableIndex(locale); - const infoList = toObjectList(tableIndex).map(info => { - const file = trimExt(info.file); - const link = fileToTableLink(file); - const columns: Record = Object.fromEntries( - info.columns.split(",").map(entry => entry.split(":")), - ); - return { ...info, file, link, columns }; - }); - return makeIndexed(infoList, "file"); }; -const getTableFiles = (locale: Locale) => { - return Object.keys(loadTableInfoDict(locale)); +const loadIdInfoList = async (locale: Locale) => { + return locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/id_list.json`)).default + : (await import(`json_in_repo/outcomes/en/id_list.json`)).default; }; -const getTalbleInfoList = (locale: Locale) => { - return Object.values(loadTableInfoDict(locale)); +const loadTableInfoDict = async (locale: Locale) => { + return locale === "ja" + ? (await import(`json_in_repo/outcomes/ja/table_info.json`)).default + : (await import(`json_in_repo/outcomes/en/table_info.json`)).default; }; -const getTable = (file: string, locale: Locale): TableInfoSet => { - const tableInfo = loadTableInfoDict(locale)[file]; - const rawTable = renameColumns(loadTable(file, locale), tableInfo.columns); - const map = getReplaceMap(locale as Locale); - const attrTable: HeaderedTable> = mapTable(rawTable, cell => - mapText(cell, map), - ); - const table = mapTable(attrTable, cell => cell.text); - const attrInfo = reduceTable( - attrTable, - (dict, row) => { - const id = row.id.text; - const infoLists = Object.entries(row).flatMap(([key, value]) => - value.infoList.length > 0 ? [[key, value.infoList] as const] : [], - ); - const infoDict = Object.fromEntries(infoLists); - return { ...dict, [id]: infoDict }; - }, - {} as TableAttrInfo, - ); +const getTableFiles = async (locale: Locale) => { + return Object.keys(await loadTableInfoDict(locale)) as TableFile[]; +}; + +const getTalbleInfoList = async (locale: Locale) => { + return Object.values(await loadTableInfoDict(locale)); +}; +const getTable = async (file: TableFile, locale: Locale): Promise => { + const tableInfo = (await loadTableInfoDict(locale))[file]; + const idInfoList = await loadIdInfoList(locale); + type ColumnKey = keyof typeof tableInfo.columns; + const tableHeader = [ + "id", + "index", + ...tableInfo.header.map(key => tableInfo.columns[key as ColumnKey]), + ]; + const tableBody = idInfoList + .filter(([_, item]) => item.type === file) + .map(([id, { index, ...item }]) => { + const row = tableInfo.header.map(key => item[key] ?? ""); + return [id, index, ...row]; + }); + const table = [tableHeader, ...tableBody] as readonly [ + readonly string[], + ...(readonly string[])[], + ]; + const rowIdList = tableBody.map(([id]) => id); + const attrInfoDict = await loadAttrInfo(locale); + const attrInfoEntries = rowIdList + .filter(id => attrInfoDict[id]) + .map(id => [id, attrInfoDict[id]]); + const attrInfo: TableAttrInfo = Object.fromEntries(attrInfoEntries); return { table, tableInfo, attrInfo }; }; -const getAllTables = (locale: Locale): TableInfoSet[] => { - const files = getTableFiles(locale); - return files.map((file: string) => getTable(file, locale)); +const getAllTables = async (locale: Locale): Promise => { + const files = await getTableFiles(locale); + return Promise.all(files.map(file => getTable(file, locale))); }; -type TableInfoDict = ReturnType; -type TableInfo = Expand; +type PromiseType> = T extends Promise ? P : never; +type TableInfoDict = PromiseType>; +type TableFile = keyof TableInfoDict; +type TableInfo = Expand; type TableAttrInfo = { [id: string]: { [key: string]: MappedInfo[] } }; type TableInfoSet = { table: HeaderedTable; tableInfo: TableInfo; attrInfo: TableAttrInfo }; export { loadTableInfoDict, getTableFiles, getTable, getAllTables, getTalbleInfoList }; -export type { TableInfo, TableInfoDict, TableAttrInfo, TableInfoSet }; +export type { TableInfo, TableInfoDict, TableAttrInfo, TableInfoSet, TableFile }; From eb1654fed3355b0bf8820d8be429d2f250c6e552 Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Mon, 18 Dec 2023 04:04:08 +0000 Subject: [PATCH 8/9] fix server copy issure and add class list --- .vscode/settings.json | 2 +- src/pages/x/[id].tsx | 51 +++++++++++++++++++++++++-- src/services/api.ts | 16 +++++---- src/services/curriculumMapTable.ts | 16 +++++++++ src/services/i18n/text.ts | 11 ++++++ src/services/itemList/libs/callApi.ts | 3 +- src/services/urls.ts | 15 +++++++- 7 files changed, 103 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c594ccd..c59365f 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[javascript]": { "editor.tabSize": 2, diff --git a/src/pages/x/[id].tsx b/src/pages/x/[id].tsx index 5a5da91..882cbf7 100755 --- a/src/pages/x/[id].tsx +++ b/src/pages/x/[id].tsx @@ -11,6 +11,7 @@ import { Tree } from "@libs/treeUtils"; import { fmt } from "@libs/utils"; import { makeCumulativeOutcomeTableData, + makeItemTableData, makeOutcomeTableData, makeTableItemTableData, } from "@services/curriculumMapTable"; @@ -27,7 +28,7 @@ import { loadOutcomesTree } from "@services/outcomes"; import type { OutcomeInfo } from "@services/outcomes"; import { searchOutcomes, searchTables } from "@services/search"; import { getAllTables, TableInfoSet } from "@services/tables"; -import { itemIdToUrl } from "@services/urls"; +import { itemIdToUrl, itemIdToUrlToEditFromUrl } from "@services/urls"; type PageProps = { outcomesTree: Tree; @@ -202,7 +203,9 @@ const CSVDownloadLinks = ({ const l4Data = makeCumulativeOutcomeTableData(items, outcomesTree, 4); const l1to4Data = makeOutcomeTableData(items, outcomesTree, 4); const tableData = makeTableItemTableData(items, allTables); + const itemsIdData = makeItemTableData(items); const linkDataList = [ + { data: itemsIdData, label: t("downloadClassess"), filename: "classes.csv" }, { data: l1Data, label: t("downloadL1"), filename: "outcomes_l1.csv" }, { data: l2Data, label: t("downloadL2"), filename: "outcomes_l2.csv" }, { data: l3Data, label: t("downloadL3"), filename: "outcomes_l3.csv" }, @@ -219,6 +222,47 @@ const CSVDownloadLinks = ({ ); }; +const ItemsTable = ({ items }: { items: ServerItemList[] }) => { + const { t } = useLocaleText("@pages/x/[id]"); + return ( + + + + + + + + + + + {items.map(item => { + const url = itemIdToUrl(item.id); + const urlToEdit = itemIdToUrlToEditFromUrl( + item.id, + item.children && item.children?.length > 0, + ); + return ( + + + + + + + ); + })} + +
    {t("item-name")}{t("item-place")}{t("item-url")}{t("item-url-to-edit")}
    {item.name}{item.place} + + {url} + + + + {urlToEdit} + +
    + ); +}; + const OutcomeAccessInfo = ({ id, isMap, name }: { id: string; isMap: boolean; name: string }) => { const { t } = useLocaleText("@pages/x/[id]"); const link = isMap ? `/map?from=${id}` : `/list?from=${id}`; @@ -315,7 +359,10 @@ const ListPage: NextPage = ({ )} {children ? ( - + <> + + + ) : ( <> diff --git a/src/services/api.ts b/src/services/api.ts index f5797a4..3fe302d 100755 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -19,12 +19,16 @@ const apiPost = async ( }; const apiGet = async (api: string): Promise => { - const res = await fetch(api, { - method: "GET", - }); - if (res.ok) { - const data = (await res.json()) as Response; - return data; + try { + const res = await fetch(api, { + method: "GET", + }); + if (res.ok) { + const data = (await res.json()) as Response; + return data; + } + } catch (e) { + console.log(e); } throw new Error(`Cannot get fromapi:${api},${JSON.stringify(res, null, 2)}`); }; diff --git a/src/services/curriculumMapTable.ts b/src/services/curriculumMapTable.ts index 004bc88..29c8428 100755 --- a/src/services/curriculumMapTable.ts +++ b/src/services/curriculumMapTable.ts @@ -3,6 +3,7 @@ import { reduceTree, Tree } from "@libs/treeUtils"; import { ServerItemList } from "@services/itemList/server"; import { OutcomeInfo } from "@services/outcomes"; import { TableInfoSet } from "@services/tables"; +import { itemIdToUrl, itemIdToUrlToEditFromUrl } from "./urls"; type LabelInfo = { index: string; @@ -90,6 +91,20 @@ const makeOutcomeTableData = ( return makeTableData(items, labels); }; +const makeItemTableData = (items: ServerItemList[]) => { + const header = ["name", "place", "url", "url_to_edit"]; + const body = items.map(item => { + const row = [ + item.name, + item.place, + itemIdToUrl(item.id), + itemIdToUrlToEditFromUrl(item.id, item.children && item.children?.length > 0), + ]; + return row; + }); + return [header, ...body]; +}; + const makeCumulativeOutcomeTableData = ( items: ServerItemList[], tree: Tree, @@ -114,4 +129,5 @@ export { makeTableTableData, makeTableItemTableData, makeCumulativeOutcomeTableData, + makeItemTableData, }; diff --git a/src/services/i18n/text.ts b/src/services/i18n/text.ts index 553c1c3..fd2ba9d 100755 --- a/src/services/i18n/text.ts +++ b/src/services/i18n/text.ts @@ -219,6 +219,17 @@ const text = { ja: "表データと授業の関連付けデータをダウンロード(csv)", en: "Download table data and association data with classes (csv)", }, + downloadClassess: { + ja: "授業一覧とurlをダウンロード(csv)", + en: "Download list of classes and urls (csv)", + }, + "item-name": { ja: "名前", en: "name" }, + "item-place": { ja: "場所", en: "place" }, + "item-url": { ja: "url", en: "url" }, + "item-url-to-edit": { + ja: "この内容を元に新しい項目を作成", + en: "Start a new edit based on this content", + }, notFound: { ja: "該当する項目が見つかりません。", en: "No matching items were found.", diff --git a/src/services/itemList/libs/callApi.ts b/src/services/itemList/libs/callApi.ts index a979fdf..996ee21 100755 --- a/src/services/itemList/libs/callApi.ts +++ b/src/services/itemList/libs/callApi.ts @@ -38,7 +38,8 @@ const shareCurriculumMapToServer = async ( const getCurriculuMapFromServer = async (id: string): Promise> => { try { - return await apiGet(`/api/v1/map/${id}`); + const data = await apiGet(`/api/v1/map/${id}`); + return { ok: true, data } as Response; } catch (e) { throw new Error(`Fail to get curriculumMap. ${e}`); } diff --git a/src/services/urls.ts b/src/services/urls.ts index 2e47621..2f90c3a 100755 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -24,6 +24,11 @@ const itemIdToUrl = (id: string) => { return `${origin}/x/${id}`; }; +const itemIdToUrlToEditFromUrl = (id: string, isMap: boolean | null | undefined) => { + const params = new URLSearchParams([["from", id]]); + return `${origin}/${isMap ? "map" : "list"}?${params.toString()}`; +}; + const objectiveIdToUrl = (id: string) => { return `${origin}/#${id}`; }; @@ -33,4 +38,12 @@ const isValidItemUrlOrId = (urlOrId: string) => { return isValidShortId(id); }; -export { origin, useFullUrl, itemUrlToId, isValidItemUrlOrId, itemIdToUrl, objectiveIdToUrl }; +export { + origin, + useFullUrl, + itemUrlToId, + isValidItemUrlOrId, + itemIdToUrl, + objectiveIdToUrl, + itemIdToUrlToEditFromUrl, +}; From b1fd49a4fa120d0e0b9be134c7e00d61971b790b Mon Sep 17 00:00:00 2001 From: Takeshi Kondo Date: Mon, 18 Dec 2023 04:14:56 +0000 Subject: [PATCH 9/9] fix type bug --- src/services/api.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/services/api.ts b/src/services/api.ts index 3fe302d..f5797a4 100755 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -19,16 +19,12 @@ const apiPost = async ( }; const apiGet = async (api: string): Promise => { - try { - const res = await fetch(api, { - method: "GET", - }); - if (res.ok) { - const data = (await res.json()) as Response; - return data; - } - } catch (e) { - console.log(e); + const res = await fetch(api, { + method: "GET", + }); + if (res.ok) { + const data = (await res.json()) as Response; + return data; } throw new Error(`Cannot get fromapi:${api},${JSON.stringify(res, null, 2)}`); };