From 03456410f84a96c0454ae377c05cda3a7a7af866 Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Mon, 20 Jun 2022 17:51:03 +0900 Subject: [PATCH] first commit. --- .gitignore | 28 + README.md | 45 + etc/common.inc.php | 324 + etc/config.inc.php | 51 + etc/copy-brainexplorer.sh | 15 + etc/copy-nimgdocs.sh | 18 + etc/copy-nimgsearch.sh | 10 + etc/dl-limit-items.csv | 10 + etc/dumpCredits.php | 154 + etc/dumpNimgdocsBulletin.php | 44 + etc/dumpPico.php | 102 + etc/dumpStaticPage.php | 207 + etc/dumpXooNIps.php | 1241 +++ etc/extra.inc.php | 188 + etc/extras.inc.php | 188 + etc/fixXooNIps.php | 310 + etc/simpflink.csv | 465 + package.json | 64 + public/.htaccess | 11 + public/favicon.ico | Bin 0 -> 3638 bytes public/index.html | 19 + public/manifest.json | 15 + public/robots.txt | 2 + public/rss.xml | 89 + src/App.css | 2 + src/App.test.tsx | 10 + src/App.tsx | 20 + src/brainexplorer/BrainExplorer.tsx | 31 + .../BrainExplorerIndex.module.css | 49 + src/brainexplorer/BrainExplorerIndex.tsx | 111 + src/brainexplorer/assets/images/logo_aist.png | Bin 0 -> 1595 bytes src/brainexplorer/assets/images/navi_home.png | Bin 0 -> 2093 bytes src/brainexplorer/assets/images/navi_info.png | Bin 0 -> 2078 bytes src/brainexplorer/lib/g3d.ts | 1035 ++ src/brainexplorer/lib/m3d.ts | 616 ++ src/common/AppRoot.tsx | 41 + src/common/XoopsPathRedirect.tsx | 35 + src/common/assets/images/mlang_english.gif | Bin 0 -> 175 bytes src/common/assets/images/mlang_japanese.gif | Bin 0 -> 126 bytes src/common/assets/images/no_avatar.gif | Bin 0 -> 985 bytes src/common/assets/images/pagact.gif | Bin 0 -> 121 bytes src/common/assets/images/paginact.gif | Bin 0 -> 121 bytes src/common/assets/images/pagneutral.gif | Bin 0 -> 121 bytes .../assets/images/rank3dbf8e94a6f72.gif | Bin 0 -> 905 bytes .../assets/images/rank3dbf8e9e7d88d.gif | Bin 0 -> 1276 bytes .../assets/images/rank3dbf8ea81e642.gif | Bin 0 -> 894 bytes .../assets/images/rank3dbf8eb1a72e7.gif | Bin 0 -> 856 bytes .../assets/images/rank3dbf8edf15093.gif | Bin 0 -> 789 bytes .../assets/images/rank3dbf8ee8681cd.gif | Bin 0 -> 779 bytes .../assets/images/rank3e632f95e81ca.gif | Bin 0 -> 475 bytes .../assets/images/smil3dbd4bf386b36.gif | Bin 0 -> 210 bytes .../assets/images/smil3dbd4d4e4c4f2.gif | Bin 0 -> 204 bytes .../assets/images/smil3dbd4d6422f04.gif | Bin 0 -> 174 bytes .../assets/images/smil3dbd4d75edb5e.gif | Bin 0 -> 204 bytes .../assets/images/smil3dbd4d8676346.gif | Bin 0 -> 176 bytes .../assets/images/smil3dbd4d99c6eaa.gif | Bin 0 -> 207 bytes .../assets/images/smil3dbd4daabd491.gif | Bin 0 -> 170 bytes .../assets/images/smil3dbd4dbc14f3f.gif | Bin 0 -> 210 bytes .../assets/images/smil3dbd4dcd7b9f4.gif | Bin 0 -> 278 bytes .../assets/images/smil3dbd4ddd6835f.gif | Bin 0 -> 202 bytes .../assets/images/smil3dbd4df1944ee.gif | Bin 0 -> 678 bytes .../assets/images/smil3dbd4e02c5440.gif | Bin 0 -> 263 bytes .../assets/images/smil3dbd4e1748cc9.gif | Bin 0 -> 209 bytes .../assets/images/smil3dbd4e29bbcc7.gif | Bin 0 -> 492 bytes .../assets/images/smil3dbd4e398ff7b.gif | Bin 0 -> 205 bytes .../assets/images/smil3dbd4e4c2e742.gif | Bin 0 -> 4959 bytes .../assets/images/smil3dbd4e5e7563a.gif | Bin 0 -> 736 bytes .../assets/images/smil3dbd4e7853679.gif | Bin 0 -> 273 bytes src/common/assets/xoops.css | 17 + src/common/lib/LangFlag.tsx | 33 + src/common/lib/Loading.tsx | 12 + src/common/lib/NoticeSiteHasBeenArchived.tsx | 15 + src/common/lib/PageNotFound.tsx | 50 + src/common/lib/XoopsCode.tsx | 132 + src/config.ts | 15 + src/credits/Credits.tsx | 47 + src/credits/CreditsIndex.tsx | 59 + src/credits/CreditsPage.tsx | 62 + src/credits/blocks/CreditsMenu.tsx | 47 + src/credits/lib/CreditsUtils.ts | 77 + src/custom/Footer.tsx | 52 + src/custom/Header.tsx | 73 + src/custom/Page.tsx | 148 + src/custom/assets/images/back.png | Bin 0 -> 569 bytes src/custom/assets/images/banner-niu.png | Bin 0 -> 1279 bytes src/custom/assets/images/banner-riken-cbs.png | Bin 0 -> 3876 bytes src/custom/assets/images/banner-riken.png | Bin 0 -> 2514 bytes src/custom/assets/images/banner-xoonips.png | Bin 0 -> 2708 bytes src/custom/assets/images/blockTitle.png | Bin 0 -> 633 bytes src/custom/assets/images/blockTitleBack.png | Bin 0 -> 98 bytes .../assets/images/blockTitleIndent12.png | Bin 0 -> 269 bytes .../assets/images/blockTitleIndent16.png | Bin 0 -> 310 bytes src/custom/assets/images/footer_back.png | Bin 0 -> 187 bytes src/custom/assets/images/h1_indent.png | Bin 0 -> 1224 bytes src/custom/assets/images/h2_background.png | Bin 0 -> 98 bytes src/custom/assets/images/jnode.png | Bin 0 -> 3051 bytes src/custom/assets/images/logo.png | Bin 0 -> 7811 bytes src/custom/assets/images/mlang_english.png | Bin 0 -> 2665 bytes src/custom/assets/images/mlang_japanese.png | Bin 0 -> 2588 bytes src/custom/assets/images/mmenu_01.png | Bin 0 -> 3804 bytes src/custom/assets/images/mmenu_02.png | Bin 0 -> 3872 bytes src/custom/assets/images/mmenu_03.png | Bin 0 -> 4571 bytes src/custom/assets/images/mmenu_04.png | Bin 0 -> 3486 bytes src/custom/assets/images/page_top.png | Bin 0 -> 167 bytes src/custom/assets/images/smenu_01.png | Bin 0 -> 1981 bytes src/custom/assets/images/smenu_03.png | Bin 0 -> 2113 bytes src/custom/assets/images/smenu_04.png | Bin 0 -> 2148 bytes src/custom/assets/style.css | 396 + src/custom/blocks/BulletinBoard.tsx | 23 + src/custom/blocks/HowToUseLinks.tsx | 40 + src/custom/blocks/HowToUseVideo.tsx | 66 + src/custom/blocks/Information.tsx | 29 + src/custom/blocks/Questionnaire.tsx | 31 + src/custom/blocks/RecentStatus.tsx | 20 + src/functions.ts | 136 + src/index.css | 1 + src/index.tsx | 18 + src/nimgcenter/NimgcenterBrainConv.tsx | 142 + src/nimgcenter/NimgenterUtilities.tsx | 34 + src/nimgcenter/assets/howtouse.json | 1 + src/nimgcenter/assets/utilities.json | 1 + src/nimgcenter/lib/BrainCoordinateUtil.ts | 152 + src/nimgdocs/Nimgdocs.tsx | 65 + src/nimgdocs/NimgdocsBulletinLog.tsx | 45 + src/nimgdocs/NimgdocsTutorials.tsx | 1863 ++++ src/nimgdocs/assets/3dbrowse.json | 1 + src/nimgdocs/assets/3dbrowse_usage.json | 1 + src/nimgdocs/assets/about.json | 1 + src/nimgdocs/assets/additem.json | 1 + src/nimgdocs/assets/bulletin.json | 1 + src/nimgdocs/assets/introduction.json | 1 + src/nimgdocs/assets/nimgcenter.json | 1 + src/nimgdocs/assets/nimgsearch.json | 1 + src/nimgsearch/Nimgsearch.module.css | 323 + src/nimgsearch/Nimgsearch.tsx | 726 ++ src/nimgsearch/assets/brainarea.json | 1 + src/nimgsearch/lib/NimgsearchUtil.ts | 268 + src/react-app-env.d.ts | 1 + src/reportWebVitals.ts | 15 + src/setupTests.ts | 5 + src/xoonips/Xoonips.module.css | 70 + src/xoonips/Xoonips.tsx | 117 + src/xoonips/XoonipsAdvancedSearch.tsx | 45 + src/xoonips/XoonipsDetailItem.tsx | 56 + .../XoonipsSearchByAdvancedKeyword.tsx | 35 + src/xoonips/XoonipsSearchByIndexId.tsx | 66 + src/xoonips/XoonipsSearchByItemType.tsx | 34 + src/xoonips/XoonipsSearchByKeyword.tsx | 37 + src/xoonips/XoonipsTop.module.css | 13 + src/xoonips/XoonipsTop.tsx | 40 + src/xoonips/XoonipsXoopsPathRedirect.tsx | 36 + src/xoonips/assets/images/icon_binder.gif | Bin 0 -> 369 bytes src/xoonips/assets/images/icon_book.gif | Bin 0 -> 375 bytes src/xoonips/assets/images/icon_conference.gif | Bin 0 -> 427 bytes src/xoonips/assets/images/icon_data.gif | Bin 0 -> 402 bytes src/xoonips/assets/images/icon_files.gif | Bin 0 -> 433 bytes src/xoonips/assets/images/icon_folder.gif | Bin 0 -> 1508 bytes src/xoonips/assets/images/icon_memo.gif | Bin 0 -> 166 bytes src/xoonips/assets/images/icon_model.gif | Bin 0 -> 409 bytes src/xoonips/assets/images/icon_nimgcenter.gif | Bin 0 -> 1034 bytes src/xoonips/assets/images/icon_paper.gif | Bin 0 -> 479 bytes .../assets/images/icon_presentation.gif | Bin 0 -> 423 bytes src/xoonips/assets/images/icon_simulator.gif | Bin 0 -> 562 bytes src/xoonips/assets/images/icon_stimulus.gif | Bin 0 -> 302 bytes src/xoonips/assets/images/icon_tool.gif | Bin 0 -> 399 bytes src/xoonips/assets/images/icon_url.gif | Bin 0 -> 574 bytes src/xoonips/assets/images/simpf_button.png | Bin 0 -> 6596 bytes src/xoonips/assets/images/star.gif | Bin 0 -> 538 bytes src/xoonips/assets/images/tree_line.png | Bin 0 -> 112 bytes src/xoonips/assets/images/tree_node.png | Bin 0 -> 1251 bytes src/xoonips/assets/simpf-links.json | 1 + src/xoonips/assets/tree.json | 1 + src/xoonips/blocks/IndexTree.module.css | 91 + src/xoonips/blocks/IndexTree.tsx | 114 + src/xoonips/blocks/Rankings.module.css | 28 + src/xoonips/blocks/Rankings.tsx | 183 + src/xoonips/blocks/RecentContents.module.css | 28 + src/xoonips/blocks/RecentContents.tsx | 80 + src/xoonips/blocks/Search.tsx | 73 + .../item-type/binder/BinderAdvancedSearch.tsx | 25 + src/xoonips/item-type/binder/BinderDetail.tsx | 100 + src/xoonips/item-type/binder/BinderList.tsx | 27 + src/xoonips/item-type/binder/BinderTop.tsx | 14 + src/xoonips/item-type/binder/index.tsx | 13 + .../item-type/book/BookAdvancedSearch.tsx | 32 + src/xoonips/item-type/book/BookDetail.tsx | 40 + src/xoonips/item-type/book/BookList.tsx | 36 + src/xoonips/item-type/book/BookTop.tsx | 14 + src/xoonips/item-type/book/index.tsx | 13 + .../conference/ConferenceAdvancedSearch.tsx | 50 + .../item-type/conference/ConferenceDetail.tsx | 34 + .../item-type/conference/ConferenceList.tsx | 38 + .../item-type/conference/ConferenceTop.tsx | 16 + .../item-type/conference/ConferenceUtil.tsx | 43 + src/xoonips/item-type/conference/index.tsx | 13 + .../item-type/data/DataAdvancedSearch.tsx | 45 + src/xoonips/item-type/data/DataDetail.tsx | 42 + src/xoonips/item-type/data/DataList.tsx | 36 + src/xoonips/item-type/data/DataTop.tsx | 16 + src/xoonips/item-type/data/DataUtil.tsx | 25 + src/xoonips/item-type/data/index.tsx | 13 + .../item-type/files/FilesAdvancedSearch.tsx | 29 + src/xoonips/item-type/files/FilesDetail.tsx | 31 + src/xoonips/item-type/files/FilesList.tsx | 30 + src/xoonips/item-type/files/FilesTop.tsx | 16 + src/xoonips/item-type/files/FilesUtil.tsx | 3 + src/xoonips/item-type/files/index.tsx | 13 + src/xoonips/item-type/index.tsx | 177 + .../item-type/lib/AdvancedSearchBase.tsx | 195 + src/xoonips/item-type/lib/DetailBase.tsx | 49 + src/xoonips/item-type/lib/ListBase.tsx | 47 + src/xoonips/item-type/lib/TopBase.tsx | 52 + src/xoonips/item-type/lib/field/Author.tsx | 29 + src/xoonips/item-type/lib/field/ChangeLog.tsx | 34 + .../item-type/lib/field/Contributer.tsx | 18 + .../item-type/lib/field/CreativeCommons.tsx | 69 + src/xoonips/item-type/lib/field/DateTime.tsx | 21 + .../item-type/lib/field/Description.tsx | 18 + .../lib/field/FileDownloadButton.module.css | 21 + .../lib/field/FileDownloadButton.tsx | 39 + src/xoonips/item-type/lib/field/FileSize.tsx | 17 + .../item-type/lib/field/FreeKeyword.tsx | 18 + src/xoonips/item-type/lib/field/ItemFile.tsx | 69 + src/xoonips/item-type/lib/field/ItemIndex.tsx | 36 + src/xoonips/item-type/lib/field/Language.tsx | 35 + .../field/LicenseAgreementDialog.module.css | 38 + .../lib/field/LicenseAgreementDialog.tsx | 119 + .../item-type/lib/field/Preview.module.css | 23 + src/xoonips/item-type/lib/field/Preview.tsx | 57 + .../item-type/lib/field/PublicationDate.tsx | 19 + src/xoonips/item-type/lib/field/Readme.tsx | 15 + src/xoonips/item-type/lib/field/RelatedTo.tsx | 53 + src/xoonips/item-type/lib/field/Rights.tsx | 23 + .../item-type/lib/field/SimPFLinkIcon.tsx | 25 + src/xoonips/item-type/lib/field/index.tsx | 39 + .../item-type/memo/MemoAdvancedSearch.tsx | 27 + src/xoonips/item-type/memo/MemoDetail.tsx | 31 + src/xoonips/item-type/memo/MemoList.tsx | 29 + src/xoonips/item-type/memo/MemoTop.tsx | 14 + src/xoonips/item-type/memo/index.tsx | 13 + .../item-type/model/ModelAdvancedSearch.tsx | 31 + src/xoonips/item-type/model/ModelDetail.tsx | 41 + src/xoonips/item-type/model/ModelList.tsx | 36 + src/xoonips/item-type/model/ModelTop.tsx | 16 + src/xoonips/item-type/model/ModelUtil.tsx | 25 + src/xoonips/item-type/model/index.tsx | 13 + .../nimgcenter/NimgcenterAdvancedSearch.tsx | 111 + .../item-type/nimgcenter/NimgcenterDetail.tsx | 126 + .../item-type/nimgcenter/NimgcenterList.tsx | 29 + .../item-type/nimgcenter/NimgcenterTop.tsx | 14 + .../item-type/nimgcenter/NimgcenterUtil.tsx | 15 + .../item-type/nimgcenter/SearchQuery.ts | 166 + src/xoonips/item-type/nimgcenter/index.tsx | 13 + .../item-type/paper/PaperAdvancedSearch.tsx | 33 + src/xoonips/item-type/paper/PaperDetail.tsx | 35 + src/xoonips/item-type/paper/PaperList.tsx | 50 + src/xoonips/item-type/paper/PaperTop.tsx | 14 + src/xoonips/item-type/paper/PaperUtil.tsx | 26 + src/xoonips/item-type/paper/index.tsx | 13 + .../PresentationAdvancedSearch.tsx | 45 + .../presentation/PresentationDetail.tsx | 35 + .../presentation/PresentationList.tsx | 39 + .../presentation/PresentationTop.tsx | 16 + .../presentation/PresentationUtil.tsx | 25 + src/xoonips/item-type/presentation/index.tsx | 13 + .../simulator/SimulatorAdvancedSearch.tsx | 43 + .../item-type/simulator/SimulatorDetail.tsx | 42 + .../item-type/simulator/SimulatorList.tsx | 36 + .../item-type/simulator/SimulatorTop.tsx | 16 + .../item-type/simulator/SimulatorUtil.tsx | 25 + src/xoonips/item-type/simulator/index.tsx | 13 + .../stimulus/StimulusAdvancedSearch.tsx | 43 + .../item-type/stimulus/StimulusDetail.tsx | 42 + .../item-type/stimulus/StimulusList.tsx | 36 + .../item-type/stimulus/StimulusTop.tsx | 16 + .../item-type/stimulus/StimulusUtil.tsx | 25 + src/xoonips/item-type/stimulus/index.tsx | 13 + .../item-type/tool/ToolAdvancedSearch.tsx | 45 + src/xoonips/item-type/tool/ToolDetail.tsx | 41 + src/xoonips/item-type/tool/ToolList.tsx | 28 + src/xoonips/item-type/tool/ToolTop.tsx | 16 + src/xoonips/item-type/tool/ToolUtil.tsx | 25 + src/xoonips/item-type/tool/index.tsx | 13 + .../item-type/url/UrlAdvancedSearch.tsx | 27 + src/xoonips/item-type/url/UrlDetail.tsx | 37 + src/xoonips/item-type/url/UrlList.tsx | 30 + src/xoonips/item-type/url/UrlTop.tsx | 14 + src/xoonips/item-type/url/UrlUtil.tsx | 30 + src/xoonips/item-type/url/index.tsx | 13 + src/xoonips/lib/AdvancedSearchQuery.ts | 119 + src/xoonips/lib/IndexUtil.ts | 94 + src/xoonips/lib/ItemUtil.ts | 740 ++ src/xoonips/lib/XoonipsListIndex.tsx | 58 + src/xoonips/lib/XoonipsListItem.module.css | 11 + src/xoonips/lib/XoonipsListItem.tsx | 215 + tsconfig.json | 26 + yarn.lock | 9200 +++++++++++++++++ 297 files changed, 26335 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 etc/common.inc.php create mode 100644 etc/config.inc.php create mode 100644 etc/copy-brainexplorer.sh create mode 100644 etc/copy-nimgdocs.sh create mode 100644 etc/copy-nimgsearch.sh create mode 100644 etc/dl-limit-items.csv create mode 100644 etc/dumpCredits.php create mode 100644 etc/dumpNimgdocsBulletin.php create mode 100644 etc/dumpPico.php create mode 100644 etc/dumpStaticPage.php create mode 100644 etc/dumpXooNIps.php create mode 100644 etc/extra.inc.php create mode 100644 etc/extras.inc.php create mode 100644 etc/fixXooNIps.php create mode 100644 etc/simpflink.csv create mode 100644 package.json create mode 100644 public/.htaccess create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 public/manifest.json create mode 100644 public/robots.txt create mode 100644 public/rss.xml create mode 100644 src/App.css create mode 100644 src/App.test.tsx create mode 100644 src/App.tsx create mode 100644 src/brainexplorer/BrainExplorer.tsx create mode 100644 src/brainexplorer/BrainExplorerIndex.module.css create mode 100644 src/brainexplorer/BrainExplorerIndex.tsx create mode 100644 src/brainexplorer/assets/images/logo_aist.png create mode 100644 src/brainexplorer/assets/images/navi_home.png create mode 100644 src/brainexplorer/assets/images/navi_info.png create mode 100644 src/brainexplorer/lib/g3d.ts create mode 100644 src/brainexplorer/lib/m3d.ts create mode 100644 src/common/AppRoot.tsx create mode 100644 src/common/XoopsPathRedirect.tsx create mode 100644 src/common/assets/images/mlang_english.gif create mode 100644 src/common/assets/images/mlang_japanese.gif create mode 100644 src/common/assets/images/no_avatar.gif create mode 100644 src/common/assets/images/pagact.gif create mode 100644 src/common/assets/images/paginact.gif create mode 100644 src/common/assets/images/pagneutral.gif create mode 100644 src/common/assets/images/rank3dbf8e94a6f72.gif create mode 100644 src/common/assets/images/rank3dbf8e9e7d88d.gif create mode 100644 src/common/assets/images/rank3dbf8ea81e642.gif create mode 100644 src/common/assets/images/rank3dbf8eb1a72e7.gif create mode 100644 src/common/assets/images/rank3dbf8edf15093.gif create mode 100644 src/common/assets/images/rank3dbf8ee8681cd.gif create mode 100644 src/common/assets/images/rank3e632f95e81ca.gif create mode 100644 src/common/assets/images/smil3dbd4bf386b36.gif create mode 100644 src/common/assets/images/smil3dbd4d4e4c4f2.gif create mode 100644 src/common/assets/images/smil3dbd4d6422f04.gif create mode 100644 src/common/assets/images/smil3dbd4d75edb5e.gif create mode 100644 src/common/assets/images/smil3dbd4d8676346.gif create mode 100644 src/common/assets/images/smil3dbd4d99c6eaa.gif create mode 100644 src/common/assets/images/smil3dbd4daabd491.gif create mode 100644 src/common/assets/images/smil3dbd4dbc14f3f.gif create mode 100644 src/common/assets/images/smil3dbd4dcd7b9f4.gif create mode 100644 src/common/assets/images/smil3dbd4ddd6835f.gif create mode 100644 src/common/assets/images/smil3dbd4df1944ee.gif create mode 100644 src/common/assets/images/smil3dbd4e02c5440.gif create mode 100644 src/common/assets/images/smil3dbd4e1748cc9.gif create mode 100644 src/common/assets/images/smil3dbd4e29bbcc7.gif create mode 100644 src/common/assets/images/smil3dbd4e398ff7b.gif create mode 100644 src/common/assets/images/smil3dbd4e4c2e742.gif create mode 100644 src/common/assets/images/smil3dbd4e5e7563a.gif create mode 100644 src/common/assets/images/smil3dbd4e7853679.gif create mode 100644 src/common/assets/xoops.css create mode 100644 src/common/lib/LangFlag.tsx create mode 100644 src/common/lib/Loading.tsx create mode 100644 src/common/lib/NoticeSiteHasBeenArchived.tsx create mode 100644 src/common/lib/PageNotFound.tsx create mode 100644 src/common/lib/XoopsCode.tsx create mode 100644 src/config.ts create mode 100644 src/credits/Credits.tsx create mode 100644 src/credits/CreditsIndex.tsx create mode 100644 src/credits/CreditsPage.tsx create mode 100644 src/credits/blocks/CreditsMenu.tsx create mode 100644 src/credits/lib/CreditsUtils.ts create mode 100644 src/custom/Footer.tsx create mode 100644 src/custom/Header.tsx create mode 100644 src/custom/Page.tsx create mode 100644 src/custom/assets/images/back.png create mode 100644 src/custom/assets/images/banner-niu.png create mode 100644 src/custom/assets/images/banner-riken-cbs.png create mode 100644 src/custom/assets/images/banner-riken.png create mode 100644 src/custom/assets/images/banner-xoonips.png create mode 100644 src/custom/assets/images/blockTitle.png create mode 100644 src/custom/assets/images/blockTitleBack.png create mode 100644 src/custom/assets/images/blockTitleIndent12.png create mode 100644 src/custom/assets/images/blockTitleIndent16.png create mode 100644 src/custom/assets/images/footer_back.png create mode 100644 src/custom/assets/images/h1_indent.png create mode 100644 src/custom/assets/images/h2_background.png create mode 100644 src/custom/assets/images/jnode.png create mode 100644 src/custom/assets/images/logo.png create mode 100644 src/custom/assets/images/mlang_english.png create mode 100644 src/custom/assets/images/mlang_japanese.png create mode 100644 src/custom/assets/images/mmenu_01.png create mode 100644 src/custom/assets/images/mmenu_02.png create mode 100644 src/custom/assets/images/mmenu_03.png create mode 100644 src/custom/assets/images/mmenu_04.png create mode 100644 src/custom/assets/images/page_top.png create mode 100644 src/custom/assets/images/smenu_01.png create mode 100644 src/custom/assets/images/smenu_03.png create mode 100644 src/custom/assets/images/smenu_04.png create mode 100644 src/custom/assets/style.css create mode 100644 src/custom/blocks/BulletinBoard.tsx create mode 100644 src/custom/blocks/HowToUseLinks.tsx create mode 100644 src/custom/blocks/HowToUseVideo.tsx create mode 100644 src/custom/blocks/Information.tsx create mode 100644 src/custom/blocks/Questionnaire.tsx create mode 100644 src/custom/blocks/RecentStatus.tsx create mode 100644 src/functions.ts create mode 100644 src/index.css create mode 100644 src/index.tsx create mode 100644 src/nimgcenter/NimgcenterBrainConv.tsx create mode 100644 src/nimgcenter/NimgenterUtilities.tsx create mode 100644 src/nimgcenter/assets/howtouse.json create mode 100644 src/nimgcenter/assets/utilities.json create mode 100644 src/nimgcenter/lib/BrainCoordinateUtil.ts create mode 100644 src/nimgdocs/Nimgdocs.tsx create mode 100644 src/nimgdocs/NimgdocsBulletinLog.tsx create mode 100644 src/nimgdocs/NimgdocsTutorials.tsx create mode 100644 src/nimgdocs/assets/3dbrowse.json create mode 100644 src/nimgdocs/assets/3dbrowse_usage.json create mode 100644 src/nimgdocs/assets/about.json create mode 100644 src/nimgdocs/assets/additem.json create mode 100644 src/nimgdocs/assets/bulletin.json create mode 100644 src/nimgdocs/assets/introduction.json create mode 100644 src/nimgdocs/assets/nimgcenter.json create mode 100644 src/nimgdocs/assets/nimgsearch.json create mode 100644 src/nimgsearch/Nimgsearch.module.css create mode 100644 src/nimgsearch/Nimgsearch.tsx create mode 100644 src/nimgsearch/assets/brainarea.json create mode 100644 src/nimgsearch/lib/NimgsearchUtil.ts create mode 100644 src/react-app-env.d.ts create mode 100644 src/reportWebVitals.ts create mode 100644 src/setupTests.ts create mode 100644 src/xoonips/Xoonips.module.css create mode 100644 src/xoonips/Xoonips.tsx create mode 100644 src/xoonips/XoonipsAdvancedSearch.tsx create mode 100644 src/xoonips/XoonipsDetailItem.tsx create mode 100644 src/xoonips/XoonipsSearchByAdvancedKeyword.tsx create mode 100644 src/xoonips/XoonipsSearchByIndexId.tsx create mode 100644 src/xoonips/XoonipsSearchByItemType.tsx create mode 100644 src/xoonips/XoonipsSearchByKeyword.tsx create mode 100644 src/xoonips/XoonipsTop.module.css create mode 100644 src/xoonips/XoonipsTop.tsx create mode 100644 src/xoonips/XoonipsXoopsPathRedirect.tsx create mode 100644 src/xoonips/assets/images/icon_binder.gif create mode 100644 src/xoonips/assets/images/icon_book.gif create mode 100644 src/xoonips/assets/images/icon_conference.gif create mode 100644 src/xoonips/assets/images/icon_data.gif create mode 100644 src/xoonips/assets/images/icon_files.gif create mode 100644 src/xoonips/assets/images/icon_folder.gif create mode 100644 src/xoonips/assets/images/icon_memo.gif create mode 100644 src/xoonips/assets/images/icon_model.gif create mode 100644 src/xoonips/assets/images/icon_nimgcenter.gif create mode 100644 src/xoonips/assets/images/icon_paper.gif create mode 100644 src/xoonips/assets/images/icon_presentation.gif create mode 100644 src/xoonips/assets/images/icon_simulator.gif create mode 100644 src/xoonips/assets/images/icon_stimulus.gif create mode 100644 src/xoonips/assets/images/icon_tool.gif create mode 100644 src/xoonips/assets/images/icon_url.gif create mode 100644 src/xoonips/assets/images/simpf_button.png create mode 100644 src/xoonips/assets/images/star.gif create mode 100644 src/xoonips/assets/images/tree_line.png create mode 100644 src/xoonips/assets/images/tree_node.png create mode 100644 src/xoonips/assets/simpf-links.json create mode 100644 src/xoonips/assets/tree.json create mode 100644 src/xoonips/blocks/IndexTree.module.css create mode 100644 src/xoonips/blocks/IndexTree.tsx create mode 100644 src/xoonips/blocks/Rankings.module.css create mode 100644 src/xoonips/blocks/Rankings.tsx create mode 100644 src/xoonips/blocks/RecentContents.module.css create mode 100644 src/xoonips/blocks/RecentContents.tsx create mode 100644 src/xoonips/blocks/Search.tsx create mode 100644 src/xoonips/item-type/binder/BinderAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/binder/BinderDetail.tsx create mode 100644 src/xoonips/item-type/binder/BinderList.tsx create mode 100644 src/xoonips/item-type/binder/BinderTop.tsx create mode 100644 src/xoonips/item-type/binder/index.tsx create mode 100644 src/xoonips/item-type/book/BookAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/book/BookDetail.tsx create mode 100644 src/xoonips/item-type/book/BookList.tsx create mode 100644 src/xoonips/item-type/book/BookTop.tsx create mode 100644 src/xoonips/item-type/book/index.tsx create mode 100644 src/xoonips/item-type/conference/ConferenceAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/conference/ConferenceDetail.tsx create mode 100644 src/xoonips/item-type/conference/ConferenceList.tsx create mode 100644 src/xoonips/item-type/conference/ConferenceTop.tsx create mode 100644 src/xoonips/item-type/conference/ConferenceUtil.tsx create mode 100644 src/xoonips/item-type/conference/index.tsx create mode 100644 src/xoonips/item-type/data/DataAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/data/DataDetail.tsx create mode 100644 src/xoonips/item-type/data/DataList.tsx create mode 100644 src/xoonips/item-type/data/DataTop.tsx create mode 100644 src/xoonips/item-type/data/DataUtil.tsx create mode 100644 src/xoonips/item-type/data/index.tsx create mode 100644 src/xoonips/item-type/files/FilesAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/files/FilesDetail.tsx create mode 100644 src/xoonips/item-type/files/FilesList.tsx create mode 100644 src/xoonips/item-type/files/FilesTop.tsx create mode 100644 src/xoonips/item-type/files/FilesUtil.tsx create mode 100644 src/xoonips/item-type/files/index.tsx create mode 100644 src/xoonips/item-type/index.tsx create mode 100644 src/xoonips/item-type/lib/AdvancedSearchBase.tsx create mode 100644 src/xoonips/item-type/lib/DetailBase.tsx create mode 100644 src/xoonips/item-type/lib/ListBase.tsx create mode 100644 src/xoonips/item-type/lib/TopBase.tsx create mode 100644 src/xoonips/item-type/lib/field/Author.tsx create mode 100644 src/xoonips/item-type/lib/field/ChangeLog.tsx create mode 100644 src/xoonips/item-type/lib/field/Contributer.tsx create mode 100644 src/xoonips/item-type/lib/field/CreativeCommons.tsx create mode 100644 src/xoonips/item-type/lib/field/DateTime.tsx create mode 100644 src/xoonips/item-type/lib/field/Description.tsx create mode 100644 src/xoonips/item-type/lib/field/FileDownloadButton.module.css create mode 100644 src/xoonips/item-type/lib/field/FileDownloadButton.tsx create mode 100644 src/xoonips/item-type/lib/field/FileSize.tsx create mode 100644 src/xoonips/item-type/lib/field/FreeKeyword.tsx create mode 100644 src/xoonips/item-type/lib/field/ItemFile.tsx create mode 100644 src/xoonips/item-type/lib/field/ItemIndex.tsx create mode 100644 src/xoonips/item-type/lib/field/Language.tsx create mode 100644 src/xoonips/item-type/lib/field/LicenseAgreementDialog.module.css create mode 100644 src/xoonips/item-type/lib/field/LicenseAgreementDialog.tsx create mode 100644 src/xoonips/item-type/lib/field/Preview.module.css create mode 100644 src/xoonips/item-type/lib/field/Preview.tsx create mode 100644 src/xoonips/item-type/lib/field/PublicationDate.tsx create mode 100644 src/xoonips/item-type/lib/field/Readme.tsx create mode 100644 src/xoonips/item-type/lib/field/RelatedTo.tsx create mode 100644 src/xoonips/item-type/lib/field/Rights.tsx create mode 100644 src/xoonips/item-type/lib/field/SimPFLinkIcon.tsx create mode 100644 src/xoonips/item-type/lib/field/index.tsx create mode 100644 src/xoonips/item-type/memo/MemoAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/memo/MemoDetail.tsx create mode 100644 src/xoonips/item-type/memo/MemoList.tsx create mode 100644 src/xoonips/item-type/memo/MemoTop.tsx create mode 100644 src/xoonips/item-type/memo/index.tsx create mode 100644 src/xoonips/item-type/model/ModelAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/model/ModelDetail.tsx create mode 100644 src/xoonips/item-type/model/ModelList.tsx create mode 100644 src/xoonips/item-type/model/ModelTop.tsx create mode 100644 src/xoonips/item-type/model/ModelUtil.tsx create mode 100644 src/xoonips/item-type/model/index.tsx create mode 100644 src/xoonips/item-type/nimgcenter/NimgcenterAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/nimgcenter/NimgcenterDetail.tsx create mode 100644 src/xoonips/item-type/nimgcenter/NimgcenterList.tsx create mode 100644 src/xoonips/item-type/nimgcenter/NimgcenterTop.tsx create mode 100644 src/xoonips/item-type/nimgcenter/NimgcenterUtil.tsx create mode 100644 src/xoonips/item-type/nimgcenter/SearchQuery.ts create mode 100644 src/xoonips/item-type/nimgcenter/index.tsx create mode 100644 src/xoonips/item-type/paper/PaperAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/paper/PaperDetail.tsx create mode 100644 src/xoonips/item-type/paper/PaperList.tsx create mode 100644 src/xoonips/item-type/paper/PaperTop.tsx create mode 100644 src/xoonips/item-type/paper/PaperUtil.tsx create mode 100644 src/xoonips/item-type/paper/index.tsx create mode 100644 src/xoonips/item-type/presentation/PresentationAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/presentation/PresentationDetail.tsx create mode 100644 src/xoonips/item-type/presentation/PresentationList.tsx create mode 100644 src/xoonips/item-type/presentation/PresentationTop.tsx create mode 100644 src/xoonips/item-type/presentation/PresentationUtil.tsx create mode 100644 src/xoonips/item-type/presentation/index.tsx create mode 100644 src/xoonips/item-type/simulator/SimulatorAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/simulator/SimulatorDetail.tsx create mode 100644 src/xoonips/item-type/simulator/SimulatorList.tsx create mode 100644 src/xoonips/item-type/simulator/SimulatorTop.tsx create mode 100644 src/xoonips/item-type/simulator/SimulatorUtil.tsx create mode 100644 src/xoonips/item-type/simulator/index.tsx create mode 100644 src/xoonips/item-type/stimulus/StimulusAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/stimulus/StimulusDetail.tsx create mode 100644 src/xoonips/item-type/stimulus/StimulusList.tsx create mode 100644 src/xoonips/item-type/stimulus/StimulusTop.tsx create mode 100644 src/xoonips/item-type/stimulus/StimulusUtil.tsx create mode 100644 src/xoonips/item-type/stimulus/index.tsx create mode 100644 src/xoonips/item-type/tool/ToolAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/tool/ToolDetail.tsx create mode 100644 src/xoonips/item-type/tool/ToolList.tsx create mode 100644 src/xoonips/item-type/tool/ToolTop.tsx create mode 100644 src/xoonips/item-type/tool/ToolUtil.tsx create mode 100644 src/xoonips/item-type/tool/index.tsx create mode 100644 src/xoonips/item-type/url/UrlAdvancedSearch.tsx create mode 100644 src/xoonips/item-type/url/UrlDetail.tsx create mode 100644 src/xoonips/item-type/url/UrlList.tsx create mode 100644 src/xoonips/item-type/url/UrlTop.tsx create mode 100644 src/xoonips/item-type/url/UrlUtil.tsx create mode 100644 src/xoonips/item-type/url/index.tsx create mode 100644 src/xoonips/lib/AdvancedSearchQuery.ts create mode 100644 src/xoonips/lib/IndexUtil.ts create mode 100644 src/xoonips/lib/ItemUtil.ts create mode 100644 src/xoonips/lib/XoonipsListIndex.tsx create mode 100644 src/xoonips/lib/XoonipsListItem.module.css create mode 100644 src/xoonips/lib/XoonipsListItem.tsx create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b60312 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +.eslintcache + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# database contents +/public/modules +#/src/xoonips/assets/*.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6fd779 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# NeuroImaging Platform +React project for nimg.neuroinf.jp + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/etc/common.inc.php b/etc/common.inc.php new file mode 100644 index 0000000..07810e9 --- /dev/null +++ b/etc/common.inc.php @@ -0,0 +1,324 @@ + $v) { + if (is_string($v)) { + $v = html_entity_decode($v, ENT_QUOTES | ENT_HTML5); + $v = mb_decode_numericentity($v, [0x0, 0x10000, 0, 0xfffff], 'UTF-8'); + $data[$k] = \Normalizer::normalize(trim($v), \Normalizer::FORM_C); + } elseif (null === $v) { + $data[$k] = ''; + } + } + } + + public static function convertToInt(&$data, $keys, $zerofill = false) + { + foreach ($keys as $key) { + if (isset($data[$key])) { + $data[$key] = '' === $data[$key] ? ($zerofill ? 0 : null) : (int) $data[$key]; + } + } + } + + public static function dropColumn(&$data, $keys) + { + foreach ($keys as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + + public static function fileCopy($from, $to) + { + system('rsync -aq '.escapeshellarg($from).' '.escapeshellarg($to), $ret); + + return 0 === $ret; + } + + public static function fixHtml($text) + { + static $config = [ + //'clean' => true, + 'hide-comments' => true, + 'indent' => true, + 'output-xhtml' => true, + 'preserve-entities' => true, + 'show-body-only' => true, + 'drop-proprietary-attributes' => true, + 'logical-emphasis' => true, + 'wrap' => 0, + 'input-encoding' => 'utf8', + 'output-encoding' => 'utf8', + 'output-bom' => false, + ]; + $text = tidy_repair_string($text, $config); + $text = preg_replace('/]*)>((?:\s*]*>.*<\/caption>)?\s*)]*)>/Us', '\2', $text); + $text = preg_replace('/<\/tr>(\s*)<\/table>/s', '\1', $text); + $text = tidy_repair_string($text, $config); + + return $text; + } + + public static function tableExists($name) + { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT 1 + FROM `${prefix}_${name}` + LIMIT 1 +SQL; + if (!($res = $xoopsDB->query($sql))) { + return false; + } + $xoopsDB->freeRecordSet($res); + + return true; + } + + public static function makeDirectory($dirname) + { + $path = MYDUMPTOOL_OUTPUTDIR.('' !== $dirname ? '/'.$dirname : ''); + if (!is_dir($path)) { + if (!@mkdir($path, 0755, true)) { + exit('Failed to create directory: '.$path.PHP_EOL); + } + } + } + + public static function saveJson($fname, $data) + { + $path = MYDUMPTOOL_OUTPUTDIR.'/'.$fname; + $data = json_encode($data, JSON_UNESCAPED_UNICODE); + if (false === $data) { + echo json_last_error_msg().PHP_EOL; + exit('Failed to encode json data: '.$path.PHP_EOL); + } + if (false === file_put_contents($path, $data)) { + exit('Failed to save json file: '.$path.PHP_EOL); + } + } + + public static function fileDownload($url, $fpath) + { + $fp = fopen($fpath, 'w'); + if (false === $fp) { + exit('Failed to open file: '.$fpath.PHP_EOL); + } + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_AUTOREFERER, true); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_FAILONERROR, true); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + $ret = curl_exec($curl); + fclose($fp); + if (false === $ret) { + unlink($fpath); + echo 'Failed to download file, unexpected error: '.$url.PHP_EOL; + + return false; + } + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + if (200 !== $code) { + unlink($fpath); + echo 'Failed to download file, invalid status code('.$code.'): '.$url.PHP_EOL; + + return false; + } + curl_close($curl); + + return true; + } + + public static function getMimeType($fpath) + { + $finfo = finfo_open(FILEINFO_MIME); + $mime = finfo_file($finfo, $fpath); + finfo_close($finfo); + $mime = preg_replace('/(;.*)$/', '', $mime); + + return $mime; + } + + public static function getModule($dirname) + { + $moduleHandler = xoops_gethandler('module'); + $moduleObj = $moduleHandler->getByDirname($dirname); + + return [ + 'id' => (int) $moduleObj->get('mid'), + 'name' => $moduleObj->get('name'), + 'dirname' => $moduleObj->get('dirname'), + 'version' => (int) $moduleObj->get('version'), + 'lastupdate' => (int) $moduleObj->get('last_update'), + ]; + } + + public static function canModuleRead($dirname, $uid) + { + $moduleHandler = xoops_gethandler('module'); + $moduleObj = $moduleHandler->getByDirname($dirname); + $moduleId = (int) $moduleObj->get('mid'); + $memberHandler = xoops_gethandler('member'); + $groupIds = 0 === $uid ? [XOOPS_GROUP_ANONYMOUS] : $memberHandler->getGroupsByUser($uid); + $groupPermHandler = xoops_gethandler('groupperm'); + $ret = $groupPermHandler->checkRight('module_read', $moduleId, $groupIds); + + return $ret; + } + + public static function mlang($s, $url, $lang) + { + if (!defined('CUBE_UTILS_ML_LANGS')) { + exit('Error: cubeUtils is not loaded'.PHP_EOL); + } + $mLanguage = $lang; + $mLanguages = explode(',', CUBE_UTILS_ML_LANGS); + $mLanguageNames = explode(',', CUBE_UTILS_ML_LANGNAMES); + // escape brackets inside of + $s = preg_replace_callback('/(\]*)(\>)/isU', 'self::mlang_escapeBracketTextBox', $s); + // escape brackets inside of + $s = preg_replace_callback('/(\]*\>)(.*)(<\/textarea\>)/isU', 'self::mlang_escapeBracket', $s); + // multilanguage image tag + $langimages = explode(',', CUBE_UTILS_ML_LANGIMAGES); + $langnames = explode(',', CUBE_UTILS_ML_LANGNAMES); + @list($url, $query) = explode('?', $url); + if (empty($query)) { + $link_base = '?'.CUBE_UTILS_ML_PARAM_NAME.'='; + } elseif (false === ($pos = strpos($query, CUBE_UTILS_ML_PARAM_NAME.'='))) { + $link_base = '?'.htmlspecialchars($query, ENT_QUOTES).'&'.CUBE_UTILS_ML_PARAM_NAME.'='; + } elseif ($pos < 2) { + $link_base = '?'.CUBE_UTILS_ML_PARAM_NAME.'='; + } else { + $link_base = '?'.htmlspecialchars(substr($query, 0, $pos - 1), ENT_QUOTES).'&'.CUBE_UTILS_ML_PARAM_NAME.'='; + } + $link_base = $url.$link_base; + $langimage_html = ''; + foreach ($mLanguages as $l => $lang) { + $langimage_html .= 'flag'; + } + $s = preg_replace('/\['.CUBE_UTILS_ML_IMAGETAG.'\]/', $langimage_html, $s); + + $s = preg_replace('/\['.CUBE_UTILS_ML_URLTAG.':([^\]]*?)\]/', $link_base.'$1', $s); + + // simple pattern to strip selected lang_tags + $s = preg_replace('/\[(\/)?([^\]]+\|)?'.preg_quote($mLanguage).'(\|[^\]]+)?\](\
)?/i', '', $s); + + // eliminate description between the other language tags. + foreach ($mLanguages as $lang) { + if ($mLanguage == $lang) { + continue; + } + $s = preg_replace_callback('/\[(?:^\/[^\]]+\|)?'.preg_quote($lang).'(?:\|[^\]]+)?\].*\[\/(?:^\/[^\]]+\|)?'.preg_quote($lang).'(?:\|[^\]]+)?(?:\]\
|\])/isU', 'self::mlang_checkNeverCross', $s); + } + + // escape brackets inside of + $s = preg_replace_callback('/(\]*)(\>)/isU', 'self::mlang_unEscapeBracketTextBox', $s); + + // escape brackets inside of + $s = preg_replace_callback('/(\]*\>)(.*)(<\/textarea\>)/isU', 'self::mlang_unEscapeBracket', $s); + + return $s; + } + + public static function mlang_escapeBracketTextBox($matches) + { + if (preg_match('/type=["\']?(?=text|hidden)["\']?/i', $matches[2])) { + return $matches[1].str_replace('[', '__ml[ml__', $matches[2]).$matches[3]; + } else { + return $matches[1].$matches[2].$matches[3]; + } + } + + public static function mlang_escapeBracket($matches) + { + return $matches[1].str_replace('[', '__ml[ml__', $matches[2]).$matches[3]; + } + + public static function mlang_unEscapeBracketTextBox($matches) + { + if (preg_match('/type=["\']?(?=text|hidden)["\']?/i', $matches[2])) { + return $matches[1].str_replace('__ml[ml__', '[', $matches[2]).$matches[3]; + } else { + return $matches[1].$matches[2].$matches[3]; + } + } + + public static function mlang_unEscapeBracket($matches) + { + return $matches[1].str_replace('__ml[ml__', '[', $matches[2]).$matches[3]; + } + + public static function mlang_checkNeverCross($matches) + { + return preg_match(CUBE_UTILS_ML_NEVERCROSSREGEX, $matches[0]) ? $matches[0] : ''; + } +} diff --git a/etc/config.inc.php b/etc/config.inc.php new file mode 100644 index 0000000..a0df0eb --- /dev/null +++ b/etc/config.inc.php @@ -0,0 +1,51 @@ + [ + 'type' => 'json', + 'file' => 'src/nimgcenter/assets/howtouse.json' + ], + '/modules/xnpnimgcenter/utilities/index.php' => [ + 'type' => 'json', + 'file' => 'src/nimgcenter/assets/utilities.json' + ], + //'/modules/nimgdocs/tutorials/index.php' => [ + // 'type' => 'tsx', + // 'file' => 'extras/nimgdocs/Tutorials.tsx', + //], + '/modules/nimgdocs/index.php?docname=about_NIMGPF' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/about.json', + ], + '/modules/nimgdocs/index.php?docname=introduction' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/introduction.json', + ], + '/modules/nimgdocs/index.php?docname=additem' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/additem.json', + ], + '/modules/nimgdocs/index.php?docname=nimgcenter' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/nimgcenter.json', + ], + '/modules/nimgdocs/index.php?docname=nimgsearch' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/nimgsearch.json', + ], + '/modules/nimgdocs/index.php?docname=3DBrowse' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/3dbrowse.json', + ], + '/modules/nimgdocs/index.php?docname=3DBrowse_usage' => [ + 'type' => 'json', + 'file' => 'src/nimgdocs/assets/3dbrowse_usage.json', + ], +]; + +$_CURLOPT_RESOLVE_VALUE = ['nimg.neuroinf.jp:443:127.0.0.1']; diff --git a/etc/copy-brainexplorer.sh b/etc/copy-brainexplorer.sh new file mode 100644 index 0000000..4a783fb --- /dev/null +++ b/etc/copy-brainexplorer.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +XOOPS_ROOT_PATH=/data/www/nimg.neuroinf.jp/html +DATADIR=$(dirname $0)/data + +SRCDIR=${XOOPS_ROOT_PATH}/modules/brainexplorer/data/MRI +DESTDIR=${DATADIR}/public/modules/brainexplorer/data/m3d + +mkdir -p ${DESTDIR} +rsync -av --delete ${SRCDIR}/ ${DESTDIR}/ + +SRCDIR=${XOOPS_ROOT_PATH}/modules/be2/data/g3d +DESTDIR=${DATADIR}/public/modules/brainexplorer/data/g3d +mkdir -p ${DESTDIR} +rsync -av --delete ${SRCDIR}/ ${DESTDIR}/ diff --git a/etc/copy-nimgdocs.sh b/etc/copy-nimgdocs.sh new file mode 100644 index 0000000..6961d72 --- /dev/null +++ b/etc/copy-nimgdocs.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +XOOPS_ROOT_PATH=/data/www/nimg.neuroinf.jp/html +DATADIR=$(dirname $0)/data + +SRCDIR=${XOOPS_ROOT_PATH}/modules/nimgdocs +DESTDIR=${DATADIR}/public/modules/nimgdocs +rsync -av --delete ${SRCDIR}/tutorials/movie/ ${DESTDIR}/tutorials/movie/ +rsync -av ${SRCDIR}/tutorials/slide.html ${DESTDIR}/tutorials/ +rsync -av --delete ${SRCDIR}/tutorials/slide/ ${DESTDIR}/tutorials/slide/ +rsync -av ${SRCDIR}/tutorials/slide2.html ${DESTDIR}/tutorials/ +rsync -av --delete ${SRCDIR}/tutorials/slide2/ ${DESTDIR}/tutorials/slide2/ +rsync -av --delete ${SRCDIR}/docs/nimgcenter_document/ ${DESTDIR}/docs/nimgcenter_document/ +rsync -av --delete ${SRCDIR}/docs/nimgsearch_document/ ${DESTDIR}/docs/nimgsearch_document/ +rsync -av --delete ${SRCDIR}/docs/pdf/ ${DESTDIR}/docs/pdf/ +rsync -av --delete ${SRCDIR}/docs/poster/ ${DESTDIR}/docs/poster/ +rsync -av --delete ${SRCDIR}/docs/viewlet/ ${DESTDIR}/docs/viewlet/ +rsync -av --delete ${SRCDIR}/docs/winword/ ${DESTDIR}/docs/winword/ diff --git a/etc/copy-nimgsearch.sh b/etc/copy-nimgsearch.sh new file mode 100644 index 0000000..e0ee810 --- /dev/null +++ b/etc/copy-nimgsearch.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +XOOPS_ROOT_PATH=/data/www/nimg.neuroinf.jp/html +DATADIR=$(dirname $0)/data + +SRCDIR=${XOOPS_ROOT_PATH}/modules/nimgsearch +DESTDIR=${DATADIR}/public/modules/nimgsearch +mkdir -p ${DESTDIR} +rsync -av --delete ${SRCDIR}/res/ ${DESTDIR}/res/ +rsync -av --delete ${SRCDIR}/help/ ${DESTDIR}/help/ diff --git a/etc/dl-limit-items.csv b/etc/dl-limit-items.csv new file mode 100644 index 0000000..f349913 --- /dev/null +++ b/etc/dl-limit-items.csv @@ -0,0 +1,10 @@ +610,fujimaki,"Norio Fujimaki",fujimaki@po.nict.go.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=610 +611,fujimaki,"Norio Fujimaki",fujimaki@po.nict.go.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=611 +612,fujimaki,"Norio Fujimaki",fujimaki@po.nict.go.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=612 +613,fujimaki,"Norio Fujimaki",fujimaki@po.nict.go.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=613 +1262,ichikawa,"Kazuhisa Ichikawa",kichi@ims.u-tokyo.ac.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=1262 +1343,kuriki,"Shinya Kuriki",sk@es.hokudai.ac.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?id=DD019 +1358,kuriki,"Shinya Kuriki",sk@es.hokudai.ac.jp,https://nimg.neuroinf.jp/modules/xoonips/detail.php?id=DD018 +6965,kiyotaka,根本清貴,kiyotaka@nemotos.net,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=6965 +6966,kiyotaka,根本清貴,kiyotaka@nemotos.net,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=6966 +6967,kiyotaka,根本清貴,kiyotaka@nemotos.net,https://nimg.neuroinf.jp/modules/xoonips/detail.php?item_id=6967 diff --git a/etc/dumpCredits.php b/etc/dumpCredits.php new file mode 100644 index 0000000..ca78eee --- /dev/null +++ b/etc/dumpCredits.php @@ -0,0 +1,154 @@ +prefix(); + +$table = 'nipfcredits_organization'; +if (!MyDumpTool::tableExists($table)) { + exit('credits module not found'.PHP_EOL); +} +MyDumpTool::makeDirectory('public/modules/credits'); + +// module +$module = MyDumpTool::getModule('credits'); + +// organization +$organization = []; +$sql = <<< SQL +SELECT + `o`.* + FROM `${prefix}_nipfcredits_organization` AS `o` +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $organization = $row; +} +$xoopsDB->freeRecordSet($res); +$organization['com_email'] = str_replace('@', ' (at) ', $organization['com_email']); +$organization['com_url'] = XOOPS_URL; +$organization['institute'] = '[en]Neuroinformatics Unit, RIKEN Center for Brain Science[/en][ja]理化学研究所 脳神経科学研究センター 神経情報基盤ユニット[/ja]'; +$organization['url'] = 'https://www.ni.riken.jp/'; +$organization['email'] = 'office (at) ni.riken.jp'; +$organization['address'] = '[en]Hirosawa 2-1, Wako, Saitama, 351-0198 Japan[/en][ja]埼玉県和光市広沢 2-1[/ja]'; +$organization['tel'] = '+81 48 (462) 1111'; + +// member +$member = []; +$sql = <<< SQL +SELECT + `m`.* + FROM `${prefix}_nipfcredits_member` AS `m` + ORDER BY `m`.`weight` +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $member[] = $row; +} +$xoopsDB->freeRecordSet($res); +$opage = getOrganizationPage($organization, $member, $module); +MyDumpTool::saveJson('public/modules/credits/0.json', $opage); + +// pages +$pages = []; +$sql = <<< SQL +SELECT + `p`.* + FROM `${prefix}_nipfcredits_page` AS `p` + ORDER BY `p`.`weight` +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + myDumpTool::convertToInt($row, ['pid', 'weight', 'lastupdate']); + $content = replaceOrganization($row['content'], $organization); + $content = normalizeHtml($content, '/modules/credits/index.php'); + $content = preg_replace('/]*)href="(?:'.preg_quote(XOOPS_URL, '/').')?\/modules\/xoonips\/(?:register|edit|user)[^"]*"(?:[^>]*)>(.*)<\/a>/Usi', '\1(closed)', $content); + $row['content'] = $content; + MyDumpTool::dropColumn($row, ['weight']); + $pages[] = [ + 'id' => $row['pid'], + 'title' => $row['title'], + ]; + MyDumpTool::saveJson('public/modules/credits/'.$row['pid'].'.json', $row); +} +$xoopsDB->freeRecordSet($res); + +$pages[] = [ + 'id' => $opage['id'], + 'title' => $opage['title'], +]; + +$data = [ + 'name' => $module['name'], + 'pages' => $pages, +]; +MyDumpTool::saveJson('public/modules/credits/index.json', $data); + +function replaceOrganization($text, $org) +{ + $map = [ + '{COMMITTEE_NAME}' => htmlspecialchars($org['committee']), + //'{COMMITTEE_EMAIL}' => ''.htmlspecialchars($org['com_email'], ENT_QUOTES).'(committee email has been closed)', + '{COMMITTEE_EMAIL}' => htmlspecialchars($org['com_email'], ENT_QUOTES), + '{INSTITUTE_NAME}' => htmlspecialchars($org['institute'], ENT_QUOTES), + '{INSTITUTE_URL}' => htmlspecialchars($org['url'], ENT_QUOTES), + '{INSTITUTE_EMAIL}' => htmlspecialchars($org['email'], ENT_QUOTES), + '{INSTITUTE_ADDRESS}' => htmlspecialchars($org['address'], ENT_QUOTES), + '{INSTITUTE_TEL}' => htmlspecialchars($org['tel'], ENT_QUOTES), + '{PLATFORM_URL}' => XOOPS_URL, + ]; + + return str_replace(array_keys($map), $map, $text); +} + +function getOrganizationPage($org, $member, $module) +{ + $members = []; + if (!empty($member)) { + $members[] = '
  • [en]Members[/en][ja]委員一覧[/ja]'; + $members[] = '
      '; + foreach ($member as $m) { + $members[] = '
    • '.htmlspecialchars($m['name'].' : '.$m['division'], ENT_QUOTES).'
    • '; + } + $members[] = '
    '; + $members[] = '
  • '; + } + $members = implode("\n", $members); + $committee = htmlspecialchars($org['committee'], ENT_QUOTES); + $institute = htmlspecialchars($org['institute'], ENT_QUOTES); + $address = htmlspecialchars($org['address'], ENT_QUOTES); + $url = htmlspecialchars($org['url'], ENT_QUOTES); + $email = htmlspecialchars($org['com_email'], ENT_QUOTES); + $html = <<< HTML +

    ${committee}

    +
      +
    • E-Mail: ${email}
    • +${members} +
    +

    ${institute}

    +
    +HTML; + + return [ + 'id' => 0, + 'title' => '[en]About Us[/en][ja]運用母体[/ja]', + 'content' => $html, + 'lastupdate' => 0, + ]; +} diff --git a/etc/dumpNimgdocsBulletin.php b/etc/dumpNimgdocsBulletin.php new file mode 100644 index 0000000..13a0a13 --- /dev/null +++ b/etc/dumpNimgdocsBulletin.php @@ -0,0 +1,44 @@ +prefix(); +$table = 'nimgdocs_bulletin'; +if (!MyDumpTool::tableExists($table)) { + exit('nimgdocs module not found'.PHP_EOL); +} +$sql = <<< SQL +SELECT + `b`.* + FROM `${prefix}_${table}` AS `b` + WHERE `b`.`accepted`=1 + ORDER BY `b`.`post_date` DESC +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} + +$data = []; +while ($row = $xoopsDB->fetchArray($res)) { + $row['tags'] = urldecode($row['tags']); + $row['text_en'] = urldecode($row['text_en']); + $row['text_jp'] = urldecode($row['text_jp']); + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['id', 'accepted']); + $row['text'] = [ + 'en' => $row['text_en'], + 'ja' => $row['text_jp'], + ]; + MyDumpTool::dropColumn($row, ['accepted', 'text_en', 'text_jp']); + $row['tags'] = explode(' ', $row['tags']); + $row['post_date'] = strtotime($row['post_date']); + $row['end_date'] = strtotime($row['end_date']); + $data[] = $row; +} +$xoopsDB->freeRecordSet($res); + +MyDumpTool::makeDirectory('src/nimgdocs/assets'); +MyDumpTool::saveJson('src/nimgdocs/assets/bulletin.json', $data); + diff --git a/etc/dumpPico.php b/etc/dumpPico.php new file mode 100644 index 0000000..b40c08d --- /dev/null +++ b/etc/dumpPico.php @@ -0,0 +1,102 @@ +prefix(); + $table = $pico.'_contents'; + if (!MyDumpTool::tableExists($table)) { + exit('pico('.$pico.') module not found'.PHP_EOL); + } + MyDumpTool::makeDirectory('public/modules/'.$pico); + $moduleHandler = xoops_gethandler('module'); + $module = $moduleHandler->getByDirname($pico); + $moduleConfigHandler = xoops_gethandler('config'); + $moduleConfig = $moduleConfigHandler->getConfigsByDirname($pico); + $permObj = PicoPermission::getInstance(); + $perms = $permObj->getPermissions($pico); + $categoryHandler = new PicoCategoryHandler($pico, $perms); + $contentHandler = new PicoContentHandler($pico); + $cats = $categoryHandler->getAllCategories(); + $confContents = []; + $confCategories = []; + $dataMap = []; + $dataSeq = 1; + foreach ($cats as $cat) { + $catData = $cat->getData(); + $modConfig = $cat->getOverriddenModConfig(); + $link = pico_common_make_category_link4html($modConfig, $catData, $pico); + MyDumpTool::decode($catData); + $confCategories[] = [ + 'id' => $catData['id'], + 'title' => $catData['cat_title'], + 'desc' => (string) $catData['cat_desc'], + 'pid' => (int) $catData['pid'], + 'weight' => (int) $catData['cat_weight'], + 'link' => $link, + ]; + $contents = $contentHandler->getCategoryContents($cat); + foreach ($contents as $content) { + $data = $content->getData(); + $link = pico_common_make_content_link4html($modConfig, $data); + MyDumpTool::decode($data); + $url = XOOPS_URL.'/modules/'.$pico.'/'.$link; + $content_en = normalizeHtml(MyDumpTool::mlang($data['body_cached'], $url, 'en'), $url); + $content_ja = normalizeHtml(MyDumpTool::mlang($data['body_cached'], $url, 'ja'), $url); + $item = [ + 'id' => $data['id'], + 'title' => $data['subject_raw'], + 'content' => [ + 'en' => $content_en, + 'ja' => $content_ja, + ], + ]; + MyDumpTool::saveJson('public/modules/'.$pico.'/'.$data['id'].'.json', $item); + $confContents[] = [ + 'id' => $item['id'], + 'title' => $data['subject_raw'], + 'cat_id' => (int) $data['cat_id'], + 'weight' => (int) $data['weight'], + 'link' => $link, + ]; + } + } + $moduleinfo = [ + 'name' => $module->get('name'), + 'dirname' => $pico, + 'message' => $moduleConfig['top_message'], + 'show_menuinmoduletop' => (int) $moduleConfig['show_menuinmoduletop'], + 'show_listasindex' => (int) $moduleConfig['show_listasindex'], + 'show_breadcrumbs' => (int) $moduleConfig['show_breadcrumbs'], + 'show_pagenavi' => (int) $moduleConfig['show_pagenavi'], + ]; + MyDumpTool::decode($moduleinfo); + $config = [ + 'module' => $moduleinfo, + 'categories' => $confCategories, + 'contents' => $confContents, + ]; + MyDumpTool::saveJson('public/modules/'.$pico.'/index.json', $config); +} diff --git a/etc/dumpStaticPage.php b/etc/dumpStaticPage.php new file mode 100644 index 0000000..092499d --- /dev/null +++ b/etc/dumpStaticPage.php @@ -0,0 +1,207 @@ + $config) { + $data = [ + 'en' => StaticFiles::getPage($page, 'en'), + 'ja' => StaticFiles::getPage($page, 'ja'), + ]; + MyDumpTool::makeDirectory(dirname($config['file'])); + switch ($config['type']) { + case 'json': + MyDumpTool::saveJson($config['file'], $data); + break; + case 'tsx': + StaticFiles::saveTsx($config['file'], $data); + break; + } +} + +class StaticFiles +{ + public static function saveTsx($fname, $data) + { + $path = MYDUMPTOOL_OUTPUTDIR.'/'.$fname; + $cname = basename($fname, '.tsx'); + $content_ja = self::makeJsx($data['ja']); + $content_en = self::makeJsx($data['en']); + $tsx = <<< TSX +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { MultiLang } from '../config'; +import Functions from '../functions'; + +interface Props { + lang: MultiLang; +} + +interface State { + isOpen: boolean; +} + +class ${cname} extends Component { + + constructor(props: Props) { + super(props); + this.state = { + isOpen: false + }; + } + + getContent(lang: MultiLang) { + const contents = { + en: <> + ${content_en} + , + ja: <> + ${content_ja} + ; + } + return contents[lang]; + } + + render() { + const { lang } = this.props; + return this.getContent(lang); + } +} + +export default ${cname}; +TSX; + file_put_contents($path, $tsx); + } + + private static function makeJsx($text) + { + $text = preg_replace_callback('/<([a-z0-9]+)((?: +[a-z]+(?:="[^"]*")?)*)( *\/?)>/Us', function ($matches) { + $tag = $matches[1]; + preg_match_all('/ +([a-z]+?)(?:="([^"]*)")??/Us', $matches[2], $out, PREG_SET_ORDER); + $attribs = []; + foreach ($out as $_out) { + $key = $_out[1]; + $value = html_entity_decode(trim($_out[2]), ENT_QUOTES | ENT_HTML5); + $attribs[$key] = $value; + } + $styles = []; + if (isset($attribs['style'])) { + foreach (array_map('trim', explode(';', $attribs['style'])) as $style) { + if ('' === $style) { + continue; + } + [$key, $value] = array_map('trim', explode(':', $style)); + $key = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $key)))); + $styles[$key] = preg_replace('/ +/U', ' ', $value); + } + } + $close = str_replace('/', ' /', trim($matches[3])); + if (isset($attribs['class'])) { + $attribs['className'] = $attribs['class']; + unset($attribs['class']); + } + if ('a' === $tag && isset($attribs['target']) && '_blank' === $attribs['target'] && !isset($attribs['rel'])) { + $attribs['rel'] = 'noopener noreferrer'; + } + foreach (['align', 'width', 'height'] as $key) { + $kmap = ['align' => 'textAlign']; + $skey = isset($kmap[$key]) ? $kmap[$key] : $key; + if (isset($attribs[$key])) { + $styles[$skey] = $attribs[$key]; + unset($attribs[$key]); + } + } + + $x = []; + foreach ($styles as $key => $style) { + $x[] = $key.': "'.$style.'"'; + } + if (empty($x)) { + unset($attribs['style']); + } else { + $attribs['style'] = '{'.implode(', ', $x).'}'; + } + + $x = ''; + foreach ($attribs as $key => $value) { + if ('' !== $x) { + $x .= ' '; + } + switch ($key) { + case 'style': + $x .= $key.'={'.$value.'}'; + break; + case 'controls': + if ('' === $value) { + $x .= $key; + } + break; + default: + $x .= $key.'="'.str_replace(''', '\'', htmlspecialchars($value, ENT_QUOTES)).'"'; + } + } + if ('' !== $x) { + $x = ' '.$x; + } + + return '<'.$tag.$x.$close.'>'; + }, $text); + + return $text; + } + + public static function getPage($page, $lang) + { + $url = XOOPS_URL.$page; + $html = self::fetch($url.(false !== strpos($page, '?') ? '&' : '?').'ml_lang='.$lang); + $xml = self::getXml($html, 'content'); + + return normalizeHtml($xml, $url); + } + + public static function getXml($html, $id) + { + $text = MyDumpTool::fixHtml($html); + $text = str_replace('&', '&', $text); + $xml = simplexml_load_string($text); + if (false === $xml) { + die('failed to load xml: '.$text.PHP_EOL); + } + $objs = $xml->xpath('//*[@id="'.$id.'"]/*'); + if (false === $objs || empty($objs)) { + die('failed to find id attribute: '.$id.PHP_EOL); + } + $ret = ''; + foreach ($objs as $obj) { + $ret .= $obj->asXml(); + } + $ret = str_replace('&', '&', $ret); + + return $ret; + } + + public static function fetch($url) + { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_AUTOREFERER, true); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_FAILONERROR, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + if (isset($GLOBALS['_CURLOPT_RESOLVE_VALUE'])) { + curl_setopt($curl, CURLOPT_RESOLVE, $GLOBALS['_CURLOPT_RESOLVE_VALUE']); + } + $ret = curl_exec($curl); + if (false === $ret) { + exit('Failed to fetch, unexpected error: '.$url.PHP_EOL); + } + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + if (200 !== $code) { + exit('Failed to download file, invalid status code('.$code.'): '.$url.PHP_EOL); + } + curl_close($curl); + + return $ret; + } +} diff --git a/etc/dumpXooNIps.php b/etc/dumpXooNIps.php new file mode 100644 index 0000000..444bd64 --- /dev/null +++ b/etc/dumpXooNIps.php @@ -0,0 +1,1241 @@ +prefix(); + +$table = 'xoonips_item_basic'; +if (!MyDumpTool::tableExists($table)) { + exit('xoonips module not found'.PHP_EOL); +} +MyDumpTool::makeDirectory('src/xoonips'); +MyDumpTool::makeDirectory('src/xoonips/assets'); +MyDumpTool::makeDirectory('public/modules/xoonips'); +MyDumpTool::makeDirectory('public/modules/xoonips/file'); + +// xoonips config +$sql = <<< SQL +SELECT + * + FROM `${prefix}_xoonips_config` +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$xoonipsConfigs = []; +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['item_id', 'last_update_date']); + $xoonipsConfigs[$row['name']] = $row['value']; +} +$xoopsDB->freeRecordSet($res); + +// Recent Contents +$ranking = []; +$limit = $xoonipsConfigs['ranking_new_num_rows']; +$sql = <<< SQL +SELECT + `ib`.`item_id`, `ib`.`doi`, `ib`.`last_update_date`, + GROUP_CONCAT(DISTINCT `it`.`title` ORDER BY `it`.`title_id` ASC SEPARATOR '\n') AS `title` + FROM `${prefix}_xoonips_ranking_new_item` AS `ri` + INNER JOIN `${prefix}_xoonips_item_basic` AS `ib` ON `ri`.`item_id`=`ib`.`item_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `ib`.`item_id`=`iil`.`item_id` + INNER JOIN `${prefix}_xoonips_item_title` AS `it` ON `ib`.`item_id`=`it`.`item_id` + WHERE `iil`.`certify_state`=2 + GROUP BY `ib`.`item_id` + ORDER BY `ib`.`last_update_date` DESC + LIMIT $limit +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['item_id', 'last_update_date']); + $ranking[] = $row; +} +$xoopsDB->freeRecordSet($res); +MyDumpTool::saveJson('public/modules/xoonips/recent-contents.json', $ranking); +unset($ranking); + +// Rankings +$ranking = []; +$limit = $xoonipsConfigs['ranking_num_rows']; +$rankingOrder = array_map('trim', explode(',', $xoonipsConfigs['ranking_order'])); +$rankingVisible = array_map('trim', explode(',', $xoonipsConfigs['ranking_visible'])); +$rankingKey2Index = [ + 'accessed' => 0, + 'downloaded' => 1, + 'contributed' => 2, + 'searched' => 3, + 'groups' => 4, +]; +function rankingSortFunc($a, $b) +{ + global $rankingOrder; + global $rankingKey2Index; + $na = $rankingOrder[$rankingKey2Index[$a]]; + $nb = $rankingOrder[$rankingKey2Index[$b]]; + + return $na === $nb ? 0 : $na > $nb; +} +$limit = $xoonipsConfigs['ranking_num_rows']; +// - most accessed items +$ranking['accessed'] = []; +if ($rankingVisible[$rankingKey2Index['accessed']]) { + $sql = <<< SQL +SELECT + `ib`.`item_id`, `ib`.`doi`, + GROUP_CONCAT(DISTINCT `it`.`title` ORDER BY `it`.`title_id` ASC SEPARATOR '\n') AS `title`, + `vi`.`count` + FROM `${prefix}_xoonips_ranking_viewed_item` AS `vi` + INNER JOIN `${prefix}_xoonips_item_basic` AS `ib` ON `vi`.`item_id`=`ib`.`item_id` + INNER JOIN `${prefix}_xoonips_item_title` AS `it` ON `ib`.`item_id`=`it`.`item_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `ib`.`item_id`=`iil`.`item_id` + WHERE `iil`.`certify_state`=2 + GROUP BY `ib`.`item_id` + ORDER BY `vi`.`count` DESC + LIMIT $limit +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['item_id', 'count']); + $ranking['accessed'][] = $row; + } + $xoopsDB->freeRecordSet($res); +} +// - most accessed items +$ranking['downloaded'] = []; +if ($rankingVisible[$rankingKey2Index['downloaded']]) { + $sql = <<< SQL +SELECT + `ib`.`item_id`, `ib`.`doi`, + GROUP_CONCAT(DISTINCT `it`.`title` ORDER BY `it`.`title_id` ASC SEPARATOR '\n') AS `title`, + `di`.`count` + FROM `${prefix}_xoonips_ranking_downloaded_item` AS `di` + INNER JOIN `${prefix}_xoonips_item_basic` AS `ib` ON `di`.`item_id`=`ib`.`item_id` + INNER JOIN `${prefix}_xoonips_item_title` AS `it` ON `ib`.`item_id`=`it`.`item_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `ib`.`item_id`=`iil`.`item_id` + WHERE `iil`.`certify_state`=2 + GROUP BY `ib`.`item_id` + ORDER BY `di`.`count` DESC + LIMIT $limit +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['item_id', 'count']); + $ranking['downloaded'][] = $row; + } + $xoopsDB->freeRecordSet($res); +} +// - most contributed users +$ranking['contributed'] = []; +if ($rankingVisible[$rankingKey2Index['contributed']]) { + $sql = <<< SQL +SELECT + `u`.`uid`, `u`.`uname`, `u`.`name`, COUNT(`cu`.`item_id`) AS `count` + FROM `${prefix}_xoonips_ranking_contributing_user` AS `cu` + INNER JOIN `${prefix}_users` AS `u` ON `cu`.`uid`=`u`.`uid` + GROUP BY `u`.`uid` + ORDER BY `count` DESC + LIMIT $limit +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['uid', 'count']); + $ranking['contributed'][] = $row; + } + $xoopsDB->freeRecordSet($res); +} +// - most searched keywords +$ranking['searched'] = []; +if ($rankingVisible[$rankingKey2Index['searched']]) { + $sql = <<< SQL +SELECT + `sk`.`keyword`, `sk`.`count` + FROM `${prefix}_xoonips_ranking_searched_keyword` AS `sk` + ORDER BY `sk`.`count` DESC + LIMIT $limit +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['count']); + $ranking['searched'][] = $row; + } + $xoopsDB->freeRecordSet($res); +} +uksort($ranking, 'rankingSortFunc'); +MyDumpTool::saveJson('public/modules/xoonips/rankings.json', $ranking); +unset($ranking); + +// get public index with certified item count +$sql = <<< SQL +SELECT + `idx`.*, + `it`.`title`, + (SELECT COUNT(*) + FROM `${prefix}_xoonips_index_item_link` AS `iil` + INNER JOIN `${prefix}_xoonips_item_basic` AS `ib` ON `iil`.`item_id`=`ib`.`item_id` + WHERE `iil`.`index_id`=`idx`.`index_id` AND `iil`.`certify_state`=2 + ) AS `num_items` + FROM `${prefix}_xoonips_index` AS `idx` + INNER JOIN `${prefix}_xoonips_item_title` AS `it` ON `idx`.`index_id`=`it`.`item_id` + WHERE `it`.`title_id`=0 + AND `idx`.`open_level`=1 +SQL; + +$limitItems = []; +$limitFiles = []; + +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$tree = []; +$root = []; +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $tree[$row['index_id']] = $row; + $root[$row['parent_index_id']][$row['sort_number']] = $row['index_id']; +} +$xoopsDB->freeRecordSet($res); +foreach ($root as &$node) { + ksort($node); +} +unset($node); + +$data[] = getTreeNode($tree, $root, 3); +MyDumpTool::saveJson('src/xoonips/assets/tree.json', $data); +unset($data, $root); + +// get simpf link data +$simpflink = parseSimPFLinkFile(); +$simpfurls = []; + +// get public items +$sql = <<< SQL +SELECT + `ib`.*, + GROUP_CONCAT(DISTINCT `it`.`title` ORDER BY `it`.`title_id` ASC SEPARATOR '\n') AS `title`, + `ity`.`display_name` AS `item_type_display_name`, `ity`.`name` AS `item_type_name`, + `u`.`uname`, `u`.`name`, `u`.`email` + FROM `${prefix}_xoonips_item_basic` AS `ib` + INNER JOIN `${prefix}_xoonips_item_title` AS `it` ON `ib`.`item_id`=`it`.`item_id` + INNER JOIN `${prefix}_xoonips_item_type` AS `ity` ON `ib`.`item_type_id`=`ity`.`item_type_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `ib`.`item_id`=`iil`.`item_id` + INNER JOIN `${prefix}_xoonips_index` AS `idx` ON `iil`.`index_id`=`idx`.`index_id` + LEFT JOIN `${prefix}_users` AS `u` ON `ib`.`uid`=`u`.`uid` + WHERE `ity`.`display_name` != 'Index' + AND `iil`.`certify_state`=2 + AND `idx`.`open_level`=1 + GROUP BY `ib`.`item_id` + ORDER BY `ib`.`item_id` ASC +SQL; + +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$items = []; +$itemIds = []; +$baseItemIds = []; +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['item_id', 'item_type_id', 'uid', 'last_update_date', 'creation_date', 'publication_year', 'publication_month', 'publication_mday']); + $row['index'] = getIndexes($tree, $row['item_id']); + if (0 === count($row['index'])) { + // no public indexes found. + continue; + } + $row['changelog'] = getChangeLogs($row['item_id']); + $row['related_to'] = getRelatedTo($row['item_id']); + $row['keyword'] = getKeyword($row['item_id']); + $row['file'] = getFile($row['item_id']); + switch ($row['item_type_name']) { + case 'xnpbinder': + appendBinderInfo($row); + break; + case 'xnpbook': + appendBookInfo($row); + break; + case 'xnpconference': + appendConferenceInfo($row); + break; + case 'xnpdata': + appendDataInfo($row); + break; + case 'xnpfiles': + appendFilesInfo($row); + break; + case 'xnpmemo': + appendMemoInfo($row); + break; + case 'xnpmodel': + appendModelInfo($row); + break; + case 'xnppaper': + appendPaperInfo($row); + break; + case 'xnppresentation': + appendPresentationInfo($row); + break; + case 'xnpsimulator': + appendSimulatorInfo($row); + break; + case 'xnpstimulus': + appendStimulusInfo($row); + break; + case 'xnptool': + appendToolInfo($row); + break; + case 'xnpurl': + appendUrlInfo($row); + break; + case 'xnpnimgcenter': + appendNimgcenterInfo($row); + break; + default: + exit('Unsupported item type: '.$row['item_type_name']); + } + if (isset($row['attachment_dl_limit']) && $row['attachment_dl_limit']) { + $found = false; + foreach ($row['file'] as $file) { + if ('preview' !== $file['file_type_name']) { + $limitFiles[] = $file['file_id']; + $found = true; + } + } + if ($found) { + $limitItems[] = [ + 'uname' => $row['uname'], + 'name' => $row['name'], + 'email' => $row['email'], + 'item_id' => $row['item_id'], + 'doi' => $row['doi'], + ]; + } + } + MyDumpTool::dropColumn($row, ['email', 'item_type_id']); + if (isset($row['rights']) && isset($row['use_cc']) && isset($row['cc_commercial_use']) && isset($row['cc_modification']) && 1 == $row['use_cc']) { + $label = 'Creative Commons Attribution'; + if (0 == $row['cc_commercial_use']) { + $label .= '-NonCommercial'; + } + switch ($row['cc_modification']) { + case 0: + $label .= '-NoDerivatives'; + break; + case 1: + $label .= '-ShareAlike'; + break; + } + $label .= ' 4.0 International License.'; + $row['rights'] = $label; + } + $items[] = $row; + + $simpfurl = ''; + if ('' != $row['doi'] && isset($simpflink['id'][$row['doi']])) { + $simpfurl = $simpflink['id'][$row['doi']]; + } elseif (isset($simpflink['item_id'][$row['item_id']])) { + $simpfurl = $simpflink['item_id'][$row['item_id']]; + } + if ('' !== $simpfurl) { + $simpfurls[] = [ + 'id' => $row['item_id'], + 'url' => $simpfurl, + ]; + } + $itemIds[] = $row['item_id']; +} +$xoopsDB->freeRecordSet($res); +MyDumpTool::saveJson('public/modules/xoonips/items.json', $items); +MyDumpTool::saveJson('src/xoonips/assets/simpf-links.json', $simpfurls); +foreach ($baseItemIds as $bid) { + if (!in_array($bid, $itemIds)) { + echo 'Warning: baseitem_id('.$bid.') not found'.PHP_EOL; + } +} + +$fp = fopen(MYDUMPTOOL_OUTPUTDIR.'/dl-limit-items.csv', 'w'); +foreach ($limitItems as $row) { + $data = [ + $row['item_id'], + $row['uname'], + $row['name'], + $row['email'], + getItemUrl($row), + ]; + fputcsv($fp, $data); +} +fclose($fp); + +// get files +$fileutil = xoonips_getutility('file'); + +$basePath = $xoonipsConfigs['upload_dir']; +$sql = <<< SQL +SELECT + `f`.`file_id`, `f`.`item_id`, `f`.`original_file_name`, + `ft`.`name` AS `file_type_name` + FROM `${prefix}_xoonips_file` AS `f` + INNER JOIN `${prefix}_xoonips_file_type` AS `ft` ON `f`.`file_type_id`=`ft`.`file_type_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `f`.`item_id`=`iil`.`item_id` + INNER JOIN `${prefix}_xoonips_index` AS `idx` ON `iil`.`index_id`=`idx`.`index_id` + WHERE `f`.`is_deleted`=0 + AND `ft`.`name` NOT IN('readme' , 'license', 'rights') + AND `iil`.`certify_state`=2 + AND `idx`.`open_level`=1 + GROUP BY `f`.`file_id` + ORDER BY `f`.`file_id` ASC +SQL; + +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$filesDir = MYDUMPTOOL_OUTPUTDIR.'/public/modules/xoonips/file'; +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['file_id', 'item_id']); + $file_id = $row['file_id']; + $item_id = $row['item_id']; + $file_name = $row['original_file_name']; + $file_type = $row['file_type_name']; + if (in_array($file_id, $limitFiles)) { + echo "download limit file found.. skip to copy.. item_id:$item_id, file_id:$file_id".PHP_EOL; + continue; + } + $file_dir = $filesDir.'/'.$file_id; + $src_file = $basePath.'/'.$file_id; + if (!is_dir($file_dir)) { + @mkdir($file_dir); + } + //if (!file_exists($file_dir.'/'.$file_name)) { + if (!MyDumpTool::fileCopy($src_file, $file_dir.'/'.$file_name)) { + echo 'failed to copy file: '.$basePath.'/'.$file_id.PHP_EOL; + } + $htaccess = $file_dir.'/.htaccess'; + $xfilename = str_replace(' ', '\\ ', $file_name); + $data = "RewriteEngine On\nRewriteBase /database/file/$file_id\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule .* $xfilename [R=301,L]\n"; + file_put_contents($htaccess, $data); + //} + if ('preview' == $file_type) { + $thumbnail_file = $file_dir.'.png'; + if (!file_exists($thumbnail_file)) { + $mimetype = $fileutil->get_mimetype($src_file, $file_name); + $thumbnail = $fileutil->get_thumbnail($src_file, $mimetype); + if (null !== $thumbnail) { + file_put_contents($thumbnail_file, $thumbnail); + } else { + createThumbnail($src_file, $mimetype, $thumbnail_file); + } + } + } +} +$xoopsDB->freeRecordSet($res); + +// functions + +function getItemUrl($row) +{ + $url = XOONIPS_URL.'/detail.php?'; + if ('' != $row['doi']) { + $url .= XNP_CONFIG_DOI_FIELD_PARAM_NAME.'='.$row['doi']; + } else { + $url .= 'item_id='.$row['item_id']; + } + + return $url; +} + +function getTreeNode($tree, $root, $index_id) +{ + $children = []; + if (isset($root[$index_id])) { + foreach ($root[$index_id] as $child_index_id) { + $children[] = getTreeNode($tree, $root, $child_index_id); + } + } + + return [ + 'id' => (int) $tree[$index_id]['index_id'], + 'title' => $tree[$index_id]['title'], + 'num_of_items' => (int) $tree[$index_id]['num_items'], + 'children' => $children, + ]; +} + +function getIndexes($tree, $item_id) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT + `iil`.`index_id` + FROM `${prefix}_xoonips_index_item_link` AS `iil` + WHERE `iil`.`item_id`=$item_id + ORDER BY `iil`.`index_id` +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $indexes = []; + while ($row = $xoopsDB->fetchArray($res)) { + $index_id = $row['index_id']; + if (isset($tree[$index_id])) { + $idxes = []; + $idxes[] = $tree[$index_id]['title']; + $parent_id = $tree[$index_id]['parent_index_id']; + while (1 != $parent_id) { + $idxes[] = $tree[$parent_id]['title']; + $parent_id = $tree[$parent_id]['parent_index_id']; + } + $idxes[] = ''; + $indexes[] = [ + 'index_id' => (int) $index_id, + 'title' => trim(implode(' / ', array_reverse($idxes))), + ]; + } + } + $xoopsDB->freeRecordSet($res); + + return $indexes; +} + +function getChangeLogs($item_id) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT `log_date`, `log` + FROM `${prefix}_xoonips_changelog` + WHERE `item_id`=$item_id + ORDER BY `log_date` DESC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = []; + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['log_date']); + $ret[] = $row; + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function getRelatedTo($item_id) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT `item_id` + FROM `${prefix}_xoonips_related_to` + WHERE `parent_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = []; + while ($row = $xoopsDB->fetchArray($res)) { + $ret[] = (int) $row['item_id']; + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function getKeyword($item_id) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xoonips_item_keyword` + WHERE `item_id`=$item_id + ORDER BY `keyword_id` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = []; + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $ret[] = $row['keyword']; + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function getFile($item_id) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT `f`.`file_id`, `f`.`original_file_name`, `f`.`mime_type`, `f`.`file_size`, `f`.`caption`, `f`.`timestamp`, + `ft`.`name` AS `file_type_name`, `ft`.`display_name` AS `file_type_display_name` + FROM `${prefix}_xoonips_file` AS `f` + INNER JOIN `${prefix}_xoonips_file_type` AS `ft` ON `f`.`file_type_id`=`ft`.`file_type_id` + WHERE `item_id`=$item_id + AND `is_deleted`=0 + ORDER BY `ft`.`name` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = []; + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, ['file_id', 'file_size']); + $ret[] = $row; + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function appendBinderInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpbinder_item_detail` + WHERE `binder_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['binder_id']); + MyDumpTool::dropColumn($detail, ['binder_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpbinder_binder_item_link` + WHERE `binder_id`=$item_id + ORDER BY `item_id` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['item_link'] = []; + while ($row2 = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row2); + $row['item_link'][] = (int) $row2['item_id']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendBookInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpbook_item_detail` + WHERE `book_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['book_id', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::convertToInt($detail, ['volume', 'number']); // for CBSN + MyDumpTool::dropColumn($detail, ['book_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpbook_author` + WHERE `book_id`=$item_id + ORDER BY `author_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['author'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['author'][] = $author['author']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendConferenceInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpconference_item_detail` + WHERE `conference_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['conference_id', 'conference_from_year', 'conference_from_month', 'conference_from_mday', 'conference_to_year', 'conference_to_month', 'conference_to_mday', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['conference_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpconference_author` + WHERE `conference_id`=$item_id + ORDER BY `author_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['author'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['author'][] = $author['author']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendDataInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpdata_item_detail` + WHERE `data_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['data_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['data_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpdata_experimenter` + WHERE `data_id`=$item_id + ORDER BY `experimenter_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['experimenter'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['experimenter'][] = $author['experimenter']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendFilesInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpfiles_item_detail` + WHERE `files_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['files_id']); + MyDumpTool::dropColumn($detail, ['files_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); +} + +function appendMemoInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpmemo_item_detail` + WHERE `memo_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['memo_id']); + MyDumpTool::dropColumn($detail, ['memo_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); +} + +function appendModelInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpmodel_item_detail` + WHERE `model_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['model_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['model_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpmodel_creator` + WHERE `model_id`=$item_id + ORDER BY `creator_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['creator'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['creator'][] = $author['creator']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendPaperInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnppaper_item_detail` + WHERE `paper_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['paper_id', 'volume', 'number']); + MyDumpTool::dropColumn($detail, ['paper_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnppaper_author` + WHERE `paper_id`=$item_id + ORDER BY `author_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['author'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['author'][] = $author['author']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendPresentationInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnppresentation_item_detail` + WHERE `presentation_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['presentation_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['presentation_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnppresentation_creator` + WHERE `presentation_id`=$item_id + ORDER BY `creator_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['creator'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['creator'][] = $author['creator']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendSimulatorInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpsimulator_item_detail` + WHERE `simulator_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['simulator_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['simulator_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpsimulator_developer` + WHERE `simulator_id`=$item_id + ORDER BY `developer_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['developer'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['developer'][] = $author['developer']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendStimulusInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpstimulus_item_detail` + WHERE `stimulus_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['stimulus_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['stimulus_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpstimulus_developer` + WHERE `stimulus_id`=$item_id + ORDER BY `developer_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['developer'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['developer'][] = $author['developer']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendToolInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnptool_item_detail` + WHERE `tool_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['tool_id', 'use_cc', 'cc_commercial_use', 'cc_modification', 'attachment_dl_limit', 'attachment_dl_notify']); + MyDumpTool::dropColumn($detail, ['tool_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnptool_developer` + WHERE `tool_id`=$item_id + ORDER BY `developer_order` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['developer'] = []; + while ($author = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($author); + $row['developer'][] = $author['developer']; + } + $xoopsDB->freeRecordSet($res); +} + +function appendUrlInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpurl_item_detail` + WHERE `url_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['url_id', 'url_count']); + MyDumpTool::dropColumn($detail, ['url_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); +} + +function appendNimgcenterInfo(&$row) +{ + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $coord_type_names = [ + 0 => 'Talairach', + 1 => 'NMI', + 2 => 'Unclear', + ]; + $item_id = $row['item_id']; + $sql = <<< SQL +SELECT `id`.*, + `ity`.`display_name` AS `base_type_display_name` + FROM `${prefix}_xnpnimgcenter_item_detail` AS `id` + INNER JOIN `${prefix}_xoonips_item_type` AS `ity` ON `id`.`base_type_name`=`ity`.`name` + WHERE `id`.`nimgcenter_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($detail = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($detail); + MyDumpTool::convertToInt($detail, ['baseitem_id', 'coord_type']); + $detail['coord_type_name'] = $coord_type_names[$detail['coord_type']]; + MyDumpTool::dropColumn($detail, ['nimgcenter_id']); + $row += $detail; + } + $xoopsDB->freeRecordSet($res); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xnpnimgcenter_talairach_list` + WHERE `nimgcenter_id`=$item_id +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $row['coordinate'] = []; + while ($t = $xoopsDB->fetchArray($res)) { + MyDumpTool::dropColumn($t, ['nimgcenter_id']); + MyDumpTool::convertToInt($t, ['x', 'y', 'z']); + $row['coordinate'][] = $t; + } + $xoopsDB->freeRecordSet($res); + global $baseItemIds; + $baseItemIds[$row['item_id']] = $row['baseitem_id']; +} + +function createThumbnail($inputFile, $mime_type, $outputFile) +{ + $finfo = finfo_open(FILEINFO_NONE); + $label = finfo_file($finfo, $inputFile); + finfo_close($finfo); + if (preg_match('/^([^\\/]*)\\/(.*)$/', $mime_type, $matches)) { + if ('audio' == $matches[1]) { + $img_type = 'audio'; + } elseif ('image' == $matches[1]) { + $img_type = 'image'; + } elseif ('video' == $matches[1]) { + $img_type = 'video'; + } elseif ('text' == $matches[1]) { + $img_type = 'text'; + } elseif ('application' == $matches[1]) { + $text_types = ['pdf', 'xml', 'msword', 'vnd.ms-excel']; + $image_types = ['vnd.ms-powerpoint', 'postscript']; + $audio_types = ['vnd.rn-realmedia']; + if (in_array($matches[2], $text_types)) { + $img_type = 'text'; + } elseif (in_array($matches[2], $audio_types)) { + $img_type = 'audio'; + } elseif (in_array($matches[2], $image_types)) { + $img_type = 'image'; + } else { + $img_type = 'application'; + } + } else { + $img_type = 'unknown'; + } + } else { + $img_type = 'unknown'; + } + $img_file = XOOPS_ROOT_PATH.'/modules/xoonips/images/thumbnail_'.$img_type.'.png'; + // create image resource + $w = 100; + $h = 100; + $im = imagecreatetruecolor($w, $h); + // label setting + $f = 2; + // font number + $lp = 5; + // label padding + $fw = imagefontwidth($f); + // font width + $fh = imagefontheight($f); + // font height + $fmaxlen = ($w - $lp * 2) / $fw; + // max label length + $labels = explode(',', $label); + $label = $labels[0]; + $llen = strlen($label); + if ($llen > $fmaxlen) { + $label = substr($label, 0, $fmaxlen - 3); + $label .= '...'; + $llen = strlen($label); + } + $lx = ($w - $llen * $fw) / 2; + $ly = $h - $fh - $lp; + // change alpha attributes and create transparent color + imageantialias($im, true); + imagealphablending($im, false); + imagesavealpha($im, true); + $transparent = imagecolorallocatealpha($im, 255, 255, 255, 0); + $col_white = imagecolorallocate($im, 255, 255, 255); + $col_gray = imagecolorallocate($im, 127, 127, 127); + $col_black = imagecolorallocate($im, 0, 0, 0); + // fill all area with transparent color + imagefill($im, 0, 0, $col_white); + imagealphablending($im, true); + $imicon = imagecreatefrompng($img_file); + imagecopy($im, $imicon, $w / 2 - 48 / 2, $h / 2 - 48 / 2, 0, 0, 48, 48); + imagepolygon($im, [0, 0, $w - 1, 0, $w - 1, $h - 1, 0, $h - 1], 4, $col_gray); + if (0 != strlen($label)) { + imagestring($im, $f, $lx, $ly, $label, $col_black); + } + imagepng($im, $outputFile); + imagedestroy($imicon); + imagedestroy($im); + + return true; +} + +function parseSimPFLinkFile() +{ + global $XOONIPS_SIMPF_LINKFILE; + $xoops_url_path = parse_url(XOOPS_URL, PHP_URL_PATH); + $ret = []; + if (isset($XOONIPS_SIMPF_LINKFILE) && file_exists($XOONIPS_SIMPF_LINKFILE)) { + $lines = file($XOONIPS_SIMPF_LINKFILE); + foreach ($lines as $line) { + list($pfname, $pfurl, $simpfurl) = explode(',', $line); + if (preg_match('/^'.preg_quote(XOOPS_URL, '/').'/', $pfurl)) { + $pUrl = parse_url($pfurl); + if ($pUrl['path'] != $xoops_url_path.'/modules/xoonips/detail.php') { + echo 'not xoonips url found in simpf link file. URL:'.$pfurl.PHP_EOL; + continue; + } + if (preg_match('/(?:^|&)(id|item_id)=([^$&]+)/', $pUrl['query'], $matches)) { + $key = $matches[1]; + $value = $matches[2]; + $ret[$key][$value] = trim($simpfurl); + } + } + } + } + + return $ret; +} diff --git a/etc/extra.inc.php b/etc/extra.inc.php new file mode 100644 index 0000000..2f6d91d --- /dev/null +++ b/etc/extra.inc.php @@ -0,0 +1,188 @@ +]*)src="(.*)"([^>]*)\/>/Us', function ($matches) use ($path) { + $url = htmlspecialchars_decode($matches[2], ENT_QUOTES); + $url = normalizeUrl($url, $path, 'imgSrc'); + $url = checkLocalFile($url, 'imgSrc'); + if (false === $url) { + exit($path.PHP_EOL); + } + + return ''; + }, $text); + $text = preg_replace_callback('/]*)href="([^"]+)"([^>]*)>/Us', function ($matches) use ($path) { + $url = htmlspecialchars_decode($matches[2], ENT_QUOTES); + if (!preg_match('/^#/', $url)) { + $url = normalizeUrl($url, $path, 'aHref'); + if (false === $url) { + exit($path.PHP_EOL); + } + $url = checkLocalFile($url, 'aHref'); + if (false === $url) { + exit($path.PHP_EOL); + } + } + + return ''; + }, $text); + $text = preg_replace_callback('/]*)src="(.*)"([^>]*)\/>/Us', function ($matches) use ($path) { + $url = htmlspecialchars_decode($matches[2], ENT_QUOTES); + $url = normalizeUrl($url, $path, 'embedSrc'); + if (false === $url) { + exit($path.PHP_EOL); + } + $url = checkLocalFile($url, 'embedSrc'); + if (false === $url) { + exit($path.PHP_EOL); + } + + return ''; + }, $text); + $text = preg_replace_callback('/]*)href="([^"]+)"([^>]*)>/Us', function ($matches) use ($path) { + $url = htmlspecialchars_decode($matches[2], ENT_QUOTES); + if (!preg_match('/^#/', $url)) { + $url = normalizeUrl($url, $path, 'aHref'); + if (false === $url) { + exit($path.PHP_EOL); + } + $url = checkLocalFile($url, 'aHref'); + if (false === $url) { + exit($path.PHP_EOL); + } + } + + return ''; + }, $text); + $text = preg_replace_callback('/]*)src="(.*)"([^>]*)\/>/Us', function ($matches) use ($path) { + $url = htmlspecialchars_decode($matches[2], ENT_QUOTES); + $url = normalizeUrl($url, $path, 'embedSrc'); + if (false === $url) { + exit($path.PHP_EOL); + } + $url = checkLocalFile($url, 'embedSrc'); + if (false === $url) { + exit($path.PHP_EOL); + } + + return ''; + }, $text); + $text = preg_replace_callback('/
    +
    + +
    +

    Brain Explorer [MRI]

    +
      +
    • + setShowInfo(false)}> + {Functions.mlang("[en]Japanese Monkey[/en][ja]ニホンザル[/ja]", lang)} +
      + (Macaca fuscata) +
      + {Functions.mlang("[en]Developmental Data[/en][ja]発達の様子[/ja]", lang)} + +
    • +
    • + setShowInfo(false)}> + {Functions.mlang("[en]Human[/en][ja]ヒト[/ja]", lang)} +
      + (Homo sapiens) + +
    • +
    +
    +
      +
    • + {Functions.mlang("[en]Japanese page.[/en][ja]English page.[/ja]", lang)} +
    • +
    +
    +
    + + AIST + +
    +
    +
    +
    {showInfo ? renderShowInfo() : renderBrainExplorerContent()}
    +
    + + ); +}; + +export default BrainExplorerIndex; diff --git a/src/brainexplorer/assets/images/logo_aist.png b/src/brainexplorer/assets/images/logo_aist.png new file mode 100644 index 0000000000000000000000000000000000000000..a92c63ce3ab158820f2503ff1f28ee540d80252e GIT binary patch literal 1595 zcmV-B2E_S^P)x{~iu z8y!?KBn?1wq^4e4mw^kDovovfrL)=YT$BlethkIkL?^A|k|wnnn08&xMaL=MyC!AR zBn*_^hnCZD!#q0VnJg^s+GOYNz6-H+!y7a8CU>e;P6|Usi*h-VW`Nr`L78-V=6Q6A zZ|Tk3zD?qT==f&b8XQI2*Vy?;D&7b>g@`2MSf$UTL|51tPPp;uclxVAz5@?TU<}v5WWUHB;u1sF3W$?vanz_$aDg`FF)jX~i-TrlRx0-1JB!@t) z4ln3RgA>F{J?UzIG)z4?HBZHSJJt7O?A&<%u$nd`0eNx-CKk+{3(}+!!z+8zyL9rV z0?f-gVeXcTHh1F*DpOZ50h{;I(P6Q5XS$_IbxJk?mRa3Rhz86Y6V_SJD0+IWdNcY7 zG>N&A=E6a)xdo@NRWo~n6Bw7-1-+=wz7=ik(I}a*-Jo)A)gaF5?!fY$$z|O5Vu8s( zYRvEx5OXf3LKvrGg5i|}@&xxr@>X0B1NQFiUW2_HyNm77kw(4S@O95kE9DZ1Msl?X3T9b(V5x4?p6{A<;x#5%a9j+#?-dQ=4Am>okar(b;nGv>V~g=!e2u7H zGI{TTSB^Zfp&I8z`asL*NYer{%opg~`Bdv}!gtZ)-B>WAPnnaW3@l5Fy~UzBCC{J_ zHP`8trD}feI$As5$IFh*fPt4Xf@3&QcHp}N9mW~5&|628nA=aV1#0|aTBT3agkP=i z0Xul}V_a1QkTY?1aW;?Ob!JLwKlAXCj_Q^klb6wyRx)6vB$iTtS5wlxEhXDqc@V&g<-y9*mUcFdL9k5}r5UW#me0@6(-@(kSO+?kP=>loqdJDJgIoUn#j> t=_tT?177=|1)!>~8qZ2OL{<1U{|4Vfs8?~MnOXn<002ovPDHLkV1i7q7jOUo literal 0 HcmV?d00001 diff --git a/src/brainexplorer/assets/images/navi_home.png b/src/brainexplorer/assets/images/navi_home.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a72d2031894db0b95eaf40f22d0d081f90389b GIT binary patch literal 2093 zcmV+|2-5e7P)cx@ZuwQXx}u5ByXXWO=IE4Hm=oQ%;u-BtDK)S0(u&Yb&#$wj_% zevM>z>#M4NwS?B1y_pDr5CXsf4hBX7Ex4e~2q*N+lMp29zGv@S4sr3ss4Hz(>iFX;__bC>O1VPXQG*t`g zU9GtVfBq%gCR|Nl$BoQ*_DWX%;FJ76RN#~AT}?TcVrQ7{X(yJCw+|Wr_%u`Ep;S}Z=$&v5>A<>qB?1;*utzQr$7ywJXIH{6H zv~xN6t`$3K5(h{vn#_VXZsnBs{g{ZA+&OhkaT%9|1(#%`h5awP%l23+wZjHnv3966P~+kKpp<%PCI&Iom>qV;}!0Cw6>`gKv8lS;~Xf)FLXQ=LnOEWOl(MR!q1V znNPp9f}Ne6%$(TH(9O#^=7le^-{p4`q_Z40{xuG~@o|De?#)L}Xsnz70d}8XIEmF0 zjbtwM)(EcOuz^Vv#?#h4mjj>v0wXTJm#~nrD_50$j(YSX?0e}Qg!wELtW>pJ^|>RU zXe7JdV@Gn&1h;M9&c8eEr)||_hCcc^2440cg-n)EY5=qh(OPlrbDwAE8J8RJECO)R zm0(g411oFKg|?D?Mp9YHIs~)X9DjfBBeX936V3O3nx@MhrkKs(g&I#nps?sLz=_5< z{*|vY=)`eoBk2e#_17r+H7d7P3F z_5+PC!4GXHOVEHSdqi6Xal(Y}(R$eNP|DXxY9ck~W=~8@yd{(HX8(ED1vi+Q`|T$u zASVBq)HNTXeE#EjroOzujvQV^&a0M0ApIhVVFz&R#P8EE;y{F7tV}6(FDi9t&$T2T z7`gq9qxJoNq3NQ#sj%}4wd=Z^deZ}}Ja#O}_(?>I1^t_-}pXjU%L)bNaDm3xK)cK76iy>oF(Ty#d3B?@E$DhH$FMo>-AGi%SEFmIMqADaBNH!mMIx8>lz&-r} z8b>795fOHTR)h*$bFPUHnz#Tr5+UMhdmc!I#?n8!@d!}JFzAdcNj~&B3ZHp^w$R6G zY-RD;kF)jsM;La{F!mYb&=lQ+>!9N}Mo?QpC0u^_o4@hiFaMP<|9&1no7u&G)@4Zh z+SZ`NwF#84br~vfyd1d`F6UpPU!(Kz^Oq6YYNkmccC?euoc_9Q}{q;irx(*IKVi@}! z+{Vji4dtX^G5oS7=4culU1B;QC|CZb)M)LZ0u517m!NGJT~%KQ5cDH}@PAXPjZ6?> zdD3OM3~4__ar_WLIn;Q?5C?&Zxk%;O4(tAfr2wb?8Uzj1=o&!@C0lbEiz^@|&{|Lm zpsYZK3g44>UWjiaZGzCj%NO>n26c^4ErG%a3R)W-644@#EQ1lUH7M;N2vAB>_Klby znn*$&rDwr(Hm_c?XEi8X2ZYO;A%Gy%WJ;22E*Z(?=N8F%lB9AO9CyiFoZI3TEupuh{kbz(HOBpA{L$40ch zGZf1ZNUEm<;l|jy=${-EPjc_$PY{pC>lfU2-+dW5VkxVZ{fwT#A(7llQ?M_k@)2m= zjCNz-Mkz}HQlXS22%!{#xNCikpg;f!2jNCYF8v$Fv<0+}f7sqqf%*l_%RJZLa3fl4 zwrttV+BIufuwXv(SN)qMnP#>}jwVc*4BZD2Yanm{EkT-SjpIO|G&nB8Wc$KDF#6CI zZo1`eD%3AnyI6(~A7L}@j5F~(kG`I6mMvS#vXy_Jd!dKhx-U&54x@GGK{O8>f|E!P z2#_KMqQusDzu~+S_v5N-uH9K%HG*}JFpVF6tpGXMJ!(YXSOEO zTzB=AoPEx@yCT=0T0!9$YCk{JLnIR6;DZmg898bUy}dmgfBXqM{d-{;QYw|~$0Kv> z0HRv3kjZ2Ufc*jA_iZoslvpgrVTT=V8#dX|l-mE_xN)Odj9W;j(*>YVEjZ_!-~8sB z=bn4+h*T;i3WWkvN`O5U-T9rWa?LCjt=y(foAlhdbLRkas|DA7`O9BkyLt2G@1JqT z86%Bci>ak|>MO7nG;KP`WHL8v)~wkkUBDUup|!65h<*rg`n&uQeGbrRyi~v7-pqdi X&VSL19K4ur00000NkvXXu0mjfBwh8F literal 0 HcmV?d00001 diff --git a/src/brainexplorer/assets/images/navi_info.png b/src/brainexplorer/assets/images/navi_info.png new file mode 100644 index 0000000000000000000000000000000000000000..28b19b72b6ea42023fb9e8f66bf1e12e627c02fb GIT binary patch literal 2078 zcmV+(2;ujMP)uc3nPB zy!fa7QpcpPpWf?!E#;i!m8=wiQVKu=D}dvGCg7zeSHLu|8rb8UvsFMqXEK>L-g)Pp z%kRGX?&I3p+M2BwFQK&8*x1gwt`79`cGfVq~ImK-m$!Z4&*EE30Y z4#?H!ptD}}p7r?H7~|t(^5f1qnwwi_X>Fywy`6@J24*5Zy*Npd3aACDMKW6nL|N`r(!T5o@xNjuhTE%LoTg+!&26x#CwjN_R6)C52I%NT##9HR(H;tHI% z5kQPw7Geg+%LHldonml!g0Nh!K#5J_$D{&UfrOv0o@8r(BkP}eY6i{1|IqGTI~gkk z{N`!v!OEnx14XscWWeF9!|>IKKX?A)dId zl`UJgaNvOl&Kgi43i)Z)Zq@!qcu%W|gE&7`#@sYK2g z5?N+}!#Yc#9J~8McJyhcChG*8nwp|mHaxT2%A^LNqbi|8B4aE(bk$;(%ui7&7FoAx zn2&#F0EI=soHw8$vY!9VoPEi zQ5Z76DNm#6CY|gfrw7STPf2dZaU!3S&sH2K9w^2n5u0{RfX>p>(=+=Bj*bj7o`+&+ z0ENckKnzL-g;Ee`$M{r2ISCjT7+}SU z6|(`9&O#=VNCl4S5;+Axp^*1co|Fg$IzVh{KN*F!Z}0^1v1vAha#59I*Nrt z9aZcA5WpmG)qHT0pVJibMF6x4WWJ&Yo};5@3jq))jdBiDP0j+SnggY9xY+|XW-@a1 zH_l*<0~E$Nrl+SVmqJ-$99WRC9F{Rzi`JUJTRHFQDM2-s-bi#E>2%<&@sAs`2W)9+ zrM;;{N?DQww1PsRL|Bf{O4lqG$b?qlr2q4i3W#z+P2oG7a@9y0YiVyzlSwCZb}pIy zon&6yJUUwNRz$I(R1S$Ei5#E=ifWV;L6D-cv5{Bw`rTz&WAxlPTSbXbl|+Q>CbqE>5EHP><;s?zFrbac?-N1OnP1+3;ciZ%N! z&#~VU!^%~!slQn`@SuaqrKh>%DxYw}#q(LUFi$3Xm=sps5d`E$p3hJ4*e%Oh`Km1Wk_A-NQZ!c6Jy2%X-ch#g z9Aej=Q6yq$`IFChPZbSU+k8#*xht=P1)#~&!&g2(A{~2$+e+BRPQV)M! zpC_A1k=6o;3j$vE-tA}-fwndv)7XRpKodufaujpTd9B=cZ5tamJj0b&UG>5~lbmzz zdDwW2H{ZXEM{Zt5Q&y8NRVD`Kymc0<=6ScFQmt60DTXjvh*-1$KJbPEdEnY6wr}4u z1GqY$>P|cT4A!q-&q)WDIqm5E`NLBM{2h+<0+1Z1;m=Fdx! z4nn%SH?ebj6DOT?dbQ`P>$!Lql8L&yPOLu2@7uSJl%2$y5DX-)fqc-yvgOM;>+C~j zvtv>9zXB@lMU7^-RU1>f5h z0Qo=|hRk+r*u#iL$nS_}x}+XXR%2*rh>3}b0#K+0T=VHqe|pVhk3F`ZkAx}|3K(Om zKk#BnDQUYT%Kl^o&Ue>t?b@|#fVH)NJ3so-kM0~C9Q@)*C!KVhhii(W=%v~P7C=8| zWea%v?|=XMYVSS34glqxtKCFj2^{+}Z=$aOc6%;c1$-szA3cK^*^3o%p#T5?07*qo IM6N<$g6&7@nE(I) literal 0 HcmV?d00001 diff --git a/src/brainexplorer/lib/g3d.ts b/src/brainexplorer/lib/g3d.ts new file mode 100644 index 0000000..9ce0441 --- /dev/null +++ b/src/brainexplorer/lib/g3d.ts @@ -0,0 +1,1035 @@ +export interface G3DHeader { + magic: number; // MagicNumber GO3D[0x47 0x4f 0x33 0x44], GH3D [0x47 0x48 0x33 0x44] or CO3D[0x43 0x4f 0x33 0x44] + width: number; // surface area silce width in pixels + height: number; // surface area slice height in pixels + depth: number; // surface area slice depth in pixels + org_width: number; // silce width in pixels + org_height: number; // slice height in pixels + org_depth: number; // slice depth in pixels + datatype: number; // 1 (byte per voxel) for gray, 3 (byte for voxel) for color + unit: number; // km:3, m:0, mm:-3, micro m:-6, nano m:-9 + grid: number; // grid width in unit + vs_width: number; // surface voxel width in unit + v_width: number; // voxel width in unit + v_height: number; // voxel height in unit + v_depth: number; // voxel depth in unit + ax: number; // rotation angle x + ay: number; // rotation angle y + az: number; // rotation angle z + ox: number; // origin x in surface voxel coordinates + oy: number; // origin y in surface voxel coordinates + oz: number; // origin z in surface voxel coordinates + s_num: number; // number of surface voxels + s_th: number; // threshold of suface voxel + option: string; // option area for additional parameters + date: string; // free format - sample: 1970/01/01 00:00:00 + coordinate: string; // Talairach, Horsley-Clarke, etc + subject: string; // subject name bm85... etc + memo: string; // memo or something for extend +} + +interface G3DImageRenderingContext { + a1: number; + a2: number; + a3: number; + b1: number; + b2: number; + b3: number; + c1: number; + c2: number; + c3: number; + d1: number; + d2: number; + d3: number; + ra1: number; + ra1_2: number; + ra2: number; + ra2_2: number; + ra3: number; + ra3_2: number; + rb1: number; + rb1_2: number; + rb2: number; + rb2_2: number; + rb3: number; + rb3_2: number; + rc1: number; + rc1_2: number; + rc2: number; + rc2_2: number; + rc3: number; + rc3_2: number; + rd1: number; + rd2: number; + rd3: number; +} + +interface G3DColor { + r: number; + g: number; + b: number; +} + +enum DrawingLineType { + VLine = 0, + VDottedLine = 1, + HLine = 2, + HDottedLine = 3, +} +const LineBrightness = 200; +const DottedLineBrightness = 128; +const EXACTSWITCH: 0 | 1 = 1; +const MaxIntensity = 200; + +class G3D { + private data: ArrayBuffer | null = null; + private header: G3DHeader | null = null; + private isLittleEndianFile = true; + private surfaceView: Int16Array | null = null; + private imgView: Uint8Array | null = null; + private imageSize = 0; + private centerX = 0; + private centerY = 0; + private centerZ = 0; + private ox = 0; + private oy = 0; + private oz = 0; + + constructor(data: ArrayBuffer) { + this._initialize(data); + } + + getHeader(): G3DHeader | null { + return this.header; + } + + drawImage(canvas: HTMLCanvasElement, ax: number, ay: number, az: number, cutdepth: number, th: number, is3d: boolean, mesh: boolean, skincolor: boolean, smoothing: boolean): boolean { + const irc: G3DImageRenderingContext = { + a1: 0, + a2: 0, + a3: 0, + b1: 0, + b2: 0, + b3: 0, + c1: 0, + c2: 0, + c3: 0, + d1: 0, + d2: 0, + d3: 0, + ra1: 0, + ra1_2: 0, + ra2: 0, + ra2_2: 0, + ra3: 0, + ra3_2: 0, + rb1: 0, + rb1_2: 0, + rb2: 0, + rb2_2: 0, + rb3: 0, + rb3_2: 0, + rc1: 0, + rc1_2: 0, + rc2: 0, + rc2_2: 0, + rc3: 0, + rc3_2: 0, + rd1: 0, + rd2: 0, + rd3: 0, + }; + // console.log(ax, ay, az, cutdepth, th, is3d, mesh, skincolor, smoothing); + if (this.header === null) { + return false; + } + + canvas.width = this.imageSize; + canvas.height = this.imageSize; + const ctx = canvas.getContext('2d'); + if (ctx === null) { + return false; + } + ctx.fillStyle = 'rgb(0, 0, 0)'; + ctx.fillRect(0, 0, this.imageSize, this.imageSize); + const img = ctx.getImageData(0, 0, this.imageSize, this.imageSize); + + this._setMatrix(ax, ay, az, irc); + switch (this.header.datatype) { + case 1: + if (is3d) { + const flag2d = [...Array(this.imageSize * this.imageSize)].map(() => false); + const ddimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + this._makeDIMG8(img, cutdepth, th, flag2d, ddimg, irc); + this._makeShadingImage(img, smoothing, false, flag2d, ddimg); + } else { + this._makeSliceImage8(img, cutdepth, irc); + } + break; + case 3: + if (is3d) { + const flag2d = [...Array(this.imageSize * this.imageSize)].map(() => false); + const ddimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + this._makeDIMG24(img, cutdepth, th, flag2d, ddimg, irc); + this._makeShadingImage(img, smoothing, skincolor, flag2d, ddimg); + } else { + this._makeSliceImage24(img, cutdepth, irc); + } + break; + } + if (mesh) { + this._drawMesh(img, irc); + } + ctx.putImageData(img, 0, 0); + return true; + } + + printHeader(): void { + if (this.header === null) { + console.log('--- invalid g3d data ---'); + return; + } + let message = '--- g3d information ---\n'; + message += 'magic: : ' + this.header.magic.toString(16) + '\n'; + message += 'width [pixel]: ' + this.header.width.toString() + '\n'; + message += 'height [pixel]: ' + this.header.height.toString() + '\n'; + message += 'depth [pixel]: ' + this.header.depth.toString() + '\n'; + message += 'org_width [pixel]: ' + this.header.org_width.toString() + '\n'; + message += 'org_height [pixel]: ' + this.header.org_height.toString() + '\n'; + message += 'org_depth [pixel]: ' + this.header.org_depth.toString() + '\n'; + message += 'datatype [byte] : ' + this.header.datatype.toString() + '\n'; + message += 'unit [m|E] : ' + this.header.unit.toString() + '\n'; + message += 'grid [unit] : ' + this.header.grid.toString() + '\n'; + message += 'surface voxel[unit] : ' + this.header.vs_width.toString() + '\n'; + message += 'voxel width [unit] : ' + this.header.v_width.toString() + '\n'; + message += 'voxel height [unit] : ' + this.header.v_height.toString() + '\n'; + message += 'voxel depth [unit] : ' + this.header.v_depth.toString() + '\n'; + message += 'ax [deg] : ' + this.header.ax.toString() + '\n'; + message += 'ay [deg] : ' + this.header.ay.toString() + '\n'; + message += 'az [deg] : ' + this.header.az.toString() + '\n'; + message += 'ox [pixel]: ' + this.header.ox.toString() + '\n'; + message += 'oy [pixel]: ' + this.header.oy.toString() + '\n'; + message += 'oz [pixel]: ' + this.header.oz.toString() + '\n'; + message += 'ox_real [unit] : ' + (this.header.ox * this.header.vs_width).toString() + '\n'; + message += 'oy_real [unit] : ' + (this.header.oy * this.header.vs_width).toString() + '\n'; + message += 'oz_real [unit] : ' + (this.header.oz * this.header.vs_width).toString() + '\n'; + message += 'surface voxels : ' + this.header.s_num.toString() + '\n'; + message += 'threshold : ' + this.header.s_th.toString() + '\n'; + message += 'date : ' + this.header.date + '\n'; + message += 'coordinate : ' + this.header.coordinate + '\n'; + message += 'subject : ' + this.header.subject + '\n'; + message += 'memo : ' + this.header.memo + '\n'; + message += 'File Type : ' + (this.isLittleEndianFile ? 'Little Endian.' : 'Big Endian'); + console.log(message); + } + + private _makeDIMG8(img: ImageData, cutdepth: number, th: number, flag2d: boolean[], ddimg: number[], irc: G3DImageRenderingContext) { + if (this.header === null) { + return; + } + const pdimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + const sw = th > this.header.s_th ? 2 : th < this.header.s_th ? 3 : EXACTSWITCH; + switch (sw) { + case 0: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + ddimg[pos] = pdimg[pos]; + let pix = this._getPixel(x, y, cutdepth, irc); + if (pix > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + ddimg[pos] = cutdepth; + continue; + } + pix = this._getPixel(x, y, pdimg[pos], irc); + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + } + } + break; + } + case 1: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + let pdPos = pdimg[pos]; + if (pdPos === 0) { + continue; + } + let pix = this._getPixel(x, y, cutdepth, irc); + if (pix > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + ddimg[pos] = cutdepth; + continue; + } + if (pdPos < cutdepth) { + pdPos = cutdepth; + } + ddimg[pos] = 0; + for (let z = pdPos; z < this.imageSize; z++) { + pix = this._getPixel(x, y, z, irc); + if (pix > th) { + ddimg[pos] = z; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + break; + } + } + } + } + break; + } + case 2: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + let pdPos = pdimg[pos]; + if (pdPos === 0) { + continue; + } + let pix = this._getPixel(x, y, cutdepth, irc); + if (pix > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + ddimg[pos] = cutdepth; + continue; + } + if (pix > this.header.s_th || pdPos < cutdepth) { + pdPos = cutdepth; + } + ddimg[pos] = 0; + for (let z = pdPos; z < this.imageSize; z++) { + pix = this._getPixel(x, y, z, irc); + if (pix > th) { + ddimg[pos] = z; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + break; + } + } + } + } + break; + } + case 3: { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + ddimg[pos] = 0; + for (let z = cutdepth; z < this.imageSize; z++) { + const pix = this._getPixel(x, y, z, irc); + if (pix > th) { + if (z === cutdepth) { + flag2d[pos] = true; + } else { + ddimg[pos] = z; + } + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + break; + } + } + } + } + break; + } + } + } + + private _makeDIMG24(img: ImageData, cutdepth: number, th: number, flag2d: boolean[], ddimg: number[], irc: G3DImageRenderingContext) { + if (this.header === null) { + return; + } + const pdimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + const sw = th > this.header.s_th ? 2 : th < this.header.s_th ? 3 : EXACTSWITCH; + switch (sw) { + case 0: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + ddimg[pos] = pdimg[pos]; + const c = this._getPixelColor(x, y, cutdepth, irc); + if (c.r > th || c.g > th || c.b > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, c); + ddimg[pos] = cutdepth; + continue; + } + this._drawPixel(img, pos, this._getPixelColor(x, y, pdimg[pos], irc)); + } + } + break; + } + case 1: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let j = (pos = 0); j < this.imageSize; j++) { + for (let i = 0; i < this.imageSize; i++, pos++) { + let pdPos = pdimg[pos]; + if (pdPos === 0) { + continue; + } + const c = this._getPixelColor(i, j, cutdepth, irc); + if (c.r > th || c.g > th || c.b > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, c); + ddimg[pos] = cutdepth; + continue; + } + if (pdPos < cutdepth) { + pdPos = cutdepth; + } + ddimg[pos] = 0; + for (let z = pdPos; z < this.imageSize; z++) { + const c = this._getPixelColor(i, j, z, irc); + if (c.r > th || c.g > th || c.b > th) { + ddimg[pos] = z; + this._drawPixel(img, pos, c); + break; + } + } + } + } + break; + } + case 2: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + let pdPos = pdimg[pos]; + if (pdPos === 0) { + continue; + } + const c = this._getPixelColor(x, y, cutdepth, irc); + if (c.r > th || c.g > th || c.b > th) { + flag2d[pos] = true; + this._drawPixel(img, pos, c); + ddimg[pos] = cutdepth; + continue; + } + if (c.r > this.header.s_th || c.g > this.header.s_th || c.b > this.header.s_th || pdPos < cutdepth) { + pdPos = cutdepth; + } + ddimg[pos] = 0; + for (let z = pdPos; z < this.imageSize; z++) { + const c = this._getPixelColor(x, y, z, irc); + if (c.r > th || c.g > th || c.b > th) { + ddimg[pos] = z; + this._drawPixel(img, pos, c); + break; + } + } + } + } + break; + } + case 3: { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + ddimg[pos] = 0; + for (let z = cutdepth; z < this.imageSize; z++) { + const c = this._getPixelColor(x, y, z, irc); + if (c.r > th || c.g > th || c.b > th) { + if (z === cutdepth) { + flag2d[pos] = true; + } else { + ddimg[pos] = z; + } + this._drawPixel(img, pos, c); + break; + } + } + } + } + break; + } + } + } + + private _makeShadingImage(img: ImageData, smoothing: boolean, skincolor: boolean, flag2d: boolean[], ddimg: number[]) { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + let timg2d = 0; + if (smoothing) { + if (x < this.imageSize - 2 && y < this.imageSize - 2) { + if (ddimg[pos] !== 0) { + const nx = (ddimg[pos] + ddimg[pos + this.imageSize] - ddimg[pos + 2] - ddimg[pos + 2 + this.imageSize]) / 4.0; + const ny = (ddimg[pos] + ddimg[pos + 1] - ddimg[pos + this.imageSize * 2] - ddimg[pos + 1 + this.imageSize * 2]) / 4.0; + timg2d = Math.round(MaxIntensity / Math.sqrt(nx * nx + ny * ny + 1)); + } + } + } else { + if (x < this.imageSize - 1 && y < this.imageSize - 1) { + if (ddimg[pos] !== 0) { + const nx = ddimg[pos] - ddimg[pos + 1]; + const ny = ddimg[pos] - ddimg[pos + this.imageSize]; + timg2d = Math.round(MaxIntensity / Math.sqrt(nx * nx + ny * ny + 1)); + } + } + } + if (!flag2d[pos]) { + if (skincolor) { + const base = pos * 4; + const c: G3DColor = { r: img.data[base], g: img.data[base + 1], b: img.data[base + 2] }; + const tmp = timg2d / Math.max(c.r, c.g, c.b); + this._drawPixel(img, pos, { r: c.r * tmp, g: c.g * tmp, b: c.b * tmp }); + } else { + const pix = timg2d; + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + } + } + } + } + } + + private _makeSliceImage8(img: ImageData, cutdepth: number, irc: G3DImageRenderingContext) { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + const pix = this._getPixel(x, y, cutdepth, irc); + this._drawPixel(img, pos, { r: pix, g: pix, b: pix }); + } + } + } + + private _makeSliceImage24(img: ImageData, cutdepth: number, irc: G3DImageRenderingContext) { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + const c = this._getPixelColor(x, y, cutdepth, irc); + this._drawPixel(img, pos, c); + } + } + } + + private _drawMesh(img: ImageData, irc: G3DImageRenderingContext) { + if (this.header === null) { + return; + } + const origx = this._fround(this.header.ox * irc.ra1 + this.header.oy * irc.rb1 + this.header.oz * irc.rc1 + irc.rd1, 5); + const origy = this._fround(this.header.ox * irc.ra2 + this.header.oy * irc.rb2 + this.header.oz * irc.rc2 + irc.rd2, 5); + + const band = this.header.grid / this.header.vs_width; + const c = { r: 0, g: 0, b: LineBrightness }; + const cs = { r: 0, g: 0, b: DottedLineBrightness }; + this._drawLine(img, Math.round(origx), c, DrawingLineType.VLine); + this._drawLine(img, Math.round(origy), c, DrawingLineType.HLine); + for (let pos = Math.round(origx) + band; pos < this.imageSize; pos += band) { + this._drawLine(img, Math.round(pos), cs, DrawingLineType.VDottedLine); + } + for (let pos = Math.round(origx) - band; pos >= 0; pos -= band) { + this._drawLine(img, Math.round(pos), cs, DrawingLineType.VDottedLine); + } + for (let pos = Math.round(origy) + band; pos < this.imageSize; pos += band) { + this._drawLine(img, Math.round(pos), cs, DrawingLineType.HDottedLine); + } + for (let pos = Math.round(origy) - band; pos >= 0; pos -= band) { + this._drawLine(img, Math.round(pos), cs, DrawingLineType.HDottedLine); + } + } + + private _drawLine(img: ImageData, value: number, c: G3DColor, type: DrawingLineType) { + switch (type) { + case DrawingLineType.VLine: { + for (let y = 0; y < this.imageSize; y++) { + this._drawPixel(img, value + y * this.imageSize, c); + } + break; + } + case DrawingLineType.VDottedLine: { + for (let y = 0; y < this.imageSize; y += 2) { + this._drawPixel(img, value + y * this.imageSize, c); + } + break; + } + case DrawingLineType.HLine: { + let pos = value * this.imageSize; + for (let x = 0; x < this.imageSize; x++, pos++) { + this._drawPixel(img, pos, c); + } + break; + } + case DrawingLineType.HDottedLine: { + let pos = value * this.imageSize; + for (let x = 0; x < this.imageSize; x += 2, pos += 2) { + this._drawPixel(img, pos, c); + } + break; + } + } + } + + private _drawPixel(img: ImageData, pos: number, c: G3DColor) { + const base = pos * 4; + img.data[base] = c.r; + img.data[base + 1] = c.g; + img.data[base + 2] = c.b; + img.data[base + 3] = 255; + } + + private _getPixelColor(x: number, y: number, z: number, irc: G3DImageRenderingContext): G3DColor { + const c: G3DColor = { r: 0, g: 0, b: 0 }; + if (this.header === null || this.imgView === null) { + return c; + } + + const tx = x * irc.a1 + y * irc.b1 + z * irc.c1 + irc.d1; + const ty = x * irc.a2 + y * irc.b2 + z * irc.c2 + irc.d2; + const tz = x * irc.a3 + y * irc.b3 + z * irc.c3 + irc.d3; + + if (tx < 0 || ty < 0 || tz < 0) return c; + if (tx > this.header.org_width - 1) return c; + if (ty > this.header.org_height - 1) return c; + if (tz > this.header.org_depth - 1) return c; + + const ix0 = Math.floor(tx); + const iy0 = Math.floor(ty); + const iz0 = Math.floor(tz); + const wix1 = tx - ix0; + const wix0 = 1.0 - wix1; + const wiy1 = ty - iy0; + const wiy0 = 1.0 - wiy1; + const wiz1 = tz - iz0; + const wiz0 = 1.0 - wiz1; + const pixel = this.header.datatype; + const line = this.header.org_width * pixel; + const area = this.header.org_height * line; + + let offset = ix0 * pixel + iy0 * line + iz0 * area; + let tmp1 = offset; + let tmp2 = offset; + const mx0 = [0, 0, 0]; + const mxy0 = [0, 0, 0]; + const mx1 = [0, 0, 0]; + const mxy1 = [0, 0, 0]; + + if (ix0 + 1 === this.header.org_width) { + tmp1 = offset; + mx0[0] = this.imgView[tmp1++]; + mx0[1] = this.imgView[tmp1++]; + mx0[2] = this.imgView[tmp1]; + } else { + tmp1 = offset; + tmp2 = tmp1 + pixel; + mx0[0] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx0[1] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx0[2] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + } + if (iy0 + 1 === this.header.org_height) { + mxy0[0] = mx0[0]; + mxy0[1] = mx0[1]; + mxy0[2] = mx0[2]; + } else { + if (ix0 + 1 === this.header.org_width) { + tmp1 = offset + line; + mx1[0] = this.imgView[tmp1++]; + mx1[1] = this.imgView[tmp1++]; + mx1[2] = this.imgView[tmp1]; + } else { + tmp1 = offset + line; + tmp2 = tmp1 + pixel; + mx1[0] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx1[1] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx1[2] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + } + mxy0[0] = mx0[0] * wiy0 + mx1[0] * wiy1; + mxy0[1] = mx0[1] * wiy0 + mx1[1] * wiy1; + mxy0[2] = mx0[2] * wiy0 + mx1[2] * wiy1; + } + if (iz0 + 1 === this.header.org_depth) { + c.b = Math.round(mxy0[0]); + c.g = Math.round(mxy0[1]); + c.r = Math.round(mxy0[2]); + return c; + } + offset = offset + area; + if (ix0 + 1 === this.header.org_width) { + tmp1 = offset; + mx0[0] = this.imgView[tmp1++]; + mx0[1] = this.imgView[tmp1++]; + mx0[2] = this.imgView[tmp1]; + } else { + tmp1 = offset; + tmp2 = offset + pixel; + mx0[0] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx0[1] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx0[2] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + } + if (iy0 + 1 === this.header.org_height) { + mxy1[0] = mx0[0]; + mxy1[1] = mx0[1]; + mxy1[2] = mx0[2]; + } else { + if (ix0 + 1 === this.header.org_width) { + tmp1 = offset + line; + mx1[0] = this.imgView[tmp1++]; + mx1[1] = this.imgView[tmp1++]; + mx1[2] = this.imgView[tmp1]; + } else { + tmp1 = offset + line; + tmp2 = tmp1 + pixel; + mx1[0] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx1[1] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + tmp1++; + tmp2++; + mx1[2] = this.imgView[tmp1] * wix0 + this.imgView[tmp2] * wix1; + } + mxy1[0] = mx0[0] * wiy0 + mx1[0] * wiy1; + mxy1[1] = mx0[1] * wiy0 + mx1[1] * wiy1; + mxy1[2] = mx0[2] * wiy0 + mx1[2] * wiy1; + } + c.b = Math.round(mxy0[0] * wiz0 + mxy1[0] * wiz1); + c.g = Math.round(mxy0[1] * wiz0 + mxy1[1] * wiz1); + c.r = Math.round(mxy0[2] * wiz0 + mxy1[2] * wiz1); + return c; + } + + private _getPixel(x: number, y: number, z: number, irc: G3DImageRenderingContext): number { + if (this.header === null || this.imgView === null) { + return 0; + } + + const tx = x * irc.a1 + y * irc.b1 + z * irc.c1 + irc.d1; + const ty = x * irc.a2 + y * irc.b2 + z * irc.c2 + irc.d2; + const tz = x * irc.a3 + y * irc.b3 + z * irc.c3 + irc.d3; + + if (tx < 0 || ty < 0 || tz < 0 || tx > this.header.org_width - 1 || ty > this.header.org_height - 1 || tz > this.header.org_depth - 1) { + return 0; + } + + const ix0 = Math.floor(tx); + const iy0 = Math.floor(ty); + const iz0 = Math.floor(tz); + const wix1 = tx - ix0; + const wix0 = 1.0 - wix1; + const wiy1 = ty - iy0; + const wiy0 = 1.0 - wiy1; + const wiz1 = tz - iz0; + const wiz0 = 1.0 - wiz1; + const pixel = this.header.datatype; + const line = this.header.org_width * pixel; + const area = this.header.org_height * line; + + let mx0: number; + let mx1: number; + let mxy0: number; + let mxy1: number; + + let offset = ix0 * pixel + iy0 * line + iz0 * area; + if (ix0 + 1 === this.header.org_width) { + mx0 = this.imgView[offset]; + } else { + mx0 = this.imgView[offset] * wix0 + this.imgView[offset + pixel] * wix1; + } + if (iy0 + 1 === this.header.org_height) { + mxy0 = mx0; + } else { + if (ix0 + 1 === this.header.org_width) { + mx1 = this.imgView[offset + line]; + } else { + mx1 = this.imgView[offset + line] * wix0 + this.imgView[offset + line + pixel] * wix1; + } + mxy0 = mx0 * wiy0 + mx1 * wiy1; + } + + if (iz0 + 1 === this.header.org_depth) { + return Math.round(mxy0); + } + + offset = offset + area; + if (ix0 + 1 === this.header.org_width) { + mx0 = this.imgView[offset]; + } else { + mx0 = this.imgView[offset] * wix0 + this.imgView[offset + pixel] * wix1; + } + if (iy0 + 1 === this.header.org_height) { + mxy1 = mx0; + } else { + if (ix0 + 1 === this.header.org_width) { + mx1 = this.imgView[offset + line]; + } else { + mx1 = this.imgView[offset + line] * wix0 + this.imgView[offset + line + pixel] * wix1; + } + mxy1 = mx0 * wiy0 + mx1 * wiy1; + } + + return Math.round(mxy0 * wiz0 + mxy1 * wiz1); + } + + private _transSurface(cutdepth: number, pdimg: number[], irc: G3DImageRenderingContext) { + if (this.header === null || this.surfaceView === null) { + return; + } + + const ix = [...Array(7)].map(() => 0); + const iy = [...Array(7)].map(() => 0); + const iz = [...Array(7)].map(() => 0); + + for (let i = 0; i < this.header.s_num * 3; i++) { + const x = this.surfaceView[i++]; + const y = this.surfaceView[i++]; + const z = this.surfaceView[i]; + + ix[0] = x * irc.ra1 + y * irc.rb1 + z * irc.rc1 + irc.rd1; + iy[0] = x * irc.ra2 + y * irc.rb2 + z * irc.rc2 + irc.rd2; + iz[0] = x * irc.ra3 + y * irc.rb3 + z * irc.rc3 + irc.rd3; + ix[1] = ix[0] + irc.ra1_2; + iy[1] = iy[0] + irc.ra2_2; + iz[1] = iz[0] + irc.ra3_2; + ix[2] = ix[0] + irc.rb1_2; + iy[2] = iy[0] + irc.rb2_2; + iz[2] = iz[0] + irc.rb3_2; + ix[3] = ix[0] + irc.rc1_2; + iy[3] = iy[0] + irc.rc2_2; + iz[3] = iz[0] + irc.rc3_2; + ix[4] = ix[1] + irc.rb1_2; + iy[4] = iy[1] + irc.rb2_2; + iz[4] = iz[1] + irc.rb3_2; + ix[5] = ix[1] + irc.rc1_2; + iy[5] = iy[1] + irc.rc2_2; + iz[5] = iz[1] + irc.rc3_2; + ix[6] = ix[2] + irc.rc1_2; + iy[6] = iy[2] + irc.rc2_2; + iz[6] = iz[2] + irc.rc3_2; + + for (let j = 0; j < 7; j++) { + const xi = Math.round(ix[j]); + const yi = Math.round(iy[j]); + if (xi < 0 || xi >= this.imageSize || yi < 0 || yi >= this.imageSize || iz[j] < cutdepth || iz[j] >= this.imageSize) { + continue; + } + const pos = xi + yi * this.imageSize; + if (pdimg[pos] === 0 || pdimg[pos] > iz[j]) { + pdimg[pos] = iz[j]; + } + } + } + } + + private _setMatrix(x: number, y: number, z: number, irc: G3DImageRenderingContext) { + if (this.header === null) { + return; + } + const xtr = [...Array(9)].map(() => 0); + const ytr = [...Array(9)].map(() => 0); + const ztr = [...Array(9)].map(() => 0); + const tmp = [...Array(9)].map(() => 0); + const tr = [...Array(9)].map(() => 0); + this._setXtr(xtr, (x * Math.PI) / 180.0); + this._setYtr(ytr, (y * Math.PI) / 180.0); + this._setZtr(ztr, (z * Math.PI) / 180.0); + this._multi(ytr, xtr, tmp); + this._multi(ztr, tmp, tr); + irc.a1 = tr[0]; + irc.b1 = tr[1]; + irc.c1 = tr[2]; + irc.a2 = tr[3]; + irc.b2 = tr[4]; + irc.c2 = tr[5]; + irc.a3 = tr[6]; + irc.b3 = tr[7]; + irc.c3 = tr[8]; + irc.d1 = -irc.a1 * this.centerX - irc.b1 * this.centerY - irc.c1 * this.centerZ + this.ox; + irc.d2 = -irc.a2 * this.centerX - irc.b2 * this.centerY - irc.c2 * this.centerZ + this.oy; + irc.d3 = -irc.a3 * this.centerX - irc.b3 * this.centerY - irc.c3 * this.centerZ + this.oz; + + const sx = this.header.org_width / this.header.width; + const sy = this.header.org_height / this.header.height; + const sz = this.header.org_depth / this.header.depth; + irc.a1 = sx * irc.a1; + irc.b1 = sx * irc.b1; + irc.c1 = sx * irc.c1; + irc.d1 = sx * irc.d1; + irc.a2 = sy * irc.a2; + irc.b2 = sy * irc.b2; + irc.c2 = sy * irc.c2; + irc.d2 = sy * irc.d2; + irc.a3 = sz * irc.a3; + irc.b3 = sz * irc.b3; + irc.c3 = sz * irc.c3; + irc.d3 = sz * irc.d3; + + this._setXtr(xtr, (-x * Math.PI) / 180.0); + this._setYtr(ytr, (-y * Math.PI) / 180.0); + this._setZtr(ztr, (-z * Math.PI) / 180.0); + this._multi(ytr, ztr, tmp); + this._multi(xtr, tmp, tr); + irc.ra1 = tr[0]; + irc.rb1 = tr[1]; + irc.rc1 = tr[2]; + irc.ra2 = tr[3]; + irc.rb2 = tr[4]; + irc.rc2 = tr[5]; + irc.ra3 = tr[6]; + irc.rb3 = tr[7]; + irc.rc3 = tr[8]; + irc.rd1 = -irc.ra1 * this.ox - irc.rb1 * this.oy - irc.rc1 * this.oz + this.centerX; + irc.rd2 = -irc.ra2 * this.ox - irc.rb2 * this.oy - irc.rc2 * this.oz + this.centerY; + irc.rd3 = -irc.ra3 * this.ox - irc.rb3 * this.oy - irc.rc3 * this.oz + this.centerZ; + irc.ra1_2 = irc.ra1 / 2; + irc.rb1_2 = irc.rb1 / 2; + irc.rc1_2 = irc.rc1 / 2; + irc.ra2_2 = irc.ra2 / 2; + irc.rb2_2 = irc.rb2 / 2; + irc.rc2_2 = irc.rc2 / 2; + irc.ra3_2 = irc.ra3 / 2; + irc.rb3_2 = irc.rb3 / 2; + irc.rc3_2 = irc.rc3 / 2; + } + + private _setXtr(xtr: number[], ang: number) { + xtr[0] = 1; + xtr[1] = 0; + xtr[2] = 0; + xtr[3] = 0; + xtr[4] = Math.cos(ang); + xtr[5] = -Math.sin(ang); + xtr[6] = 0; + xtr[7] = Math.sin(ang); + xtr[8] = Math.cos(ang); + } + + private _setYtr(ytr: number[], ang: number) { + ytr[0] = Math.cos(ang); + ytr[1] = 0; + ytr[2] = Math.sin(ang); + ytr[3] = 0; + ytr[4] = 1; + ytr[5] = 0; + ytr[6] = -Math.sin(ang); + ytr[7] = 0; + ytr[8] = Math.cos(ang); + } + + private _setZtr(ztr: number[], ang: number) { + ztr[0] = Math.cos(ang); + ztr[1] = -Math.sin(ang); + ztr[2] = 0; + ztr[3] = Math.sin(ang); + ztr[4] = Math.cos(ang); + ztr[5] = 0; + ztr[6] = 0; + ztr[7] = 0; + ztr[8] = 1; + } + + private _multi(l: number[], r: number[], a: number[]) { + a[0] = l[0] * r[0] + l[1] * r[3] + l[2] * r[6]; + a[3] = l[3] * r[0] + l[4] * r[3] + l[5] * r[6]; + a[6] = l[6] * r[0] + l[7] * r[3] + l[8] * r[6]; + a[1] = l[0] * r[1] + l[1] * r[4] + l[2] * r[7]; + a[4] = l[3] * r[1] + l[4] * r[4] + l[5] * r[7]; + a[7] = l[6] * r[1] + l[7] * r[4] + l[8] * r[7]; + a[2] = l[0] * r[2] + l[1] * r[5] + l[2] * r[8]; + a[5] = l[3] * r[2] + l[4] * r[5] + l[5] * r[8]; + a[8] = l[6] * r[2] + l[7] * r[5] + l[8] * r[8]; + } + + private _fround(x: number, n: number) { + return Math.round(x * Math.pow(10, n)) / Math.pow(10, n); + } + + private _initialize(data: ArrayBuffer): boolean { + // check browser endianness + const isBigEndian = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x11; + // check magic header and file endianness + const view = new DataView(data); + const magic = view.getInt32(0, true); + let needSwap = false; + switch (magic) { + case 0x474f3344: + case 0x47483344: + case 0x434f3344: + break; + case 0x44334f47: + case 0x44334847: + case 0x44334f43: + needSwap = true; + break; + default: + console.log('invalid magic header found, data is not g3d format.'); + return false; + } + this.isLittleEndianFile = isBigEndian ? needSwap : !needSwap; + // read header + const decoder = new TextDecoder('utf-8'); + let headerOffset = 0; + this.header = { + magic: view.getInt32(headerOffset, this.isLittleEndianFile), + width: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + height: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + depth: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + org_width: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + org_height: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + org_depth: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + datatype: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + unit: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + grid: view.getFloat64((headerOffset += 4), this.isLittleEndianFile), + vs_width: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + v_width: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + v_height: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + v_depth: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + ax: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + ay: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + az: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + ox: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + oy: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + oz: view.getFloat64((headerOffset += 8), this.isLittleEndianFile), + s_num: view.getInt32((headerOffset += 8), this.isLittleEndianFile), + s_th: view.getInt32((headerOffset += 4), this.isLittleEndianFile), + option: decoder.decode(new Uint8Array(data, (headerOffset += 4), 256)).replace(/\0+$/, ''), + date: decoder.decode(new Uint8Array(data, (headerOffset += 256), 32)).replace(/\0+$/, ''), + coordinate: decoder.decode(new Uint8Array(data, (headerOffset += 32), 32)).replace(/\0+$/, ''), + subject: decoder.decode(new Uint8Array(data, (headerOffset += 32), 256)).replace(/\0+$/, ''), + memo: decoder.decode(new Uint8Array(data, (headerOffset += 256), 256)).replace(/\0+$/, ''), + }; + // set surface and image views + const surfaceOffset = 964; + const surfaceLength = this.header.s_num * 3; + this.surfaceView = new Int16Array(data, surfaceOffset, surfaceLength); + if (needSwap) { + // fix endianness + let offset = surfaceOffset; + for (let i = 0; i < surfaceLength; i++) { + const ui8 = new Uint8Array(data, offset, 2); + ui8.reverse(); + offset += 2; + } + } + const imgOffset = surfaceOffset + surfaceLength * 2; + const imgLength = this.header.org_width * this.header.org_height * this.header.org_depth * this.header.datatype; + this.imgView = new Uint8Array(data, imgOffset, imgLength); + // set offset for image rendering + this.imageSize = Math.ceil(Math.sqrt(this.header.width * this.header.width + this.header.height * this.header.height + this.header.depth * this.header.depth)) + 1; + this.centerX = (this.imageSize - 1) / 2.0; + this.centerY = (this.imageSize - 1) / 2.0; + this.centerZ = (this.imageSize - 1) / 2.0; + this.ox = (this.header.width - 1) / 2.0; + this.oy = (this.header.height - 1) / 2.0; + this.oz = (this.header.depth - 1) / 2.0; + // set data + this.data = data; + return true; + } +} + +export default G3D; diff --git a/src/brainexplorer/lib/m3d.ts b/src/brainexplorer/lib/m3d.ts new file mode 100644 index 0000000..550aad5 --- /dev/null +++ b/src/brainexplorer/lib/m3d.ts @@ -0,0 +1,616 @@ +export interface M3DHeader { + magic: number; + x: number; + y: number; + z: number; + filename: string; + subject: string; + direction: string; + date: number; + fov: number; + weight: number; + ax: number; + ay: number; + az: number; + ox: number; + oy: number; + oz: number; + dlen: number; + dth: number; + memo: string; +} + +interface M3DImageRenderingContext { + a1: number; + a2: number; + a3: number; + b1: number; + b2: number; + b3: number; + c1: number; + c2: number; + c3: number; + d1: number; + d2: number; + d3: number; + ra1: number; + ra1_2: number; + ra2: number; + ra2_2: number; + ra3: number; + ra3_2: number; + rb1: number; + rb1_2: number; + rb2: number; + rb2_2: number; + rb3: number; + rb3_2: number; + rc1: number; + rc1_2: number; + rc2: number; + rc2_2: number; + rc3: number; + rc3_2: number; + rd1: number; + rd2: number; + rd3: number; +} + +/* MeshLength unit is same as fov unit */ +const MeshLength = 10; +enum DrawingLineType { + VLine = 0, + VDottedLine = 1, + HLine = 2, + HDottedLine = 3, +} +const LineBrightness = 200; +const DottedLineBrightness = 128; +const EXACTSWITCH: 0 | 1 = 1; +const MaxIntensity = 200; + +class M3D { + private data: ArrayBuffer; + private header: M3DHeader | null = null; + private xposView: Int32Array | null = null; + private zposView: Int32Array | null = null; + private yposView: Int32Array | null = null; + private img3dView: Uint8ClampedArray | null = null; + private imageSize = 0; // imagesize size : max (size_x, size_y, size_z) + private centerX = 0; // camera coordinates origin : x + private centerY = 0; // camera coordinates origin : y + private centerZ = 0; // camera coordinates origin : z + private ox = 0; // object coordinates origin : x + private oy = 0; // object coordinates origin : y + private oz = 0; // object coordinates origin : z + + constructor(data: ArrayBuffer) { + this.data = data; + this._initialize(); + } + + isBigEndian(): boolean { + return new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x11; + } + + getHeader(): M3DHeader | null { + return this.header; + } + + drawImage(canvas: HTMLCanvasElement, ax: number, ay: number, az: number, cutdepth: number, th: number, is3d: boolean, mesh: boolean): boolean { + const irc: M3DImageRenderingContext = { + a1: 0, + a2: 0, + a3: 0, + b1: 0, + b2: 0, + b3: 0, + c1: 0, + c2: 0, + c3: 0, + d1: 0, + d2: 0, + d3: 0, + ra1: 0, + ra1_2: 0, + ra2: 0, + ra2_2: 0, + ra3: 0, + ra3_2: 0, + rb1: 0, + rb1_2: 0, + rb2: 0, + rb2_2: 0, + rb3: 0, + rb3_2: 0, + rc1: 0, + rc1_2: 0, + rc2: 0, + rc2_2: 0, + rc3: 0, + rc3_2: 0, + rd1: 0, + rd2: 0, + rd3: 0, + }; + // console.log(ax, ay, az, cutdepth, th, dim, mesh) + if (this.header === null) { + return false; + } + + canvas.width = this.imageSize; + canvas.height = this.imageSize; + const ctx = canvas.getContext('2d'); + if (ctx === null) { + return false; + } + ctx.fillStyle = 'rgb(0, 0, 0)'; + ctx.fillRect(0, 0, this.imageSize, this.imageSize); + const img = ctx.getImageData(0, 0, this.imageSize, this.imageSize); + + this._setMatrix(ax, ay, az, irc); + if (is3d) { + const ddimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + this._makeDIMG(img, cutdepth, th, ddimg, irc); + this._makeShadingImage(img, ddimg); + } else { + this._makeSliceImage(img, cutdepth, irc); + } + if (mesh) { + this._drawMesh(img, irc); + } + ctx.putImageData(img, 0, 0); + return true; + } + + private _makeDIMG(img: ImageData, cutdepth: number, th: number, ddimg: number[], irc: M3DImageRenderingContext) { + if (this.header === null) { + return; + } + const pdimg = [...Array(this.imageSize * this.imageSize)].map(() => 0); + const sw = th > this.header.dth ? 1 : th < this.header.dth ? 2 : EXACTSWITCH; + switch (sw) { + case 0: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let j = 0; j < this.imageSize; j++) { + for (let i = 0; i < this.imageSize; i++, pos++) { + ddimg[pos] = pdimg[pos]; + const pix = this._getPixel(i, j, cutdepth, irc); + if (pix > th) { + this._drawPixel(img, pos, pix, pix, pix); + ddimg[pos] = cutdepth; + continue; + } + } + } + break; + } + case 1: { + this._transSurface(cutdepth, pdimg, irc); + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + let pdPos = (ddimg[pos] = pdimg[pos]); + if (pdPos === 0) { + continue; + } + const pix = this._getPixel(x, y, cutdepth, irc); + if (pix > th) { + this._drawPixel(img, pos, pix, pix, pix); + ddimg[pos] = cutdepth; + continue; + } + if (pdPos < cutdepth) { + pdPos = cutdepth; + } + ddimg[pos] = 0; + for (let z = pdPos; z < this.imageSize; z++) { + const pix = this._getPixel(x, y, z, irc); + if (pix > th) { + ddimg[pos] = z; + break; + } + } + } + } + break; + } + case 2: { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + ddimg[pos] = 0; + for (let z = cutdepth; z < this.imageSize; z++) { + const pix = this._getPixel(x, y, z, irc); + if (pix > th) { + if (z === cutdepth) { + this._drawPixel(img, pos, pix, pix, pix); + } else { + ddimg[pos] = z; + } + break; + } + } + } + } + break; + } + } + } + + private _makeShadingImage(img: ImageData, ddimg: number[]) { + let pos = 0; + for (let x = 0; x < this.imageSize; x++) { + for (let y = 0; y < this.imageSize; y++, pos++) { + const base = pos * 4; + if (img.data[base] === 0) { + let timg2d = 0; + if (x < this.imageSize - 2 && y < this.imageSize - 2) { + if (ddimg[pos] !== 0) { + const nx = (ddimg[pos] + ddimg[pos + this.imageSize] - ddimg[pos + 2] - ddimg[pos + 2 + this.imageSize]) / 4.0; + const ny = (ddimg[pos] + ddimg[pos + 1] - ddimg[pos + this.imageSize * 2] - ddimg[pos + 1 + this.imageSize * 2]) / 4.0; + const n = Math.sqrt(nx * nx + ny * ny + 1); + timg2d = Math.round(MaxIntensity / n); + } + } + this._drawPixel(img, pos, timg2d, timg2d, timg2d); + } + } + } + } + + private _makeSliceImage(img: ImageData, cutdepth: number, irc: M3DImageRenderingContext) { + let pos = 0; + for (let y = 0; y < this.imageSize; y++) { + for (let x = 0; x < this.imageSize; x++, pos++) { + const pix = this._getPixel(x, y, cutdepth, irc); + this._drawPixel(img, pos, pix, pix, pix); + } + } + } + + private _drawMesh(img: ImageData, irc: M3DImageRenderingContext) { + let origx: number; + let origy: number; + if (this.header === null) { + return; + } + if (this.header.ox !== 0 && this.header.oy !== 0) { + origx = this.header.ox * irc.ra1 + this.header.oy * irc.rb1 + this.header.oz * irc.rc1 + irc.rd1; + origy = this.header.ox * irc.ra2 + this.header.oy * irc.rb2 + this.header.oz * irc.rc2 + irc.rd2; + } else { + origx = this.centerX; + origy = this.centerY; + } + origx = this._fround(origx, 5); + origy = this._fround(origy, 5); + + const band = (this.header.x * MeshLength) / this.header.fov; + const r = 0; + const g = 0; + const b = LineBrightness; + const sr = 0; + const sg = 0; + const sb = DottedLineBrightness; + this._drawLine(img, Math.round(origx), r, g, b, DrawingLineType.VLine); + this._drawLine(img, Math.round(origy), r, g, b, DrawingLineType.HLine); + for (let pos = Math.round(origx) + band; pos < this.imageSize; pos += band) { + this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.VDottedLine); + } + for (let pos = Math.round(origx) - band; pos >= 0; pos -= band) { + this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.VDottedLine); + } + for (let pos = Math.round(origy) + band; pos < this.imageSize; pos += band) { + this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.HDottedLine); + } + for (let pos = Math.round(origy) - band; pos >= 0; pos -= band) { + this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.HDottedLine); + } + } + + private _drawLine(img: ImageData, value: number, r: number, g: number, b: number, type: DrawingLineType) { + switch (type) { + case DrawingLineType.VLine: { + let pos = value; + for (let y = 0; y < this.imageSize; y++, pos += this.imageSize) { + this._drawPixel(img, pos, r, g, b); + } + break; + } + case DrawingLineType.VDottedLine: { + let pos = value; + for (let y = 0; y < this.imageSize; y += 2, pos += this.imageSize * 2) { + this._drawPixel(img, pos, r, g, b); + } + break; + } + case DrawingLineType.HLine: { + let pos = value * this.imageSize; + for (let x = 0; x < this.imageSize; x++, pos++) { + this._drawPixel(img, pos, r, g, b); + } + break; + } + case DrawingLineType.HDottedLine: { + let pos = value * this.imageSize; + for (let x = 0; x < this.imageSize; x += 2, pos += 2) { + this._drawPixel(img, pos, r, g, b); + } + break; + } + default: + break; + } + } + + private _drawPixel(img: ImageData, pos: number, r: number, g: number, b: number) { + let base = pos * 4; + img.data[base++] = r; + img.data[base++] = g; + img.data[base++] = b; + img.data[base] = 255; + } + + private _getPixel(x: number, y: number, z: number, irc: M3DImageRenderingContext): number { + const len = [...Array(8)].map(() => 0); + const ratio = [...Array(8)].map(() => 0); + + const tx = x * irc.a1 + y * irc.b1 + z * irc.c1 + irc.d1; + const ty = x * irc.a2 + y * irc.b2 + z * irc.c2 + irc.d2; + const tz = x * irc.a3 + y * irc.b3 + z * irc.c3 + irc.d3; + + let ix = Math.floor(tx); + let iy = Math.floor(ty); + let iz = Math.floor(tz); + + if (this.header === null || this.img3dView === null) { + return 0; + } + if (ix < 0 || ix + 1 >= this.header.x || iy < 0 || iy + 1 >= this.header.y || iz < 0 || iz + 1 >= this.header.z) { + return 0; + } + + const ax1 = Math.abs(tx - ix); + const ax2 = Math.abs(1 - ax1); + const ay1 = Math.abs(ty - iy); + const ay2 = Math.abs(1 - ay1); + const az1 = Math.abs(tz - iz); + const az2 = Math.abs(1 - az1); + + let sumlen1 = 0; + for (let i = 0; i < 8; i++) { + len[i] = i & 1 ? ax2 : ax1; + len[i] += i & 2 ? ay2 : ay1; + len[i] += i & 4 ? az2 : az1; + if (len[i] < 0.001) { + ix += i & 1 ? 1 : 0; + iy += i & 2 ? 1 : 0; + iz += i & 4 ? 1 : 0; + return this.img3dView[ix + iy * this.header.x + iz * this.header.x * this.header.y]; + } + sumlen1 += ratio[i] = 1 / len[i]; + } + let pos = ix + iy * this.header.x + iz * this.header.x * this.header.y; + let sumlen2 = this.img3dView[pos] * ratio[0]; + sumlen2 += this.img3dView[pos + 1] * ratio[1]; + sumlen2 += this.img3dView[pos + this.header.x] * ratio[2]; + sumlen2 += this.img3dView[pos + 1 + this.header.x] * ratio[3]; + pos += this.header.x * this.header.y; + sumlen2 += this.img3dView[pos] * ratio[4]; + sumlen2 += this.img3dView[pos + 1] * ratio[5]; + sumlen2 += this.img3dView[pos + this.header.x] * ratio[6]; + sumlen2 += this.img3dView[pos + 1 + this.header.x] * ratio[7]; + + return Math.round(sumlen2 / sumlen1); + } + + private _transSurface(cutdepth: number, pdimg: number[], irc: M3DImageRenderingContext) { + if (this.header === null || this.xposView === null || this.yposView === null || this.zposView === null) { + return; + } + const ix = [...Array(8)].map(() => 0); + const iy = [...Array(8)].map(() => 0); + const iz = [...Array(8)].map(() => 0); + for (let i = 0; i < this.header.dlen; i++) { + const x = this.xposView[i]; + const y = this.yposView[i]; + const z = this.zposView[i]; + ix[0] = x * irc.ra1 + y * irc.rb1 + z * irc.rc1 + irc.rd1; + iy[0] = x * irc.ra2 + y * irc.rb2 + z * irc.rc2 + irc.rd2; + iz[0] = x * irc.ra3 + y * irc.rb3 + z * irc.rc3 + irc.rd3; + ix[1] = ix[0] + irc.ra1_2; + iy[1] = iy[0] + irc.ra2_2; + iz[1] = iz[0] + irc.ra3_2; + ix[2] = ix[0] + irc.rb1_2; + iy[2] = iy[0] + irc.rb2_2; + iz[2] = iz[0] + irc.rb3_2; + ix[3] = ix[0] + irc.rc1_2; + iy[3] = iy[0] + irc.rc2_2; + iz[3] = iz[0] + irc.rc3_2; + ix[4] = ix[1] + irc.rb1_2; + iy[4] = iy[1] + irc.rb2_2; + iz[4] = iz[1] + irc.rb3_2; + ix[5] = ix[1] + irc.rc1_2; + iy[5] = iy[1] + irc.rc2_2; + iz[5] = iz[1] + irc.rc3_2; + ix[6] = ix[2] + irc.rc1_2; + iy[6] = iy[2] + irc.rc2_2; + iz[6] = iz[2] + irc.rc3_2; + ix[7] = ix[4] + irc.rc1_2; + iy[7] = iy[4] + irc.rc2_2; + iz[7] = iz[4] + irc.rc3_2; + for (let j = 0; j < 8; j++) { + const xi = Math.round(ix[j]); + const yi = Math.round(iy[j]); + if (xi < 0 || xi >= this.imageSize || yi < 0 || yi >= this.imageSize || iz[j] < cutdepth || iz[j] >= this.imageSize) { + continue; + } + const pos = xi + yi * this.imageSize; + if (pdimg[pos] === 0 || pdimg[pos] > iz[j]) { + pdimg[pos] = iz[j]; + } + } + } + } + + private _setMatrix(x: number, y: number, z: number, irc: M3DImageRenderingContext) { + const xtr = [...Array(9)].map(() => 0); + const ytr = [...Array(9)].map(() => 0); + const ztr = [...Array(9)].map(() => 0); + const tmp = [...Array(9)].map(() => 0); + const tr = [...Array(9)].map(() => 0); + this._setXtr(xtr, (x * Math.PI) / 180.0); + this._setYtr(ytr, (y * Math.PI) / 180.0); + this._setZtr(ztr, (z * Math.PI) / 180.0); + this._multi(ytr, xtr, tmp); + this._multi(ztr, tmp, tr); + irc.a1 = tr[0]; + irc.b1 = tr[1]; + irc.c1 = tr[2]; + irc.a2 = tr[3]; + irc.b2 = tr[4]; + irc.c2 = tr[5]; + irc.a3 = tr[6]; + irc.b3 = tr[7]; + irc.c3 = tr[8]; + irc.d1 = -irc.a1 * this.centerX - irc.b1 * this.centerY - irc.c1 * this.centerZ + this.ox; + irc.d2 = -irc.a2 * this.centerX - irc.b2 * this.centerY - irc.c2 * this.centerZ + this.oy; + irc.d3 = -irc.a3 * this.centerX - irc.b3 * this.centerY - irc.c3 * this.centerZ + this.oz; + + this._setXtr(xtr, (-x * Math.PI) / 180.0); + this._setYtr(ytr, (-y * Math.PI) / 180.0); + this._setZtr(ztr, (-z * Math.PI) / 180.0); + this._multi(ytr, ztr, tmp); + this._multi(xtr, tmp, tr); + irc.ra1 = tr[0]; + irc.rb1 = tr[1]; + irc.rc1 = tr[2]; + irc.ra2 = tr[3]; + irc.rb2 = tr[4]; + irc.rc2 = tr[5]; + irc.ra3 = tr[6]; + irc.rb3 = tr[7]; + irc.rc3 = tr[8]; + irc.rd1 = -irc.ra1 * this.ox - irc.rb1 * this.oy - irc.rc1 * this.oz + this.centerX; + irc.rd2 = -irc.ra2 * this.ox - irc.rb2 * this.oy - irc.rc2 * this.oz + this.centerY; + irc.rd3 = -irc.ra3 * this.ox - irc.rb3 * this.oy - irc.rc3 * this.oz + this.centerZ; + irc.ra1_2 = irc.ra1 / 2; + irc.rb1_2 = irc.rb1 / 2; + irc.rc1_2 = irc.rc1 / 2; + irc.ra2_2 = irc.ra2 / 2; + irc.rb2_2 = irc.rb2 / 2; + irc.rc2_2 = irc.rc2 / 2; + irc.ra3_2 = irc.ra3 / 2; + irc.rb3_2 = irc.rb3 / 2; + irc.rc3_2 = irc.rc3 / 2; + } + + private _setXtr(xtr: number[], ang: number) { + xtr[0] = 1; + xtr[1] = 0; + xtr[2] = 0; + xtr[3] = 0; + xtr[4] = Math.cos(ang); + xtr[5] = -Math.sin(ang); + xtr[6] = 0; + xtr[7] = Math.sin(ang); + xtr[8] = Math.cos(ang); + } + + private _setYtr(ytr: number[], ang: number) { + ytr[0] = Math.cos(ang); + ytr[1] = 0; + ytr[2] = Math.sin(ang); + ytr[3] = 0; + ytr[4] = 1; + ytr[5] = 0; + ytr[6] = -Math.sin(ang); + ytr[7] = 0; + ytr[8] = Math.cos(ang); + } + + private _setZtr(ztr: number[], ang: number) { + ztr[0] = Math.cos(ang); + ztr[1] = -Math.sin(ang); + ztr[2] = 0; + ztr[3] = Math.sin(ang); + ztr[4] = Math.cos(ang); + ztr[5] = 0; + ztr[6] = 0; + ztr[7] = 0; + ztr[8] = 1; + } + + private _multi(l: number[], r: number[], a: number[]) { + a[0] = l[0] * r[0] + l[1] * r[3] + l[2] * r[6]; + a[3] = l[3] * r[0] + l[4] * r[3] + l[5] * r[6]; + a[6] = l[6] * r[0] + l[7] * r[3] + l[8] * r[6]; + a[1] = l[0] * r[1] + l[1] * r[4] + l[2] * r[7]; + a[4] = l[3] * r[1] + l[4] * r[4] + l[5] * r[7]; + a[7] = l[6] * r[1] + l[7] * r[4] + l[8] * r[7]; + a[2] = l[0] * r[2] + l[1] * r[5] + l[2] * r[8]; + a[5] = l[3] * r[2] + l[4] * r[5] + l[5] * r[8]; + a[8] = l[6] * r[2] + l[7] * r[5] + l[8] * r[8]; + } + + private _fround(x: number, n: number) { + return Math.round(x * Math.pow(10, n)) / Math.pow(10, n); + } + + private _initialize(): void { + const view = new DataView(this.data); + const magic = view.getInt32(0, true); + const isLittleEndianFile = magic === 0x4d523344; + if (!isLittleEndianFile && magic !== 0x4433524d) { + console.log('invalid data format, this data is not m3d format'); + return; + } + let offset = 0; + const decoder = new TextDecoder('utf-8'); + this.header = { + magic: view.getInt32(offset, isLittleEndianFile), + x: view.getInt32((offset += 4), isLittleEndianFile), + y: view.getInt32((offset += 4), isLittleEndianFile), + z: view.getInt32((offset += 4), isLittleEndianFile), + filename: decoder.decode(new Uint8Array(this.data, (offset += 4), 256)).replace(/\0+$/, ''), + subject: decoder.decode(new Uint8Array(this.data, (offset += 256), 256)).replace(/\0+$/, ''), + direction: decoder.decode(new Uint8Array(this.data, (offset += 256), 4)).replace(/\0+$/, ''), + date: view.getUint32((offset += 4), isLittleEndianFile), + fov: view.getFloat64((offset += 4), isLittleEndianFile), + weight: view.getFloat64((offset += 8), isLittleEndianFile), + ax: view.getFloat64((offset += 8), isLittleEndianFile), + ay: view.getFloat64((offset += 8), isLittleEndianFile), + az: view.getFloat64((offset += 8), isLittleEndianFile), + ox: view.getFloat64((offset += 8), isLittleEndianFile), + oy: view.getFloat64((offset += 8), isLittleEndianFile), + oz: view.getFloat64((offset += 8), isLittleEndianFile), + dlen: view.getInt32((offset += 8), isLittleEndianFile), + dth: view.getInt32((offset += 4), isLittleEndianFile), + memo: decoder.decode(new Uint8Array(this.data, (offset += 4), 256)).replace(/\0+$/, ''), + }; + // swap byte order if endian is not matched + if ((this.isBigEndian() && isLittleEndianFile) || (!this.isBigEndian() && !isLittleEndianFile)) { + const start = 864; + const end = start + this.header.dlen * 4 * 3; + for (let i = start; i < end; i += 4) { + const data = new Uint8Array(this.data, i, 4); + data.reverse(); + } + } + this.xposView = new Int32Array(this.data, 864, this.header.dlen); + this.yposView = new Int32Array(this.data, 864 + this.header.dlen * 4, this.header.dlen); + this.zposView = new Int32Array(this.data, 864 + this.header.dlen * 8, this.header.dlen); + this.img3dView = new Uint8ClampedArray(this.data, 864 + this.header.dlen * 12, this.header.x * this.header.y * this.header.z); + // set image generation offset + this.imageSize = Math.ceil(Math.max(this.header.x, this.header.y, this.header.z) * Math.sqrt(3.0)) + 1; + this.centerX = (this.imageSize - 1) / 2.0; + this.centerY = (this.imageSize - 1) / 2.0; + this.centerZ = (this.imageSize - 1) / 2.0; + this.ox = (this.header.x - 1) / 2.0; + this.oy = (this.header.y - 1) / 2.0; + this.oz = (this.header.z - 1) / 2.0; + } +} + +export default M3D; diff --git a/src/common/AppRoot.tsx b/src/common/AppRoot.tsx new file mode 100644 index 0000000..117aa45 --- /dev/null +++ b/src/common/AppRoot.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from "react"; +import { useCookies } from "react-cookie"; +import ReactGA from "react-ga4"; +import { useLocation } from "react-router-dom"; +import Config, { MultiLang } from "../config"; +import Page from "../custom/Page"; + +const AppMain: React.FC = () => { + const [cookies, setCookie] = useCookies(); + const [lang, setLang] = useState(["en", "ja"].includes(cookies.ml_lang) ? cookies.ml_lang : "en"); + const location = useLocation(); + + useEffect(() => { + if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") { + ReactGA.send("pageview"); + } + const params = new URLSearchParams(location.search); + const ml_lang = params.get("ml_lang"); + if (ml_lang != null && ["en", "ja"].includes(ml_lang)) { + if (cookies.ml_lang !== ml_lang) { + setCookie("ml_lang", ml_lang); + } + if (lang !== ml_lang) { + setLang(ml_lang as MultiLang); + } + } + window.scrollTo(0, 0); + }, [cookies, setCookie, lang, location]); + + return ; +}; + +const AppRoot: React.FC = () => { + if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") { + ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID); + } + + return ; +}; + +export default AppRoot; diff --git a/src/common/XoopsPathRedirect.tsx b/src/common/XoopsPathRedirect.tsx new file mode 100644 index 0000000..2fa3259 --- /dev/null +++ b/src/common/XoopsPathRedirect.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Navigate } from "react-router"; +import { useLocation } from "react-router-dom"; +import { MultiLang } from "../config"; +import PageNotFound from "./lib/PageNotFound"; + +interface Props { + lang: MultiLang; +} + +const XoopsPathRedirect: React.FC = (props: Props) => { + const { lang } = props; + const location = useLocation(); + + const getRedirectUrl = () => { + const { pathname } = location; + switch (pathname || "") { + case "/index.php": { + return "/"; + } + } + return ""; + }; + + if (location.pathname === "/") { + return null; + } + const url = getRedirectUrl(); + if (url === "") { + return ; + } + return ; +}; + +export default XoopsPathRedirect; diff --git a/src/common/assets/images/mlang_english.gif b/src/common/assets/images/mlang_english.gif new file mode 100644 index 0000000000000000000000000000000000000000..4ff614eb6e91a3a1bd15910b176062815e5e1fa6 GIT binary patch literal 175 zcmZ?wbhEHbPMvkT)|82+yPeNgyybHcp!flwkm& zGzJDBDu4Ix-QBx)ckkZKfEVb1WI=W@aF{SiTS!<4G&VGI@N=oWIFP{5!q2Lsl3}3e i)W)i-G-pQOVg^=GC!rM&J|-NUq!GL-=B1$mgEau6X*loz literal 0 HcmV?d00001 diff --git a/src/common/assets/images/mlang_japanese.gif b/src/common/assets/images/mlang_japanese.gif new file mode 100644 index 0000000000000000000000000000000000000000..57682060bb4f074227d132cebc5647ab3937e8bc GIT binary patch literal 126 zcmZ?wbhEHb=Ksu@|I=pvKXV2sbcVtBE>PAu?ToQ8 z0}{{yu|awnIE)z>Ib=LGEO2CC<`YwyuwWrmD+7DFy%l literal 0 HcmV?d00001 diff --git a/src/common/assets/images/no_avatar.gif b/src/common/assets/images/no_avatar.gif new file mode 100644 index 0000000000000000000000000000000000000000..62fff0fda9e71efb4049f128f71b9702f2675e28 GIT binary patch literal 985 zcmbWy=}(#m0D$pVxs;g|l?|hgFtu73I!cV0n=!y9#no=oidxfnET-$6OCc;rZgJO%o7Nf}UeiqoatWxL*`w>_Hu*#aC4A>bnO!LY|` z(N~O#xp|pNx1L{?oth&%(b$vIC$%Q&Qr>2$b!gW=LW9l1gc#G^scy2*o}R^v<_Qui z4;_k|n!duUb@Rg0BK7jRR&Irq8rit{$IfX;Kq|;VfD{hO8Ho3*rh;~NOj54$Jnz|s z_~Dei%+58i4DHhv=1*BLrN(*2JZf#l)+{mlFI@wyYg8^eAd#YiTe9sONRS99j4)kZ zWbl$!PG)zd^n+@K-UPN^ho~BV?ME%TsHqwjL7If=+cU!QR@SPs;`=w$cu5II4U@9PQdmcEZvneS9_1~@6w{g4 zWC=Ll&+SZ-`d^=_lY({m`P?rF^2AKuhPS#?z2Vyu+gu`fopuNMOq28HfW%Y~00qAK E56O(&lK=n! literal 0 HcmV?d00001 diff --git a/src/common/assets/images/pagact.gif b/src/common/assets/images/pagact.gif new file mode 100644 index 0000000000000000000000000000000000000000..3f5b855afc641760ce695f2cdb70dcde94ec0a09 GIT binary patch literal 121 zcmZ?wbhEHb6l4%#Sj56`W$%=jV8>Y#>i<65`~Uxc1{hHM$->CMz`~#d5&)@XV7Bwv zbZ4W=D#z8l&K`-s4@4fR6qV?EBECk-i^)L1C-eF>v)v%yu4|?rc<9 z<+z&H*(34yfyg73q7r>i#Mel9F&PN>WM02!)*Jl&-0R)*{3Tc(b+{+Tply-5Ihh SYU2u#Ws#BVZ-g>2SOWkpSt^47 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3dbf8e94a6f72.gif b/src/common/assets/images/rank3dbf8e94a6f72.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c7c979b8af21c0d2aaa8490fbd36066cede5a74 GIT binary patch literal 905 zcmV;419tpJNk%w1VMzcD0QP?X;^N}-^YiiX@#Ev;>gww3?Cjs)-`?Kd>FMe4@bKp5 z=GxlY^78WQ>+9s?lq{^z_`^+~?=#@9*#b+ELrv+x7MJ#(+WDePo+tK9poG z;*M?q?Tx~GWazlK{@hWUdT_*~mHzOG>$*|)_V)bDALYu8h;C%%!C=gYIM|YgpLb{W z$3geRB;IITo|1^GgJ#^UTHCFo>9DNMy}Y$_SJ9qE^2J*J`^v$Wdi3acjAKlpgLu4) zS@qLQ|HvEv+EBlJV*cJx{oruJePR6VfBx@^=;-MG$r!|mNdC+>xq?vHi(IgWZuQH4 zzJ6t=eQ4vbljoz7wvL9pw6u3-ViH!{l1&xl7l8KLzl1*`6WJ@hqm71KMppp)Jetf7B zm8Yt!t*R8LdxI}s8FpG&IVUzAP(pBmx4FB$zrn+Q2Lk|o00I$y27T77e+JIb3>Vba zeb@&8&(S4(2G0-JR49o(V~y?a@re!=iG2o*4%mbGjRyD=^x%M`6>>sULWEG^LO}`w z?0fh}0Rw{+BThu561O*flBoH7V0X2+*AWVokb0(jH zE*0?UIrHYADNkf*V8KL9nJkTvDrM>vY9a&_5G***rvL&6jS%obV1cVwuvfE|?fO;H zoEc{3DE@gHm*NBh1LPvXtCw#jJW|{^p$J$oMLz^e065&>!s3UB9VaT1!pNNwUfy&# zB4!7hHi9Ty#;lpMXV8Bh6bQgN0D}k-Tr1cj2*Bz9uwUb5Fd>hit_)fWXE0lk2o)y6 z07>Fv1B^z;lP_=nJkf*!sZjY@@EFkq0Di*zDZstab@AcL52A5~2#GW}!1$=rh`oFG z^66V5AfNyQKLP+ifFt}Bpa6ga47i^F4g4p-00?6IOU(h8rdV fK_3eY_|pInG@<~BCia11BM<}znE*YdzQtV<4?nDPgPS46?+U;KG|I2 z8#EJIWuJCQtd&Wm%gY)jW1dUfmeBR99JP34L#cob%GGHaG&Z2$TGwyQtCl9#q*8;9 zBcG?sIirTP&Up@Vkqhb--AmJ~4dK%r?F#|es+ko^XGSF4Au(e_KGUa|>V{u!ZplWq za@H(+0%C)(dT8bO@Jr6*3dEZQJ2z$+YO!d8&l7Q9=+_7I@`CgibdJ^ zr3MnQCqfA}p`?$mF&VnW?y%a6DLW3m-t9MgvL|}`mb*%-t^HeuU0fZ#ev-T8JFk4M zD9JDcxrdVKc|~y>^r-%5{3u>JY+2RL-P<#CmJxWSR7XD9NRRvLxA!LoA+G?hzuJtVUKY=O66bZiSrWS2ggYKQZ%z=A;Kd8lT&Eti>H#Tzjap; bTJsN4-PDq|2WeEo1x8nU**9lxfJ^@Y9kgU9 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3dbf8ea81e642.gif b/src/common/assets/images/rank3dbf8ea81e642.gif new file mode 100644 index 0000000000000000000000000000000000000000..50f3de786e3763d6643cb1cefad69d6d322aae46 GIT binary patch literal 894 zcmV-^1A+WUNk%w1VMzcD0QP?X?d|R2;^N`q;qLD4^YioLgwv>-{0Qe z-s$P-^78WV@bK&F>*nU>|JqUH(T3?T!85aM7Md=(xC#eRlQBe(kMiwRBg;fI;1WWW0-65fJk=T-k{?9tunL43^c&wC-!=_T%%EtWAJ;aGf|NF}K$*|?-cXxwXxWQg_rxUp?SJ>=q|Uv(@9*#B%8jsxZsoyXtAl3St)r)XXw|WCu3K8w zdt?6bivR79h;C%`=y?0#a{ls(jAKmv%pbOnhMQzQytK5GWG?Bjtjvfwr-C^D$Q$C0 zZRf;o|JqRg@QS{EW!ZgX=;-L%+uPXK*w@$B-QC^x_V(G?+5i9lA^8La004ggEC2ui z07(E106+)-fPaF5AYfO6h>3-ViH(bmjSdqBkcpC%kW)HGG#nc%m71KMpqZPUpOp~< ztOEcNs;pS9e1ktGcVQ`3JXs_fc1==(x4FB$zrn+Q#k#z{z`}wF1pxv93`hV80^P8G z3IWyu27Lnw))e1rWQjH*jqL95iSmf=jS=620tt-;`vg%?0BJ@K43!WWM3_*}!iIt# zFdzu=0RexBfP64g1OgohQB=SQf=DArk03>o9AvWN$B-itU8sPiK?4If2MmWq2PLCBdhIG6}> z3|TT|%a}F);T(B#<;$4`1vo%}fCUQ-EVyP+L64xX3|i}M&EUZvK}p`E84^bcl{Q2g z1s`7gc=ANci5o}Ws6qe$*Z~}vfH+Zw0@<;1N91ON2PidKm^s0wQTg-f*SAmf9=&?@ zkq{U-@Bn~;0tn=1fCCr!SHJ>@$Wa0sF8q-L7dv>-h=L3{2%&_DP}tyu5jOIG022f_ UfB*$h(8vV=_#q;R7zF|VJFmCidjJ3c literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3dbf8eb1a72e7.gif b/src/common/assets/images/rank3dbf8eb1a72e7.gif new file mode 100644 index 0000000000000000000000000000000000000000..64d8f29673326a090371735557325997306696f3 GIT binary patch literal 856 zcmV-e1E>5)Nk%w1VMzcD0QP+V?d|RJ^Yj1OQR3p_?(Xj6(U^cW2+<-`?Kdo|1^_>gw{vTK@8hn|g4+ePXU#TIsN?{>(S~;c};fIREX9 z^~-+Pl7|2L%8z|^=fAqEgJ$B6ZRohTj(TOrfIHhJG<-uUVmwMEIUEHi%<;soz+)=}QVgJb({@zf-rc&8`WaF@t z_vED8t)u_zmHf;f#ED3hWGd2i)q7+9`s4lJaR2R)|HvD) zbXVER#=Nw&{_l$Z@QU~3)2DrC_Qyg0$QtJ6=Ire3<>lq@@bK&F>)P7d^78WF;Nb7? z@7&zn3-ViH(bmj)stlZf|RoC?*VB7-%h+nw*}Xo1LGKnWdwU zbOZ!)1OR-4FA7dvMo=C|VJBG?R)V#+xx2l;!F|KHy1c%?!nVxD&&dx10|x^JcmxjF zadUz<3yE4EjpXL&iRy^w>E-Y2hy~(^aSxCkW)zc%{QUhB6mTHHeg^xf3E-en5g;y- z2uL7+NeT@l5Iu}IvEs#ygEmT}Xc41Eh$B6k9K=Qqfi?!(zy#nxpCBF_j;LthLkEo- zjd1GZ=@V!op*ws244N}((MCe#2&wbnz#1WRAmNB|CE?bt2ETgcnlMj<0{=if=>o%` zTDESr*R~z_cJ4m_5$+5~q=d+iA!WWu*+6Cjf+jWu_5B-oaN)y=`z!`5n6P2Qiv3b{ zd^xgJH~~V74gg2Z9zio?Oi2S~36?4l8l_&{`gQC?vst@_EnBs2+l}-T06?7ho4tcB z1o5(BhlC6)#xzpS+&OgViKsV!9)0@q?bjQ*iD1AD8vq6h2tg#GM2{SO#Dt)7rcwL% i@#oi1bRWKa`u4f!pMH(7F@PNr^g+M@1vJ!0AOJfF<=RUC literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3dbf8edf15093.gif b/src/common/assets/images/rank3dbf8edf15093.gif new file mode 100644 index 0000000000000000000000000000000000000000..704398e896f9b1406e07ec0ceb8b7f89cdef35fa GIT binary patch literal 789 zcmZ?wbhEHb^km>=_!iIb|5o6>y3~IkyZ&6T{eLUq-@SlCH7R%2ME(2J`R}~d)#~KK zwTb`Ucg>sB@$ZcJ>s^8WPe||Sj@;Q4FeOFrW_Qf72FE{Vnr2@=9j1MuT8o*&+YBn z^qaLQFLvx$*j#aU_VjIiZ3m}Ub>yYo%ZQnspK+wq-9ZSU;u?(J)v&^2j- z2(uC=pF_mt=`&}~oi=0EoOw$YE?=;$DcIJBOWH)s$&iiFGi2@hjhnY_-?@9krY+lc z?22D^c;B&uC-)rLf4nImsM9aD_v-bVowqw~+`9JQZpZ!RhrKc`8k0L-zkS#A;nU|g zU*o@Y271{WGq5m-R2VK|RjT{Pz@?H=AaJOjkzLH@2LGc&oqXC_Q*M0d?iXSYQkd9~ z*wOAL@7$+TX%y%+QN?!=3+LpgK2r^&)v6X9<+ht=_nPtV&B+-79#$1=ZUpJSytK?~ z=BZ+>tFl*JxJ@Jk6%M;iljRW*;Vg0qxVAQSf17Odx6_g9;))J-sb+_1$80UWz0X&B z`dS6C7@dg52IV6yY`qNJYB>^xN=KWy<^5zVijq%tt6GbtB%SPK*IRd`qhL|s^YaVc zr~8#iZOOQ_%yxH}tn}B{SDTDNc3gPK%_9)%VRGs0ZS#VMogLr*$=3XM_w@K&@$^0$ H0S0RT(G+NM literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3dbf8ee8681cd.gif b/src/common/assets/images/rank3dbf8ee8681cd.gif new file mode 100644 index 0000000000000000000000000000000000000000..95428a8b96f24e07c3fc77cfe68812bf7998de9b GIT binary patch literal 779 zcmZ?wbhEHb^km>=_!i9Y_vNGicTRk}cH3%z@ir~(MW*}j6q^O*O@EWE?j(0y zNp4-ASAI8U^4BJISR&j8$8R_W3rwe!{7jlKUrAW8G;#fK+XZhi39uphTx_q7Du+`j?UKh z&ff0czP1TnlO|ZIn`oNqc}$)@bN1Y6GiJ@1w`Ae+1hv>whWQ@sNm4mq!u$n z@TmJF9tAU#OD8TWdra)~DElK(^yJu7_1I}CokoHz>{1`@o{7v~?l;H2(n-sKk)gZF zrB_L}DkSdGvUl^;Hho;k$i%|ADXw+_bC<%Zu#H)+2d=&r*$}>2?Z&OI*;ltDXPn-} zyL#H%NN&Fdk(r=}dHdMf=xqh}k5=;k Jm*rrv1^^~_L`?ty literal 0 HcmV?d00001 diff --git a/src/common/assets/images/rank3e632f95e81ca.gif b/src/common/assets/images/rank3e632f95e81ca.gif new file mode 100644 index 0000000000000000000000000000000000000000..224fbaaa5c0f968fb105353d0e15ede8c3634735 GIT binary patch literal 475 zcmV<10VMuMNk%w1VMzcD0Q4UK_V)Jm_4V}h^z-xc^78WW@$v8P@9ysI?d|RC?Ck67 z>+0(2>FMd{=;-I?=jP_-<>lq%cU3-l=!2?hupIPif75fcIuB;Wd#h)y! z>nN*~acOw({w~DA-Ne5k zW2S)oL!}>_K7QhR J+g6#u8UXBwP#gdN literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4d4e4c4f2.gif b/src/common/assets/images/smil3dbd4d4e4c4f2.gif new file mode 100644 index 0000000000000000000000000000000000000000..ee53c7d186c7113f84aaa0c12d1a9b6d4567aa42 GIT binary patch literal 204 zcmZ?wbhEHbFp<&)wLfCdgn70NsH>R{#J2 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4d6422f04.gif b/src/common/assets/images/smil3dbd4d6422f04.gif new file mode 100644 index 0000000000000000000000000000000000000000..9a75a92b33fc6aeb42ce95b0dc293089925d2681 GIT binary patch literal 174 zcmZ?wbhEHbk^W_CtmW{!ey zW_oFAj*dcvzkfimf^%t7s)AEdVsT~;gW^vXRt5%k1|5(lkbw@&eif^(?e%F%>e|Z0 zlhijeL^My(oS4DpgY&^ Zev$gu%th963>Qx8O0E9lJduIH8UTjBLEr!Y literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4d75edb5e.gif b/src/common/assets/images/smil3dbd4d75edb5e.gif new file mode 100644 index 0000000000000000000000000000000000000000..b1baee2b73b59b56adeb7bfc88660a8de77e94b1 GIT binary patch literal 204 zcmZ?wbhEHbTqjKs_w1>el{($pLsg$RHDfM5mZ(xg-cr=rB-%p3;ApDe5l3_J`v zAZ;Lv99TjocuHo?6=^(cZOAAr<8++U*@Kt);V$QeLEcRn(QEoDcK)0nknllR!n;MJ xEor8%_SVCSPkB^4)Ta2P{GMyZa`QwA7vD)4*N79Jaz$=s9dI!3P#0vd1^`?kNjCrh literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4d8676346.gif b/src/common/assets/images/smil3dbd4d8676346.gif new file mode 100644 index 0000000000000000000000000000000000000000..5777f687dd2a7c9c97ae4e8f973770c0ba509053 GIT binary patch literal 176 zcmZ?wbhEHbY42nNlSQ!}D8FWBeKn6N62UM)Ow%3Q@QHF5hjN}H* zgb0aSPHY)gQ=VOkcztYVQbx*c%{PDKP6!-lPBIcuW8$6Qyr98OS?03kDz~Y=D@v#2 X@Gvzko1uP{Q^roLc}o|gFoQJ!@8~}@ literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4d99c6eaa.gif b/src/common/assets/images/smil3dbd4d99c6eaa.gif new file mode 100644 index 0000000000000000000000000000000000000000..87a792e89feed8fc48b155565ad06d33e12b61ae GIT binary patch literal 207 zcmZ?wbhEHbTqjKs_w1>el{($pLsg$RHDfM5mZ(xg-cr=rB-%p3;ApDe5l3_J`v zAZ;Lv99Y69cuHo?6=^(cZOAAr6Lp+d(UiyKHlM4PE@u!^fz>po>&){55*U7nvNADo zId42ML-f|ehAK88_9gxkgs++%lUFn|n%U%R>~1KwPUM8#ZtIm2@7O-6FfmvI03ESJ Ak^lez literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4daabd491.gif b/src/common/assets/images/smil3dbd4daabd491.gif new file mode 100644 index 0000000000000000000000000000000000000000..95a63f2d6ae0b68ec902399c3dd2089ce07db543 GIT binary patch literal 170 zcmZ?wbhEHb;Wd#h)y!3=HfHIv_nD109&XDppD8a`~Zt<8 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4dbc14f3f.gif b/src/common/assets/images/smil3dbd4dbc14f3f.gif new file mode 100644 index 0000000000000000000000000000000000000000..77c71c07be59af34beca0c77f39fa48450c37fb4 GIT binary patch literal 210 zcmZ?wbhEHbgxLc6$23b2ZD19|4%af-@@>3F2nzSlmGt}{{R2@|NpQ5 zKLsg(0L6cbzM0t>iJ3VHzM1KzsX00d5&r%G!3xf$NvR4>MTy0kISh(FSy&kuco}p+ zdO#LAutaU}gxLc6$23bKgaNIF2n!7!vD80{0DM@;NRr`CxMLP|FjsU zO`B$HZ2T{m`TzgdAk{Y z42nNlSQ!`u8FWC#f*j_+ny0|vvLs{D#7LG#g$-|4OE@sA9Od4AFkm|Kd~XH`0k@Ux z3pZ%2iEupR+#)O!v+;FKh)h+ioyaK_q2&^4CzK|B=)BON`e3KF9OGFDp4B_lquALE i8kwS98TqUY_-bPunK&j+WODORWni1hR<5DQU=09d9aH20 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4ddd6835f.gif b/src/common/assets/images/smil3dbd4ddd6835f.gif new file mode 100644 index 0000000000000000000000000000000000000000..01b5a602484a21464d341220b76478fc7f9ddfb4 GIT binary patch literal 202 zcmZ?wbhEHbY42nNlSQ!{N7<52dK^8f%1PORnX3Y&@ zNw$_a(kLTcE}^CobmYN4myU^UnGK&Z7VlA9v3Od7hJ^@M`@yVh2Mx0KT`0QpdS{Ay h9Bb%L&z+$mfm&}QCmv{?5aIJo<5k2PrA`J0YXCI9L-YUu literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4df1944ee.gif b/src/common/assets/images/smil3dbd4df1944ee.gif new file mode 100644 index 0000000000000000000000000000000000000000..49697c5836b663192aeb9625e1586f6c02fb26be GIT binary patch literal 678 zcmZ?wbhEHb*M`@asCm_c;uIPci)3%;4(k%D}+z{}sdUjEw)=xBr_lEGr{}g>QvojJia}<0t(@RrxbQB`| z{R4s(oJ*5Z6`YC^i!*Z=6o0a?iU1YsfD8e-)qyotL7^)+vM)v1)BkDMxm|t-js)cU zxtd7+Js`it-AP8OH$257Nk!8_e2dV;3u==*xV;K(6l_FJx`<5g;bB{EV|k}&s_f>u zJ^L4(7CQX8p?bBdc0#QlzmPyjxP6NrpHohRk+X{;*k&#U9tIso21W*6AY@?io#46c zf@Si#sIDK(hLLB|jTV+nc{WpV-hopJg%y_~`3@%XG)#LS&cVUKx%gy*iij|mQ#ONd z2M0^D4-G#6m>0b_;>)%Af23QQ*s z8eFuP<{P|M&}7zuZ03z8u6&y-w~ou7quC&4QM9g7qK#5t+|!e59Bd}td(1V_!$!_S ipQ-JH%7pfSCl1~&f*I!pryqR$W0%*HueV$k8LR<8EC4|O literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4e02c5440.gif b/src/common/assets/images/smil3dbd4e02c5440.gif new file mode 100644 index 0000000000000000000000000000000000000000..004261bd7ff563a35837e8009b80ef0d9a2029b4 GIT binary patch literal 263 zcmZ?wbhEHbd~CZ#Gk6(tsD<}fJ!WMO4z5Mj^(nFw;218cm3YF9vH-xG&5r(OwpFa=InV5a|2 z!Z`DfP)ot47ZVv88?#b1CgrU5&=Z>N$kP($mmxFLPeY$YQ{cm{2@MLzOO}NOeOnw+ geDv+r*Ifq_l<(NHHVL*k`6Y37a+a_wDKc0C08ds}VgLXD literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4e1748cc9.gif b/src/common/assets/images/smil3dbd4e1748cc9.gif new file mode 100644 index 0000000000000000000000000000000000000000..50f9984accfae423e00821df1debc9bbfc79aba0 GIT binary patch literal 209 zcmZ?wbhEHb3F2nzSlmGt}{tp7j|Nnpe z|0z%&3Kah-`etTlBxdF)_-3Y;rsn7d~CZ#Gk6(tsD<}fJ!WMO4s;APMO zX#!c~z!JH^Q!;C=NaI;+O-EswSqEkw5C}T#u;+cknk$PGZmF$L?qp)RtS%zKAndWE zVZosQ9h3BBz_d(VUkEmTJ8JaPph?Np-m?PgEat8 C@lb65 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4e29bbcc7.gif b/src/common/assets/images/smil3dbd4e29bbcc7.gif new file mode 100644 index 0000000000000000000000000000000000000000..4a96ea1baa5a12561f5e74249bf8a56e512f93ee GIT binary patch literal 492 zcmZ?wbhEHbUIrbI z7LY{_Eb$vWBeUj~a4;TtCbv<ZNFNG`%&x~-ghN1jI!qx8vO+NrF0VTVs}xtQ<<3~Rm0OB~N#Xj;I3@;b047&}1^@s6 literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4e398ff7b.gif b/src/common/assets/images/smil3dbd4e398ff7b.gif new file mode 100644 index 0000000000000000000000000000000000000000..15402e7b7c22082d75c6497226300f918e910ae8 GIT binary patch literal 205 zcmZ?wbhEHbTqjKs_w1>el{($pLsg$RHDfM5mZ(xg-cr=rB-%p3;ApDe5l3_J`v zAZ;Lv99TjpcuHo?6=^)XouNh0;NZv7I|f0A9rnBrSaW5P!Y#G+sdZ%w0}~j2h&pQV y9CZo~%V}?3CFwCEph5QKvxgjZEs>nuBD^PET_bJ;)$GrDyCi9a^pVLN4AuaK4ow*V literal 0 HcmV?d00001 diff --git a/src/common/assets/images/smil3dbd4e4c2e742.gif b/src/common/assets/images/smil3dbd4e4c2e742.gif new file mode 100644 index 0000000000000000000000000000000000000000..99368ba26abf66bbf3cad6436aa012f1dadebc6f GIT binary patch literal 4959 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 = (props: Props) => { + const { lang, className, image } = props; + const location = useLocation(); + const params = new URLSearchParams(location.search); + const flagLang = lang === "en" ? "ja" : "en"; + params.set("ml_lang", flagLang); + const url = location.pathname + "?" + params.toString(); + const imageSrc = image || langResources[flagLang].image; + return ( + + {langResources[flagLang].title} + + ); +}; + +export default LangFlag; diff --git a/src/common/lib/Loading.tsx b/src/common/lib/Loading.tsx new file mode 100644 index 0000000..c3dce88 --- /dev/null +++ b/src/common/lib/Loading.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import Spinner from "react-spinner-material"; + +const Loading: React.FC = () => { + return ( +
    + +
    + ); +}; + +export default Loading; diff --git a/src/common/lib/NoticeSiteHasBeenArchived.tsx b/src/common/lib/NoticeSiteHasBeenArchived.tsx new file mode 100644 index 0000000..0a0615b --- /dev/null +++ b/src/common/lib/NoticeSiteHasBeenArchived.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const NoticeSiteHasBeenArchived: React.FC = (props: Props) => { + const { lang } = props; + const notice = "[en]This site has been archived since FY2019 and is no longer updated.[/en][ja]このサイトは、2019年度よりアーカイブサイトとして運用されています。[/ja]"; + return

    {Functions.mlang(notice, lang)}

    ; +}; + +export default NoticeSiteHasBeenArchived; diff --git a/src/common/lib/PageNotFound.tsx b/src/common/lib/PageNotFound.tsx new file mode 100644 index 0000000..9db5c9d --- /dev/null +++ b/src/common/lib/PageNotFound.tsx @@ -0,0 +1,50 @@ +import React, { useEffect } from "react"; +import { Helmet } from "react-helmet-async"; +import { useLocation, useNavigate } from "react-router-dom"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const PageNotFound: React.FC = (props) => { + const { lang } = props; + const location = useLocation(); + const navigate = useNavigate(); + const url = "/"; + + useEffect(() => { + let timer: NodeJS.Timeout | null = null; + if (location.pathname !== "/" && process.env.NODE_ENV === "production") { + if (timer === null) { + timer = setTimeout(() => { + navigate(url); + }, 5000); + } + } + return () => { + if (timer !== null) { + clearTimeout(timer); + timer = null; + } + }; + }, [location, navigate]); + + return ( +
    + + Page Not Found - {Functions.siteTitle(lang)} + +

    Page Not Found

    +
    +

    The page you were trying to access doesn't exist.

    +

    + If the page does not automatically reload, please click here +

    +
    +
    + ); +}; + +export default PageNotFound; diff --git a/src/common/lib/XoopsCode.tsx b/src/common/lib/XoopsCode.tsx new file mode 100644 index 0000000..1538945 --- /dev/null +++ b/src/common/lib/XoopsCode.tsx @@ -0,0 +1,132 @@ +import ReactHtmlParser, { convertNodeToElement, DomElement, DomNode, Transform } from "@orrisroot/react-html-parser"; +import React from "react"; +import { HashLink } from "react-router-hash-link"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; + text: string; + dohtml?: boolean; + dosmiley?: boolean; + doxcode?: boolean; + doimage?: boolean; + dobr?: boolean; +} + +const preConvertXCode = (text: string, doxcode: boolean): string => { + if (doxcode) { + return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => { + return "[code]" + Functions.base64Encode(m1) + "[/code]"; + }); + } + return text; +}; + +const postConvertXCode = (text: string, doxcode: boolean, doimage: boolean): string => { + if (doxcode) { + return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => { + const text = convertXCode(Functions.htmlspecialchars(Functions.base64Decode(m1)), doimage); + return '
    ' + text + "
    "; + }); + } + return text; +}; + +const convertClickable = (text: string) => { + text = text.replace(/(^|[^\]_a-zA-Z0-9-="'/]+)((?:https?|ftp)(?::\/\/[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+[a-zA-Z0-9=]))/g, (...matches) => { + return matches[1] + '' + matches[2] + ""; + }); + text = text.replace(/(^|[^\]_a-zA-Z0-9-="'/:.]+)([a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)/g, (...matches) => { + return matches[1] + '' + matches[2] + ""; + }); + return text; +}; + +const convertXCode = (text: string, doimage: boolean): string => { + // TODO: implement + return text; +}; + +const convertSmiley = (text: string) => { + // TODO: implement + return text; +}; + +const convertBr = (text: string): string => { + return text.replace(/(\r?\n|\r)/g, "
    "); +}; + +const cssConvert = (text: string): object => { + const ret: any = {}; + text.split(";").forEach((line) => { + const line_ = line.trim(); + if (line.length === 0) { + return; + } + const kv = line_.split(":"); + const key = Functions.camelCase(kv[0].trim()); + const value = kv[1].trim(); + ret[key] = value; + }); + return ret; +}; + +const xoopsTransform: Transform = (node: DomNode, index: number | string, transform?: Transform): React.ReactNode => { + if (node.type === "tag") { + const node_ = node as DomElement; + if (node_.name === "a") { + const url = node_.attribs?.["href"] || "/"; + const download = (node_.attribs && node_.attribs["download"]) || ""; + const rel = (node_.attribs && node_.attribs["rel"]) || ""; + const klass = (node_.attribs && node_.attribs["class"]) || ""; + const isFile = download !== "" || /\.(zip|pdf|png|gif|jpg)$/.test(url); + const isExternal = /external/.test(rel) || /external/.test(klass) || /^(mailto|https?:?\/\/)/.test(url); + if (!isFile && !isExternal) { + const style = node_.attribs?.["style"] || ""; + const title = node_.attribs?.["title"]; + return ( + + {node_.children.map((value: DomNode, index: number) => { + return convertNodeToElement(value, index, transform); + })} + + ); + } + } + if (node_.name === "img") { + const src = (node_.attribs && node_.attribs["src"]) || ""; + node_.attribs["src"] = src.replace("`XOOPS_URL`", process.env.PUBLIC_URL); + return convertNodeToElement(node_, index, transform); + } + } +}; + +const XoopsCode: React.FC = (props: Props) => { + const { lang } = props; + let text = props.text; + const dohtml = !!props.dohtml; + const dosmiley = !!props.dosmiley; + const doxcode = !!props.doxcode; + const doimage = !!props.doimage; + const dobr = !!props.dobr; + text = preConvertXCode(text, doxcode); + if (!dohtml) { + text = Functions.htmlspecialchars(text); + text = convertClickable(text); + } + if (dosmiley) { + text = convertSmiley(text); + } + if (doxcode) { + text = convertXCode(text, doimage); + } + if (dobr) { + text = convertBr(text); + } + text = postConvertXCode(text, doxcode, doimage); + text = Functions.mlang(text, lang); + return
    {ReactHtmlParser(text, { transform: xoopsTransform })}
    ; +}; + +export default XoopsCode; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..f9f9e50 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,15 @@ +const SITE_TITLE = "NeuroImaging Platform"; +const SITE_SLOGAN = ""; +const GOOGLE_ANALYTICS_TRACKING_ID = "UA-3757403-1"; +const XOONIPS_ITEMTYPES = ["book", "paper", "presentation", "data", "tool", "nimgcenter", "model", "url"]; + +export type MultiLang = "en" | "ja"; + +const Config = { + SITE_TITLE, + SITE_SLOGAN, + GOOGLE_ANALYTICS_TRACKING_ID, + XOONIPS_ITEMTYPES, +}; + +export default Config; diff --git a/src/credits/Credits.tsx b/src/credits/Credits.tsx new file mode 100644 index 0000000..df34ade --- /dev/null +++ b/src/credits/Credits.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { Navigate, Route, Routes, useLocation } from "react-router-dom"; +import PageNotFound from "../common/lib/PageNotFound"; +import { MultiLang } from "../config"; +import CreditsIndex from "./CreditsIndex"; +import CreditsPage from "./CreditsPage"; + +interface Props { + lang: MultiLang; +} + +const CreditsTop: React.FC = (props: Props) => { + const { lang } = props; + const location = useLocation(); + const params = new URLSearchParams(location.search); + const paramId = params.get("id"); + if (null === paramId) { + return ; + } + if (/^[1-9][0-9]*$/.test(paramId)) { + const id = parseInt(paramId, 10); + return ; + } + return ; +}; + +const CreditsRedirectToTop: React.FC = () => { + const location = useLocation(); + const url = location.pathname + "index.php" + location.search + location.hash; + return ; +}; + +const Credits: React.FC = (props: Props) => { + const { lang } = props; + return ( + <> + + } /> + } /> + } /> + } /> + + + ); +}; + +export default Credits; diff --git a/src/credits/CreditsIndex.tsx b/src/credits/CreditsIndex.tsx new file mode 100644 index 0000000..1d8c436 --- /dev/null +++ b/src/credits/CreditsIndex.tsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState } from "react"; +import { Helmet } from "react-helmet-async"; +import { Link } from "react-router-dom"; +import Loading from "../common/lib/Loading"; +import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived"; +import PageNotFound from "../common/lib/PageNotFound"; +import { MultiLang } from "../config"; +import Functions from "../functions"; +import CreditsUtils, { CreditsIndexData } from "./lib/CreditsUtils"; + +interface Props { + lang: MultiLang; +} + +const CreditsIndex: React.FC = (props: Props) => { + const { lang } = props; + const [loading, setLoading] = useState(true); + const [index, setIndex] = useState(null); + + useEffect(() => { + const updatePage = async () => { + const index = await CreditsUtils.getIndex(); + setLoading(false); + setIndex(index); + }; + updatePage(); + }, [lang]); + + if (loading) { + return ; + } + if (null === index) { + return ; + } + const mtitle = Functions.mlang(index.name, lang); + return ( + <> + + + {mtitle} - {Functions.siteTitle(lang)} + + +

    {mtitle}

    + +
      + {index.pages.map((page) => { + const title = Functions.mlang(page.title, lang); + const url = CreditsUtils.getPageUrl(page.id); + return ( +
    • + {title} +
    • + ); + })} +
    + + ); +}; +export default CreditsIndex; diff --git a/src/credits/CreditsPage.tsx b/src/credits/CreditsPage.tsx new file mode 100644 index 0000000..503beae --- /dev/null +++ b/src/credits/CreditsPage.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from "react"; +import { Helmet } from "react-helmet-async"; +import Loading from "../common/lib/Loading"; +import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived"; +import PageNotFound from "../common/lib/PageNotFound"; +import XoopsCode from "../common/lib/XoopsCode"; +import { MultiLang } from "../config"; +import Functions from "../functions"; +import CreditsUtils, { CreditsIndexData, CreditsPageDetailData } from "./lib/CreditsUtils"; + +interface Props { + lang: MultiLang; + id: number; +} + +const CreditsPage: React.FC = (props: Props) => { + const { lang, id } = props; + const [loading, setLoading] = useState(true); + const [index, setIndex] = useState(null); + const [page, setPage] = useState(null); + + useEffect(() => { + const updatePage = async () => { + const index = await CreditsUtils.getIndex(); + const page = await CreditsUtils.getPage(id); + setLoading(false); + setIndex(index); + setPage(page); + }; + updatePage(); + }, [id]); + + if (loading) { + return ; + } + if (page === null || index === null) { + return ; + } + + const mtitle = Functions.mlang(index.name, lang); + const title = Functions.mlang(page.title, lang); + const lastupdate = page.lastupdate === 0 ? "" : Functions.formatDate(page.lastupdate, "MMMM Do, YYYY"); + return ( + <> + + + {title} - {mtitle} - {Functions.siteTitle(lang)} + + +

    {title}

    + + {"" !== lastupdate && ( +
    + {Functions.mlang("[en]Last Update[/en][ja]最終更新日[/ja]", lang)} : {lastupdate} +
    + )} +
    + + + ); +}; +export default CreditsPage; diff --git a/src/credits/blocks/CreditsMenu.tsx b/src/credits/blocks/CreditsMenu.tsx new file mode 100644 index 0000000..c8def03 --- /dev/null +++ b/src/credits/blocks/CreditsMenu.tsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import Loading from "../../common/lib/Loading"; +import PageNotFound from "../../common/lib/PageNotFound"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; +import CreditsUtils, { CreditsMenuData } from "../lib/CreditsUtils"; + +interface Props { + lang: MultiLang; +} + +const CreditsMenu: React.FC = (props: Props) => { + const { lang } = props; + const [loading, setLoading] = useState(true); + const [menu, setMenu] = useState(null); + + useEffect(() => { + const updatePage = async () => { + const menu = await CreditsUtils.getMenu(); + setLoading(false); + setMenu(menu); + }; + updatePage(); + }, [lang]); + + if (loading) { + return ; + } + if (menu === null || menu.length === 0) { + return ; + } + const links = menu.map((item, idx) => { + const title = Functions.mlang(item.title, lang); + const style = idx === 0 ? "menuTop" : "menuMain"; + return ( +
  • + + {title} + +
  • + ); + }); + return
      {links}
    ; +}; + +export default CreditsMenu; diff --git a/src/credits/lib/CreditsUtils.ts b/src/credits/lib/CreditsUtils.ts new file mode 100644 index 0000000..62d0616 --- /dev/null +++ b/src/credits/lib/CreditsUtils.ts @@ -0,0 +1,77 @@ +import axios from "axios"; + +export interface CreditsPageData { + id: number; + title: string; +} + +export interface CreditsPageDetailData extends CreditsPageData { + content: string; + lastupdate: number; +} + +export interface CreditsIndexData { + name: string; + pages: CreditsPageData[]; +} + +export interface CreditsMenuData { + title: string; + link: string; +} + +class CreditsUtils { + private pathname: string; + private index?: CreditsIndexData | null; + + constructor(path: string) { + this.pathname = path; + } + + getIndexUrl(): string { + return this.pathname + "/"; + } + + getPageUrl(id: number): string { + return this.getIndexUrl() + (0 === id ? "aboutus.php" : "?id=" + id); + } + + async getIndex(): Promise { + if (typeof this.index === "undefined") { + try { + const url = this.getIndexUrl() + "index.json"; + const response = await axios.get(url); + const index = response.data as CreditsIndexData; + this.index = index; + } catch (err) { + this.index = null; + } + } + return this.index; + } + + async getMenu(): Promise { + const menu: CreditsMenuData[] = []; + const index = await this.getIndex(); + if (index !== null) { + index.pages.forEach((page) => { + menu.push({ title: page.title, link: this.getPageUrl(page.id) }); + }); + } + return menu; + } + + async getPage(id: number): Promise { + let page = null; + try { + const url = this.getIndexUrl() + id + ".json"; + const response = await axios.get(url); + page = response.data as CreditsPageDetailData; + } catch (err) { + // ignore + } + return page; + } +} + +export default new CreditsUtils("/modules/credits"); diff --git a/src/custom/Footer.tsx b/src/custom/Footer.tsx new file mode 100644 index 0000000..2f6a73b --- /dev/null +++ b/src/custom/Footer.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { useLocation } from "react-router-dom"; +import { HashLink } from "react-router-hash-link"; +import { MultiLang } from "../config"; +import imageBannerNiu from "./assets/images/banner-niu.png"; +import imageBannerRikenCbs from "./assets/images/banner-riken-cbs.png"; +import imageBannerRiken from "./assets/images/banner-riken.png"; +import imageBannerXoonips from "./assets/images/banner-xoonips.png"; + +interface Props { + lang: MultiLang; +} + +const Footer: React.FC = (props: Props) => { + const location = useLocation(); + return ( +
    +
    + + Go Page Top + +
    +
    + + + RIKEN + + + + + RIKEN Center for Brain Science + + + + + Neuroinformatics Unit, RIKEN Center for Brain Science + + + + + XooNIps + + +
    +
    + Copyright (C) 2018 Neuroinformatics Unit, RIKEN Center for Brain Science +
    +
    + ); +}; + +export default Footer; diff --git a/src/custom/Header.tsx b/src/custom/Header.tsx new file mode 100644 index 0000000..5d2da08 --- /dev/null +++ b/src/custom/Header.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import LangFlag from "../common/lib/LangFlag"; +import Config, { MultiLang } from "../config"; +import imageJnode from "./assets/images/jnode.png"; + +interface Props { + lang: MultiLang; +} + +const Header: React.FC = (props: Props) => { + return ( + + ); +}; + +export default Header; diff --git a/src/custom/Page.tsx b/src/custom/Page.tsx new file mode 100644 index 0000000..ed9c149 --- /dev/null +++ b/src/custom/Page.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { Outlet, Route, Routes } from "react-router-dom"; +import BrainExplorerIndex from "../brainexplorer/BrainExplorer"; +import XoopsPathRedirect from "../common/XoopsPathRedirect"; +import { MultiLang } from "../config"; +import CreditsMenu from "../credits/blocks/CreditsMenu"; +import Credits from "../credits/Credits"; +import NimgcenterUtilities from "../nimgcenter/NimgenterUtilities"; +import Nimgdocs from "../nimgdocs/Nimgdocs"; +import Nimgsearch from "../nimgsearch/Nimgsearch"; +import IndexTree from "../xoonips/blocks/IndexTree"; +import Ranking from "../xoonips/blocks/Rankings"; +import RecentContents from "../xoonips/blocks/RecentContents"; +import Search2 from "../xoonips/blocks/Search"; +import Xoonips from "../xoonips/Xoonips"; +import BulletinBoard from "./blocks/BulletinBoard"; +import HowToUseLinks from "./blocks/HowToUseLinks"; +import HowToUseVideo from "./blocks/HowToUseVideo"; +import Information from "./blocks/Information"; +import Questionnaire from "./blocks/Questionnaire"; +import RecentStatus from "./blocks/RecentStatus"; +import Footer from "./Footer"; +import Header from "./Header"; + +interface Props { + lang: MultiLang; +} + +const MainContent: React.FC = (props: Props) => { + return ( +
    +
    + +
    +
    + ); +}; + +const CenterColumn: React.FC = (props: Props) => { + const { lang } = props; + return ( +
    + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + +
    + ); +}; + +const CenterColumnBlock: React.FC = (props: Props) => { + const { lang } = props; + return ( + <> +
    + + + + + + +
    +
    +
    +
    +
    XooNIps Ranking
    +
    + +
    +
    +
    +
    +
    +
    XooNIps Update
    +
    + +
    +
    +
    +
    + + ); +}; + +const LeftColumn: React.FC = (props: Props) => { + const { lang } = props; + return ( +
    + +
    +
    Index Tree
    +
    + +
    +
    +
    +
    Site Information
    +
    + +
    +
    +
    + ); +}; + +const XoopsSearchBlock: React.FC = (props: Props) => { + const { lang } = props; + return ( +
    +
    Item Search
    +
    + +
    +
    + ); +}; + +const Page: React.FC = (props: Props) => { + const { lang } = props; + return ( +
    +
    +
    + + }> + } /> + + + + }> + } /> + + +
    +
    +
    + ); +}; + +export default Page; diff --git a/src/custom/assets/images/back.png b/src/custom/assets/images/back.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ef988148722c497b2e7ca56731a7661e636c0e GIT binary patch literal 569 zcmV-90>=G`P)0ssI2cupP-00062NklhM$3S!lMEgK= zd3dqY!xeT~3OlWZoz@HNv|VARRl`mTVW-xx6NR0sFq=brVU}uoD(nV2)84TQoN21* zyePBO`lpWFNKHF#p{C>f{bR>Y9o!-pb>2TJb^8mjlSBe;p`nY0om#^!a{HmC0>1`! zBV81@r7o&rckWFo+~(g;3?&c=68ups>{JcAOw&Q0)cjGIcM5iD4ZFq1ai3?<$0bEpj_9@=kr2ivET0qXgD*6Vd4K($&e)&PV6#e3gwHz7b>uh&keBLvRpbGO^2 zgh0RFr%tDn5TFi+!(cEF0{i`bG#XJtU_2gEyWLI*P}}WxGMNa0^?E&>PAMTUpU-0n z0cyEiQbNEm4AV4u6avQt7I+CPcqOo)(WuEc{MXghMs)-L#k@#Z<|dcv00000NkvXX Hu0mjf1wj8M literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/banner-niu.png b/src/custom/assets/images/banner-niu.png new file mode 100644 index 0000000000000000000000000000000000000000..d22c41a55fea2d938a5bd8bd31ee212593ba6eca GIT binary patch literal 1279 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 literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/blockTitleIndent12.png b/src/custom/assets/images/blockTitleIndent12.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2af0f08cfd5f76641b3ec1b03f543f055f7ca3 GIT binary patch literal 269 zcmV+o0rLKdP)sAU13PuB^U4U2{W&=hjoj7q~pOKMKI#AdXhz)^Q z3W&Kd{P6ADw|%*}xk*5-9}t@Zu{;p-0x>hzPyzx0Al3k46%dfyxZpp~knay4KI~6PNf8GMvyf;20(}4e z{n5E|=VF0UTtLi3q5%l-?%lh6D^{#9f_saw0hr+L-@iXV-inQl6$5#TBm>aBb@uGp z7?`(6HsJH;&o7rOSyBO1E(7us(FXkf{rlIhUAs0)N=kwP)s7?s?%cU^F(4qI7^uVt zh>d|*nkX-P{P^)rcX#(hpi~eL+W@f=5cA^=L}-YEoV!UvLLv?*>;lA~lqo_~G=BT` zZEsRiQXJH|CNSp`PDlt^0EpFrSQ*{9_zge+7LbiF=Q0oh0P4akuPD=;AOHXW07*qo IM6N<$f(zV(&;S4c literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/footer_back.png b/src/custom/assets/images/footer_back.png new file mode 100644 index 0000000000000000000000000000000000000000..a0598937d55fc61451441cd208bba8f3a39def36 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^Qb6p-!2~2Xnoq0%Qca#Njv*C{Z>PA69uDAP`8wSr zgFR@&lUH%6LXY=-{aE1Ftc~^N6(J9@# zn=Kbd^dDNxA@cp)-s1Nj|bd?$V8V-(Kxy-uX-8 n-c!9mk(mp&q%~dY+Qev=JW09e&#ogt*D!dx`njxgN@xNAqajQz literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/h1_indent.png b/src/custom/assets/images/h1_indent.png new file mode 100644 index 0000000000000000000000000000000000000000..67892a06a5f5a6d9b79045ac561e6c3fb871cb94 GIT binary patch literal 1224 zcmV;(1ULJMP)hAJ?VMt_wedu4wE!8We_EnK_nT6Z&@;h;pK-EL4QM42B>I&KbiU1K~8AZ-O#zSo?}}L=jMv` z^`zL*CO!O((Woi}rCfKwAxz+$S9&?m{sb0G0mLbSwp3x-P|miljA1p&*3?M$zE%iI zJa-5d2?EZ3v6t<{_x6LGz_M+vZQEGQwy~0Jefj(X&a#GdD@nG5(pJ<;i}6P43lNbG z__tmi#&OoY9j#j$fMs1s^(SY?a}uP_vb4XvBl;Ug{50c7y>bNCdIx!Yc^{HA`HVIaORVpje9;eZEgab zaYZ>$tcma3ZnssQD%$@W76V~imM1jj3m7qSDh00o90!hPlW?*!9&Q58bl@BPmUg;T zb@a@lz;sIwen` zpt1CND81h~3JDa5%B^3j)^+4W*{t&NnZ#v`DTZj#s{<~qyEnYH-zbKQpGc@Y8$|DR zjunV_T0~pF2Ts1Yp>$3)5T(Aboiil>zqPa&@SL}^0koq4~V?pzLw1BO!kc!o zy{n0`lKHhE93s4TAcBQ?Oyh|Q#F)$LF7&E%wHq@MI*gLWFdURNmf>I)IE+47KLRg9 z-n~X6_RxfsF_bGTfMO@r2IyrU@*RZryq`}Ha2UGPl^INq3BqHTM?^!E9v3yUP26yT z5=Jm3jb!!6p%gU?^*mt+o&Ey_-1*<><$=XGiG|-s9QUMv7onqTg%2huY6!&)Wr`n` zCp6I?G=RMN(#xL2aKF&)g+1~AAa#X_xa8QPxOX>w$29QkM-WyP5;{5SVHdjgn}EZH z@OTRoITZgKF1*K_Z{GH#LVDhv>0W2L`8#7_>k%y66~aMO^drQ)u!pB!&iE2uAW%4n z`@+^gUO5z7f!y(6OCxgOSRt)=@374O^3L~1R|0_}i3*M}ZY|D6A#0@(3w^fuFboDI mE-V}&(TE`T^)HS1|LRZ1r0M}kbG*?20000td&kz6yWM!jQ8tEi0h*;99Ur=ZN*Fv{{an^LB{Ts59mgMa literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/jnode.png b/src/custom/assets/images/jnode.png new file mode 100644 index 0000000000000000000000000000000000000000..53765ad8f3e2d0cda464c8d46f7c49584798ba66 GIT binary patch literal 3051 zcmVgwy;+}yIaxP*+BP<56-V}m<(oOqL~^Yrw*Spd;z0LQnf+1c6+ zN^b*3e*{vF3}u}ObF4Oyw3@ulYOd8&rL-o0u^@Dy>+J0K`1t+({hmq!?&##z)zv0c zfdoBr0ZxekWTF6bvjKLw2ZF%>cfu2k+B25T28P-IWvqdmwD0fl`TF~aQ6tJ>02@nQ z9ZygWYjFT=vjBU(qRxuM)_H@#UtFkTW3iP(mxn5dkQ$J!6@!|MtG4Lq=!<1V!Ce5% zV*p@kbqO$78(viwb5<^sX?CfBu5X7OCEx8JD@>gtxQR)KrE*@6`MfS-{!y1-7gC`tMPXI1I|qr; zg+dRt!_xqT(BPoIfTG2M3Oem+v5svp8$i*2$a+Si_`#q%je|dgmPut+uWAJFRI5T1C7l9k=J88 z9sx>(30!;wV4MhTkpKPq0h8GPj@6Zjf0jT2O_ov@m%jdcS!($#cWSG z2Sa!gQEG%Y0H{m=;^N~jhD7@M^%b1jgT9JyFaY)P?kPq|=<;MV$;R;@i9d=^? zX{QH?%lrKNFOoCq@#_F>tpJVD0Dsg0OpgRqk_cUx5rDD?Se2wi0J2R00$Z5t@bv%x z{#h>(8DNqcioyK*`dE>8-sJ1JP5|@u_`FX55kqpnPyplU?!HX`07i?iM*s&^m;U|z z!%zUkPyo3~0LD-N$4>yoP5{D90LD)M!%YClPXKifFF*hQ2&_p&K~#7FoXb-}24Mh3 z;cu+>w{47bjWY*%|2xU9tDfs~$p2A)p&6Frg(p16iW2oFBLBj$yn;`;{L?M}MhZVL zLJ>fzT&dRT^+vT)E&*urt@h85w7==_0J^ z^-jip@_-xb7hR1Y6Wa(7&d5X{?k>=?9S;qW372qi$1G7`ECOS3_b~20Y;4{4Z`k_L ztbG%B`k|`2sxe6f%PT%1Q7RJ4I2>+La&k&4{~u}T8JSrbzS+z@__mzfyx(NLD}Ti> zJc3Gdj#)M!)+1wkgA{NMjEIy@dvs}(o1c7zHC1EBhv(*Z+{IzapvL^XcNS=eDIL>S} zIOPfK?P;&tnWEX*;voef6=9Q;q}&SKTHW29uNr@Dl0S0k(rD+oIcfHXA=UHYhAxJ| z@CqD3P3^)W$a(_i1DK+a%gtu~_0_&?rf&ybPK( zL$l;tuAK-uH-44U>{}rbm&cbG%O}Dly9;!;6N)Uev($u&ASue-9o`gqa@oWkSF>5| zX;Z&S{?Y9_=aJ0Csv~N2w^7YR`$Q^5$~8Bbw@%)@d$+y)mjlY0yf=cj0&~iQ6-T#w zz@UuqSHe7)HKyC~@w1l1`-u;VNh^`s-ky}yPFdKlW*a$q?N9W2<-}83mOg_6g1F@IzC#{kNWL1ogLT_7)lp0sQ{JBQ?` zv*hpPSSZY?Gv1D0yz-Ehhq` zG2w%&(T=rG1pgX(QX-bgWWs-7bV}5q zU*vw~!TNk@gRv3*-l1x?2{NLX zGnqgxL^Wa9zv%&8dF|`;^4_^G`DJCLB7n~Zx!4XgCX(Y!9QFcp&O}8;#$vJFO^uD; z#Jb?2;D@VU#>m8Cei> zdK#V&AjbEQaW;L)&T_DKp2MM0EHW}RRbc4AZ@mGL$m-XIt9?EDGXq23P71LFh&Xh6aZI zVgv+0i<_l0W-3jaW^J0WOLiii^#$-fen$|XDe~6-krN^Vopb8XnAauR?`*L3#^aIa zjo!cfwx4_j+Db6oa95VQpTmqkeEbI6oZ^S(*xMWqhfkyadT_*$w_HR76_LISdFQS8 zS^k*6wXpql2e;q1J^;V22pJ>*OH!Z<%p*+_<>tG>VvK9IThk-xRyo;lQL(2^MX^4s zId$5n z-E67zZI>A77Oe0+W!|XO=E{oWE6~bZ^3Sq-lN5xeZCDU4aWGS^iDGi^-c8aqjYDMG te=|`S2bce>sNZ)wU(zH=()4<7@CQWfu|#G`MD73p002ovPDHLkV1ky&|L*_* literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/logo.png b/src/custom/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..44d7976a885fca110c69a1421cdbf7b755106750 GIT binary patch literal 7811 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 X2fj`9SE$6<00000NkvXXu0mjf_F3BK literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/mlang_japanese.png b/src/custom/assets/images/mlang_japanese.png new file mode 100644 index 0000000000000000000000000000000000000000..48991b2550a365f47a4b0c2fdd3f768800076ff1 GIT binary patch literal 2588 zcmV+%3gh*OP)X0{{R3@PK=H00093P)t-sk{&CO zBP@y~F^MQEgDNqEDJ**`ErTN^gd{3@EHaBFD}yN}eJ&=8Dl(BOE{Y;1dm<%)F)n#B zEPW&^ge*COGCGScIC(KNY%MKoGB0#2GHfg}Y&1J+E;ws3GG#O`b22oDDJPOEGEM^u zIROGP0|YDp0Vx9uB?1c~004v?Ba$&YdQeizuCv6lw8N{p#ILK%uB=WjElm#-gB&Y_ zCNX*@HIZs>{rvm>_4@w$^#1hnOEWP&2n%c?D0&|$ktH#LF*SB5BzrSDH3SHab$I^r z?O`7!a33UsJV5@;%>2E)jdF7J{{8;y<^J8={lvlj$-@2G)kHKfT^=KCAtah$VE*gt zb1E$V8np)C&~7K|x&|9*inMa~?JS?DGB7 z)MGL;bTvc$)7t*y!;O{ly}x2CG~uGD?zFPxt+4;x;;DOodNf1bt+H}CKYBbn z>3@Fe4jk+X4%!tLwjv_%la!S#IEO1jgj8Ps<>~Uv%wjn^|K;%W!pOv)p54K_WIIE! zjFiixqhdBa!lR^%Ra4`+xvYeUv67L;sHJK=Li)|o>&C?6yu{+h$@SIP(i<=20v7B6 z1=)9UYC%I=IyqB4J7GdfxSE@3JUpC)HR(a+VctZhwDw5qAp$idT_oNFB=|LgfoK0Z!K zLwYr-8+8w79t&wsiit<99u`SL!)+Eb)mHJ!{t zBwZvnZiN8={{CkxGKHB}WoGUP+N<^i-IO+E9^4Ewk5RD{jvxNa-BYHhwZ`SlvzZWa zh#tIUEk=3&srjkv&7#_qQKVD@0}Ta~ISkVYIFw`zIYM@><_u^6^?Gmj-oxF9pdvyD zt#LN1irvS>=KZbnuUt;08dk4DnE>Pk80PuG1t7`Ma7v7SR8KqKPkkAGJV%_G zg-JFN^rJzVB&3?yoa;7<&mx`ysiUh>LQ2t!U!Kl9nfoB(*9T>zV9dcc2{kiFQpNa7 z-zS00P;LMXr;TUs?Q1XIx9nc(G$JDQ?w3Sm^Y6Ff*|+ApU&rXTdxgYXza^Q~JBnsM zoB#5gs?nDnpo&t;7%KrF$dKCdr{_*4w6oYbSk2B-jU{~2_DlVpui9<%wi zM>$7^MiBv)0Jjlt@!0R~VUf1jxX6IC_|(L>i6rHYQ=_611MtYy^r%suh+|NJ#m0Le z1q?tSt0zg4AU%Z&fC>!Y76H)NPQ*bAGEyC@DgR7BPe@vtAmFmTVSE$}MivHkJ>Rz2(hI9pB-1gay5!Y(Y4 zq|jK^I9ZSiYDxk_4T!s+M!Ge@NgZ0Xm~G+WggUoh3^;~75~T2eiiC&^gm!Sq#uA6s zNS_Bd@EN?nDuoH4WYATBy)VdIy^;pZ*76j#+!=%hY-6F_<6z7qXjj&lSCHZbv+IR4 zOhv?-3NsPh|KiU83U(~;E3r0>@izc)aaC#GKC5!3--3VT(OLbV1<2mL%bv4Rf5iz6 zG+?kPL~jjPS_%FT(mxPCEbXFe^F%q=HN01yu2R`$X_^D8kTL1m~GJu{_^Alz(cU@ zUQ*Kqzx=tldT}9`dSAJ4s@+=p{iM~=!yD!9RX09DyE(M6!+bk)}Ju~w|E*Bn=J1${xZWeFyktjfdr-1qG$#h z!en|e*{S4Qit)N};{`>bA#<}gGg`+Wh?B);6?qES+jjr}`riIGB=iLj`KocoQh+Yf zy{y~haHeE=YK@~?zz0Bv52oUC<_vKFj9a9#BYcLL#SOx#X+3!o8o z;L*WJB}Vlz?cRmp&t^c|#kH_PFUzRxW9Q^!#pVwx;e$b%TiQQzad@kkI-~2Z_ z<~vZSQ=P~!bhFTZNV9g@B=2-+$ZYdEVA_i<5VV?e91;P#{c|=5D1J4D}xw z66^**QR8O_)Zn}_7|1#d%K8zakf$_jt=U0{^IFE|2y=Ui=u~x3k>=5$Wg_89cJ*?B zB9mfE0#%SOvnT`v8B&RCxh16L6@(DU&76oLw;}K+Md{=H}*2 z%4666wY9ZFLqkGBLP0@6baZr@nwr?y*k@*Dva+(9o11NIZEc2ZzCG)c;9zjT?==*VorhK|ny6nVBPMjW&h1cGLfD z(En@A{{(rrU8d1SfSWZnHD7&^WQ3P&kE9;3{R3;QMUB5ItojbA|1PxuJhJ>wO-(;O zK7W6I4Pc!EW2zCP{~M(GYinyOPHRoA@E}!!092t|jkXn?|37St8CHvArQ0l&lPDk>)@CoV27@$vB@A|eV^kQ#^FCo3-`BqZbG zv<*HaH}8`>wp{c3wVo#oqbSCC$#oP8k*29gk~l^SPZ0G<3PRXE;4z>fri-pJ9#w>J zR|IhshBy`b)$TDEj>cI&E(TXAJePopvMejF#G67Xm6TN}0?utuC6(63Xrr}ON}^3z zj0FMe0mBq<8Oj0jRH>vJ#RA^3Pxk)(hFO+PrnC8Cwp?9buat_8b{{N2Uu)=X8kJ!7 z1ZmdW-Tq(?yX|&ubmLG~Zb@bd+5z1Cx#U$rX@k)cqp)Aw?bp8T=s3$x7N=x>c1~AW zKc=lkaeVT0%KE%C@H~s`C%DPd2CFBjp-rbLm95O(9hfUnnY+91x&K}7oLuVd2meOy zhkJ5v2Cu3qrB22(6-M?FL6BroSO_mcltfWc$_Y?`8f7j{B1H8q@cRP<@p5u-+TcV2 z)Pc31E+$loA*KLqfPXgATUAk6je=k}kzY;im*3P;I~4qp`ptAW8J5&*y3s&%Ja^sC3BhqcJS4tzA~Pe3&0F#OY&LfsmLLj}psDw8!HK zGA)_xAW;a~{kkIZP#TsKv_N~jlP6uBi7u<#uWO1V3w)a_SIwWlfMJ+w4+&O#z1}bh zD{N=Z4y{?c8s0k8qy-`(2SW&=9_Z>!2_%$Cbj2;d0e)foQglO+fna?Ec2YF0Q3dy~ z#Iwj^B6A!Md8yi{p`T;OZ|WG5wfbiFYw@;tN= zSFdtB`o)?IP2^$zXlH}p%+c&#w|>uFVBh|A>kr_UgLXW3-l23ROO~WIAKpUNu00Yu zT6JVQ+1|F}*y`g)j;u{3+X-nsQ7%c%-RPyBJarnbzv!8>=dx1ki4!MsnS3Fg?gjeN zh1`jA=g#gT&HeoYzHBa+$>j4@q$;1uoyazppFMXXpFULLu$RcOe!%$)doEtQbosL@ zSFRqo1YEPzDaHBgT&x}jzeH%~*2C8~Z{0kjlxT<5HJwSzBCSd+9NumOz$=-ITY&*T z%$}l1t%~AML|klK_WFeLr|SbMwAVmDQ;vyH{i($Qhg)=Nk#0Zrr?e>(-Sk zD{fx~4$=_1*Uc%=44qwsq*95{8c+IATihv-Mn7JJ(DaJ)Rs7rSEg~LFMy;$K%c9)4h*M<~e}L{GL9$aPK+r zVrenJb=A!gFRXuMDy+Q&3kYLbO;0SV!)LjD1ZZ(Up#WG<5Z;vG$_L;Re!i@d<%-4F z!m!TC@=HM!;i=S<;IYBp#@^v)))UoibKy;n`jw8y@#@*L*HmvX!iCkv@^*)Ahwh_n zhnE5PcQrJ?bwhpN4PiN!GGj5eCQom6)B*w<0WEo&!{QDj;0PI8+W5(b@Uj6m=yBq&FS)h0-%&8qhHy|_tKV(pnZUk@}W&jl&2hE1ZrpFCN;3v3T z!%w$cbh8S_>-+z```_+vMKlCK=Q)LSi}>U;p4TzqHJ;Nr`s8&xG(@FaQT!r31UD%h zhj}sKF=65S?$IN{`^YEm@IRwu5#_cKfZ;OJJ#^e>*jxSHDlmOhmedzmd-R@z(^+{A zs-==`?JA{HRt+boJ?mO-om2S|PG(Uf1!ONXhff_t?|$zahW``PXbgqlmMzl%$Zauk z4@gQEE?)Zn^5uU%!v+OJk!doah^$y`3Cl8)C7MivL_#2Z8c<0lswzOTj3u$5u_|Fj zL4xLyxDsoVMOLc_zc#t8LDLPXK)-V7`>WTkU;XEslcr6O2^3trJV|9G2EcO}pbHa4 zMnbijE;_sRr_-J7PoBWk02`mJD`8n7yV42#RDx1*q$Zh&tFf4fYZ2im_BRj$NKs8h zSpUBRgV%Jz(w0YKLq`MlP3RMR~| z;0|m8V5q?C%DcObo}6A1M})Z|<3t>YxE2w9@i?u6laj7d+pvQ0+>L>OgEwy;yfu8A z2azz;v8gJ$3hdoGe5+X5x6e1t?)?Va7iZ5stL*%+8)F9%2ra*;ZR)f@=i@AvmTQwK z)pRx$rA?{Mjiz3HQW#1oA>vv@_$5%HPEs;d%q*K%`RLg2@W9~UU*msGOioSv+%Xvi z7NB-)NA?A8*G#F>Yrox7DoENc2WB77_nsggA}j~1PI0EUXWL`0n9S}V9?>gM3d;Fi z#Z}RShXA;#x%b>B(dbNitQ+Ogjqr?8Myot-o%~kB|Smu*ic+=J*8L6f*+f zdaZ{at#Foa%X_>RR?1!nbSfViAwi)IvyRj5dOXEC;EPL-8@{gl$Gtusm)Id!K5XZnsV z`~LD#H@ch6xMdC@cRQ=6h)+&s;l1gy%kQS5EaXM4=cM7Ew5GdfqRP?9R2aFv9WWRE z*ykJ(aV;YJBuA5~42fDGv>Yib4J4;z&r(1) zEZ0$(N~^jVh5jOsCeHo+$J#d!Loc$7fM6eh5KzAha;N*wQTI;at;x3vcS>G5@7{q< zI=}NULRFOO(2aJ@;wd`=U%aDfMAxUa)Y1#~a@ot|3zl2Bvmg0DWg@OcgrB1L4OCFY zh?~xeFs!oZks1BhOdYE@48s5b^_TevI%^lgXfQZyAfJ3y3D3u6J$ zz3>>lj|*=hE(nXoT??%U@IvdRXxytmsdpadu88je z*!ypOKlv^P@Yym)AJj7U6++8gjNdJD>6TjNZe79gG%YFfkZ6U0w SLv%|30000KP)&`00093P)t-sQBhG- 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 M07*qoM6N<$g4hVKqyPW_ literal 0 HcmV?d00001 diff --git a/src/custom/assets/images/page_top.png b/src/custom/assets/images/page_top.png new file mode 100644 index 0000000000000000000000000000000000000000..b2a3fc5e2ea4da4994b4842abe1f215e568df26e GIT binary patch literal 167 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 aTV4aR$Hql#o4Vcr0000 td, +.even { + background-color: #fdfaf7; + border-bottom: 1px solid #cacaca; + border-right: 1px solid #cacaca; +} + +tr.odd > td, +.odd { + background-color: #ffffff; + border-bottom: 1px solid #cacaca; + border-right: 1px solid #cacaca; +} + +.outer { + border: 1px solid #cacaca; +} + +.clearfix::after { + content: ""; + display: block; + clear: both; +} + +.hidden { + display: none; +} + +#page { + margin: 0 auto; + color: #666666; + background: #fffff8 url(images/back.png) repeat-x; +} + +#header { + position: relative; + height: 153px; +} + +#header .logo { + margin: 0; + padding: 0; + position: absolute; + left: 0; + top: 6px; + height: 81px; + width: 345px; + background-image: url(images/logo.png); +} +#header .logo a { + display: inline-block; + height: 81px; + width: 345px; +} +#header .jnode { + position: absolute; + right: 20px; + top: 19px; +} +#header .menubar { + position: absolute; + left: 0; + top: 92px; +} +#header .menubar ul.mainmenu { + text-align: left; + list-style: none; + margin: 0; + padding: 0; +} +#header .menubar ul.mainmenu li { + float: left; + margin: 0; + padding: 0; +} +#hmm01 { + display: block; + height: 54px; + width: 159px; + background: url(images/mmenu_01.png) no-repeat; + text-indent: -9999px; +} +#hmm02 { + display: block; + height: 54px; + width: 151px; + background: url(images/mmenu_02.png) no-repeat; + text-indent: -9999px; +} +#hmm03 { + display: block; + height: 54px; + width: 160px; + background: url(images/mmenu_03.png) no-repeat; + text-indent: -9999px; +} +#hmm04 { + display: block; + height: 54px; + width: 150px; + background: url(images/mmenu_04.png) no-repeat; + text-indent: -9999px; +} +#hsm01 { + display: block; + height: 54px; + width: 60px; + background: url(images/smenu_01.png) no-repeat; + text-indent: -9999px; +} +#hsm03 { + display: block; + height: 54px; + width: 58px; + background: url(images/smenu_03.png) no-repeat; + text-indent: -9999px; +} +#hsm04 { + display: block; + height: 54px; + width: 58px; + background: url(images/smenu_04.png) no-repeat; + text-indent: -9999px; +} +#header .menubar ul.mainmenu li .lang { + padding-top: 30px; + margin-left: 7px; +} +#header .menubar ul.mainmenu li .lang a { + margin: 0 3px; +} + +#footer .pageTop { + margin: 10px 10px 0; + text-align: right; +} +#footer .pageTop a { + display: inline-block; + width: 65px; + height: 19px; + text-decoration: none; + background: url(images/page_top.png) no-repeat; +} +#footer .link { + text-align: right; + padding: 5px 10px; +} +#footer .link span { + margin-left: 10px; +} +#footer .copyright { + padding: 16px 16px 30px; + text-align: right; + color: #ffcb00; + background: #535353 url(images/footer_back.png) repeat-x; +} + +.col2::after { + content: ""; + display: block; + clear: both; +} + +#centercolumn { + float: right; + padding: 15px; + width: 75%; + min-height: 500px; +} + +#leftcolumn { + margin-top: 5px; + float: left; + overflow: hidden; + width: 25%; + max-width: 400px; + background-color: #343434; + border-radius: 0 10px 10px 0; + padding: 8px 8px 8px 0; +} + +.block:not(:first-child) { + margin-top: 10px; +} + +#leftcolumn .block .blockTitle { + font-weight: bold; + height: 27px; + padding: 5px 10px 0 23px; + border-radius: 0 5px 5px 0; + box-shadow: 0 3px 8px rgba(255, 255, 255, 0.5) inset; + color: #fdfdfd; + background: url(images/blockTitleIndent12.png) no-repeat 8px center #ef6e00; + white-space: nowrap; +} + +#leftcolumn .block .blockContent { + margin-top: 3px; + padding: 5px; + background-color: #d7d7d7; + color: #333333; + border-radius: 0 5px 5px 0; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3) inset; +} + +#leftcolumn .block .blockContent a { + color: #333333; +} + +#leftcolumn .block .blockContent a:hover { + color: #ff6600; + text-decoration: none; +} + +#leftcolumn .block .blockContent ul.mainmenu { + margin: 5px 0; + padding-left: 0; +} +#leftcolumn .block .blockContent ul li { + padding: 0; +} + +#leftcolumn .block .blockContent .mainmenu a.menuMain { + padding-left: 3px; +} + +#leftcolumn .blockContent .mainmenu a, +#leftcolumn .blockContent .usermenu a { + display: block; + margin-bottom: 4px; + padding: 3px; + border-bottom: 1px dotted #aaa; +} + +#centerCcolumn .block .blockTitle { + font-weight: bold; + height: 30px; + padding: 8px 10px 0 28px; + border-radius: 3px 3px 3px 3px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5), + 0 0 10px rgba(255, 255, 255, 0.5) inset; + color: #fdfdfd; + background: url(images/blockTitleIndent16.png) no-repeat 8px center #ef6e00; + white-space: nowrap; +} + +#centerCcolumn .block .blockContent { + margin-top: 3px; + padding: 5px; +} + +#centerLcolumn { + float: left; + width: 49%; +} +#centerRcolumn { + float: right; + width: 49%; +} + +#centerLcolumn .block .blockTitle, +#centerRcolumn .block .blockTitle { + margin: 5px 0; + padding: 3px 5px; + color: #555555; + font-weight: bold; + font-size: 110%; + background: url(images/blockTitleBack.png) repeat; + white-space: nowrap; +} + +#main h1 { + background:url(images/h1_indent.png) no-repeat left center; + padding: 5px 0 5px 33px; +} +#main h2 { + background:url(images/h2_background.png); + padding: 5px; +} \ No newline at end of file diff --git a/src/custom/blocks/BulletinBoard.tsx b/src/custom/blocks/BulletinBoard.tsx new file mode 100644 index 0000000..4760417 --- /dev/null +++ b/src/custom/blocks/BulletinBoard.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const BulletinBoard: React.FC = (props: Props) => { + const { lang } = props; + return ( +
    +
    {Functions.mlang("[en]Bulletin Board[/en][ja]掲示板[/ja]", lang)}
    +
    +

    {Functions.mlang("[en]There is no post yet.[/en][ja]まだ投稿がありません[/ja]", lang)}

    + {Functions.mlang("[en]View expired posts[/en][ja]掲示期間が終了した投稿を見る[/ja]", lang)} +
    +
    + ); +}; + +export default BulletinBoard; diff --git a/src/custom/blocks/HowToUseLinks.tsx b/src/custom/blocks/HowToUseLinks.tsx new file mode 100644 index 0000000..3229143 --- /dev/null +++ b/src/custom/blocks/HowToUseLinks.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const HowToUseLinks: React.FC = (props: Props) => { + const { lang } = props; + const title = "[en]How to use NIMG-PF[/en][ja]NIMG-PFの使い方[/ja]"; + const content = { + en: ( + + ), + ja: ( + + ), + }; + return ( +
    +
    {Functions.mlang(title, lang)}
    +
    {content[lang]}
    +
    + ); +}; + +export default HowToUseLinks; diff --git a/src/custom/blocks/HowToUseVideo.tsx b/src/custom/blocks/HowToUseVideo.tsx new file mode 100644 index 0000000..6c36d10 --- /dev/null +++ b/src/custom/blocks/HowToUseVideo.tsx @@ -0,0 +1,66 @@ +import React, { useState } from "react"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const HowToUseVideo: React.FC = (props) => { + const { lang } = props; + const [skip, setSkip] = useState(false); + + const onClickShowMovie = (e: React.MouseEvent) => { + e.preventDefault(); + setSkip(false); + }; + + const onClickSkipMovie = (e: React.MouseEvent) => { + e.preventDefault(); + setSkip(true); + }; + + const url = "/modules/nimgdocs/docs/viewlet/"; + const name = "ty-02" + (lang === "en" ? "eng" : "jap"); + const preview = `${url}/${name}_small.png`; + const poster = `${url}/${name}.png`; + const movieWebm = `${url}/${name}.webm`; + const movieMp4 = `${url}/${name}.mp4`; + + return ( +
    +
    +
    +
    {Functions.mlang("[en]Function and how to use NIMG-PF[/en][ja]NIMG-PFの機能と使い方[/ja]", lang)}
    + {skip ? ( + <> + + How to Use + + ) : ( + <> + + + + )} +
    +
    +
    + ); +}; + +export default HowToUseVideo; diff --git a/src/custom/blocks/Information.tsx b/src/custom/blocks/Information.tsx new file mode 100644 index 0000000..f20bcf5 --- /dev/null +++ b/src/custom/blocks/Information.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import NoticeSiteHasBeenArchived from "../../common/lib/NoticeSiteHasBeenArchived"; +import XoopsCode from "../../common/lib/XoopsCode"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const Information: React.FC = (props: Props) => { + const { lang } = props; + const title = "[en]Information[/en][ja]お知らせ[/ja]"; + const content = { + en: "This site was opened to the public at 23 Jun 2009. When you submit an application form of user registration, please write it correctly. The application may not be approved, if it includes incorrect information or only abbreviation for address, organization, and division name. (Currently, user registration is closed.)", + ja: "このサイトは2009年6月23日に一般公開され、一般ユーザの登録を受け付けています。ユーザ登録されると、データ登録やダウンロードが可能になるなど、より多くの機能をご利用出来ます。ユーザ登録の際には、正確な情報を入力していただくようお願いします。不正確な情報が記入されていたり、住所・機関・所属などが短縮形だけであるなどの場合は、登録が承認されないことがあります。 (現在、新規登録を受け付けておりません。)", + }; + return ( +
    +
    {Functions.mlang(title, lang)}
    +
    + + +
    +
    + ); +}; + +export default Information; diff --git a/src/custom/blocks/Questionnaire.tsx b/src/custom/blocks/Questionnaire.tsx new file mode 100644 index 0000000..23ddb0a --- /dev/null +++ b/src/custom/blocks/Questionnaire.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const Questionnaire: React.FC = (props: Props) => { + const { lang } = props; + const title = "[en]Questionnaire and Contact address[/en][ja]NIMG-PFの使い方[/ja]"; + return ( +
    +
    {Functions.mlang(title, lang)}
    +
    +
    + {Functions.mlang("[en]Please fill out the questionnaire.[/en][ja]アンケートにご協力ください[/ja]", lang)} ⇒{" "} + + {Functions.mlang("[en]Go to the page of questionnaire.[/en][ja]アンケートのページへ[/ja]", lang)} + +
    +
    {Functions.mlang("[en]Our e-mail address is as follows. If you have any questions, please let me know.[/en][ja]NIMG-PFについてご質問等ありましたら、以下までお願いします。[/ja]", lang)}
    +

    + {Functions.mlang("[en]Contact information of NIMG-PF[/en][ja]NIMG-PFについてのご連絡先[/ja]", lang)}: NIMG-info@ml.nict.go.jp +

    +
    +
    + ); +}; + +export default Questionnaire; diff --git a/src/custom/blocks/RecentStatus.tsx b/src/custom/blocks/RecentStatus.tsx new file mode 100644 index 0000000..670076f --- /dev/null +++ b/src/custom/blocks/RecentStatus.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { MultiLang } from "../../config"; +import Functions from "../../functions"; + +interface Props { + lang: MultiLang; +} + +const RecentStatus: React.FC = (props: Props) => { + const { lang } = props; + const title = "[en]NIMG-PF recent status[/en][ja]NIMG-PF更新情報[/ja]"; + return ( +
    +
    {Functions.mlang(title, lang)}
    +
    +
    + ); +}; + +export default RecentStatus; diff --git a/src/functions.ts b/src/functions.ts new file mode 100644 index 0000000..0138891 --- /dev/null +++ b/src/functions.ts @@ -0,0 +1,136 @@ +import moment from "moment"; +import XRegExp from "xregexp"; +import Config, { MultiLang } from "./config"; + +const escape = (str: string): string => { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { + return "%" + c.charCodeAt(0).toString(16); + }); +}; + +const unescape = (str: string): string => { + return decodeURIComponent(str); +}; + +const htmlspecialchars = (str: string): string => { + return str.replace(/(<|>|&|'|")/g, (match) => { + switch (match) { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return ""; + }); +}; + +const camelCase = (str: string): string => { + return str.replace(/[-_](.)/g, (...matches) => { + return matches[1].toUpperCase(); + }); +}; + +const snakeCase = (str: string): string => { + var camel = camelCase(str); + return camel.replace(/[A-Z]/g, (...matches) => { + return "_" + matches[0].charAt(0).toLowerCase(); + }); +}; + +const pascalCase = (str: string): string => { + var camel = camelCase(str); + return camel.charAt(0).toUpperCase() + camel.slice(1); +}; + +const base64Encode = (str: string): string => { + return btoa(unescape(encodeURIComponent(str))); +}; + +const base64Decode = (str: string): string => { + return decodeURIComponent(escape(atob(str))); +}; + +const formatDate = (timestamp: number, format: string): string => { + return moment(new Date(timestamp * 1000)).format(format); +}; + +const ordinal = (num: number): string => { + const hundred = num % 100; + if (hundred >= 10 && hundred <= 20) { + return "th"; + } + const ten = num % 10; + const suffix = ["st", "nd", "rd"]; + return ten > 0 && ten < 4 ? suffix[ten - 1] : "th"; +}; + +const mlang = (str: string, lang: MultiLang): string => { + const mlLangs = ["en", "ja"]; + // escape brackets inside of + let text = str.replace(/(]*)(>)/gis, (whole, m1, m2, m3) => { + if (m2.match(/type=["']?(?:text|hidden)["']?/is)) { + return m1 + m2.replace(/\[/g, "__ml[ml__") + m3; + } + return whole; + }); + // escape brackets inside of + text = text.replace(/(]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => { + return m1 + m2.replace(/\[/g, "__ml[ml__") + m3; + }); + // simple pattern to strip selected lang_tags + const re = new RegExp("\\[/?(?:[^\\]]+\\|)?" + lang + "(?:\\|[^\\]]+)?\\](?:
    )?", "g"); + text = text.replace(re, ""); + // eliminate description between the other language tags. + mlLangs.forEach((mlLang) => { + if (mlLang !== lang) { + const re = XRegExp("\\[(?:[^/][^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\].*?\\[/(?:[^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\](?:
    )?", "isg"); + text = text.replace(re, (whole) => { + return whole.match(/<\/table>/) ? whole : ""; + }); + } + }); + // unescape brackets inside of + text = text.replace(/(]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => { + return m1 + m2.replace(/__ml\[ml__/g, "[") + m3; + }); + // unescape brackets inside of + text = text.replace(/(]*)(>)/gis, (whole, m1, m2, m3) => { + if (m2.match(/type=["']?(?:text|hidden)["']?/is)) { + return m1 + m2.replace(/__ml\[ml__/g, "[") + m3; + } + return whole; + }); + return text; +}; + +const siteTitle = (lang: MultiLang): string => { + return mlang(Config.SITE_TITLE, lang); +}; + +const siteSlogan = (lang: MultiLang): string => { + return mlang(Config.SITE_SLOGAN, lang); +}; + +const Functions = { + escape, + unescape, + htmlspecialchars, + camelCase, + snakeCase, + pascalCase, + base64Encode, + base64Decode, + formatDate, + ordinal, + mlang, + siteTitle, + siteSlogan, +}; + +export default Functions; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..2daa7e9 --- /dev/null +++ b/src/index.css @@ -0,0 +1 @@ +@import-normalize; diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..0d9fff5 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import "react-app-polyfill/stable"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; +import reportWebVitals from "./reportWebVitals"; + +const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/nimgcenter/NimgcenterBrainConv.tsx b/src/nimgcenter/NimgcenterBrainConv.tsx new file mode 100644 index 0000000..e959965 --- /dev/null +++ b/src/nimgcenter/NimgcenterBrainConv.tsx @@ -0,0 +1,142 @@ +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { MultiLang } from "../config"; +import BrainCoordinateUtil, { BrainCoordinate, ConversionType } from "./lib/BrainCoordinateUtil"; + +interface Props { + lang: MultiLang; +} + +const NimgcenterBrainConv: React.FC = (props: Props) => { + const defaultInputFormat = "\\d+\\.?\\d*"; + const defaultOutputFormat = "%x,%y,%z\\n"; + + const [inputFormat, setInputFormat] = useState(defaultInputFormat); + const [inputArea, setInputArea] = useState(""); + const [outputFormat, setOutputFormat] = useState(defaultOutputFormat); + const [outputArea, setOutputArea] = useState(""); + const [conversionType, setConversionType] = useState("icbm_spm2tal"); + + const parseInput = (input: string, format: string): BrainCoordinate[] => { + const ret: BrainCoordinate[] = []; + const regObj = new RegExp(format, "g"); + const values = input.match(regObj) || []; + for (let i = 0; i + 2 < values.length; i += 3) { + ret.push({ x: parseFloat(values[i]), y: parseFloat(values[i + 1]), z: parseFloat(values[i + 2]) }); + } + return ret; + }; + + const formatNumber = (value: number): string => { + const digit = 4; + const width = 7; + const ret = value.toFixed(digit); + return ret.length < width ? " ".repeat(width - ret.length) + ret : ret; + }; + + const onChangeInputFormat = (event: React.ChangeEvent) => { + setInputFormat(event.target.value); + }; + + const onClickDefaultInputFormatButton = (event: React.MouseEvent) => { + setInputFormat(defaultInputFormat); + }; + + const onChangeInputArea = (event: React.ChangeEvent) => { + setInputArea(event.target.value); + }; + + const onChangeOutputFormat = (event: React.ChangeEvent) => { + setOutputFormat(event.target.value); + }; + + const onClickDefaultOutputFormatButton = (event: React.MouseEvent) => { + setOutputFormat(defaultOutputFormat); + }; + + const onChangeMatrixType = (event: React.ChangeEvent) => { + if (BrainCoordinateUtil.isConversionType(event.target.value)) { + setConversionType(event.target.value as ConversionType); + } + }; + + const onClickClearButton = (event: React.MouseEvent) => { + setInputArea(""); + setOutputArea(""); + }; + + const onClickConvertButton = (event: React.MouseEvent) => { + const inputValues = parseInput(inputArea, inputFormat); + const outputValues = inputValues.map((inputValue) => BrainCoordinateUtil.convertCoordinate(conversionType, inputValue)); + const outputTextArea = outputValues + .map((value) => { + return outputFormat.replace("%x", formatNumber(value.x)).replace("%y", formatNumber(value.y)).replace("%z", formatNumber(value.z)).replace("\\n", "\n"); + }) + .join(""); + setOutputArea(outputTextArea); + }; + + const matrix = BrainCoordinateUtil.getConversionMatrix(conversionType); + const matrixArea = "Conversion Matrix:\n" + matrix.map((vect) => " " + vect.map((value) => formatNumber(value)).join(", ")).join("\n"); + return ( +
    +
    + Brain Coordinate Converter is able to convert list of brain coordinate. How To Use +
    + + + + + + + +
    + Input format: (Regular expression). +
    + + +
    + Output format: (%x,%y,%z is replaced. and \n) +
    + + +
    + Input: +
    +