commit 03456410f84a96c0454ae377c05cda3a7a7af866 Author: Yoshihiro OKUMURA Date: Mon Jun 20 17:51:03 2022 +0900 first commit. 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 0000000..a92c63c Binary files /dev/null and b/src/brainexplorer/assets/images/logo_aist.png differ diff --git a/src/brainexplorer/assets/images/navi_home.png b/src/brainexplorer/assets/images/navi_home.png new file mode 100644 index 0000000..b0a72d2 Binary files /dev/null and b/src/brainexplorer/assets/images/navi_home.png differ diff --git a/src/brainexplorer/assets/images/navi_info.png b/src/brainexplorer/assets/images/navi_info.png new file mode 100644 index 0000000..28b19b7 Binary files /dev/null and b/src/brainexplorer/assets/images/navi_info.png differ 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 0000000..4ff614e Binary files /dev/null and b/src/common/assets/images/mlang_english.gif differ diff --git a/src/common/assets/images/mlang_japanese.gif b/src/common/assets/images/mlang_japanese.gif new file mode 100644 index 0000000..5768206 Binary files /dev/null and b/src/common/assets/images/mlang_japanese.gif differ diff --git a/src/common/assets/images/no_avatar.gif b/src/common/assets/images/no_avatar.gif new file mode 100644 index 0000000..62fff0f Binary files /dev/null and b/src/common/assets/images/no_avatar.gif differ diff --git a/src/common/assets/images/pagact.gif b/src/common/assets/images/pagact.gif new file mode 100644 index 0000000..3f5b855 Binary files /dev/null and b/src/common/assets/images/pagact.gif differ diff --git a/src/common/assets/images/paginact.gif b/src/common/assets/images/paginact.gif new file mode 100644 index 0000000..7a2bc62 Binary files /dev/null and b/src/common/assets/images/paginact.gif differ diff --git a/src/common/assets/images/pagneutral.gif b/src/common/assets/images/pagneutral.gif new file mode 100644 index 0000000..b731713 Binary files /dev/null and b/src/common/assets/images/pagneutral.gif differ diff --git a/src/common/assets/images/rank3dbf8e94a6f72.gif b/src/common/assets/images/rank3dbf8e94a6f72.gif new file mode 100644 index 0000000..1c7c979 Binary files /dev/null and b/src/common/assets/images/rank3dbf8e94a6f72.gif differ diff --git a/src/common/assets/images/rank3dbf8e9e7d88d.gif b/src/common/assets/images/rank3dbf8e9e7d88d.gif new file mode 100644 index 0000000..a545faf Binary files /dev/null and b/src/common/assets/images/rank3dbf8e9e7d88d.gif differ diff --git a/src/common/assets/images/rank3dbf8ea81e642.gif b/src/common/assets/images/rank3dbf8ea81e642.gif new file mode 100644 index 0000000..50f3de7 Binary files /dev/null and b/src/common/assets/images/rank3dbf8ea81e642.gif differ diff --git a/src/common/assets/images/rank3dbf8eb1a72e7.gif b/src/common/assets/images/rank3dbf8eb1a72e7.gif new file mode 100644 index 0000000..64d8f29 Binary files /dev/null and b/src/common/assets/images/rank3dbf8eb1a72e7.gif differ diff --git a/src/common/assets/images/rank3dbf8edf15093.gif b/src/common/assets/images/rank3dbf8edf15093.gif new file mode 100644 index 0000000..704398e Binary files /dev/null and b/src/common/assets/images/rank3dbf8edf15093.gif differ diff --git a/src/common/assets/images/rank3dbf8ee8681cd.gif b/src/common/assets/images/rank3dbf8ee8681cd.gif new file mode 100644 index 0000000..95428a8 Binary files /dev/null and b/src/common/assets/images/rank3dbf8ee8681cd.gif differ diff --git a/src/common/assets/images/rank3e632f95e81ca.gif b/src/common/assets/images/rank3e632f95e81ca.gif new file mode 100644 index 0000000..224fbaa Binary files /dev/null and b/src/common/assets/images/rank3e632f95e81ca.gif differ diff --git a/src/common/assets/images/smil3dbd4bf386b36.gif b/src/common/assets/images/smil3dbd4bf386b36.gif new file mode 100644 index 0000000..51e517a Binary files /dev/null and b/src/common/assets/images/smil3dbd4bf386b36.gif differ diff --git a/src/common/assets/images/smil3dbd4d4e4c4f2.gif b/src/common/assets/images/smil3dbd4d4e4c4f2.gif new file mode 100644 index 0000000..ee53c7d Binary files /dev/null and b/src/common/assets/images/smil3dbd4d4e4c4f2.gif differ diff --git a/src/common/assets/images/smil3dbd4d6422f04.gif b/src/common/assets/images/smil3dbd4d6422f04.gif new file mode 100644 index 0000000..9a75a92 Binary files /dev/null and b/src/common/assets/images/smil3dbd4d6422f04.gif differ diff --git a/src/common/assets/images/smil3dbd4d75edb5e.gif b/src/common/assets/images/smil3dbd4d75edb5e.gif new file mode 100644 index 0000000..b1baee2 Binary files /dev/null and b/src/common/assets/images/smil3dbd4d75edb5e.gif differ diff --git a/src/common/assets/images/smil3dbd4d8676346.gif b/src/common/assets/images/smil3dbd4d8676346.gif new file mode 100644 index 0000000..5777f68 Binary files /dev/null and b/src/common/assets/images/smil3dbd4d8676346.gif differ diff --git a/src/common/assets/images/smil3dbd4d99c6eaa.gif b/src/common/assets/images/smil3dbd4d99c6eaa.gif new file mode 100644 index 0000000..87a792e Binary files /dev/null and b/src/common/assets/images/smil3dbd4d99c6eaa.gif differ diff --git a/src/common/assets/images/smil3dbd4daabd491.gif b/src/common/assets/images/smil3dbd4daabd491.gif new file mode 100644 index 0000000..95a63f2 Binary files /dev/null and b/src/common/assets/images/smil3dbd4daabd491.gif differ diff --git a/src/common/assets/images/smil3dbd4dbc14f3f.gif b/src/common/assets/images/smil3dbd4dbc14f3f.gif new file mode 100644 index 0000000..77c71c0 Binary files /dev/null and b/src/common/assets/images/smil3dbd4dbc14f3f.gif differ diff --git a/src/common/assets/images/smil3dbd4dcd7b9f4.gif b/src/common/assets/images/smil3dbd4dcd7b9f4.gif new file mode 100644 index 0000000..2e4dfa5 Binary files /dev/null and b/src/common/assets/images/smil3dbd4dcd7b9f4.gif differ diff --git a/src/common/assets/images/smil3dbd4ddd6835f.gif b/src/common/assets/images/smil3dbd4ddd6835f.gif new file mode 100644 index 0000000..01b5a60 Binary files /dev/null and b/src/common/assets/images/smil3dbd4ddd6835f.gif differ diff --git a/src/common/assets/images/smil3dbd4df1944ee.gif b/src/common/assets/images/smil3dbd4df1944ee.gif new file mode 100644 index 0000000..49697c5 Binary files /dev/null and b/src/common/assets/images/smil3dbd4df1944ee.gif differ diff --git a/src/common/assets/images/smil3dbd4e02c5440.gif b/src/common/assets/images/smil3dbd4e02c5440.gif new file mode 100644 index 0000000..004261b Binary files /dev/null and b/src/common/assets/images/smil3dbd4e02c5440.gif differ diff --git a/src/common/assets/images/smil3dbd4e1748cc9.gif b/src/common/assets/images/smil3dbd4e1748cc9.gif new file mode 100644 index 0000000..50f9984 Binary files /dev/null and b/src/common/assets/images/smil3dbd4e1748cc9.gif differ diff --git a/src/common/assets/images/smil3dbd4e29bbcc7.gif b/src/common/assets/images/smil3dbd4e29bbcc7.gif new file mode 100644 index 0000000..4a96ea1 Binary files /dev/null and b/src/common/assets/images/smil3dbd4e29bbcc7.gif differ diff --git a/src/common/assets/images/smil3dbd4e398ff7b.gif b/src/common/assets/images/smil3dbd4e398ff7b.gif new file mode 100644 index 0000000..15402e7 Binary files /dev/null and b/src/common/assets/images/smil3dbd4e398ff7b.gif differ diff --git a/src/common/assets/images/smil3dbd4e4c2e742.gif b/src/common/assets/images/smil3dbd4e4c2e742.gif new file mode 100644 index 0000000..99368ba Binary files /dev/null and b/src/common/assets/images/smil3dbd4e4c2e742.gif differ diff --git a/src/common/assets/images/smil3dbd4e5e7563a.gif b/src/common/assets/images/smil3dbd4e5e7563a.gif new file mode 100644 index 0000000..172ecf8 Binary files /dev/null and b/src/common/assets/images/smil3dbd4e5e7563a.gif differ diff --git a/src/common/assets/images/smil3dbd4e7853679.gif b/src/common/assets/images/smil3dbd4e7853679.gif new file mode 100644 index 0000000..a66421e Binary files /dev/null and b/src/common/assets/images/smil3dbd4e7853679.gif differ diff --git a/src/common/assets/xoops.css b/src/common/assets/xoops.css new file mode 100644 index 0000000..88f768a --- /dev/null +++ b/src/common/assets/xoops.css @@ -0,0 +1,17 @@ +img {border: 0;} + +#xoopsHiddenText {visibility: hidden; color: #000000; font-weight: normal; font-style: normal; text-decoration: none;} + +.pagneutral {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagneutral.gif);} +.pagact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagact.gif);} +.paginact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/paginact.gif);} + + +#mainmenu a {text-align:left; display: block; margin: 0; padding: 4px;} +#mainmenu a.menuTop {padding-left: 3px;} +#mainmenu a.menuMain {padding-left: 3px;} +#mainmenu a.menuSub {padding-left: 9px;} + +#usermenu a {text-align:left; display: block; margin: 0; padding: 4px;} +#usermenu a.menuTop {} +#usermenu a.highlight {color: #0000ff; background-color: #fcc;} diff --git a/src/common/lib/LangFlag.tsx b/src/common/lib/LangFlag.tsx new file mode 100644 index 0000000..32e7469 --- /dev/null +++ b/src/common/lib/LangFlag.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { Link, useLocation } from "react-router-dom"; +import { MultiLang } from "../../config"; +import mlangEnglish from "../assets/images/mlang_english.gif"; +import mlangJapanese from "../assets/images/mlang_japanese.gif"; + +interface Props { + lang: MultiLang; + className?: string; + image?: string; +} + +const langResources = { + en: { image: mlangEnglish, title: "English" }, + ja: { image: mlangJapanese, title: "Japanese" }, +}; + +const LangFlag: React.FC = (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 0000000..f7ef988 Binary files /dev/null and b/src/custom/assets/images/back.png differ diff --git a/src/custom/assets/images/banner-niu.png b/src/custom/assets/images/banner-niu.png new file mode 100644 index 0000000..d22c41a Binary files /dev/null and b/src/custom/assets/images/banner-niu.png differ diff --git a/src/custom/assets/images/banner-riken-cbs.png b/src/custom/assets/images/banner-riken-cbs.png new file mode 100644 index 0000000..5002bbb Binary files /dev/null and b/src/custom/assets/images/banner-riken-cbs.png differ diff --git a/src/custom/assets/images/banner-riken.png b/src/custom/assets/images/banner-riken.png new file mode 100644 index 0000000..ce30314 Binary files /dev/null and b/src/custom/assets/images/banner-riken.png differ diff --git a/src/custom/assets/images/banner-xoonips.png b/src/custom/assets/images/banner-xoonips.png new file mode 100644 index 0000000..337104d Binary files /dev/null and b/src/custom/assets/images/banner-xoonips.png differ diff --git a/src/custom/assets/images/blockTitle.png b/src/custom/assets/images/blockTitle.png new file mode 100644 index 0000000..3eae8af Binary files /dev/null and b/src/custom/assets/images/blockTitle.png differ diff --git a/src/custom/assets/images/blockTitleBack.png b/src/custom/assets/images/blockTitleBack.png new file mode 100644 index 0000000..e0cc52a Binary files /dev/null and b/src/custom/assets/images/blockTitleBack.png differ diff --git a/src/custom/assets/images/blockTitleIndent12.png b/src/custom/assets/images/blockTitleIndent12.png new file mode 100644 index 0000000..3d2af0f Binary files /dev/null and b/src/custom/assets/images/blockTitleIndent12.png differ diff --git a/src/custom/assets/images/blockTitleIndent16.png b/src/custom/assets/images/blockTitleIndent16.png new file mode 100644 index 0000000..f5afaf9 Binary files /dev/null and b/src/custom/assets/images/blockTitleIndent16.png differ diff --git a/src/custom/assets/images/footer_back.png b/src/custom/assets/images/footer_back.png new file mode 100644 index 0000000..a059893 Binary files /dev/null and b/src/custom/assets/images/footer_back.png differ diff --git a/src/custom/assets/images/h1_indent.png b/src/custom/assets/images/h1_indent.png new file mode 100644 index 0000000..67892a0 Binary files /dev/null and b/src/custom/assets/images/h1_indent.png differ diff --git a/src/custom/assets/images/h2_background.png b/src/custom/assets/images/h2_background.png new file mode 100644 index 0000000..e0cc52a Binary files /dev/null and b/src/custom/assets/images/h2_background.png differ diff --git a/src/custom/assets/images/jnode.png b/src/custom/assets/images/jnode.png new file mode 100644 index 0000000..53765ad Binary files /dev/null and b/src/custom/assets/images/jnode.png differ diff --git a/src/custom/assets/images/logo.png b/src/custom/assets/images/logo.png new file mode 100644 index 0000000..44d7976 Binary files /dev/null and b/src/custom/assets/images/logo.png differ diff --git a/src/custom/assets/images/mlang_english.png b/src/custom/assets/images/mlang_english.png new file mode 100644 index 0000000..14755f1 Binary files /dev/null and b/src/custom/assets/images/mlang_english.png differ diff --git a/src/custom/assets/images/mlang_japanese.png b/src/custom/assets/images/mlang_japanese.png new file mode 100644 index 0000000..48991b2 Binary files /dev/null and b/src/custom/assets/images/mlang_japanese.png differ diff --git a/src/custom/assets/images/mmenu_01.png b/src/custom/assets/images/mmenu_01.png new file mode 100644 index 0000000..3852dfa Binary files /dev/null and b/src/custom/assets/images/mmenu_01.png differ diff --git a/src/custom/assets/images/mmenu_02.png b/src/custom/assets/images/mmenu_02.png new file mode 100644 index 0000000..e9feaf9 Binary files /dev/null and b/src/custom/assets/images/mmenu_02.png differ diff --git a/src/custom/assets/images/mmenu_03.png b/src/custom/assets/images/mmenu_03.png new file mode 100644 index 0000000..21f4150 Binary files /dev/null and b/src/custom/assets/images/mmenu_03.png differ diff --git a/src/custom/assets/images/mmenu_04.png b/src/custom/assets/images/mmenu_04.png new file mode 100644 index 0000000..fe4a81f Binary files /dev/null and b/src/custom/assets/images/mmenu_04.png differ diff --git a/src/custom/assets/images/page_top.png b/src/custom/assets/images/page_top.png new file mode 100644 index 0000000..b2a3fc5 Binary files /dev/null and b/src/custom/assets/images/page_top.png differ diff --git a/src/custom/assets/images/smenu_01.png b/src/custom/assets/images/smenu_01.png new file mode 100644 index 0000000..d3236dc Binary files /dev/null and b/src/custom/assets/images/smenu_01.png differ diff --git a/src/custom/assets/images/smenu_03.png b/src/custom/assets/images/smenu_03.png new file mode 100644 index 0000000..49fbef8 Binary files /dev/null and b/src/custom/assets/images/smenu_03.png differ diff --git a/src/custom/assets/images/smenu_04.png b/src/custom/assets/images/smenu_04.png new file mode 100644 index 0000000..e85e119 Binary files /dev/null and b/src/custom/assets/images/smenu_04.png differ diff --git a/src/custom/assets/style.css b/src/custom/assets/style.css new file mode 100644 index 0000000..ed1a3f8 --- /dev/null +++ b/src/custom/assets/style.css @@ -0,0 +1,396 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root { + width: 100%; + min-width: 860px; + max-width: 1500px; +} + +body { + font-family: Arial, Helvetica, sans-serif; + color: #000000; + background-color: #fffff8; + margin: 0; + font-size: 80%; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Georgia, "Times New Roman", Times, serif; + margin-bottom: 0.5rem; + font-weight: bold; + line-height: 1.2; + color: inherit; +} + +h1 { + font-size: 1.802em; +} + +h2 { + font-size: 1.602em; +} + +h3 { + font-size: 1.424em; +} + +h4 { + font-size: 1.266em; +} + +h5 { + font-size: 1.125em; +} + +h6 { + font-size: 1em; +} + +a { + color: #ff6600; + text-decoration: none; +} + +a:hover { + color: #ff0000; + text-decoration: underline; +} + +hr { + border-top: 0; + border-right: 0; + border-bottom: 1px solid #cccccc; + border-left: 0; +} + +ul, +ol { + margin: 0.5em 0; + padding: 0 0.5em 0 2em; +} + +li { + line-height: 1.5; + padding: 0.5em 0; +} + +table { + width: 100%; + border-collapse: separate; + border-spacing: 2px; +} + +td, +th { + padding: 5px; +} + +th, +td, +.head { + vertical-align: top; +} + +.foot { + vertical-align: middle; +} + +th, +.head, +.foot { + font-weight: bold; + background-color: #fef6ef; + border-bottom: 1px solid #cacaca; + border-right: 1px solid #cacaca; +} + +tr.even > 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: +
    +