diff --git a/.gitignore b/.gitignore index 4d29575..d2c82ed 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,12 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +# database contents +/dl-limit-items.csv +/public/database/items.json +/public/database/file +/public/credits/images +/public/credits/*.json +/src/database/assets/*.json +/src/pico/assets/*.json diff --git a/etc/common.inc.php b/etc/common.inc.php new file mode 100644 index 0000000..e072605 --- /dev/null +++ b/etc/common.inc.php @@ -0,0 +1,190 @@ + $v) { + if (is_string($v)) { + $v = html_entity_decode($v, ENT_QUOTES | ENT_HTML5); + $v = mb_decode_numericentity($v, array(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, + '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); + + 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)) { + exit('Failed to create directory: '.$path.PHP_EOL); + } + } + } + + public static function saveJson($fname, $data) + { + $path = MYDUMPTOOL_OUTPUTDIR.'/'.$fname; + if (false === file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE))) { + 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) { + exit('Failed to download file, 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 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; + } +} diff --git a/etc/config.inc.php b/etc/config.inc.php new file mode 100644 index 0000000..0f33781 --- /dev/null +++ b/etc/config.inc.php @@ -0,0 +1,7 @@ +prefix(); + $table = $pico.'_contents'; + if (!MyDumpTool::tableExists($table)) { + exit('pico('.$pico.') module not found'.PHP_EOL); + } + MyDumpTool::makeDirectory('public/'.$pico); + MyDumpTool::makeDirectory('public/'.$pico.'/images'); + $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 = []; + 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); + $content = MyDumpTool::fixHtml($data['body_cached']); + $imageId = 1; + $dataId = []; + $content = preg_replace_callback('/]*)src="(.*)"([^>]*)\/>/Us', function ($matches) use (&$imageId, $pico, $data, $link) { + $url = $matches[2]; + if (!preg_match('/^http/', $url)) { + if (preg_match('/^\//', $url)) { + $url = XOOPS_URL.$url; + } else { + $xpath = dirname($link); + $url = XOOPS_URL.'/modules/'.$pico.'/'.$xpath.'/'.$url; + } + } + $fname = '/'.$pico.'/images/'.$data['id'].'_'.$imageId.'.dat'; + $fpath = MYDUMPTOOL_OUTPUTDIR.'/public'.$fname; + MyDumpTool::fileDownload($url, $fpath); + $mime = MyDumpTool::getMimeType($fpath); + switch ($mime) { + case 'image/gif': + $fpath_ = preg_replace('/.dat$/', '.gif', $fpath); + $fname = preg_replace('/.dat$/', '.gif', $fname); + rename($fpath, $fpath_); + $fpath = $fpath_; + break; + case 'image/png': + $fpath_ = preg_replace('/.dat$/', '.png', $fpath); + $fname = preg_replace('/.dat$/', '.png', $fname); + rename($fpath, $fpath_); + $fpath = $fpath_; + break; + case 'image/jpeg': + $fpath_ = preg_replace('/.dat$/', '.jpeg', $fpath); + $fname = preg_replace('/.dat$/', '.jpeg', $fname); + rename($fpath, $fpath_); + $fpath = $fpath_; + break; + default: + exit('unknown mime type '.$mime.' found: '.$fname.' from '.$url.PHP_EOL); + } + ++$imageId; + + return ''; + }, $content); + $content = preg_replace_callback('/]*)href="([^"]+)"([^>]*)>/Us', function ($matches) use (&$dataId, $pico, $data, $link) { + $url = $matches[2]; + if (preg_match('/^(.+)\/([^\/]+\.pdf)(\?.+)?$/', $url, $m)) { + if (!isset($dataId[$m[1]])) { + $dataId[$m[1]] = count($dataId) + 1; + } + $id = $dataId[$m[1]]; + $fname = '/'.$pico.'/data/'.$data['id'].'/'.$id.'/'.urldecode($m[2]); + $fpath = MYDUMPTOOL_OUTPUTDIR.'/public'.$fname; + if (!file_exists($fpath)) { + MyDumpTool::makeDirectory('public/'.$pico.'/data'); + MyDumpTool::makeDirectory('public/'.$pico.'/data/'.$data['id']); + MyDumpTool::makeDirectory('public/'.$pico.'/data/'.$data['id'].'/'.$id); + if (preg_match('/^\//', $url)) { + $url = XOOPS_URL.$url; + } + MyDumpTool::fileDownload($url, $fpath); + } + $url = '/'.$pico.'/data/'.$data['id'].'/'.$id.'/'.$m[2]; + } + + return ''; + }, $content); + $item = [ + 'id' => $data['id'], + 'title' => $data['subject_raw'], + 'content' => $content, + ]; + MyDumpTool::saveJson('public/'.$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, + ]; +} diff --git a/etc/dumpXooNIps4.php b/etc/dumpXooNIps4.php new file mode 100644 index 0000000..078b27e --- /dev/null +++ b/etc/dumpXooNIps4.php @@ -0,0 +1,837 @@ +prefix(); + +$table = 'xoonips_index'; +if (!MyDumpTool::tableExists($table)) { + exit('xoonips module not found'.PHP_EOL); +} +MyDumpTool::makeDirectory('src/database'); +MyDumpTool::makeDirectory('src/database/assets'); +MyDumpTool::makeDirectory('public/database'); +MyDumpTool::makeDirectory('public/database/file'); + + +// get public index with certified item count +$sql = <<< SQL +SELECT + `idx`.*, + (SELECT COUNT(*) + FROM `${prefix}_xoonips_index_item_link` AS `iil` + INNER JOIN `${prefix}_xoonips_item` AS `item` ON `iil`.`item_id`=`item`.`item_id` + WHERE `iil`.`index_id`=`idx`.`index_id` AND `iil`.`certify_state`=2 + ) AS `num_items` + FROM `${prefix}_xoonips_index` AS `idx` + WHERE `idx`.`open_level`=1 +SQL; + + +$limitItems = array(); +$limitFiles = array(); + +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$tree = array(); +$root = array(); +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $tree[$row['index_id']] = $row; + $root[$row['parent_index_id']][$row['weight']] = $row['index_id']; +} +$xoopsDB->freeRecordSet($res); +foreach ($root as &$node) { + ksort($node); +} +unset($node); + +$data[] = getTreeNode($tree, $root, 2); +MyDumpTool::saveJson('src/database/assets/tree.json', $data); +unset($data, $root); + +// get item types +$itemTypes = getItemTypes(); +MyDumpTool::saveJson('itemTypes.json', $itemTypes); + +// get public items +$sql = <<< SQL +SELECT + `item`.*, + `ity`.`name` AS `item_type_display_name` + FROM `${prefix}_xoonips_item` AS `item` + INNER JOIN `${prefix}_xoonips_item_type` AS `ity` ON `item`.`item_type_id`=`ity`.`item_type_id` + INNER JOIN `${prefix}_xoonips_index_item_link` AS `iil` ON `item`.`item_id`=`iil`.`item_id` + INNER JOIN `${prefix}_xoonips_index` AS `idx` ON `iil`.`index_id`=`idx`.`index_id` + WHERE `iil`.`certify_state`=2 + AND `idx`.`open_level`=1 + GROUP BY `item`.`item_id` + ORDER BY `item`.`item_id` ASC +SQL; +if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); +} +$itemsRaw = array(); +$items = array(); +while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('item_id', 'item_type_id', 'last_update_date', 'creation_date')); + $item_id = $row['item_id']; + $item_type = strtolower($row['item_type_display_name']); + $row['item_type_name'] = $item_type; + $row['owner'] = getUsers($item_id); + $row['index'] = getIndexes($tree, $row['item_id']); + if (0 === count($row['index'])) { + // no public indexes found. + continue; + } + $row['detail'] = getItemDetail($item_id, $item_type); + $itemsRaw[] = $row; + if (1 !== count($row['owner'])) { + die('does\'nt suuport more than one item owner'.PHP_EOL); + } + $item = array( + 'item_id' => $item_id, + 'doi' => $row['doi'], + 'description' => '', + 'last_update_date' => $row['last_update_date'], + 'creation_date' => $row['creation_date'], + 'publication_year' => 0, + 'publication_month' => 0, + 'publication_mday' => 0, + 'lang' => 'eng', + 'title' => '', + 'item_type_display_name' => $row['item_type_display_name'], + 'item_type_name' => 'xnp'.$row['item_type_name'], + 'uname' => $row['owner'][0]['uname'], + 'name' => $row['owner'][0]['name'], + 'item_url' => getItemUrl($row), + 'index' => $row['index'], + 'changelog' => array(), + 'related_to' => array(), + 'keyword' => array(), + 'file' => array() + ); + foreach ($row['detail'] as $gxml => $gdata) { + foreach ($gdata as $fxml => $fdata) { + if ('title' === $gxml && 'title' === $fxml) { + $item['title'] = $fdata[0]; + } + if ('langs' === $gxml && 'lang' === $fxml) { + $item['lang'] = $fdata[0]; + } + if ('changelogs' === $gxml && 'changelog' === $fxml) { + foreach ($fdata as $value) { + $item['changelog'][] = array( + 'log_date' => (int) $value['log_date'], + 'log' => $value['log'], + ); + } + } + if ('links' === $gxml && 'link' === $fxml) { + $item['related_to'] = $fdata; + } + if ('free_keywords' === $gxml && 'keyword' === $fxml) { + $item['keyword'] = $fdata; + } + if (in_array($fxml, array('file', 'image'))) { + foreach ($fdata as $value) { + $item['file'][] = array( + 'file_id' => (int) $value['file_id'], + 'original_file_name' => $value['original_file_name'], + 'mime_type' => $value['mime_type'], + 'file_size' => (int) $value['file_size'], + 'caption' => $value['caption'], + 'timestamp' => (int) $value['timestamp'], + 'file_type_name' => preg_replace('/_files$/', '_file', $gxml), + 'file_type_display_name' => preg_replace('/Files$/', 'File', ucwords(str_replace('_', ' ', strtolower($gxml)))), + ); + } + } + // binder + if ('binder' === $item_type) { + if ('research_areas' === $gxml && 'research_area' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one research_area in binder'.PHP_EOL); + } + $item['research_area'] = empty($fdata) ? '' : $fdata[0]; + } + if ('species' === $gxml && 'species' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one species in binder'.PHP_EOL); + } + $item['species'] = empty($fdata) ? '' : $fdata[0]; + } + if ('scales' === $gxml && 'scale' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one scale in binder'.PHP_EOL); + } + $item['scale'] = empty($fdata) ? '' : $fdata[0]; + } + if ('methods' === $gxml && 'method' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one method in binder'.PHP_EOL); + } + $item['method'] = empty($fdata) ? '' : $fdata[0]; + } + if ('summary' === $gxml && 'summary' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one summary in binder'.PHP_EOL); + } + $item['description'] = $fdata[0]; + } + if ('agree' === $gxml && 'agree' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one agree in binder'.PHP_EOL); + } + $item['agree'] = (1 === $fdata[0] ? 'Agree' : 'Disagree'); + } + if ('approver' === $gxml && 'approver' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one approver in binder'.PHP_EOL); + } + $item['referee'] = $fdata[0]; + } + if ('guide_to_temporaryuser' === $gxml && 'guide_to_temporaryuser' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one guide_to_temporaryuser in binder'.PHP_EOL); + } + $item['guide_to_temporaryuser'] = ("1" === $fdata[0] ? '' : 'No'); + } + } + if ('data' === $item_type) { + if ('date' === $gxml && 'date' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one date in presentation'.PHP_EOL); + } + $d = explode('/', date('Y/m/d', $fdata[0])); + $item['publication_year'] = (int) $d[0]; + $item['publication_month'] = (int) $d[1]; + $item['publication_mday'] = (int) $d[2]; + } + if ('outline' === $gxml && 'description' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one data_type in data'.PHP_EOL); + } + $item['description'] = $fdata[0]; + } + if ('data_type' === $gxml && 'data_type' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one data_type in data'.PHP_EOL); + } + $item['data_type'] = $fdata[0]; + } + if ('readme' === $gxml && 'readme' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one readme in data'.PHP_EOL); + } + $item['readme'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('rights' === $gxml && 'rights' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one rights in data'.PHP_EOL); + } + $item['rights'] = substr($fdata[0], 4); + if (false === $item['rights']) { + $item['rights'] = ''; + } + $item['use_cc'] = (int) substr($fdata[0], 0, 1); + $item['cc_commercial_use'] = (int) substr($fdata[0], 1, 1); + $item['cc_modification'] = (int) substr($fdata[0], 2, 1); + } + if ('download_limitation' === $gxml && 'attachment_dl_limit' === $fxml) { + if (1 != count($fdata)) { + die('does\'nt support more than one attachment_dl_limit in data'.PHP_EOL); + } + $item['attachment_dl_limit'] = $fdata[0]; + } + if ('download_notification' === $gxml && 'attachment_dl_notify' === $fxml) { + if (1 != count($fdata)) { + die('does\'nt support more than one attachment_dl_notify in data'.PHP_EOL); + } + $item['attachment_dl_notify'] = $fdata[0]; + } + if ('experimenters' === $gxml && 'experimenter' === $fxml) { + $item['experimenter'] = $fdata; + } + if ('approver' === $gxml && 'approver' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one approver in data'.PHP_EOL); + } + $item['referee'] = $fdata[0]; + } + } + if ('paper' === $item_type) { + if ('publication_year' === $gxml && 'publication_year' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one publication year in paper'.PHP_EOL); + } + $item['publication_year'] = empty($fdata[0]) ? 0 : $fdata[0]; + } + if ('journal' === $gxml && 'journal' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one journal in paper'.PHP_EOL); + } + $item['journal'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('volume' === $gxml && 'volume' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one volume in paper'.PHP_EOL); + } + $item['volume'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('number' === $gxml && 'number' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one jo in paper'.PHP_EOL); + } + $item['number'] = empty($fdata[0]) ? null : (int) $fdata[0]; + } + if ('page' === $gxml && 'page' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one jo in paper'.PHP_EOL); + } + $item['page'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('abstract' === $gxml && 'description' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one description in paper'.PHP_EOL); + } + $item['abstract'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('pubmedid' === $gxml && 'pubmedid' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one pubmedid in paper'.PHP_EOL); + } + $item['pubmed_id'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('authors' === $gxml && 'name' === $fxml) { + $item['author'] = $fdata; + } + if ('approver' === $gxml && 'approver' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one approver in paper'.PHP_EOL); + } + $item['referee'] = $fdata[0]; + } + } + if ('presentation' === $item_type) { + if ('date' === $gxml && 'date' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one date in presentation'.PHP_EOL); + } + $d = explode('/', date('Y/m/d', $fdata[0])); + $item['publication_year'] = (int) $d[0]; + $item['publication_month'] = (int) $d[1]; + $item['publication_mday'] = (int) $d[2]; + } + if ('outline' === $gxml && 'description' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one data_type in data'.PHP_EOL); + } + $item['description'] = $fdata[0]; + } + if ('presentation_type' === $gxml && 'presentation_type' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one presentation_type in presentation'.PHP_EOL); + } + $item['presentation_type'] = $fdata[0]; + } + if ('readme' === $gxml && 'readme' === $fxml) { + if (1 < count($fdata)) { + die('does\'nt support more than one readme in presentation'.PHP_EOL); + } + $item['readme'] = empty($fdata[0]) ? '' : $fdata[0]; + } + if ('rights' === $gxml && 'rights' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one rights in presentation'.PHP_EOL); + } + $item['rights'] = substr($fdata[0], 4); + if (false === $item['rights']) { + $item['rights'] = ''; + } + $item['use_cc'] = (int) substr($fdata[0], 0, 1); + $item['cc_commercial_use'] = (int) substr($fdata[0], 1, 1); + $item['cc_modification'] = (int) substr($fdata[0], 2, 1); + } + if ('download_limitation' === $gxml && 'attachment_dl_limit' === $fxml) { + if (1 != count($fdata)) { + die('does\'nt support more than one attachment_dl_limit in presentation'.PHP_EOL); + } + $item['attachment_dl_limit'] = $fdata[0]; + } + if ('download_notification' === $gxml && 'attachment_dl_notify' === $fxml) { + if (1 != count($fdata)) { + die('does\'nt support more than one attachment_dl_notify in presentation'.PHP_EOL); + } + $item['attachment_dl_notify'] = $fdata[0]; + } + if ('creators' === $gxml && 'creator' === $fxml) { + $item['creator'] = $fdata; + } + if ('approver' === $gxml && 'approver' === $fxml) { + if (1 !== count($fdata)) { + die('does\'nt support more than one approver in presentation'.PHP_EOL); + } + $item['referee'] = $fdata[0]; + } + } + if ('tool' === $item_type) { + die('does\'nt support item type tool'.PHP_EOL); + } + } + } + if ('binder' === $item_type) { + $item['extra'] = ''; + $item['item_link'] = $item['related_to']; + $item['related_to'] = []; + } + if (isset($item['attachement_dl_limit']) && 1 === $item['attachement_dl_limit']) { + $limitItems[] = array( + 'item_id' => $item['item_id'], + 'uname' => $item['uname'], + 'uname' => $item['name'], + 'email' => $row['owner'][0]['email'], + ); + foreach ($item['file'] as $file) { + if ('preview' !== $file['file_type_name']) { + $limitFiles[] = $file['file_id']; + } + } + } + $items[] = $item; +} +$xoopsDB->freeRecordSet($res); +MyDumpTool::saveJson('itemsRaw.json', $itemsRaw); +MyDumpTool::saveJson('public/database/items.json', $items); + +$fp = fopen(MYDUMPTOOL_OUTPUTDIR.'/dl-limit-items.csv', 'w'); +foreach ($limitItems as $row) { + $data = array( + $row['item_id'], + $row['uname'], + $row['name'], + $row['email'], + getItemUrl($row), + ); + fputcsv($fp, $data); +} +fclose($fp); + +// files +$basePath = XOOPS_TRUST_PATH.'/uploads/xoonips/item'; +$filesDir = MYDUMPTOOL_OUTPUTDIR.'/public/database/file'; +foreach ($items as $item) { + $item_id = $item['item_id']; + foreach ($item['file'] as $file) { + $file_id = $file['file_id']; + $file_name = $file['original_file_name']; + $file_type = $file['file_type_name']; + $mime_type = $file['mime_type']; + 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.'/'.$item_id.'/'.$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)) { + createThumbnail($src_file, $mime_type, $thumbnail_file); + } + } + } +} + + +// functions +function getItemUrl($row) +{ + $url = XOOPS_URL.'/modules/xoonips/detail.php?'; + if ('' != $row['doi']) { + $url .= 'id='.$row['doi']; + } else { + $url .= 'item_id='.$row['item_id']; + } + + return $url; +} + +function getTreeNode($tree, $root, $index_id) +{ + $children = array(); + if (isset($root[$index_id])) { + foreach ($root[$index_id] as $child_index_id) { + $children[] = getTreeNode($tree, $root, $child_index_id); + } + } + + return array( + '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 = array(); + while ($row = $xoopsDB->fetchArray($res)) { + $index_id = $row['index_id']; + if (isset($tree[$index_id])) { + $idxes = array(); + $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[] = array( + 'index_id' => (int) $index_id, + 'title' => trim(implode(' / ', array_reverse($idxes))), + ); + } + } + $xoopsDB->freeRecordSet($res); + + return $indexes; +} + +function getUsers($item_id) { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + static $users = false; + if (false === $users) { + $users = loadUsers(); + } + $sql = <<< SQL +SELECT * + FROM `${prefix}_xoonips_item_users_link` + WHERE `item_id`=$item_id + ORDER BY `weight` +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $owners = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('item_id', 'uid', 'weight')); + $uid = $row['uid']; + $owners[] = $users[$uid]; + } + $xoopsDB->freeRecordSet($res); + + return $owners; +} + +function loadUsers() { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT * + FROM `${prefix}_users` +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $users = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('uid')); + $uid = $row['uid']; + $users[$uid] = array( + 'uid' => $uid, + 'uname' => $row['uname'], + 'name' => $row['name'], + ); + } + $xoopsDB->freeRecordSet($res); + + return $users; +} + +function getItemTypes() { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT * + FROM `${prefix}_xoonips_item_type` + WHERE `released`=1 + ORDER BY `weight` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $item_types = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('item_type_id')); + $item_type_id = $row['item_type_id']; + $item_type = strtolower($row['name']); + $item_types[$item_type] = array( + 'id' => $item_type_id, + 'name' => $row['name'], + 'description' => $row['description'], + 'groups' => getItemFieldGroups($item_type_id) + ); + } + $xoopsDB->freeRecordSet($res); + + return $item_types; +} +function getItemFieldGroups($item_type_id) { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT `itfgl`.*, + `ifg`.`name`, `ifg`.`xml` + FROM `${prefix}_xoonips_item_type_field_group_link` AS `itfgl` + INNER JOIN `${prefix}_xoonips_item_field_group` AS `ifg` ON `itfgl`.`group_id`=`ifg`.`group_id` + WHERE `itfgl`.`item_type_id`=$item_type_id + AND `itfgl`.`released`=1 + AND `ifg`.`released`=1 + ORDER BY `itfgl`.`weight` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('item_type_id', 'group_id', 'occurrence')); + $xml = $row['xml']; + $group_id = $row['group_id']; + if (isset($ret[$xml])) { + die('Duplicated field group xml found in '.$item_type_id.':'.$group_id.PHP_EOL); + } + $ret[$row['xml']] = array( + 'id' => $group_id, + 'label' => $row['name'], + 'fields' => getItemFieldDetails($group_id), + ); + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function getItemFieldDetails($group_id) { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $sql = <<< SQL +SELECT `ifgfdl`.`group_id`, + `ifd`.*, + `dt`.`name` AS `data_type_name`, + `vt`.`name` AS `view_type_name` + FROM `${prefix}_xoonips_item_field_group_field_detail_link` AS `ifgfdl` + INNER JOIN `${prefix}_xoonips_item_field_detail` AS `ifd` ON `ifgfdl`.`item_field_detail_id`=`ifd`.`item_field_detail_id` + INNER JOIN `${prefix}_xoonips_data_type` AS `dt` ON `ifd`.`data_type_id`=`dt`.`data_type_id` + INNER JOIN `${prefix}_xoonips_view_type` AS `vt` ON `ifd`.`view_type_id`=`vt`.`view_type_id` + WHERE `ifgfdl`.`group_id`=$group_id + AND `ifgfdl`.`released`=1 + AND `ifd`.`released`=1 + AND `ifd`.`detail_display`=1 + ORDER BY `ifgfdl`.`weight` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $ret = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + MyDumpTool::convertToInt($row, array('group_id', 'item_field_detail_id')); + $xml = $row['xml']; + $item_field_detail_id = $row['item_field_detail_id']; + if (isset($ret[$xml])) { + die('Duplicated field detail xml found in '.$group_id.':'.$item_field_id.PHP_EOL); + } + $ret[$row['xml']] = array( + 'id' => $item_field_detail_id, + 'label' => $row['name'], + 'table' => $row['table_name'], + 'column' => $row['column_name'], + 'data_type' => $row['data_type_name'], + 'view_type' => $row['view_type_name'], + 'list' => getSelectList($row['list']) + ); + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} +function getSelectList($name) { + global $xoopsDB; + $prefix = $xoopsDB->prefix(); + $ret = array(); + if ('' === $name) { + return $ret; + } + $sql = <<< SQL +SELECT * + FROM `${prefix}_xoonips_item_field_value_set` + WHERE `select_name`='$name' + ORDER BY `weight` ASC +SQL; + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $ret[$row['title_id']] = $row['title']; + } + $xoopsDB->freeRecordSet($res); + + return $ret; +} + +function getItemDetail($item_id, $item_type) { + global $xoopsDB; + global $itemTypes; + $prefix = $xoopsDB->prefix(); + $itemType = $itemTypes[$item_type]; + $ret = array(); + foreach ($itemType['groups'] as $gxml => $gdata) { + $gret = array(); + foreach ($gdata['fields'] as $fxml => $fdata) { + //var_dump($gxml.':'.$fxml); + $table = $fdata['table']; + $sql = <<< SQL +SELECT * + FROM `${prefix}_${table}` AS `t` + WHERE `item_id`=$item_id +SQL; + switch ($table) { + case 'xoonips_item': + break; + case 'xoonips_item_title': + $sql .= ' AND `item_field_detail_id`='.$fdata['id']; + $sql .= ' ORDER BY `title_id` ASC'; + break; + case 'xoonips_item_keyword': + $sql .= ' ORDER BY `keyword_id` ASC'; + break; + case 'xoonips_item_users_link': + $sql .= ' ORDER BY `weight` ASC'; + break; + case 'xoonips_item_changelog': + $sql .= ' ORDER BY `log_date` DESC'; + break; + case 'xoonips_item_related_to': + break; + case 'xoonips_item_file'; + $sql .= ' AND `group_id`='.$gdata['id']; + break; + case 'xoonips_index_item_link': + $sql .= ' AND `certify_state`=2'; + break; + default: + if (preg_match('/^xoonips_item_extend/', $table)) { + $sql .= ' AND `group_id`='.$gdata['id']; + } else { + die('unsupported table'.PHP_EOL); + } + } + if (!($res = $xoopsDB->query($sql))) { + var_dump($xoopsDB); + exit(); + } + $fret = array(); + while ($row = $xoopsDB->fetchArray($res)) { + MyDumpTool::decode($row); + $value = $row[$fdata['column']]; + switch($fdata['data_type']) { + case 'int': + $value = (int) $value; + break; + } + switch ($fdata['view_type']) { + case 'preview': + case 'file upload': + case 'change log': + $value = $row; + break; + } + $fret[] = $value; + } + $xoopsDB->freeRecordSet($res); + $gret[$fxml] = $fret; + } + $ret[$gxml] = $gret; + } + + return $ret; +} + +function createThumbnail($inputFile, $mimeType, $outputFile) +{ + switch ($mimeType) { + case 'image/jpeg': + $srcImage = imagecreatefromjpeg($inputFile); + break; + case 'image/gif': + $srcImage = imagecreatefromgif($inputFile); + break; + case 'image/png': + $srcImage = imagecreatefrompng($inputFile); + break; + default: + die('unknown image format found'.PHP_EOL); + } + list($srcWidth, $srcHeight) = getimagesize($inputFile); + $maxWidth = 120; + $maxHeight = 120; + $width = $srcWidth; + $height = $srcHeight; + if ($width > $maxWidth) { + $height = intval($height * $maxWidth / $width); + $width = $maxWidth; + } + if ($height > $maxHeight) { + $width = intval($width * $maxHeight / $height); + $height = $maxHeight; + } + $image = imagecreatetruecolor($width, $height); + imagecopyresampled($image, $srcImage, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight); + imagepng($image, $outputFile); + imagedestroy($srcImage); + imagedestroy($image); + if (!file_exists($outputFile)) { + var_dump($outputFile);exit(); + } +} diff --git a/package.json b/package.json index ca3f846..b6a8d2b 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,41 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.25", + "@fortawesome/free-solid-svg-icons": "^5.11.2", + "@fortawesome/react-fontawesome": "^0.1.7", + "@types/async-lock": "^1.1.1", "@types/jest": "24.0.22", + "@types/lokijs": "^1.5.2", "@types/node": "12.12.6", "@types/react": "16.9.11", "@types/react-dom": "16.9.4", + "@types/react-helmet": "^5.0.14", + "@types/react-html-parser": "^2.0.1", + "@types/react-overlays": "^1.1.3", + "@types/react-router-dom": "^5.1.2", + "@types/react-router-hash-link": "^1.2.1", + "@types/xregexp": "^3.0.30", + "async-lock": "^1.2.2", + "axios": "^0.19.0", + "lokijs": "^1.5.8", + "moment": "^2.24.0", + "rc-tree": "3.0.0-alpha.37", "react": "^16.11.0", + "react-app-polyfill": "^1.0.4", + "react-cookie": "^4.0.1", "react-dom": "^16.11.0", + "react-ga": "^2.7.0", + "react-helmet": "^5.2.1", + "react-html-parser": "^2.0.2", + "react-image-lightbox": "^5.1.0", + "react-overlays": "^0.9.1", + "react-router-dom": "^5.1.2", + "react-router-hash-link": "^1.2.2", "react-scripts": "3.2.0", - "typescript": "3.7.2" + "react-spinner-material": "^1.1.3", + "typescript": "3.7.2", + "xregexp": "^4.2.4" }, "scripts": { "start": "react-scripts start", @@ -25,12 +52,14 @@ "production": [ ">0.2%", "not dead", - "not op_mini all" + "not op_mini all", + "ie 11" ], "development": [ "last 1 chrome version", "last 1 firefox version", - "last 1 safari version" + "last 1 safari version", + "ie 11" ] } } diff --git a/public/favicon.ico b/public/favicon.ico index c2c86b8..f984a29 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index c240d2c..67ed982 100644 --- a/public/index.html +++ b/public/index.html @@ -7,37 +7,17 @@ - - + + + + - - React App + OpenNeuro Repository - Welcome
- diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fa313ab..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index bd5d4b5..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..e1e9993 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,21 +1,11 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "OpenNeuro Repository", + "name": "OpenNeuro Repository", "icons": [ { "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", + "sizes": "32x32", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/src/App.css b/src/App.css index afc3885..3a6841e 100644 --- a/src/App.css +++ b/src/App.css @@ -1,22 +1,2 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #09d3ac; -} +@import url("./common/assets/xoops.css"); +@import url("./custom/assets/style.css"); diff --git a/src/App.tsx b/src/App.tsx index 226ee63..8c6b082 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,20 @@ -import React from 'react'; -import logo from './logo.svg'; +import React, { Component } from 'react'; +import { CookiesProvider } from 'react-cookie'; +import { BrowserRouter } from 'react-router-dom'; import './App.css'; +import AppRoot from './common/AppRoot'; -const App: React.FC = () => { - return ( -
- ); +class App extends Component { + + render() { + return ( + + + + + + ); + } } -export default App; +export default App; \ No newline at end of file diff --git a/src/common/AppRoot.tsx b/src/common/AppRoot.tsx new file mode 100644 index 0000000..cf7df19 --- /dev/null +++ b/src/common/AppRoot.tsx @@ -0,0 +1,67 @@ +import React, { Component } from 'react'; +import { ReactCookieProps, withCookies } from 'react-cookie'; +import ReactGA from 'react-ga'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; +import Config, { MultiLang } from '../config'; +import Page from '../custom/Page'; + +interface Props extends RouteComponentProps, ReactCookieProps { } +interface State { + lang: MultiLang; +} + +class AppRoot extends Component { + + constructor(props: Props) { + super(props); + this.state = { lang: 'en' }; + if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') { + ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID); + } + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const params = new URLSearchParams(nextProps.location.search); + const param_lang = params.get('ml_lang'); + const cookie_lang = typeof nextProps.cookies !== 'undefined' ? nextProps.cookies.get('ml_lang') : null; + let lang = param_lang || (typeof cookie_lang === 'string' ? cookie_lang : null) || prevState.lang; + if (lang !== 'en' && lang !== 'ja') { + lang = 'en'; + } + if (cookie_lang !== lang) { + if (typeof nextProps.cookies !== 'undefined') { + nextProps.cookies.set('ml_lang', lang, { path: '/' }); + } + } + if (prevState.lang !== lang) { + return { lang }; + } + return null; + } + + componentDidMount() { + const { pathname } = this.props.location; + if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') { + ReactGA.set({ page: pathname }); + ReactGA.pageview(pathname); + } + } + + componentDidUpdate(prevProps: Props) { + const { pathname, search } = this.props.location; + if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') { + ReactGA.set({ page: pathname }); + ReactGA.pageview(pathname); + } + if (pathname !== prevProps.location.pathname || search !== prevProps.location.search) { + window.scrollTo(0, 0); + } + } + + render() { + const { lang } = this.state; + return ; + } +} + +export default withCookies(withRouter(AppRoot)); diff --git a/src/common/XoopsPathRedirect.tsx b/src/common/XoopsPathRedirect.tsx new file mode 100644 index 0000000..aaf8a18 --- /dev/null +++ b/src/common/XoopsPathRedirect.tsx @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import { Redirect, RouteComponentProps } from 'react-router'; +import { MultiLang } from '../config'; +import PageNotFound from './lib/PageNotFound'; + +interface Params { + module: string; + pathname: string; +} + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +class XoopsPathRedirect extends Component { + + getRedirectUrl() { + const { pathname } = this.props.location; + switch (pathname || '') { + case '/index.php': { + return '/'; + } + } + return ''; + } + + render() { + const { lang } = this.props; + if (this.props.location.pathname === '/') { + return null; + } + const url = this.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/blocks/SelectLang.tsx b/src/common/blocks/SelectLang.tsx new file mode 100644 index 0000000..7c1480b --- /dev/null +++ b/src/common/blocks/SelectLang.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router'; +import { MultiLang } from '../../config'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +const SelectLang = (props: Props) => { + const { lang, history } = props; + const langs: MultiLang[] = ['en', 'ja']; + const titles = { + en: 'English', + ja: 'Japanese', + }; + return ( +
+ + +
+ ); +} + +export default withRouter(SelectLang); \ No newline at end of file diff --git a/src/common/lib/Loading.tsx b/src/common/lib/Loading.tsx new file mode 100644 index 0000000..7eb83da --- /dev/null +++ b/src/common/lib/Loading.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Spinner from 'react-spinner-material'; + +const Loading = () => { + return ( +
+ +
+ ); +} + +export default Loading; \ No newline at end of file diff --git a/src/common/lib/NoticeSiteHasBeenArchived.tsx b/src/common/lib/NoticeSiteHasBeenArchived.tsx new file mode 100644 index 0000000..c44a297 --- /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 = (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; \ No newline at end of file diff --git a/src/common/lib/PageNotFound.tsx b/src/common/lib/PageNotFound.tsx new file mode 100644 index 0000000..cde0a08 --- /dev/null +++ b/src/common/lib/PageNotFound.tsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import Helmet from 'react-helmet'; +import { RouteComponentProps, withRouter } from 'react-router'; +import { MultiLang } from '../../config'; +import Functions from '../../functions'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +class PageNotFound extends Component { + + private url = '/'; + private timer: NodeJS.Timer | null = null; + + goToTopPage() { + if (this.props.location.pathname !== '/') { + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + this.timer = null; + this.props.history.push(this.url); + }, 5000); + } + } + + componentWillUnmount() { + if (this.timer) { + clearTimeout(this.timer); + } + } + + render() { + const { lang } = this.props; + if (process.env.NODE_ENV === 'production') { + this.goToTopPage(); + } + 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 withRouter(PageNotFound); \ No newline at end of file diff --git a/src/common/lib/XoopsCode.tsx b/src/common/lib/XoopsCode.tsx new file mode 100644 index 0000000..ec3a0ae --- /dev/null +++ b/src/common/lib/XoopsCode.tsx @@ -0,0 +1,128 @@ +import React, { ReactElement } from 'react'; +import ReactHtmlParser, { convertNodeToElement } from 'react-html-parser'; +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\]/sg, (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\]/sg, (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, '
'); +} + +interface TransformParsedNode { + type: string; + next: object | null; + prev: object | null; + parent: object | null; + name: string; + attribs: any; + children: object[]; + data: string; +} + +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 transform = (node: object, idx: number): ReactElement | null | void => { + const node_ = node as TransformParsedNode; + if (node_.type === 'tag' && node_.name === 'a') { + const url = node_.attribs && node_.attribs['href']; + const isExternal = !url || /^(mailto|https?:?\/\/)/.test(url); + if (!isExternal) { + const style = (node_.attribs && node_.attribs['style']) || ''; + const title = (node_.attribs && node_.attribs['title']) || ''; + return {node_.children.map((value: object, index: number) => { + return convertNodeToElement(value, index, transform); + })}; + } + } +} + +const XoopsCode = (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 })}
; +} + +export default XoopsCode; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..cc73b35 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,17 @@ +const SITE_TITLE = 'OpenNeuro Repository'; +const SITE_SLOGAN = 'Welcome'; +const GOOGLE_ANALYTICS_TRACKING_ID = 'UA-122146455-1'; +const XOONIPS_ITEMTYPES = ['binder', 'paper', 'data', 'tool', 'presentation', 'model']; +const PICO_MODULES = ['credits']; + +export type MultiLang = 'en' | 'ja'; + +const Config = { + SITE_TITLE, + SITE_SLOGAN, + GOOGLE_ANALYTICS_TRACKING_ID, + XOONIPS_ITEMTYPES, + PICO_MODULES, +} + +export default Config; \ No newline at end of file diff --git a/src/custom/Categories.tsx b/src/custom/Categories.tsx new file mode 100644 index 0000000..6659aad --- /dev/null +++ b/src/custom/Categories.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import { Link } from 'react-router-dom'; +import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived'; +import Config, { MultiLang } from '../config'; +import Functions from '../functions'; + +interface Props { + lang: MultiLang; +} + + +const Categories: React.FC = (props: Props) => { + const { lang } = props; + const title = Functions.mlang('[en]Search by Categories[/en][ja]カテゴリから検索[/ja]', lang); + return ( +
+ + {title} - {Config.SITE_TITLE} + +

{title}

+ +
+
+
{Functions.mlang('[ja]動物種[/ja][en]Species[/en]', lang)}
+
+
    +
  • {Functions.mlang('[ja]ヒト[/ja][en]Human[/en]', lang)}
  • +
  • {Functions.mlang('[ja]非ヒト霊長目[/ja][en]Nonhuman Primate[/en]', lang)}
  • +
  • {Functions.mlang('[ja]齧歯目[/ja][en]Rodents[/en]', lang)}
  • +
  • {Functions.mlang('[ja]上記以外の脊椎動物[/ja][en]Other Vertebrates[/en]', lang)}
  • +
  • {Functions.mlang('[ja]節足動物[/ja][en]Arthropod[/en]', lang)}
  • +
  • {Functions.mlang('[ja]節足動物以外の無脊椎動物[/ja][en]Other Invertebrates[/en]', lang)}
  • +
  • {Functions.mlang('[ja]植物[/ja][en]Plant[/en]', lang)}
  • +
  • {Functions.mlang('[ja]その他[/ja][en]Others[/en]', lang)}
  • +
+
+
+
+
{Functions.mlang('[ja]スケール[/ja][en]Scale[/en]', lang)}
+
    +
  • {Functions.mlang('[ja]分子レベル[/ja][en]Molecular level[/en]', lang)}
  • +
  • {Functions.mlang('[ja]細胞レベル[/ja][en]Cellular level[/en]', lang)}
  • +
  • {Functions.mlang('[ja]組織・ネットワークレベル[/ja][en]Tissue/Network level[/en]', lang)}
  • +
  • {Functions.mlang('[ja]個体レベル[/ja][en]Individual level[/en]', lang)}
  • +
  • {Functions.mlang('[ja]個体集団レベル[/ja][en]Population level[/en]', lang)}
  • +
  • {Functions.mlang('[ja]マルチレベル[/ja][en]Multiple levels[/en]', lang)}
  • +
  • {Functions.mlang('[ja]その他[/ja][en]Others[/en]', lang)}
  • +
+
+
+
+
{Functions.mlang('[ja]メソッド[/ja][en]Method[/en]', lang)}
+
+
    +
  • {Functions.mlang('[ja]電気生理[/ja][en]Electrophysiology[/en]', lang)}
  • +
  • {Functions.mlang('[ja]脳イメージング[/ja][en]Brain Imaging[/en]', lang)}
  • +
  • {Functions.mlang('[ja]数理モデル・解析[/ja][en]Mathematical Modeling/Analysis[/en]', lang)}
  • +
  • {Functions.mlang('[ja]シミュレーション[/ja][en]Simulation[/en]', lang)}
  • +
  • {Functions.mlang('[ja]分子生物学[/ja][en]Molecular Biology[/en]', lang)}
  • +
  • {Functions.mlang('[ja]細胞シグナル伝達[/ja][en]Cell Signaling[/en]', lang)}
  • +
  • {Functions.mlang('[ja]心理物理学[/ja][en]Psychophysics[/en]', lang)}
  • +
  • {Functions.mlang('[ja]神経遺伝学[/ja][en]Neurogenetics[/en]', lang)}
  • +
  • {Functions.mlang('[ja]計算論的神経科学[/ja][en]Computational Neuroscience[/en]', lang)}
  • +
  • {Functions.mlang('[ja]行動学的神経科学[/ja][en]Behavioral Neuroscience[/en]', lang)}
  • +
  • {Functions.mlang('[ja]臨床神経科学[/ja][en]Clinical Neuroscience[/en]', lang)}
  • +
  • {Functions.mlang('[ja]統合神経科学[/ja][en]Integrative Neuroscience[/en]', lang)}
  • +
  • {Functions.mlang('[ja]ブレインマシンインタフェイス[/ja][en]Brain Machine Interface[/en]', lang)}
  • +
+
+
+
+
+
+
{Functions.mlang('[ja]タイトル[/ja][en]Title[/en]', lang)}
+
+
    +
  • A - E
  • +
  • F - J
  • +
  • K - N
  • +
  • O - S
  • +
  • T - X
  • +
  • Y - Z
  • +
  • Others
  • +
+
+
+
+
{Functions.mlang('[ja]コンテンツ分野[/ja][en]Research Areas[/en]', lang)}
+
+
    +
  • Visiome Platform
  • +
  • Brain Machine Interface Platform
  • +
  • Invertebrate Brain Platform
  • +
  • Brain Transcriptome Database
  • +
  • Cerebellar Platform
  • +
  • NeuroImaging Platform
  • +
  • Dynamic Brain Platform
  • +
  • Comprehensive Brain Science Platform
  • +
  • Mouse Phenotype Database
  • +
  • Simulation Platform
  • +
  • Brain Science Dictionary
  • +
  • ViBrism Database
  • +
  • CBS Neuroinformatics
  • +
  • Comparative Neuroscience Platform
  • +
+
+
+
+ +
+ ); +} + +export default Categories; diff --git a/src/custom/Footer.tsx b/src/custom/Footer.tsx new file mode 100644 index 0000000..0b6cb1e --- /dev/null +++ b/src/custom/Footer.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { MultiLang } from '../config'; + +interface Props { + lang: MultiLang; +} + +const Footer: React.FC = (props: Props) => { + return ( +
+ 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..08d9fe4 --- /dev/null +++ b/src/custom/Header.tsx @@ -0,0 +1,89 @@ +import React, { ChangeEvent, Component, FormEvent } from 'react'; +import { Link, RouteComponentProps, withRouter } from 'react-router-dom'; +import SelectLang from '../common/blocks/SelectLang'; +import Config, { MultiLang } from '../config'; +import Functions from '../functions'; +import ItemUtil from '../database/lib/ItemUtil'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + keyword: string; + showCreditsMenu: boolean; +} + +class Header extends Component { + constructor(props: Props) { + super(props); + this.handleChangeKeyword = this.handleChangeKeyword.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.onMouseEnter = this.onMouseEnter.bind(this); + this.onMouseLeave = this.onMouseLeave.bind(this); + this.state = { keyword: '', showCreditsMenu: false }; + } + + handleChangeKeyword(event: ChangeEvent) { + const keyword = event.target.value; + this.setState({ keyword }); + } + + handleSubmit(event: FormEvent) { + event.preventDefault(); + const keyword = this.state.keyword.trim(); + if (keyword !== '') { + const url = ItemUtil.getSearchByKeywordUrl('all', this.state.keyword); + this.props.history.push(url); + } + } + + onMouseEnter() { + this.setState({ showCreditsMenu: true }); + } + + onMouseLeave() { + this.setState({ showCreditsMenu: false }); + } + + render() { + const { lang } = this.props; + return ( +
+
+ {Config.SITE_TITLE} +
+
+
+ INCF Japan Node +
+
+ +
+
+
+
+
+
    +
  • {Functions.mlang('[en]Contents[/en][ja]コンテンツ[/ja]', lang)}
  • +
  • {Functions.mlang('[en]Categories[/en][ja]カテゴリから検索[/ja]', lang)}
  • +
  • {Functions.mlang('[en]Overview[/en][ja]概要[/ja]', lang)}
  • +
  • {Functions.mlang('[en]News[/en][ja]ニュース[/ja]', lang)}
  • +
  • {Functions.mlang('[en]Credits[/en][ja]サイト情報[/ja]', lang)} + {this.state.showCreditsMenu && +
    + Instruction for Use + Copyrights + Privacy Policy + About Us +
    + } +
  • +
+
+ ); + + } +} + +export default withRouter(Header); diff --git a/src/custom/News.tsx b/src/custom/News.tsx new file mode 100644 index 0000000..82b9f96 --- /dev/null +++ b/src/custom/News.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived'; +import Config, { MultiLang } from '../config'; +import Functions from '../functions'; + +interface Props { + lang: MultiLang; +} + +const News: React.FC = (props: Props) => { + const { lang } = props; + const ctitle = Functions.mlang('[en]News[/en][ja]ニュース[/ja]', lang); + const title = 'What\'s new'; + return ( +
+ + {title} - {ctitle} - {Config.SITE_TITLE} + +

{ctitle}

+ +
+ {title} +
+
+ Poster : nijcadmin +  on 2018-05-29 15:51:21 +
+
+
+
    +
  • We start accepting new datasets and contents from you. Just select "User Menu » Register" for a workflow to register your dataset.
  • +
  • INCF J-Node OpenNeuro Repository has been officially public at the begining of July, 2018.
  • +
  • Based on a policy of RIKEN, OpenNeuro Repository will be closed soon within March, 2019, although we had just opened this service.
  • +
+
+

IMPORTANT NOTICE:

+
    +
  • OpenNeuro Repository has stopped its service based on the new policy of INCF J-node.
  • +
  • Please DO NOT reposit new contents in this database any more.
  • +
  • Please DO NOT try to create a new account.
  • +
+
+
+
+
+ ); +} + +export default News; diff --git a/src/custom/Overview.tsx b/src/custom/Overview.tsx new file mode 100644 index 0000000..f726f8a --- /dev/null +++ b/src/custom/Overview.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived'; +import Config, { MultiLang } from '../config'; +import Functions from '../functions'; +import imageOverview1 from './assets/images/overview1.png'; +import imageOverview2 from './assets/images/overview2.png'; + +interface Props { + lang: MultiLang; +} + +const Overview: React.FC = (props: Props) => { + const { lang } = props; + const title = Functions.mlang('[en]Overview of OpenNeuro Repository[/en][ja]OpenNeuro Repository 概要[/ja]', lang); + return ( +
+ + {title} - {Config.SITE_TITLE} + +

{title}

+ +
+ Overview +
+

+ The INCF J-Node OpenNeuro Repository archives research contents, such as primary data (the first-order information) used for peer-reviewed published papers and program codes for data analysis or computational models as supplemental materials of the papers. + It covers a variety of contents at multiple scales of time and space, ranging from molecular level to organ and individual levels, across species. The registered contents will be reused by the communities of neuroscience to promote reproducibility, transparency and data accessibility of the research. +

+
+ Sharing data for your published work with OpenNuero Repository! +
+ +
+ ); +} + +export default Overview; diff --git a/src/custom/Page.tsx b/src/custom/Page.tsx new file mode 100644 index 0000000..c55ce8e --- /dev/null +++ b/src/custom/Page.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import XoopsPathRedirect from '../common/XoopsPathRedirect'; +import Config, { MultiLang } from '../config'; +import Database from '../database/Database'; +import DatabaseXoopsPathRedirect from '../database/DatabaseXoopsPathRedirect'; +import Pico from '../pico/Pico'; +import PicoXoopsPathRedirect from '../pico/PicoXoopsPathRedirect'; +import Categories from './Categories'; +import Footer from './Footer'; +import Header from './Header'; +import News from './News'; +import Overview from './Overview'; +import Welcome from './Welcome'; + +interface Props { + lang: MultiLang; +} + +const Page: React.FC = (props: Props) => { + const { lang } = props; + return
+
+
+
+ + } /> + } /> + } /> + } /> + } /> + {Config.PICO_MODULES.map((name) => + } /> + )} + } /> + {Config.PICO_MODULES.map((name) => + } /> + )} + + +
+
+
+
; +} + +export default Page; diff --git a/src/custom/Welcome.tsx b/src/custom/Welcome.tsx new file mode 100644 index 0000000..460d8c8 --- /dev/null +++ b/src/custom/Welcome.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived'; +import Config, { MultiLang } from '../config'; +import imageOpenAccess from './assets/images/open-access.png'; + +interface Props { + lang: MultiLang; +} + +const Welcome: React.FC = (props: Props) => { + const { lang } = props; + return ( +
+ + {Config.SITE_TITLE} - {Config.SITE_SLOGAN} + +

Welcome

+ +
+ Open Access ! +
+

Welcome to J-Node OpenNeuro repository!

+

+ The INCF Japan Node platform OpenNeuro repository provides a reliable opportunity for researchers in the field of experimental and computational neuroscience to share supplemental materials to their peer-reviewed publications in academic journals and conference proceedings that are available in the public domain. + It aims at playing a core role for promoting and accelerating neuroscience. + Toward this end, we encourage databasing of +

+
    +
  • complete sets of experimental image and/or time-series data,
  • +
  • audiovisual materials,
  • +
  • computational models,
  • +
+

+ which have been described and/or analyzed in the publications, in particular if they are too large in size and/or too complicated to make them public as usual supplemental-material-mechanisms provided by academic journals, or if the authors wish to retain unique services of INCF Japan Node and J-Node OpenNeuro repository such as real-time cloud computing services for the data registered in J-Node OpenNeuro repository. + Each of those materials may be provided by standardized human/machine readable formats, such as model description languages, commonly utilized program source codes and/or executable/machine-readable binary codes. J-Node OpenNeuro repository will welcome those materials prior to publication to guarantee data accessibility, which will be accessible only for limited number of peer-reviewers during review process and then opened in the public domain immediately after publication. +

+
+ ); +} + +export default Welcome; diff --git a/src/custom/assets/images/incf-japan-node.png b/src/custom/assets/images/incf-japan-node.png new file mode 100644 index 0000000..b6ab2ed Binary files /dev/null and b/src/custom/assets/images/incf-japan-node.png differ diff --git a/src/custom/assets/images/indent.png b/src/custom/assets/images/indent.png new file mode 100644 index 0000000..3763ebc Binary files /dev/null and b/src/custom/assets/images/indent.png differ diff --git a/src/custom/assets/images/logo.png b/src/custom/assets/images/logo.png new file mode 100644 index 0000000..724efdf Binary files /dev/null and b/src/custom/assets/images/logo.png differ diff --git a/src/custom/assets/images/open-access.png b/src/custom/assets/images/open-access.png new file mode 100644 index 0000000..19e16e9 Binary files /dev/null and b/src/custom/assets/images/open-access.png differ diff --git a/src/custom/assets/images/overview1.png b/src/custom/assets/images/overview1.png new file mode 100644 index 0000000..eb99950 Binary files /dev/null and b/src/custom/assets/images/overview1.png differ diff --git a/src/custom/assets/images/overview2.png b/src/custom/assets/images/overview2.png new file mode 100644 index 0000000..13324e3 Binary files /dev/null and b/src/custom/assets/images/overview2.png differ diff --git a/src/custom/assets/style.css b/src/custom/assets/style.css new file mode 100644 index 0000000..94611e9 --- /dev/null +++ b/src/custom/assets/style.css @@ -0,0 +1,399 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root, +#page { + height: 100%; + width: 100%; +} + +body { + font-family: Arial, Helvetica, sans-serif; + color: #888888; + background-color: #fffeee; + margin: 0; + font-size: 90%; +} + +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; + border-left: solid 15px #f44903; + /* background: url(images/indent.png) no-repeat left center; */ + text-indent: 10px; +} + +h2 { + font-size: 1.602em; + border-left: solid 10px #f44903; + text-indent: 10px; +} + +h3 { + font-size: 1.424em; + border-left: solid 5px #f44903; + text-indent: 10px; +} + +h4 { + font-size: 1.266em; +} + +h5 { + font-size: 1.125em; +} + +h6 { + font-size: 1em; +} + +a { + color: #f44903; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +table { + width: 100%; +} + +td, +th { + padding: 10px; + text-align: left; + color: #000000; +} +th, +td.head { + vertical-align: top; +} +td { + vertical-align: middle; +} + +th { + font-weight: bold; + border-bottom: 1px solid #ccc; + background-color: #e0e0e0; +} + +tr.even { + background-color: #f9f9f9; +} + +tr.odd { + background-color: #fdfdfd; +} + +.head, +.foot { + font-weight: bold; + color: #000000; + background-color: #e0e0e0; +} + +.even { + background-color: #f9f9f9; +} + +.odd { + background-color: #fdfdfd; +} + +.clearfix::after { + content: ""; + display: block; + clear: both; +} + +.hidden { + display: none; +} + +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.font-weight-bold { + font-weight: bold; +} + +.font-italic { + font-style: italic; +} + +.block .blockTitle { + height: 21px; + margin-bottom: 0.4rem; + padding: 0 10px 0 32px; + color: #369; + line-height: 21px; + font-weight: bold; +} + +.block .blockContent { + padding: 0 10px; + font-size: 13px; +} + +header { + min-width: 960px; + padding-top: 26px; +} + +header .logo { + display: block; + float: left; + width: 260px; + height: 100px; + background-image: url(./images/logo.png); +} + +header .utility { + float: right; + text-align: right; +} + +header .utility .incf-japan-node { + margin: 10px 10px 10px 0; + display: block; + float: left; + width: 160px; + height: 32px; + background-image: url(./images/incf-japan-node.png); +} + +header .utility .search-box { + margin: 10px 0 10px 10px; + float: right; +} + +header .utility .search-box input { + width: 195px; + padding: 9px 10px; + border: none; + background: none; + font: normal 11px Arial, Helvetica, sans-serif; + color: #808080; + border: solid 3px #e99e0f; + border-radius: 10px; +} + +header ul.mainmenu { + margin: 0 40px; + padding: 0; + text-align: right; + list-style-type: none; +} + +header ul.mainmenu li { + margin: 0 2px; + width: 120px; + height: 35px; + line-height: 35px; + font-size: 0.8em; + display: inline-block; + text-align: center; + font-weight: bold; + color: #888888; + background-color: #ffffff; + border: 1px #cccccc solid; + border-width: 1px 1px 0 1px; +} + +header ul.mainmenu li a { + color: #888888; +} + +header ul.mainmenu li .submenu { + width: 120px; + position: relative; + float: left; + top: -1px; + background-color: #e0e0e0; + z-index: 10; + animation: fadeIn 1s ease 0s 1 normal; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +header ul.mainmenu li .submenu .item { + display: block; + margin: 1px; + background-color: #ffffff; +} + +footer { + min-width: 960px; + margin: 20px auto; + text-align: center; +} + +#page { + width: 960px; + margin: 0 auto; + line-height: 1.5; +} + +#main { + padding: 20px 20px; + color: #ffffff; + background-color: #000000; + width: 960px; +} + +.mainContent { + margin: 0 20px; +} + +.welcome, +.overview { + text-align: justify; +} + +.welcome ul { + padding-left: 20px; + margin: 10px 0; +} + +.welcome ul li { + list-style-type: square; +} + +.overview .overview1, +.overview .overview2 { + text-align: center; +} + +.news .itemHead { + padding: 10px; + border-bottom: 1px #f0f0f0 solid; +} +.news .itemTitle { + font-weight: bold; + font-size: 1.266em; +} +.news .itemInfo { + text-align: right; + padding: 3px; + font-size: 85%; +} +.news .itemInfo .username { + color: #f44903; +} +.news .itemText ul { + padding-left: 20px; +} +.news .itemText .notice { + font-weight: bold; + margin-left: 20px; +} +.news .itemText .notice ul { + padding-left: 0; +} +.news .itemText .notice ul li { + list-style-type: none; +} +.categories .container { + float: left; + width: 270px; + margin: 8px; + background-color: #e8e8e8; + border-radius: 5px; +} +.categories .container.reseach_areas { + width: 556px; +} +.categories .name { + padding: 10px 0 10px 14px; + font-size: 13px; + font-weight: bold; + color: #000000; +} +.categories .content { + padding: 10px; + height: 389px; + border: 1px solid #e8e8e8; + background-color: #ffffff; + border-radius: 5px; + line-height: 1.7; +} +.categories .content ul { + margin: 10px 0; + padding-left: 20px; +} + +#main .bottom_of_content_body, +#main .jumpToTop { + text-align: right; +} + +#main .pico_body { + margin: 0; + padding: 0; +} + +#main .openneuroFreeKeyword > div:not(:last-child), +#main .openneuroAuthor > div:not(:last-child) { + padding-bottom: 10px; + border-bottom: 1px solid #333333; +} +#main .openneuroFreeKeyword > div:not(:first-child), +#main .openneuroAuthor > div:not(:first-child) { + padding-top: 10px; +} +#main .openneuroRelatedTo > tbody > tr > td { + padding: 10px 0; +} +#main .openneuroRelatedTo > tbody > tr:first-child > td { + padding-top: 0; +} +#main .openneuroRelatedTo > tbody > tr:last-child > td { + padding-bottom: 0; +} +.listIcon { + width: 80px; + text-align: center; +} +.listIcon img { + vertical-align: middle; +} +.listBody { + vertical-align: top; +} diff --git a/src/database/Database.module.css b/src/database/Database.module.css new file mode 100644 index 0000000..8ea86ba --- /dev/null +++ b/src/database/Database.module.css @@ -0,0 +1,70 @@ +.database { + margin: 0; +} + +.database :global(.list) { + width: 100%; +} + +.database :global(.listTable) { + width: 100%; + border-collapse: separate; + border-spacing: 5px; +} + +.database :global(.listTable .listIcon), +.database :global(.listTable .listExtra) { + vertical-align: middle; + text-align: center; + width: 65px; + line-height: 0; +} + +.database :global(.itemDetail) { + border-collapse: separate; + border-spacing: 1px; +} +.database :global(.itemDetail .head) { + width: 30%; +} + +.database :global(.itemDetail .readme), +.database :global(.itemDetail .rights) { + background-color: #ffffff; + width: 100%; + max-height: 200px; + overflow: auto; +} + +.database :global(.advancedSearch .head) { + width: 30%; +} + +.database :global(.advancedSearch .search) { + text-align: center; + margin: 10px; +} + +.database :global(.advancedSearch .itemtype) { + margin-bottom: 5px; +} + +.database :global(.advancedSearch .itemtype .itemtypeName), +.database :global(.advancedSearch .itemtype .itemtypeFields) { + border-collapse: separate; + border-spacing: 1px; +} + +.database :global(.advancedSearch .itemtype .itemtypeName th) { + padding: 5px; +} + +.database :global(.advancedSearch .fieldDateLabel) { + display: inline-block; + width: 50px; +} + +.database :global(.advancedSearch .fieldDate select), +.database :global(.advancedSearch .fieldDate input) { + margin: 0 5px 0 0; +} diff --git a/src/database/Database.tsx b/src/database/Database.tsx new file mode 100644 index 0000000..51a6e3a --- /dev/null +++ b/src/database/Database.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import styles from './Database.module.css'; +import DatabaseAdvancedSearch from './DatabaseAdvancedSearch'; +import DatabaseDetailItem from './DatabaseDetailItem'; +import DatabaseSearchByAdvancedKeyword from './DatabaseSearchByAdvancedKeyword'; +import DatabaseSearchByIndexId from './DatabaseSearchByIndexId'; +import DatabaseSearchByItemType from './DatabaseSearchByItemType'; +import DatabaseSearchByKeyword from './DatabaseSearchByKeyword'; +import DatabaseTop from './DatabaseTop'; + +interface Props { + lang: MultiLang; +} + +const Database = (props: Props) => { + const { lang } = props; + return ( +
+ + {Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} - {Functions.siteTitle(lang)} + + + } /> + ) => } /> + ) => } /> + } /> + ) => } /> + ) => } /> + } /> + } /> + ) => } /> + ) => } /> + + +
+ ); +} + +export default Database; \ No newline at end of file diff --git a/src/database/DatabaseAdvancedSearch.tsx b/src/database/DatabaseAdvancedSearch.tsx new file mode 100644 index 0000000..6375e4c --- /dev/null +++ b/src/database/DatabaseAdvancedSearch.tsx @@ -0,0 +1,48 @@ +import React, { Component } from 'react'; +import { RouteComponentProps } from 'react-router'; +import Config, { MultiLang } from '../config'; +import Functions from '../functions'; +import ItemType from './item-type'; +import AdvancedSearchQuery from './lib/AdvancedSearchQuery'; +import ItemUtil from './lib/ItemUtil'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +class DatabaseAdvancedSearch extends Component { + + private query: AdvancedSearchQuery = new AdvancedSearchQuery(); + + constructor(props: Props) { + super(props); + this.handleClickSearchButton = this.handleClickSearchButton.bind(this); + } + + handleClickSearchButton() { + if (!this.query.empty()) { + const url = ItemUtil.getSearchByAdvancedKeywordsUrl(this.query); + this.props.history.push(url); + } + } + + render() { + const { lang } = this.props; + return ( +
+

{Functions.mlang('[en]Search Items[/en][ja]アイテム検索[/ja]', lang)}

+
+ +
+ {Config.XOONIPS_ITEMTYPES.map((type) => { + return ; + })} +
+ +
+
+ ); + } +} + +export default DatabaseAdvancedSearch; \ No newline at end of file diff --git a/src/database/DatabaseDetailItem.tsx b/src/database/DatabaseDetailItem.tsx new file mode 100644 index 0000000..0a469ef --- /dev/null +++ b/src/database/DatabaseDetailItem.tsx @@ -0,0 +1,89 @@ +import React, { Component } from 'react'; +import Helmet from 'react-helmet'; +import { RouteComponentProps } from 'react-router'; +import Loading from '../common/lib/Loading'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import ItemType from './item-type'; +import ItemUtil, { Item } from './lib/ItemUtil'; + +interface Params { + id: string; + doi: string; +} + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + loading: boolean; + item: Item | null; +} + +class DatabaseDetailItem extends Component { + + private id: number; + private doi: string; + + constructor(props: Props) { + super(props); + this.state = { + loading: true, + item: null, + }; + const { params } = this.props.match; + this.id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0; + this.doi = typeof params.doi !== 'undefined' ? params.doi : ''; + } + + componentDidMount() { + this.updateItem(); + } + + componentDidUpdate() { + const { params } = this.props.match; + const id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0; + const doi = typeof params.doi !== 'undefined' ? params.doi : ''; + if (this.id !== id || this.doi !== doi) { + this.id = id; + this.doi = doi; + this.updateItem(); + } + } + + updateItem() { + if (this.doi !== '') { + ItemUtil.getByDoi(this.doi, (item) => { + this.setState({ loading: false, item }); + }); + } else if (this.id !== 0) { + ItemUtil.get(this.id, (item) => { + this.setState({ loading: false, item }); + }); + } + } + + render() { + const { lang } = this.props; + if (this.state.loading) { + return ; + } + if (this.state.item === null) { + return ; + } + return ( + <> + + {Functions.mlang(this.state.item.title, lang)} - {Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} - {Functions.siteTitle(lang)} + +

{Functions.mlang('[en]Item Detail[/en][ja]アイテム詳細[/ja]', lang)}

+
+ + + ); + } +} + +export default DatabaseDetailItem; \ No newline at end of file diff --git a/src/database/DatabaseSearchByAdvancedKeyword.tsx b/src/database/DatabaseSearchByAdvancedKeyword.tsx new file mode 100644 index 0000000..72427aa --- /dev/null +++ b/src/database/DatabaseSearchByAdvancedKeyword.tsx @@ -0,0 +1,56 @@ +import React, { Component } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { MultiLang } from '../config'; +import AdvancedSearchQuery from './lib/AdvancedSearchQuery'; +import DatabaseListItem from './lib/DatabaseListItem'; +import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil'; + +interface Props extends RouteComponentProps { + lang: MultiLang +} + +interface State { + search: string; + query: AdvancedSearchQuery; +} + +class DatabaseSearchByAdvancedKeyword extends Component { + + constructor(props: Props) { + super(props); + const search = props.location.search; + const query = ItemUtil.getAdvancedSearchQueryByQuery(search); + this.state = { search, query }; + this.searchFunc = this.searchFunc.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const search = nextProps.location.search; + if (prevState.search !== search) { + const query = ItemUtil.getAdvancedSearchQueryByQuery(search); + return { search, query }; + } + return null; + } + + getUrl() { + return ItemUtil.getSearchByAdvancedKeywordsUrl(this.state.query); + } + + searchFunc(condition: SortCondition, func: SearchCallbackFunc) { + ItemUtil.getListByAdvancedSearchQuery(this.state.query, condition, func); + } + + render() { + const { lang } = this.props; + const baseUrl = this.getUrl(); + return ( +
+

Listing item

+ +
+ ); + } +} + +export default DatabaseSearchByAdvancedKeyword; diff --git a/src/database/DatabaseSearchByIndexId.tsx b/src/database/DatabaseSearchByIndexId.tsx new file mode 100644 index 0000000..0ece7d8 --- /dev/null +++ b/src/database/DatabaseSearchByIndexId.tsx @@ -0,0 +1,89 @@ +import React, { Component, Fragment } from 'react'; +import Helmet from 'react-helmet'; +import { RouteComponentProps } from 'react-router'; +import { Link } from 'react-router-dom'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import DatabaseListIndex from './lib/DatabaseListIndex'; +import DatabaseListItem from './lib/DatabaseListItem'; +import IndexUtil, { Index, INDEX_ID_PUBLIC } from './lib/IndexUtil'; +import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil'; + +interface Params { + id: string; +} + +export interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + indexId: number; +} + +class DatabaseSearchByIndexId extends Component { + + constructor(props: Props) { + super(props); + const { params } = props.match; + this.state = { + indexId: params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : INDEX_ID_PUBLIC, + } + this.searchFunc = this.searchFunc.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const { params } = nextProps.match; + const indexId = params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : INDEX_ID_PUBLIC; + if (prevState.indexId !== indexId) { + return { indexId }; + } + return null; + } + + getUrl() { + if (this.state.indexId === 0) { + return '/'; + } + return IndexUtil.getUrl(this.state.indexId); + } + + searchFunc(condition: SortCondition, func: SearchCallbackFunc) { + if (this.state.indexId === 0) { + const res = { total: 0, data: [] }; + func(res); + } else { + ItemUtil.getListByIndexId(this.state.indexId, condition, func); + } + } + + render() { + const { lang } = this.props; + const index = IndexUtil.get(this.state.indexId); + if (index === null) { + return ; + } + const baseUrl = this.getUrl(); + const pIndexes = IndexUtil.getParents(this.state.indexId); + const parents = pIndexes.map((value: Index) => { + const url: string = IndexUtil.getUrl(value.id); + const title = Functions.mlang(value.title, lang); + return / {title} ; + }); + const title = pIndexes.map((value) => { return '/' + Functions.mlang(value.title, lang); }).join(''); + return ( +
+ + {Functions.mlang(title, lang)} - {Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} - {Functions.siteTitle(lang)} + +

{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}

+
{parents}
+ + +
+ ); + } +} + +export default DatabaseSearchByIndexId; diff --git a/src/database/DatabaseSearchByItemType.tsx b/src/database/DatabaseSearchByItemType.tsx new file mode 100644 index 0000000..257538b --- /dev/null +++ b/src/database/DatabaseSearchByItemType.tsx @@ -0,0 +1,73 @@ +import React, { Component } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import DatabaseListItem from './lib/DatabaseListItem'; +import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil'; + +interface Params { + itemType: string; + subItemType: string; +} + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + itemType: string; + subItemType: string; +} + +class DatabaseSearchByItemType extends Component { + + constructor(props: Props) { + super(props); + const { params } = this.props.match; + this.state = { + itemType: params.itemType ? params.itemType : '', + subItemType: params.subItemType ? params.subItemType : '', + }; + this.searchFunc = this.searchFunc.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const { params } = nextProps.match; + const item_type = params.itemType ? params.itemType : ''; + const sub_item_type = params.subItemType ? params.subItemType : ''; + if (prevState.itemType !== item_type || prevState.subItemType !== sub_item_type) { + return { item_type, sub_item_type }; + } + return null; + } + + searchFunc(condition: SortCondition, func: SearchCallbackFunc) { + if (this.state.itemType === '') { + const res = { total: 0, data: [] }; + func(res); + } else { + ItemUtil.getListByItemType(this.state.itemType, this.state.subItemType, condition, func); + } + } + + getUrl() { + let url = ItemUtil.getItemTypeSearchUrl(this.state.itemType); + if (this.state.subItemType !== '') { + url += '/' + this.state.subItemType; + } + return url; + } + + render() { + const { lang } = this.props; + const baseUrl = this.getUrl(); + return ( +
+

{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}

+ +
+ ); + } +} + +export default DatabaseSearchByItemType; diff --git a/src/database/DatabaseSearchByKeyword.tsx b/src/database/DatabaseSearchByKeyword.tsx new file mode 100644 index 0000000..8961fd8 --- /dev/null +++ b/src/database/DatabaseSearchByKeyword.tsx @@ -0,0 +1,60 @@ +import React, { Component } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import DatabaseListItem from './lib/DatabaseListItem'; +import ItemUtil, { KeywordSearchType, SearchCallbackFunc, SortCondition } from './lib/ItemUtil'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + type: KeywordSearchType; + keyword: string; +} + +class DatabaseSearchByKeyword extends Component { + + constructor(props: Props) { + super(props); + const { type, keyword } = ItemUtil.getSearchKeywordByQuery(this.props.location.search); + this.state = { type, keyword }; + this.searchFunc = this.searchFunc.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const { type, keyword } = ItemUtil.getSearchKeywordByQuery(nextProps.location.search); + if (prevState.type !== type || prevState.keyword !== keyword) { + return { type, keyword }; + } + return null; + } + + getUrl() { + return ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword); + } + + searchFunc(condition: SortCondition, func: SearchCallbackFunc) { + if (this.state.keyword === '') { + const res = { total: 0, data: [] }; + func(res); + } else { + ItemUtil.getListByKeyword(this.state.type, this.state.keyword, condition, func); + } + } + + render() { + const { lang } = this.props; + const baseUrl = this.getUrl(); + return ( +
+

{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}

+

{Functions.mlang('[en]Search Keyword[/en][ja]検索キーワード[/ja]', lang)} : {this.state.keyword}

+ +
+ ); + } +} + +export default DatabaseSearchByKeyword; diff --git a/src/database/DatabaseTop.module.css b/src/database/DatabaseTop.module.css new file mode 100644 index 0000000..a950542 --- /dev/null +++ b/src/database/DatabaseTop.module.css @@ -0,0 +1,20 @@ +.itemTypes .itemType { + width: 50%; + padding: 5px; + vertical-align: top; +} + +.itemTypes .itemType :global(table) { + width: auto; +} + +.itemTypes .itemType :global(table .itemTypeName) { + vertical-align: middle; + font-size: large; +} + +.itemTypes .itemType > div { + padding: 10px; + background-color: white; + color: black; +} diff --git a/src/database/DatabaseTop.tsx b/src/database/DatabaseTop.tsx new file mode 100644 index 0000000..3e88a06 --- /dev/null +++ b/src/database/DatabaseTop.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import Config, { MultiLang } from '../config'; +import styles from './DatabaseTop.module.css'; +import ItemType from './item-type'; + +interface Props { + lang: MultiLang; +} + +const DatabaseTop = (props: Props) => { + const { lang } = props; + const types: string[][] = []; + const len = Config.XOONIPS_ITEMTYPES.length; + for (let i = 0; i < Math.ceil(len / 2); i++) { + const j = i * 2; + const p = Config.XOONIPS_ITEMTYPES.slice(j, j + 2); + types.push(p); + } + return ( + + + {types.map((value, idx) => { + return ( + + {value.map((type, idx) => { + return ( + + ); + })} + + ); + })} + +
+ {type !== '' && } +
+ ); +} + +export default DatabaseTop; diff --git a/src/database/DatabaseXoopsPathRedirect.tsx b/src/database/DatabaseXoopsPathRedirect.tsx new file mode 100644 index 0000000..a38c0d2 --- /dev/null +++ b/src/database/DatabaseXoopsPathRedirect.tsx @@ -0,0 +1,150 @@ +import React, { Component } from 'react'; +import { Redirect, RouteComponentProps, withRouter } from 'react-router'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +class DatabaseXoopsPathRedirect extends Component { + + getRedirectUrl(): string { + const { location } = this.props; + const pathname = location.pathname || ''; + const query = new URLSearchParams(location.search); + const search = new RegExp('^/modules/xoonips(?:/+(.*))?$'); + const matches = pathname.match(search); + if (matches === null) { + return ''; + } + const path = matches[1] || ''; + switch (path) { + case '': + case 'index.php': { + return '/database'; + } + case 'detail.php': { + const id = query.get('id'); + if (id !== null) { + return '/database/item/id/' + Functions.escape(id); + } + const itemId = query.get('item_id'); + if (itemId !== null && itemId.match(/^\d+$/) !== null) { + return '/database/item/' + Functions.escape(itemId); + } + return ''; + } + case 'listitem.php': { + const indexId = query.get('index_id'); + if (indexId !== null && indexId.match(/^\d+$/) !== null) { + const params = new URLSearchParams(); + const map: any = { + orderby: { key: 'orderby', isNumber: false }, + order_dir: { key: 'order_dir', isNumber: true }, + itemcount: { key: 'itemcount', isNumber: true }, + page: { key: 'page', isNumber: true } + }; + for (let k in map) { + const v = query.get(k); + if (v === null || v.length === 0) { + continue; + } + if (map[k].isNumber && v.match(/^\d+$/) !== null) { + continue; + } + params.set(map[k].key, v); + } + const paramStr = params.toString(); + return '/database/list/' + Functions.escape(indexId) + (paramStr.length > 0 ? '?' + paramStr : ''); + } + break; + } + case 'itemselect.php': { + const op = query.get('op'); + if (op === null) { + break; + } + switch (op) { + case 'quicksearch': { + const keyword = query.get('keyword'); + const itemType = query.get('search_itemtype'); + if (keyword === null || itemType === null || keyword === '') { + return ''; + } + const type = itemType.replace('xnp', ''); + if (itemType !== 'basic' && itemType !== 'all' && itemType.match(/^xnp.+/) === null) { + return ''; + } + const params = new URLSearchParams({ type, keyword }); + const map: any = { + orderby: { key: 'orderby', isNumber: false }, + orderdir: { key: 'order_dir', isNumber: true }, + item_per_page: { key: 'itemcount', isNumber: true }, + page: { key: 'page', isNumber: true } + }; + for (let k in map) { + const v = query.get(k); + if (v === null || v.length === 0) { + continue; + } + if (map[k].isNumber && v.match(/^\d+$/) !== null) { + continue; + } + params.set(map[k].key, v); + } + return '/database/search?' + params.toString(); + } + case 'itemtypesearch': { + const itemType = query.get('search_itemtype'); + if (itemType === null || itemType.match(/^xnp.+/) === null) { + return ''; + } + const type = itemType.replace('xnp', ''); + return '/database/search/itemtype/' + Functions.escape(type); + } + case 'itemsubtypesearch': { + let type = ''; + let subtype = ''; + query.forEach((v, k) => { + if (k.match(/^xnp[a-z]+$/) !== null && !!v) { + type = k.replace('xnp', ''); + return; + } + }) + if (type === '') { + return ''; + } + query.forEach((v, k) => { + if (k.match(`^xnp${type}_.+$`) !== null && !!v) { + subtype = v; + return; + } + }); + if (subtype === '') { + return ''; + } + return '/database/search/itemtype/' + Functions.escape(type) + '/' + Functions.escape(subtype); + } + } + return ''; + } + case 'advanced_search.php': { + return '/database/advanced'; + } + } + return ''; + } + + render() { + const { lang } = this.props; + const url = this.getRedirectUrl(); + if (url === '') { + return ; + } + return ; + } +} + +export default withRouter(DatabaseXoopsPathRedirect); diff --git a/src/database/assets/images/icon_binder.gif b/src/database/assets/images/icon_binder.gif new file mode 100644 index 0000000..c438f03 Binary files /dev/null and b/src/database/assets/images/icon_binder.gif differ diff --git a/src/database/assets/images/icon_book.gif b/src/database/assets/images/icon_book.gif new file mode 100644 index 0000000..6deea1b Binary files /dev/null and b/src/database/assets/images/icon_book.gif differ diff --git a/src/database/assets/images/icon_conference.gif b/src/database/assets/images/icon_conference.gif new file mode 100644 index 0000000..ec56f37 Binary files /dev/null and b/src/database/assets/images/icon_conference.gif differ diff --git a/src/database/assets/images/icon_data.gif b/src/database/assets/images/icon_data.gif new file mode 100644 index 0000000..30d35c8 Binary files /dev/null and b/src/database/assets/images/icon_data.gif differ diff --git a/src/database/assets/images/icon_files.gif b/src/database/assets/images/icon_files.gif new file mode 100644 index 0000000..e102eea Binary files /dev/null and b/src/database/assets/images/icon_files.gif differ diff --git a/src/database/assets/images/icon_folder.gif b/src/database/assets/images/icon_folder.gif new file mode 100644 index 0000000..7a69a82 Binary files /dev/null and b/src/database/assets/images/icon_folder.gif differ diff --git a/src/database/assets/images/icon_memo.gif b/src/database/assets/images/icon_memo.gif new file mode 100644 index 0000000..e6b4cd1 Binary files /dev/null and b/src/database/assets/images/icon_memo.gif differ diff --git a/src/database/assets/images/icon_model.gif b/src/database/assets/images/icon_model.gif new file mode 100644 index 0000000..493d673 Binary files /dev/null and b/src/database/assets/images/icon_model.gif differ diff --git a/src/database/assets/images/icon_paper.gif b/src/database/assets/images/icon_paper.gif new file mode 100644 index 0000000..d01d631 Binary files /dev/null and b/src/database/assets/images/icon_paper.gif differ diff --git a/src/database/assets/images/icon_presentation.gif b/src/database/assets/images/icon_presentation.gif new file mode 100644 index 0000000..c7e1b31 Binary files /dev/null and b/src/database/assets/images/icon_presentation.gif differ diff --git a/src/database/assets/images/icon_simulator.gif b/src/database/assets/images/icon_simulator.gif new file mode 100644 index 0000000..a9f359e Binary files /dev/null and b/src/database/assets/images/icon_simulator.gif differ diff --git a/src/database/assets/images/icon_stimulus.gif b/src/database/assets/images/icon_stimulus.gif new file mode 100644 index 0000000..09de6b8 Binary files /dev/null and b/src/database/assets/images/icon_stimulus.gif differ diff --git a/src/database/assets/images/icon_tool.gif b/src/database/assets/images/icon_tool.gif new file mode 100644 index 0000000..b71b3f6 Binary files /dev/null and b/src/database/assets/images/icon_tool.gif differ diff --git a/src/database/assets/images/icon_url.gif b/src/database/assets/images/icon_url.gif new file mode 100644 index 0000000..e4f1dfa Binary files /dev/null and b/src/database/assets/images/icon_url.gif differ diff --git a/src/database/assets/images/simpf_button.png b/src/database/assets/images/simpf_button.png new file mode 100644 index 0000000..1f93ad6 Binary files /dev/null and b/src/database/assets/images/simpf_button.png differ diff --git a/src/database/assets/images/star.gif b/src/database/assets/images/star.gif new file mode 100644 index 0000000..f90543e Binary files /dev/null and b/src/database/assets/images/star.gif differ diff --git a/src/database/assets/images/tree_line.png b/src/database/assets/images/tree_line.png new file mode 100644 index 0000000..2865cd2 Binary files /dev/null and b/src/database/assets/images/tree_line.png differ diff --git a/src/database/assets/images/tree_node.png b/src/database/assets/images/tree_node.png new file mode 100644 index 0000000..b23e4b1 Binary files /dev/null and b/src/database/assets/images/tree_node.png differ diff --git a/src/database/blocks/IndexTree.module.css b/src/database/blocks/IndexTree.module.css new file mode 100644 index 0000000..7b1427b --- /dev/null +++ b/src/database/blocks/IndexTree.module.css @@ -0,0 +1,91 @@ +.indexTree { + border-top: 1px solid #999999; + border-left: 1px solid #999999; + border-bottom: 1px solid #404040; + border-right: 1px solid #404040; + background-color: white; + height: 400px; + width: calc(100% - 6px); + overflow: auto; + margin: 0 auto; + padding: 3px; + line-height: 100%; +} + +.formButton { + margin: 3px 3px 10px; +} + +.indexTree div span { + color: #333; + font-size: 12px; + font-weight: bold; + line-height: 19px; + vertical-align: bottom; +} + +.indexTree div span:hover { + color: #f60; +} + +.indexTree:global(.rc-tree .rc-tree-treenode) { + line-height: 20px; + white-space: nowrap; +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent) { + display: inline-block; +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit) { + display: inline-block; + height: 20px; + width: 9px; + background: url(../assets/images/tree_line.png); +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:not(:last-child)) { + background-position: -9px 0; +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:not(:last-child).rc-tree-indent-unit-end) { + background-position: -18px 0; +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:last-child.rc-tree-indent-unit-end) { + background-position: -27px 0; +} + +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-switcher) { + display: inline-block; + height: 20px; + width: 16px; + margin-right: 2px; + background: url(../assets/images/tree_node.png); + cursor: pointer; +} +.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher-noop) { + background-position: 0 0; + cursor: auto; +} +.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher_open) { + background-position: -16px 0; +} +.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher_close) { + background-position: -32px 0; +} +.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher-noop) { + background-position: 0 -20px; + cursor: auto; +} +.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_open) { + background-position: -16px -20px; +} +.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_close) { + background-position: -32px -20px; +} + +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper) { + display: inline-block; + height: 20px; + cursor: pointer; +} +.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper .rc-tree-title) { + vertical-align: middle; + line-height: 20px; +} \ No newline at end of file diff --git a/src/database/blocks/IndexTree.tsx b/src/database/blocks/IndexTree.tsx new file mode 100644 index 0000000..148e2cb --- /dev/null +++ b/src/database/blocks/IndexTree.tsx @@ -0,0 +1,124 @@ +import Tree from 'rc-tree'; +import { DataNode, EventDataNode } from 'rc-tree/lib/interface'; +import React, { Component, ReactText } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router'; +import { MultiLang } from '../../config'; +import Functions from '../../functions'; +import IndexUtil, { Index, INDEX_ID_PUBLIC } from '../lib/IndexUtil'; +import styles from './IndexTree.module.css'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + tree: DataNode[]; + expandableKeys: string[]; + expandedKeys: string[]; + selectedKeys: number[]; +} + +class IndexTree extends Component { + + constructor(props: Props) { + super(props); + this.handleClickOpenAll = this.handleClickOpenAll.bind(this); + this.handleClickCloseAll = this.handleClickCloseAll.bind(this); + this.handleExpand = this.handleExpand.bind(this); + this.handleSelect = this.handleSelect.bind(this); + const res = this.load(); + this.state = { + tree: res.elements, + expandableKeys: res.keys, + expandedKeys: res.expandedKeys, + selectedKeys: [], + }; + } + + load() { + const { lang } = this.props; + let keys: string[] = []; + let eKeys: string[] = []; + const makeTreeNode = (index: Index, depth: number): DataNode => { + const title = Functions.mlang(index.title, lang) + (index.numOfItems > 0 ? ' (' + index.numOfItems + ')' : ''); + const children = IndexUtil.getChildren(index.id); + if (children.length === 0) { + return { key: String(index.id), title: title }; + } + const childTreeNodes = children.map((value: Index) => { + return makeTreeNode(value, depth + 1); + }); + if (depth < 1) { + eKeys.push(String(index.id)); + } + keys.push(String(index.id)); + return { key: String(index.id), title: title, children: childTreeNodes }; + }; + let elements: DataNode[] = []; + const index = IndexUtil.get(INDEX_ID_PUBLIC); + if (index !== null) { + elements.push(makeTreeNode(index, 0)); + } + return { + elements: elements, + keys: keys, + expandedKeys: eKeys, + } + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const { lang } = this.props; + if (prevProps.lang !== lang) { + const res = this.load(); + this.setState({ + tree: res.elements, + selectedKeys: [], + }); + } + } + + handleClickOpenAll() { + this.setState({ expandedKeys: this.state.expandableKeys }) + } + + handleClickCloseAll() { + this.setState({ expandedKeys: [] }) + } + + handleExpand(expandedKeys: ReactText[], info: { + node: EventDataNode; + expanded: boolean; + nativeEvent: MouseEvent; + }): void { + const keys: string[] = expandedKeys.map((key) => { + return typeof key === 'string' ? key : String(key); + }); + this.setState({ expandedKeys: keys }); + } + + handleSelect(selectedKeys: ReactText[], info: { + event: 'select'; + selected: boolean; + node: EventDataNode; + selectedNodes: DataNode[]; + nativeEvent: MouseEvent; + }): void { + const selectedKey = selectedKeys.shift() || 0; + const key = typeof selectedKey === 'string' ? parseInt(selectedKey, 10) : selectedKey; + const url = IndexUtil.getUrl(key); + this.props.history.push(url); + this.setState({ selectedKeys: [] }); + } + + render() { + return ( +
+ + + +
+ ); + } +} + +export default withRouter(IndexTree); diff --git a/src/database/blocks/Search.tsx b/src/database/blocks/Search.tsx new file mode 100644 index 0000000..3ea0738 --- /dev/null +++ b/src/database/blocks/Search.tsx @@ -0,0 +1,90 @@ +import React, { ChangeEvent, Component, FormEvent } from 'react'; +import { Link, RouteComponentProps, withRouter } from 'react-router-dom'; +import Config, { MultiLang } from '../../config'; +import Functions from '../../functions'; +import ItemUtil, { KeywordSearchType } from '../lib/ItemUtil'; + +interface Props extends RouteComponentProps { + lang: MultiLang; +} + +interface State { + type: KeywordSearchType; + keyword: string; + pathname: string; +} + +class Search extends Component { + + constructor(props: Props) { + super(props); + this.state = { + type: 'all', + keyword: '', + pathname: '', + }; + this.handleChangeType = this.handleChangeType.bind(this); + this.handleChangeKeyword = this.handleChangeKeyword.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + const pathname = nextProps.location.pathname; + if (pathname !== prevState.pathname) { + if (pathname === '/database/search') { + const { type, keyword } = ItemUtil.getSearchKeywordByQuery(nextProps.location.search); + return { type, keyword, pathname }; + } + return { + type: prevState.type, + keyword: prevState.keyword, + pathname + }; + } + return null; + } + + handleChangeKeyword(event: ChangeEvent) { + const keyword = event.target.value.trim(); + this.setState({ keyword }); + } + + handleChangeType(event: ChangeEvent) { + const type = event.target.value as KeywordSearchType; + this.setState({ type }); + } + + handleSubmit(event: FormEvent) { + event.preventDefault(); + const url = ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword); + this.props.history.push(url); + } + + render() { + const { lang } = this.props; + const options = [ + { value: 'all', label: '[en]All[/en][ja]全て[/ja]' }, + { value: 'basic', label: '[en]Title & Keyword[/en][ja]タイトル&キーワード[/ja]' }, + ]; + Config.XOONIPS_ITEMTYPES.forEach((type) => { + options.push({value: type, label: Functions.pascalCase(type)}) + }); + return ( +
+ +    + +
+ +      + {Functions.mlang('[en]Advanced[/en][ja]詳細検索[/ja]', lang)} +
+ ); + } +} + +export default withRouter(Search); \ No newline at end of file diff --git a/src/database/item-type/binder/BinderAdvancedSearch.tsx b/src/database/item-type/binder/BinderAdvancedSearch.tsx new file mode 100644 index 0000000..55c33b3 --- /dev/null +++ b/src/database/item-type/binder/BinderAdvancedSearch.tsx @@ -0,0 +1,26 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class BinderAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'binder'; + this.title = 'Binder'; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + ]; + return rows; + } +} + +export default BinderAdvancedSearch; diff --git a/src/database/item-type/binder/BinderDetail.tsx b/src/database/item-type/binder/BinderDetail.tsx new file mode 100644 index 0000000..5da597c --- /dev/null +++ b/src/database/item-type/binder/BinderDetail.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import Functions from '../../../functions'; +import { ItemBinder } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import OpenNeuroFreeKeyword from '../lib/field/OpenNeuroFreeKeyword'; +import OpenNeuroRelatedTo from '../lib/field/OpenNeuroRelatedTo'; + +interface Props { + lang: MultiLang; + item: ItemBinder; +} + +class BinderDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemBinder; + // OPENNEURO: + const getResearchAreas = (value: string) => { + const list: { [key: string]: string } = { + visiome: "Visiome Platform", + bmi: "Brain Machine Interface Platform", + invbrain: "Invertebrate Brain Platform", + braintx: "Brain Transcriptome Database", + cerebellum: "Cerebellar Platform", + nimg: "NeuroImaging Platform", + dynamicbrain: "Dynamic Brain Platform", + cbsn: "Comprehensive Brain Science Platform", + "mouse-phenotype": "Mouse Phenotype Database", + sim: "Simulation Platform", + bsd: "Brain Science Dictionary", + vibrism: "ViBrism Database", + "cbs-ni": "CBS Neuroinformatics", + cns: "Comparative Neuroscience Platform" + }; + return list.hasOwnProperty(value) ? Functions.mlang(list[value], lang) : ''; + }; + const getSpecies = (value: string) => { + const list: { [key: string]: string } = { + "_human_": "[ja]ヒト[/ja][en]Human[/en]", + "_nonhuman primate_": "[ja]非ヒト霊長目[/ja][en]Nonhuman Primate[/en]", + "_rodents_": "[ja]齧歯目[/ja][en]Rodents[/en]", + "_other vertebrates_": "[ja]上記以外の脊椎動物[/ja][en]Other Vertebrates[/en]", + "_arthropod_": "[ja]節足動物[/ja][en]Arthropod[/en]", + "_other invertebrates_": "[ja]節足動物以外の無脊椎動物[/ja][en]Other Invertebrates[/en]", + "_plant_": "[ja]植物[/ja][en]Plant[/en]", + "_others_": "[ja]その他[/ja][en]Others[/en]" + }; + return list.hasOwnProperty(value) ? Functions.mlang(list[value], lang) : ''; + } + const getScale = (value: string) => { + const list: { [key: string]: string } = { + "molecular level": "[ja]分子レベル[/ja][en]Molecular level[/en]", + "cellular level": "[ja]細胞レベル[/ja][en]Cellular level[/en]", + "tissue/network level": "[ja]組織・ネットワークレベル[/ja][en]Tissue/Network level[/en]", + "individual level": "[ja]個体レベル[/ja][en]Individual level[/en]", + "population level": "[ja]個体集団レベル[/ja][en]Population level[/en]", + "multiple levels": "[ja]マルチレベル[/ja][en]Multiple levels[/en]", + "others": "[ja]その他[/ja][en]Others[/en]" + + }; + return list.hasOwnProperty(value) ? Functions.mlang(list[value], lang) : ''; + } + const getMethod = (value: string) => { + const list: { [key: string]: string } = { + "electrophysiology": "[ja]電気生理[/ja][en]Electrophysiology[/en]", + "brain imaging": "[ja]脳イメージング[/ja][en]Brain Imaging[/en]", + "mathematical modeling/analysis": "[ja]数理モデル・解析[/ja][en]Mathematical Modeling/Analysis[/en]", + "simulation": "[ja]シミュレーション[/ja][en]Simulation[/en]", + "molecular biology": "[ja]分子生物学[/ja][en]Molecular Biology[/en]", + "cell signaling": "[ja]細胞シグナル伝達[/ja][en]Cell Signaling[/en]", + "psychophysics": "[ja]心理物理学[/ja][en]Psychophysics[/en]", + "neurogenetics": "[ja]神経遺伝学[/ja][en]Neurogenetics[/en]", + "computational neuroscience": "[ja]計算論的神経科学[/ja][en]Computational Neuroscience[/en]", + "behavioral neuroscience": "[ja]行動学的神経科学[/ja][en]Behavioral Neuroscience[/en]", + "clinical neuroscience": "[ja]臨床神経科学[/ja][en]Clinical Neuroscience[/en]", + "integrative neuroscience": "[ja]統合神経科学[/ja][en]Integrative Neuroscience[/en]", + "brain machine interface": "[ja]ブレインマシンインタフェイス[/ja][en]Brain Machine Interface[/en]" + }; + return list.hasOwnProperty(value) ? Functions.mlang(list[value], lang) : ''; + } + return [ + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: 'ID', value: item.doi }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[ja]コンテンツ分野[/ja][en]Research Areas[/en]', value: getResearchAreas(item.research_area) }, + { label: '[ja]動物種[/ja][en]Species[/en]', value: getSpecies(item.species) }, + { label: '[ja]スケール[/ja][en]Scale[/en]', value: getScale(item.scale) }, + { label: '[ja]メソッド[/ja][en]Method[/en]', value: getMethod(item.method) }, + { label: '[ja]サマリー[/ja][en]Summary[/en]', value: }, + { label: '[ja]フリーキーワード[/ja][en]Free keyword[/en]', value: }, + { label: '[ja]同意[/ja][en]Agree[/en]', value: Functions.mlang(item.agree, lang) }, + { label: '[ja]最終更新日[/ja][en]Last Update Date[/en]', value: }, + { label: '[ja]作成日[/ja][en]Creation Date[/en]', value: }, + { label: '[ja]所有者[/ja][en]Contributor[/en]', value: }, + { label: '[ja]査読者[/ja][en]Referee[/en]', value: Functions.mlang(item.referee, lang) }, + { label: '[ja]更新履歴[/ja][en]Change Log[/en]', value: }, + { label: '[ja]関連アイテム[/ja][en]Related Item[/en]', value: }, + ]; + } +} + +export default BinderDetail; \ No newline at end of file diff --git a/src/database/item-type/binder/BinderList.tsx b/src/database/item-type/binder/BinderList.tsx new file mode 100644 index 0000000..fe982ab --- /dev/null +++ b/src/database/item-type/binder/BinderList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_binder.gif'; +import { ItemBinder } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class BinderList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Binder'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemBinder; + // OPENNEURO: + return ( + <> + {Functions.mlang(item.title, lang)} + + ); + } +} + +export default BinderList; diff --git a/src/database/item-type/binder/BinderTop.tsx b/src/database/item-type/binder/BinderTop.tsx new file mode 100644 index 0000000..0e383ca --- /dev/null +++ b/src/database/item-type/binder/BinderTop.tsx @@ -0,0 +1,16 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_binder.gif'; + +class BinderTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'binder'; + this.label = 'Binder'; + this.icon = iconFile; + // OPENNEURO: + this.description = 'Binder'; + } +} + +export default BinderTop; \ No newline at end of file diff --git a/src/database/item-type/binder/index.tsx b/src/database/item-type/binder/index.tsx new file mode 100644 index 0000000..3ae15c1 --- /dev/null +++ b/src/database/item-type/binder/index.tsx @@ -0,0 +1,13 @@ +import BinderAdvancedSearch from './BinderAdvancedSearch'; +import BinderDetail from './BinderDetail'; +import BinderList from './BinderList'; +import BinderTop from './BinderTop'; + +const ItemTypeBinder = { + Top: BinderTop, + List: BinderList, + Detail: BinderDetail, + AdvancedSearch: BinderAdvancedSearch, +}; + +export default ItemTypeBinder; diff --git a/src/database/item-type/book/BookAdvancedSearch.tsx b/src/database/item-type/book/BookAdvancedSearch.tsx new file mode 100644 index 0000000..b08534f --- /dev/null +++ b/src/database/item-type/book/BookAdvancedSearch.tsx @@ -0,0 +1,38 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class BookAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'book'; + this.title = 'Book'; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['author'] = ''; + this.state.values['editor'] = ''; + this.state.values['publisher'] = ''; + this.state.values['publication_year'] = ''; + this.state.values['isbn'] = ''; + this.state.values['file.book_pdf.original_file_name'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Book Title[/en][ja]著書名[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Author[/en][ja]著者[/ja]', value: this.renderFieldInputText('author', 50) }, + { label: '[en]Editor[/en][ja]編集者[/ja]', value: this.renderFieldInputText('editor', 50) }, + { label: '[en]Publisher[/en][ja]出版社[/ja]', value: this.renderFieldInputText('publisher', 50) }, + { label: '[en]Publication Year[/en][ja]出版年[/ja]', value: this.renderFieldInputText('publication_year', 10) }, + { label: 'ISBN', value: this.renderFieldInputText('isbn', 50) }, + { label: '[en]PDF File[/en][ja]PDF ファイル[/ja]', value: this.renderFieldInputText('file.book_pdf.original_file_name', 50) }, + ]; + return rows; + } +} + +export default BookAdvancedSearch; diff --git a/src/database/item-type/book/BookDetail.tsx b/src/database/item-type/book/BookDetail.tsx new file mode 100644 index 0000000..9099abe --- /dev/null +++ b/src/database/item-type/book/BookDetail.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemBook } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; + +class BookDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemBook; + return [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Book Title[/en][ja]著書名[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Author[/en][ja]著者[/ja]', value: }, + { label: '[en]Editor[/en][ja]編集者[/ja]', value: Functions.mlang(item.editor, lang) }, + { label: '[en]Publisher[/en][ja]出版社[/ja]', value: Functions.mlang(item.publisher, lang) }, + { label: '[en]Publication Year[/en][ja]出版年[/ja]', value: item.publication_year }, + { label: 'URL', value: {item.url} }, + { label: '[en]PDF File[/en][ja]PDF ファイル[/ja]', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + } +} + +export default BookDetail; diff --git a/src/database/item-type/book/BookList.tsx b/src/database/item-type/book/BookList.tsx new file mode 100644 index 0000000..9267be1 --- /dev/null +++ b/src/database/item-type/book/BookList.tsx @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_book.gif'; +import { ItemBook } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class BookList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Book'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemBook; + const authors = item.author.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {authors} + + ); + } +} + +export default BookList; diff --git a/src/database/item-type/book/BookTop.tsx b/src/database/item-type/book/BookTop.tsx new file mode 100644 index 0000000..4fb2c35 --- /dev/null +++ b/src/database/item-type/book/BookTop.tsx @@ -0,0 +1,15 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_book.gif'; + +class BookTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'book'; + this.label = 'Book'; + this.icon = iconFile; + this.description = '[en]Related book collection.[/en][ja]関連書籍[/ja]'; + } +} + +export default BookTop; \ No newline at end of file diff --git a/src/database/item-type/book/index.tsx b/src/database/item-type/book/index.tsx new file mode 100644 index 0000000..3187d8b --- /dev/null +++ b/src/database/item-type/book/index.tsx @@ -0,0 +1,13 @@ +import BookTop from './BookTop'; +import BookList from './BookList'; +import BookDetail from './BookDetail'; +import BookAdvancedSearch from './BookAdvancedSearch'; + +const ItemTypeBook = { + Top: BookTop, + List: BookList, + Detail: BookDetail, + AdvancedSearch: BookAdvancedSearch, +}; + +export default ItemTypeBook; diff --git a/src/database/item-type/conference/ConferenceAdvancedSearch.tsx b/src/database/item-type/conference/ConferenceAdvancedSearch.tsx new file mode 100644 index 0000000..505835c --- /dev/null +++ b/src/database/item-type/conference/ConferenceAdvancedSearch.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { ItemConferenceSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class ConferenceAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'conference'; + this.title = 'Conference'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['presentation_type'] = ''; + this.state.values['author'] = ''; + this.state.values['conference_from_year'] = year; + this.state.values['conference_from_month'] = month; + this.state.values['conference_from_mday'] = mday; + this.state.values['conference_to_year'] = year; + this.state.values['conference_to_month'] = month; + this.state.values['conference_to_mday'] = mday; + this.setIgnoreKey('conference_from_year'); + this.setIgnoreKey('conference_from_month'); + this.setIgnoreKey('conference_from_mday'); + this.setIgnoreKey('conference_to_year'); + this.setIgnoreKey('conference_to_month'); + this.setIgnoreKey('conference_to_mday'); + } + + renderDate() { + return ( + <> +
+ {this.renderFieldDate('From', 'conference_from_year', 'conference_from_month', 'conference_from_mday')} + +
+
+ {this.renderFieldDate('To', 'conference_to_year', 'conference_to_month', 'conference_to_mday')} +
+ + ); + } + + getRows() { + const rows = [ + { label: '[en]Presentation Title[/en][ja]発表議題[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]', value: this.renderFieldSelect('presentation_type', ItemConferenceSubTypes) }, + { label: '[en]Author[/en][ja]発表者[/ja]', value: this.renderFieldInputText('author', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderDate() } + ]; + return rows; + } +} + +export default ConferenceAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/conference/ConferenceDetail.tsx b/src/database/item-type/conference/ConferenceDetail.tsx new file mode 100644 index 0000000..6d2d6e3 --- /dev/null +++ b/src/database/item-type/conference/ConferenceDetail.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemConference } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import ConferenceUtil from './ConferenceUtil'; + +class ConferenceDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemConference; + return [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Conference Title[/en][ja]学会名[/ja]', value: Functions.mlang(item.conference_title, lang) }, + { label: '[en]Place[/en][ja]開催地[/ja]', value: item.place }, + { label: '[en]Date[/en][ja]日付[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Presentation Title[/en][ja]発表議題[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Author[/en][ja]発表者[/ja]', value: }, + { label: '[en]Abstract[/en][ja]要約[/ja]', value: }, + { label: '[en]Presentation File[/en][ja]発表資料[/ja]', value: }, + { label: '[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]', value: }, + { label: '[en]Conference Paper[/en][ja]学会資料[/ja]', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + } +} + +export default ConferenceDetail; \ No newline at end of file diff --git a/src/database/item-type/conference/ConferenceList.tsx b/src/database/item-type/conference/ConferenceList.tsx new file mode 100644 index 0000000..b03298c --- /dev/null +++ b/src/database/item-type/conference/ConferenceList.tsx @@ -0,0 +1,33 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import { ItemConference } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; +import ConferenceUtil from './ConferenceUtil'; +import iconFile from '../../assets/images/icon_conference.gif'; +import Functions from '../../../functions'; + +class ConferenceList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Conference'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemConference; + const authors = item.author.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {Functions.mlang(item.conference_title, lang)} ()
+ {authors} + + ); + } +} + +export default ConferenceList; \ No newline at end of file diff --git a/src/database/item-type/conference/ConferenceTop.tsx b/src/database/item-type/conference/ConferenceTop.tsx new file mode 100644 index 0000000..8ba23b7 --- /dev/null +++ b/src/database/item-type/conference/ConferenceTop.tsx @@ -0,0 +1,17 @@ +import { ItemConferenceSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_conference.gif'; + +class ConferenceTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'conference'; + this.label = 'Conference'; + this.icon = iconFile; + this.description = '[en]Electrical presentation files for conference.[/en][ja]学会発表[/ja]'; + this.subTypes = ItemConferenceSubTypes; + } +} + +export default ConferenceTop; \ No newline at end of file diff --git a/src/database/item-type/conference/ConferenceUtil.tsx b/src/database/item-type/conference/ConferenceUtil.tsx new file mode 100644 index 0000000..6cdbe2b --- /dev/null +++ b/src/database/item-type/conference/ConferenceUtil.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemConference, ItemConferenceSubType, ItemConferenceSubTypes } from '../../lib/ItemUtil'; + +interface PresentationTypeProps { + lang: MultiLang; + type: ItemConferenceSubType; +} + +const PresentationType = (props: PresentationTypeProps) => { + const { type } = props; + const subtype = ItemConferenceSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +interface ConferenceDateProps { + lang: MultiLang; + item: ItemConference; +} + +const ConferenceDate = (props: ConferenceDateProps) => { + const { item } = props; + const monthStr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const from = 'From: ' + monthStr[item.conference_from_month - 1] + ' ' + item.conference_from_mday + ', ' + item.conference_from_year; + const to = 'To: ' + monthStr[item.conference_to_month - 1] + ' ' + item.conference_to_mday + ', ' + item.conference_to_year; + return ({from} {to}); +} + +const ConferenceUtil = { + PresentationType, + ConferenceDate, +} + +export default ConferenceUtil; \ No newline at end of file diff --git a/src/database/item-type/conference/index.tsx b/src/database/item-type/conference/index.tsx new file mode 100644 index 0000000..75272c4 --- /dev/null +++ b/src/database/item-type/conference/index.tsx @@ -0,0 +1,13 @@ +import ConferenceTop from './ConferenceTop'; +import ConferenceList from './ConferenceList'; +import ConferenceDetail from './ConferenceDetail'; +import ConferenceAdvancedSearch from './ConferenceAdvancedSearch'; + +const ItemTypeConference = { + Top: ConferenceTop, + List: ConferenceList, + Detail: ConferenceDetail, + AdvancedSearch: ConferenceAdvancedSearch, +}; + +export default ItemTypeConference; \ No newline at end of file diff --git a/src/database/item-type/data/DataAdvancedSearch.tsx b/src/database/item-type/data/DataAdvancedSearch.tsx new file mode 100644 index 0000000..e408d8b --- /dev/null +++ b/src/database/item-type/data/DataAdvancedSearch.tsx @@ -0,0 +1,46 @@ +import { ItemDataSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class DataAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'data'; + this.title = 'Data'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['data_type'] = ''; + this.state.values['experimenter'] = ''; + this.state.values['publication_year'] = year; + this.state.values['publication_month'] = month; + this.state.values['publication_mday'] = mday; + this.state.values['file.preview.caption'] = ''; + this.state.values['file.data_file.original_file_name'] = ''; + this.setIgnoreKey('publication_year'); + this.setIgnoreKey('publication_month'); + this.setIgnoreKey('publication_mday'); + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Data Type[/en][ja]データタイプ[/ja]', value: this.renderFieldSelect('data_type', ItemDataSubTypes) }, + { label: '[en]Experimenter[/en][ja]実験者[/ja]', value: this.renderFieldInputText('experimenter', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + { label: '[en]Data File[/en][ja]データファイル[/ja]', value: this.renderFieldInputText('file.data_file.original_file_name', 50) }, + ]; + return rows; + } +} + +export default DataAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/data/DataDetail.tsx b/src/database/item-type/data/DataDetail.tsx new file mode 100644 index 0000000..6578faf --- /dev/null +++ b/src/database/item-type/data/DataDetail.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import Functions from '../../../functions'; +import ItemUtil, { ItemData } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import OpenNeuroAuthor from '../lib/field/OpenNeuroAuthor'; +import OpenNeuroFreeKeyword from '../lib/field/OpenNeuroFreeKeyword'; +import OpenNeuroRelatedTo from '../lib/field/OpenNeuroRelatedTo'; +import SimPFLinkIcon from '../lib/field/SimPFLinkIcon'; +import DataUtil from './DataUtil'; + +class DataDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemData; + // OPENNEURO: + const fields = [ + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[ja]概要[/ja][en]Outline[/en]', value: }, + { label: '[ja]プレビュー[/ja][en]Preview[/en]', value: }, + { label: '[ja]データファイル[/ja][en]Data File[/en]', value: }, + { label: '[ja]日付[/ja][en]Date[/en]', value: }, + { label: '[ja]データタイプ[/ja][en]Data Type[/en]', value: }, + { label: '[ja]実験者[/ja][en]Experimenter[/en]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: '[ja]フリーキーワード[/ja][en]Free keyword[/en]', value: }, + { label: '[ja]最終更新日[/ja][en]Last Update Date[/en]', value: }, + { label: '[ja]作成日[/ja][en]Creation Date[/en]', value: }, + { label: '[ja]所有者[/ja][en]Contributor[/en]', value: }, + { label: '[ja]査読者[/ja][en]Referee[/en]', value: Functions.mlang(item.referee, lang) }, + { label: '[ja]更新履歴[/ja][en]Change Log[/en]', value: }, + { label: '[ja]関連アイテム[/ja][en]Related Item[/en]', value: }, + ]; + const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id); + if (simpfLinkUrl !== '') { + const field = { label: 'Online Simulation', value: }; + fields.splice(14, 0, field); + } + return fields; + } +} + +export default DataDetail; \ No newline at end of file diff --git a/src/database/item-type/data/DataList.tsx b/src/database/item-type/data/DataList.tsx new file mode 100644 index 0000000..bb39050 --- /dev/null +++ b/src/database/item-type/data/DataList.tsx @@ -0,0 +1,53 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_data.gif'; +import ItemUtil, { ItemData } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; +import DataUtil from './DataUtil'; + +class DataList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Data'; + this.icon = iconFile; + } + + renderIcon() { + const { lang } = this.props; + const item = this.props.item as ItemData; + const data = item.file.filter((value) => { + return value.file_type_name === 'preview'; + }); + let imgSrc = this.icon; + let imgAlt = this.label; + if (data.length > 0) { + const preview = data.pop(); + if ('undefined' !== typeof preview) { + imgSrc = ItemUtil.getPreviewFileUrl(preview); + if ('' !== preview.caption) { + imgAlt = Functions.mlang(preview.caption, lang); + } + } + } + return {imgAlt}; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemData; + const authors = item.experimenter.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+
+ {authors} + + ); + } +} + +export default DataList; \ No newline at end of file diff --git a/src/database/item-type/data/DataTop.tsx b/src/database/item-type/data/DataTop.tsx new file mode 100644 index 0000000..0deae6a --- /dev/null +++ b/src/database/item-type/data/DataTop.tsx @@ -0,0 +1,18 @@ +import { ItemDataSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_data.gif'; + +class DataTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'data'; + this.label = 'Data'; + this.icon = iconFile; + // OPENNEURO: + this.description = '[en]Experimental data, Numerical data/Image/Movie/etc...[/en][ja]実験結果の数値データ/画像/動画など[/ja]'; + this.subTypes = ItemDataSubTypes; + } +} + +export default DataTop; \ No newline at end of file diff --git a/src/database/item-type/data/DataUtil.tsx b/src/database/item-type/data/DataUtil.tsx new file mode 100644 index 0000000..9944054 --- /dev/null +++ b/src/database/item-type/data/DataUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemDataSubType, ItemDataSubTypes } from '../../lib/ItemUtil'; + +interface DataTypeProps { + lang: MultiLang; + type: ItemDataSubType; +} + +const DataType = (props: DataTypeProps) => { + const { type } = props; + const subtype = ItemDataSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const DataUtil = { + DataType, +} + +export default DataUtil; \ No newline at end of file diff --git a/src/database/item-type/data/index.tsx b/src/database/item-type/data/index.tsx new file mode 100644 index 0000000..21a88d8 --- /dev/null +++ b/src/database/item-type/data/index.tsx @@ -0,0 +1,13 @@ +import DataTop from './DataTop'; +import DataList from './DataList'; +import DataDetail from './DataDetail'; +import DataAdvancedSearch from './DataAdvancedSearch'; + +const ItemTypeData = { + Top: DataTop, + List: DataList, + Detail: DataDetail, + AdvancedSearch: DataAdvancedSearch, +}; + +export default ItemTypeData; \ No newline at end of file diff --git a/src/database/item-type/files/FilesAdvancedSearch.tsx b/src/database/item-type/files/FilesAdvancedSearch.tsx new file mode 100644 index 0000000..57b27d3 --- /dev/null +++ b/src/database/item-type/files/FilesAdvancedSearch.tsx @@ -0,0 +1,30 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class FilesAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'files'; + this.title = 'Files'; + this.state.values['title'] = ''; + this.state.values['data_file_name'] = ''; + this.state.values['data_file_mimetype'] = ''; + this.state.values['data_file_filetype'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '- [en]File Name[/en][ja]ファイル名[/ja]', value: this.renderFieldInputText('data_file_name', 50) }, + { label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]', value: this.renderFieldInputText('data_file_mimetype', 50) }, + { label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]', value: this.renderFieldInputText('data_file_filetype', 20) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + ]; + return rows; + } +} + +export default FilesAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/files/FilesDetail.tsx b/src/database/item-type/files/FilesDetail.tsx new file mode 100644 index 0000000..89091d0 --- /dev/null +++ b/src/database/item-type/files/FilesDetail.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemFiles } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; + +class FilesDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemFiles; + return [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Data File[/en][ja]データファイル[/ja]', value: }, + { label: '- [en]File Name[/en][ja]ファイル名[/ja]', value: item.data_file_name }, + { label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]', value: item.data_file_mimetype }, + { label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]', value: item.data_file_filetype }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + } +} + +export default FilesDetail; diff --git a/src/database/item-type/files/FilesList.tsx b/src/database/item-type/files/FilesList.tsx new file mode 100644 index 0000000..465237d --- /dev/null +++ b/src/database/item-type/files/FilesList.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_files.gif'; +import { ItemFiles } from '../../lib/ItemUtil'; +import Contributer from '../lib/field/Contributer'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class FilesList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Files'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemFiles; + return ( + <> + {Functions.mlang(item.title, lang)}
+
+ {item.data_file_mimetype} + + ); + } +} + +export default FilesList; \ No newline at end of file diff --git a/src/database/item-type/files/FilesTop.tsx b/src/database/item-type/files/FilesTop.tsx new file mode 100644 index 0000000..3e6fae9 --- /dev/null +++ b/src/database/item-type/files/FilesTop.tsx @@ -0,0 +1,17 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_files.gif'; +import { ItemFilesSubTypes } from '../../lib/ItemUtil'; + +class FilesTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'files'; + this.label = 'Files'; + this.icon = iconFile; + this.description = '[en]Various type of File.[/en][ja]ファイル[/ja]'; + this.subTypes = ItemFilesSubTypes; + } +} + +export default FilesTop; \ No newline at end of file diff --git a/src/database/item-type/files/FilesUtil.tsx b/src/database/item-type/files/FilesUtil.tsx new file mode 100644 index 0000000..0e10e42 --- /dev/null +++ b/src/database/item-type/files/FilesUtil.tsx @@ -0,0 +1,5 @@ + +const FilesUtil = { +} + +export default FilesUtil; \ No newline at end of file diff --git a/src/database/item-type/files/index.tsx b/src/database/item-type/files/index.tsx new file mode 100644 index 0000000..00ebdea --- /dev/null +++ b/src/database/item-type/files/index.tsx @@ -0,0 +1,13 @@ +import FilesTop from './FilesTop'; +import FilesList from './FilesList'; +import FilesDetail from './FilesDetail'; +import FilesAdvancedSearch from './FilesAdvancedSearch'; + +const ItemTypeFiles = { + Top: FilesTop, + List: FilesList, + Detail: FilesDetail, + AdvancedSearch: FilesAdvancedSearch, +}; + +export default ItemTypeFiles; \ No newline at end of file diff --git a/src/database/item-type/index.tsx b/src/database/item-type/index.tsx new file mode 100644 index 0000000..900cacc --- /dev/null +++ b/src/database/item-type/index.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import { MultiLang } from '../../config'; +import AdvancedSearchQuery from '../lib/AdvancedSearchQuery'; +import { Item, ItemBinder, ItemBook, ItemConference, ItemData, ItemFiles, ItemModel, ItemPaper, ItemPresentation, ItemSimulator, ItemStimulus, ItemTool, ItemUrl, ItemMemo } from '../lib/ItemUtil'; +import ItemTypeBinder from './binder'; +import ItemTypeBook from './book'; +import ItemTypeConference from './conference'; +import ItemTypeData from './data'; +import ItemTypeFiles from './files'; +import ItemTypeMemo from './memo'; +import ItemTypeModel from './model'; +import ItemTypePaper from './paper'; +import ItemTypePresentation from './presentation'; +import ItemTypeSimulator from './simulator'; +import ItemTypeStimulus from './stimulus'; +import ItemTypeTool from './tool'; +import ItemTypeUrl from './url'; + +interface TopProps { + lang: MultiLang; + type: string; +} +const Top = (props: TopProps) => { + const { lang, type } = props; + switch (type) { + case 'xnpbinder': + return ; + case 'xnpbook': + return ; + case 'xnpconference': + return ; + case 'xnpdata': + return ; + case 'xnpfiles': + return ; + case 'xnpmemo': + return ; + case 'xnpmodel': + return ; + case 'xnppaper': + return ; + case 'xnppresentation': + return ; + case 'xnpsimulator': + return ; + case 'xnpstimulus': + return ; + case 'xnptool': + return ; + case 'xnpurl': + return ; + default: + return null; + } +} + +interface ListProps { + lang: MultiLang; + item: Item; +} +const List = (props: ListProps) => { + const { lang, item } = props; + switch (item.item_type_name) { + case 'xnpbinder': + return ; + case 'xnpbook': + return ; + case 'xnpconference': + return ; + case 'xnpdata': + return ; + case 'xnpfiles': + return ; + case 'xnpmemo': + return ; + case 'xnpmodel': + return ; + case 'xnppaper': + return ; + case 'xnppresentation': + return ; + case 'xnpsimulator': + return ; + case 'xnpstimulus': + return ; + case 'xnptool': + return ; + case 'xnpurl': + return ; + default: + return null; + } +} + +interface DetailProps { + lang: MultiLang; + item: Item; +} +const Detail = (props: DetailProps) => { + const { lang, item } = props; + switch (item.item_type_name) { + case 'xnpbinder': + return ; + case 'xnpbook': + return ; + case 'xnpconference': + return ; + case 'xnpdata': + return ; + case 'xnpfiles': + return ; + case 'xnpmemo': + return ; + case 'xnpmodel': + return ; + case 'xnppaper': + return ; + case 'xnppresentation': + return ; + case 'xnpsimulator': + return ; + case 'xnpstimulus': + return ; + case 'xnptool': + return ; + case 'xnpurl': + return ; + default: + return null; + } +} + +interface AdvancedSearchProps { + lang: MultiLang; + type: string; + query: AdvancedSearchQuery; +} +const AdvancedSearch = (props: AdvancedSearchProps) => { + const { lang, type, query } = props; + switch (type) { + case 'xnpbinder': + return ; + case 'xnpbook': + return ; + case 'xnpconference': + return ; + case 'xnpdata': + return ; + case 'xnpfiles': + return ; + case 'xnpmemo': + return ; + case 'xnpmodel': + return ; + case 'xnppaper': + return ; + case 'xnppresentation': + return ; + case 'xnpsimulator': + return ; + case 'xnpstimulus': + return ; + case 'xnptool': + return ; + case 'xnpurl': + return ; + default: + return null; + } +} + +const ItemType = { + Top, + List, + Detail, + AdvancedSearch +} + +export default ItemType; \ No newline at end of file diff --git a/src/database/item-type/lib/AdvancedSearchBase.tsx b/src/database/item-type/lib/AdvancedSearchBase.tsx new file mode 100644 index 0000000..f1c5286 --- /dev/null +++ b/src/database/item-type/lib/AdvancedSearchBase.tsx @@ -0,0 +1,189 @@ +import React, { ChangeEvent, Component } from 'react'; +import { MultiLang } from '../../../config'; +import Functions from '../../../functions'; +import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery'; +import { ItemSubTypes } from '../../lib/ItemUtil'; + +export interface AdvancedSearchBaseProps { + lang: MultiLang; + query: AdvancedSearchQuery; +} + +interface State { + show: boolean; + values: any; +} + +class AdvancedSearchBase extends Component { + + protected type: string = 'base' + protected title: string = 'Base'; + protected query: AdvancedSearchQuery; + protected ignoreKeys: string[] = []; + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.state = { + show: false, + values: {}, + } + this.query = props.query; + this.handleChangeTitleCheck = this.handleChangeTitleCheck.bind(this); + } + + updateQuery(key: string, value: string) { + if (this.ignoreKeys.includes(key)) { + this.query.delete(this.type, key); + } else { + this.query.set(this.type, key, value); + } + } + + setIgnoreKey(key: string) { + if (!this.ignoreKeys.includes(key)) { + this.ignoreKeys = this.ignoreKeys.concat(key); + } + this.query.delete(this.type, key); + } + + deleteIgnoreKey(key: string) { + if (this.ignoreKeys.includes(key)) { + this.ignoreKeys = this.ignoreKeys.filter((v) => { + return (v !== key); + }); + } + this.query.set(this.type, key, this.state.values[key]); + } + + updateField(key: string, value: string) { + let values = Object.assign({}, this.state.values); + values[key] = value; + this.updateQuery(key, value); + this.setState({ values }); + } + + handleChangeTitleCheck(e: ChangeEvent) { + const show = e.target.checked; + if (show) { + Object.keys(this.state.values).forEach((key) => { + const value = this.state.values[key]; + this.updateQuery(key, value); + }); + } else { + this.query.deleteType(this.type); + } + this.setState({ show }); + } + + getRows(): { label: string, value: JSX.Element }[] { + return []; + } + + renderFieldInputText(key: string, size: number) { + const onChange = (e: ChangeEvent) => { + this.updateField(key, e.target.value); + }; + return ( + + ); + } + + renderFieldSelect(key: string, values: ItemSubTypes) { + const onChange = (e: ChangeEvent) => { + this.updateField(key, e.target.value); + }; + const options = values.map(({ type, label }, i) => { + return ; + }); + return ( + + ); + } + + renderFieldDate(label: string, keyYear: string, keyMonth: string, keyMday: string) { + const onChange = (e: ChangeEvent) => { + if (e.target.checked) { + this.deleteIgnoreKey(keyYear); + keyMonth !== '' && this.deleteIgnoreKey(keyMonth); + keyMday !== '' && this.deleteIgnoreKey(keyMday); + } else { + this.setIgnoreKey(keyYear); + keyMonth !== '' && this.setIgnoreKey(keyMonth); + keyMday !== '' && this.setIgnoreKey(keyMday); + } + }; + const month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const monthOptions = month.map((value, i) => { + return ; + }) + let mdayOptions: JSX.Element[] = []; + for (let i = 1; i <= 31; i++) { + mdayOptions.push() + } + return ( +
+ + {label.length !== 0 && } + {keyMonth !== '' && + + } + {keyMday !== '' && + + } + this.updateField(keyYear, e.target.value)} /> +
+ ); + } + + renderBody() { + const { lang } = this.props; + if (this.state.show === false) { + return null; + } + const rows = this.getRows(); + const fields = rows.map((value, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + return ( + + {Functions.mlang(value.label, lang)} + {value.value} + + ); + }); + return ( + + + {fields} + +
+ ); + } + + render() { + return ( +
+ + + + + + +
+ + {this.title} +
+ {this.renderBody()} +
+ ); + } + +} + +export default AdvancedSearchBase; \ No newline at end of file diff --git a/src/database/item-type/lib/DetailBase.tsx b/src/database/item-type/lib/DetailBase.tsx new file mode 100644 index 0000000..3208d12 --- /dev/null +++ b/src/database/item-type/lib/DetailBase.tsx @@ -0,0 +1,43 @@ +import React, { Component, ReactNode } from 'react'; +import { MultiLang } from '../../../config'; +import Functions from '../../../functions'; +import { Item } from '../../lib/ItemUtil'; + +export interface DetailBaseField { + label: string; + value: ReactNode; +} + +export interface DetailBaseProps { + lang: MultiLang; + item: Item +} + +class DetailBase extends Component { + + getFields(): DetailBaseField[] { + return []; + } + + render() { + const { lang } = this.props; + const elements = this.getFields().map((value, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + return ( + + {Functions.mlang(value.label, lang)} + {value.value} + + ) + }); + return ( + + + {elements} + +
+ ); + } +} + +export default DetailBase; \ No newline at end of file diff --git a/src/database/item-type/lib/ListBase.tsx b/src/database/item-type/lib/ListBase.tsx new file mode 100644 index 0000000..e907973 --- /dev/null +++ b/src/database/item-type/lib/ListBase.tsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import { MultiLang } from '../../../config'; +import ItemUtil, { Item } from '../../lib/ItemUtil'; +import SimPFLinkIcon from './field/SimPFLinkIcon'; + +export interface ListBaseProps { + lang: MultiLang; + item: Item +}; + +class ListBase extends Component { + + protected label = ''; + protected icon = ''; + protected url: string; + protected simpfLinkUrl: string; + + constructor(props: ListBaseProps) { + super(props); + this.url = ItemUtil.getUrl(props.item); + this.simpfLinkUrl = ItemUtil.getSimPFLinkUrl(props.item.item_id); + } + + renderIcon() { + return {this.label}; + } + + renderBody() { + return <>; + } + + render() { + const { lang } = this.props; + return ( + + + + + + + + +
+ {this.renderIcon()} + + {this.renderBody()} + + +
+ ); + } +} + +export default ListBase; \ No newline at end of file diff --git a/src/database/item-type/lib/TopBase.tsx b/src/database/item-type/lib/TopBase.tsx new file mode 100644 index 0000000..a41f596 --- /dev/null +++ b/src/database/item-type/lib/TopBase.tsx @@ -0,0 +1,47 @@ +import React, { Component, Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import { MultiLang } from '../../../config'; +import Functions from '../../../functions'; +import ItemUtil, { ItemSubTypes } from '../../lib/ItemUtil'; + +export interface TopBaseProps { + lang: MultiLang; +} + +class TopBase extends Component { + + protected type: string = ''; + protected label: string = ''; + protected icon: string = ''; + protected description: string = ''; + protected subTypes: ItemSubTypes = []; + + render() { + const { lang } = this.props; + const url = ItemUtil.getItemTypeSearchUrl(this.type); + const links = this.subTypes.map(({ type, label }, i) => { + return {i > 0 && ' / '}{label}; + }); + return ( +
+ + + + + + + +
+ {this.label} + + {this.label} +
+
+
{Functions.mlang(this.description, lang)}
+ {links} +
+ ); + } +} + +export default TopBase; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Author.tsx b/src/database/item-type/lib/field/Author.tsx new file mode 100644 index 0000000..9181ca8 --- /dev/null +++ b/src/database/item-type/lib/field/Author.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; + +interface Props { + lang: MultiLang; + author: string[]; +} + +const Author = (props: Props) => { + const { author } = props; + if (author.length === 0) { + return null; + } + const elements = author.map((value, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + return {value}; + }); + return {elements}
; +} + +export default Author; \ No newline at end of file diff --git a/src/database/item-type/lib/field/ChangeLog.tsx b/src/database/item-type/lib/field/ChangeLog.tsx new file mode 100644 index 0000000..f3d9ba9 --- /dev/null +++ b/src/database/item-type/lib/field/ChangeLog.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; +import { ItemBasicChangeLog } from '../../../lib/ItemUtil'; +import DateTime from './DateTime'; + +interface Props { + lang: MultiLang; + changelog: ItemBasicChangeLog[]; +} + +const ChangeLog = (props: Props) => { + const { lang, changelog } = props; + if (changelog.length === 0) { + return null; + } + const elements = changelog.map((value, i) => { + return ( + + + {Functions.mlang(value.log, lang)} + + ); + }); + return ({elements}
); +} + +export default ChangeLog; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Contributer.tsx b/src/database/item-type/lib/field/Contributer.tsx new file mode 100644 index 0000000..63261a9 --- /dev/null +++ b/src/database/item-type/lib/field/Contributer.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; + +interface Props { + lang: MultiLang; + uname: string; + name: string +} + +const Contributer = (props: Props) => { + const { lang, name, uname } = props; + const unsubscribed = '([en]Unsubscribed User[/en][ja]退会済みユーザ[/ja])'; + const label = uname === '' ? unsubscribed : (name === '' ? uname : name + ' (' + uname + ')'); + return {Functions.mlang(label, lang)}; +} + +export default Contributer; \ No newline at end of file diff --git a/src/database/item-type/lib/field/CreativeCommons.tsx b/src/database/item-type/lib/field/CreativeCommons.tsx new file mode 100644 index 0000000..b545f02 --- /dev/null +++ b/src/database/item-type/lib/field/CreativeCommons.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; + +export type CreativeCommonsType = 'by' | 'by-nc' | 'by-nc-nd' | 'by-nc-sa' | 'by-nd' | 'by-sa'; + +export const getCreativeCommonsType = (ccCommercialUse: number, ccModification: number): CreativeCommonsType => { + const cc = ccCommercialUse * 10 + ccModification; + switch (cc) { + case 0: + return 'by-nc-nd'; + case 1: + return 'by-nc-sa'; + case 2: + return 'by-nc'; + case 10: + return 'by-nd'; + case 11: + return 'by-sa'; + case 12: + default: + return 'by'; + } +} + +interface Props { + lang: MultiLang; + type: CreativeCommonsType; +} + +const CreativeCommons = (props: Props) => { + const { type } = props; + const url = 'http://creativecommons.org/licenses/' + type + '/4.0/'; + const logoUrl = 'https://i.creativecommons.org/l/' + type + '/4.0/88x31.png'; + const labels = { + by: 'Attribution', + nc: 'NonCommercial', + nd: 'NoDerivatives', + sa: 'ShareAlike', + } + const label = type.split('-').map((value) => { + const prop = value as 'by' | 'nc' | 'nd' | 'sa'; + return labels[prop]; + }).join('-'); + return ( + + + + + + + +
Creative Commons LicenseThis work is licensed under a Criative Commons {label} 4.0 International License.
+ ); +} + +export default CreativeCommons; \ No newline at end of file diff --git a/src/database/item-type/lib/field/DateTime.tsx b/src/database/item-type/lib/field/DateTime.tsx new file mode 100644 index 0000000..0e9b1dc --- /dev/null +++ b/src/database/item-type/lib/field/DateTime.tsx @@ -0,0 +1,21 @@ +import moment from 'moment'; +import React from 'react'; +import { MultiLang } from '../../../../config'; + +interface Props { + lang: MultiLang; + date: number; + onlyDate?: boolean +} + +const DateTime = (props: Props) => { + const { date, onlyDate } = props; + const d = moment(new Date(date * 1000)); + let format = 'MMM D, Y'; + if (typeof onlyDate === 'undefined' || !onlyDate) { + format += ' HH:mm:ss'; + } + return {d.format(format)}; +} + +export default DateTime; diff --git a/src/database/item-type/lib/field/Description.tsx b/src/database/item-type/lib/field/Description.tsx new file mode 100644 index 0000000..353b3d9 --- /dev/null +++ b/src/database/item-type/lib/field/Description.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import XoopsCode from '../../../../common/lib/XoopsCode'; +import { MultiLang } from '../../../../config'; + +interface Props { + lang: MultiLang; + description: string; + className?: string; +} + +const Description = (props: Props) => { + const { lang, description, className } = props; + const textarea = ; + const name = typeof className === 'undefined' ? 'description' : className; + return (
{textarea}
); +} + +export default Description; \ No newline at end of file diff --git a/src/database/item-type/lib/field/FileDownloadButton.module.css b/src/database/item-type/lib/field/FileDownloadButton.module.css new file mode 100644 index 0000000..dd40e7e --- /dev/null +++ b/src/database/item-type/lib/field/FileDownloadButton.module.css @@ -0,0 +1,21 @@ +.downloadButton { + display: inline-block; + padding: 7px 20px; + text-decoration: none !important; + font-weight: normal !important; + background: #f0f0f0; + color: #000 !important; + border: solid 1px #e0e0e0; + box-shadow: 2px 2px #bbbbbb; + border-radius: 5px; +} + +.downloadButton:hover { + background: #e8e8e8 !important; + border: solid 1px #cccccc; +} + +.downloadButton:active { + transform: translate(2px, 2px); + box-shadow: none; +} \ No newline at end of file diff --git a/src/database/item-type/lib/field/FileDownloadButton.tsx b/src/database/item-type/lib/field/FileDownloadButton.tsx new file mode 100644 index 0000000..968d441 --- /dev/null +++ b/src/database/item-type/lib/field/FileDownloadButton.tsx @@ -0,0 +1,55 @@ +import React, { Component } from 'react'; +import { MultiLang } from '../../../../config'; +import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil'; +import styles from './FileDownloadButton.module.css'; +import LicenseAgreementDialog from './LicenseAgreementDialog'; + +interface Props { + lang: MultiLang; + file: ItemBasicFile; + rights: string; + useCc: number; + ccCommercialUse: number; + ccModification: number; +} + +interface State { + show: boolean; +} + +class FileDownloadButton extends Component { + + constructor(props: Props) { + super(props); + this.state = { + show: false, + }; + this.handleClickDownload = this.handleClickDownload.bind(this); + this.unsetShow = this.unsetShow.bind(this); + } + + handleClickDownload(e: React.MouseEvent) { + if (this.props.rights !== '') { + e.stopPropagation(); + e.preventDefault(); + this.setState({ show: true }); + } + } + + unsetShow() { + this.setState({ show: false }); + } + + render() { + const { lang, file, rights, useCc, ccCommercialUse, ccModification } = this.props; + const url = ItemUtil.getFileUrl(this.props.file); + return ( + <> + Download + + + ); + } +} + +export default FileDownloadButton; \ No newline at end of file diff --git a/src/database/item-type/lib/field/FileSize.tsx b/src/database/item-type/lib/field/FileSize.tsx new file mode 100644 index 0000000..0198e89 --- /dev/null +++ b/src/database/item-type/lib/field/FileSize.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; + +interface Props { + lang: MultiLang; + size: number; +} + +const FileSize = (props: Props) => { + const { size } = props; + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const power = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0; + const label = Math.round(size / Math.pow(1024, power) * 10) / 10 + ' ' + units[power] + return {label}; +} + +export default FileSize; \ No newline at end of file diff --git a/src/database/item-type/lib/field/FreeKeyword.tsx b/src/database/item-type/lib/field/FreeKeyword.tsx new file mode 100644 index 0000000..d33437e --- /dev/null +++ b/src/database/item-type/lib/field/FreeKeyword.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; + +interface Props { + lang: MultiLang; + keyword: string[]; +} + +const FreeKeyword = (props: Props) => { + const { keyword } = props; + if (keyword.length === 0) { + return null; + } + const label = keyword.join(', '); + return ({label}); +} + +export default FreeKeyword; \ No newline at end of file diff --git a/src/database/item-type/lib/field/ItemFile.tsx b/src/database/item-type/lib/field/ItemFile.tsx new file mode 100644 index 0000000..16f540f --- /dev/null +++ b/src/database/item-type/lib/field/ItemFile.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; +import { ItemBasicFile } from '../../../lib/ItemUtil'; +import DateTime from './DateTime'; +import FileSize from './FileSize'; +import OpenNeuroFileDownloadButton from './OpenNeuroFileDownloadButton'; + +interface Props { + lang: MultiLang; + file: ItemBasicFile[]; + type: string; + rights?: string; + useCc?: number; + ccCommercialUse?: number; + ccModification?: number; + downloadLimit?: number; +} + +const ItemFile = (props: Props) => { + const { lang, file, type } = props; + const rights = typeof props.rights === 'undefined' ? '' : props.rights; + const useCc = typeof props.useCc === 'undefined' ? 0 : props.useCc; + const ccCommercialUse = typeof props.ccCommercialUse === 'undefined' ? 0 : props.ccCommercialUse; + const ccModification = typeof props.ccModification === 'undefined' ? 0 : props.ccModification; + const downloadLimit = typeof props.downloadLimit === 'undefined' ? 0 : props.downloadLimit; + const data = file.find((value) => { + return value.file_type_name === type; + }); + if (typeof data === 'undefined') { + return null; + } + return ( +
+ {data.original_file_name}
+ + + + + + +
Type: {data.mime_type}{downloadLimit === 0 && }
Size:
Last updated:
+ {downloadLimit === 1 && <>
({Functions.mlang('[en]File has been removed[/en][ja]ファイルは削除されました[/ja]', lang)})} +
+ ); +} + +export default ItemFile; \ No newline at end of file diff --git a/src/database/item-type/lib/field/ItemIndex.tsx b/src/database/item-type/lib/field/ItemIndex.tsx new file mode 100644 index 0000000..02937f6 --- /dev/null +++ b/src/database/item-type/lib/field/ItemIndex.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; +import IndexUtil from '../../../lib/IndexUtil'; +import { ItemBasicIndex } from '../../../lib/ItemUtil'; + +interface Props { + lang: MultiLang; + index: ItemBasicIndex[]; +} + +const ItemIndex = (props: Props) => { + const { lang, index } = props; + if (index.length === 0) { + return null; + } + const elements = index.map((value, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + const url = IndexUtil.getUrl(value.index_id); + return ({Functions.mlang(value.title, lang)}); + }); + return ({elements}
); +} + +export default ItemIndex; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Language.tsx b/src/database/item-type/lib/field/Language.tsx new file mode 100644 index 0000000..4099c0b --- /dev/null +++ b/src/database/item-type/lib/field/Language.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { ItemBasicLang } from '../../../lib/ItemUtil'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; + +interface Props { + lang: MultiLang; + itemLang: ItemBasicLang; +} + +const Language = (props: Props) => { + const { lang, itemLang } = props; + const langStr = { + eng: '[en]English[/en][ja]英語[/ja]', + jpn: '[en]Japanese[/en][ja]日本語[/ja]', + fra: '[en]French[/en][ja]フランス語[/ja]', + deu: '[en]German[/en][ja]ドイツ語[/ja]', + esl: '[en]Spanish[/en][ja]スペイン語[/ja]', + ita: '[en]Italian[/en][ja]イタリア語[/ja]', + dut: '[en]Dutch[/en][ja]オランダ語[/ja]', + sve: '[en]Swedish[/en][ja]スウェーデン語[/ja]', + nor: '[en]Norwegian[/en][ja]ノルウェー語[/ja]', + dan: '[en]Danish[/en][ja]デンマーク語[/ja]', + fin: '[en]Finnish[/en][ja]フィンランド語[/ja]', + por: '[en]Portuguese[/en][ja]ポルトガル語[/ja]', + chi: '[en]Chinese[/en][ja]中国語[/ja]', + kor: '[en]Korean[/en][ja]韓国語[/ja]', + } + if (!(itemLang in langStr)) { + return null; + } + return ({Functions.mlang(langStr[itemLang], lang)}); +} + +export default Language; \ No newline at end of file diff --git a/src/database/item-type/lib/field/LicenseAgreementDialog.module.css b/src/database/item-type/lib/field/LicenseAgreementDialog.module.css new file mode 100644 index 0000000..b2f4525 --- /dev/null +++ b/src/database/item-type/lib/field/LicenseAgreementDialog.module.css @@ -0,0 +1,38 @@ +.overlay { + position: fixed; + z-index: 90; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #000000; + opacity: 0.8; +} + +.dialog { + position: fixed; + background-color: #d8d8d8; + width: 570px; + z-index: 100; + top: 50%; + left: 50%; + right: auto; + bottom: auto; + margin: 0 auto; + padding: 20px; + transform: translate(-50%, -50%); + border-radius: 5px; +} + +.box { + background-color: #fff; + padding: 10px; +} + +.download { + text-align: center; +} + +.download button { + margin: 5px 5px 0; +} \ No newline at end of file diff --git a/src/database/item-type/lib/field/LicenseAgreementDialog.tsx b/src/database/item-type/lib/field/LicenseAgreementDialog.tsx new file mode 100644 index 0000000..3d065a6 --- /dev/null +++ b/src/database/item-type/lib/field/LicenseAgreementDialog.tsx @@ -0,0 +1,115 @@ +import React, { ChangeEvent, Component, MouseEvent } from 'react'; +import { Modal } from 'react-overlays'; +import { RouteComponentProps, withRouter } from 'react-router'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; +import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil'; +import DateTime from './DateTime'; +import FileSize from './FileSize'; +import styles from './LicenseAgreementDialog.module.css'; +import Rights from './Rights'; + +interface Props extends RouteComponentProps { + lang: MultiLang; + file: ItemBasicFile; + rights: string; + useCc: number; + ccCommercialUse: number; + ccModification: number; + show: boolean; + unsetShow: () => void; +} + +interface State { + show: boolean; + disabled: boolean; +} + +class LicenseAgreementDialog extends Component { + + constructor(props: Props) { + super(props); + this.state = { + show: props.show, + disabled: false, + }; + this.handleChangeCheckbox = this.handleChangeCheckbox.bind(this); + this.handleClickDownload = this.handleClickDownload.bind(this); + this.handleClickCancel = this.handleClickCancel.bind(this); + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State) { + if (nextProps.show && !prevState.show) { + return { disabled: true, show: nextProps.show }; + } + return null; + } + + handleChangeCheckbox(e: ChangeEvent) { + const disabled = e.target.value === '0'; + this.setState({ disabled }); + } + + handleClickDownload(e: MouseEvent) { + this.props.unsetShow(); + this.setState({ show: false }); + } + + handleClickCancel(e: MouseEvent) { + this.props.unsetShow(); + this.setState({ show: false }); + } + + renderBackdrop(props: any) { + return
; + } + + render() { + const { lang } = this.props; + const date = new Date(this.props.file.timestamp); + const timestamp = Math.floor(date.valueOf() / 1000); + const url = ItemUtil.getFileUrl(this.props.file); + return ( + +
+
+ {Functions.mlang('[en]Download file information[/en][ja]ダウンロードするファイルの情報[/ja]', lang)} +
+ {this.props.file.original_file_name}
+ + + + + + +
Type: {this.props.file.mime_type}
Size:
Last updated:
+
+
+
+
+ {Functions.mlang('[en]License agreement[/en][ja]ファイルのライセンス[/ja]', lang)} +
+ {Functions.mlang('[en]Please read the following license agreement carefully.[/en][ja]このファイルには下記のライセンスが設定されています。[/ja]', lang)} +
+ + {Functions.mlang('[en]I accept the terms in the license agreement.[/en][ja]ライセンスに同意します。[/ja]', lang)}
+ {Functions.mlang('[en]I do not accept the terms in the license agreement.[/en][ja]ライセンスに同意しません。[/ja]', lang)}
+
+
+
+
+
+ Acceptance is needed to download this file. +
+ + + + +
+
+
+ ); + } +} + +export default withRouter(LicenseAgreementDialog); \ No newline at end of file diff --git a/src/database/item-type/lib/field/OpenNeuroAuthor.tsx b/src/database/item-type/lib/field/OpenNeuroAuthor.tsx new file mode 100644 index 0000000..f4dfea8 --- /dev/null +++ b/src/database/item-type/lib/field/OpenNeuroAuthor.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; + +interface Props { + lang: MultiLang; + author: string[]; +} + +const OpenNeuroAuthor = (props: Props) => { + const { lang, author } = props; + if (author.length === 0) { + return null; + } + const label = author.map((value, idx) =>
{Functions.mlang(value, lang)}
); + return
{label}
; +} + +export default OpenNeuroAuthor; \ No newline at end of file diff --git a/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.module.css b/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.module.css new file mode 100644 index 0000000..2aca8fb --- /dev/null +++ b/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.module.css @@ -0,0 +1,22 @@ +.downloadButton { + display: inline-block; + padding: 7px 20px; + text-decoration: none !important; + font-weight: normal !important; + background: chocolate; + color: #ffffff !important; + border: solid 1px #cccccc; + box-shadow: 2px 2px #bbbbbb; + border-radius: 5px; + white-space: nowrap; +} + +.downloadButton:hover { + background: orangered !important; + border: solid 1px #888888; +} + +.downloadButton:active { + transform: translate(2px, 2px); + box-shadow: none; +} \ No newline at end of file diff --git a/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.tsx b/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.tsx new file mode 100644 index 0000000..9e7de2f --- /dev/null +++ b/src/database/item-type/lib/field/OpenNeuroFileDownloadButton.tsx @@ -0,0 +1,57 @@ +import { faDownload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { Component } from 'react'; +import { MultiLang } from '../../../../config'; +import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil'; +import LicenseAgreementDialog from './LicenseAgreementDialog'; +import styles from './OpenNeuroFileDownloadButton.module.css'; + +interface Props { + lang: MultiLang; + file: ItemBasicFile; + rights: string; + useCc: number; + ccCommercialUse: number; + ccModification: number; +} + +interface State { + show: boolean; +} + +class OpenNeuroFileDownloadButton extends Component { + + constructor(props: Props) { + super(props); + this.state = { + show: false, + }; + this.handleClickDownload = this.handleClickDownload.bind(this); + this.unsetShow = this.unsetShow.bind(this); + } + + handleClickDownload(e: React.MouseEvent) { + if (this.props.rights !== '') { + e.stopPropagation(); + e.preventDefault(); + this.setState({ show: true }); + } + } + + unsetShow() { + this.setState({ show: false }); + } + + render() { + const { lang, file, rights, useCc, ccCommercialUse, ccModification } = this.props; + const url = ItemUtil.getFileUrl(this.props.file); + return ( + <> + Download + + + ); + } +} + +export default OpenNeuroFileDownloadButton; \ No newline at end of file diff --git a/src/database/item-type/lib/field/OpenNeuroFreeKeyword.tsx b/src/database/item-type/lib/field/OpenNeuroFreeKeyword.tsx new file mode 100644 index 0000000..d190709 --- /dev/null +++ b/src/database/item-type/lib/field/OpenNeuroFreeKeyword.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; + +interface Props { + lang: MultiLang; + keyword: string[]; +} + +const OpenNeuroFreeKeyword = (props: Props) => { + const { lang, keyword } = props; + if (keyword.length === 0) { + return null; + } + const label = keyword.map((value, idx) =>
{Functions.mlang(value, lang)}
); + return
{label}
; +} + +export default OpenNeuroFreeKeyword; \ No newline at end of file diff --git a/src/database/item-type/lib/field/OpenNeuroRelatedTo.tsx b/src/database/item-type/lib/field/OpenNeuroRelatedTo.tsx new file mode 100644 index 0000000..c57806f --- /dev/null +++ b/src/database/item-type/lib/field/OpenNeuroRelatedTo.tsx @@ -0,0 +1,73 @@ +import React, { Component } from 'react'; +import ItemType from '../..'; +import { MultiLang } from '../../../../config'; +import ItemUtil from '../../../lib/ItemUtil'; + +interface Props { + lang: MultiLang + relatedTo: number[]; +} + +interface State { + elements: JSX.Element[]; +} + +class OpenNeuroRelatedTo extends Component { + + private isActive: boolean; + + constructor(props: Props) { + super(props); + this.state = { + elements: [], + }; + this.isActive = false; + } + + componentDidMount() { + this.isActive = true; + this.updateElements(this.props.relatedTo); + } + + componentDidUpdate(prevProps: Props) { + if (JSON.stringify(this.props.relatedTo) !== JSON.stringify(prevProps.relatedTo)) { + this.updateElements(this.props.relatedTo); + } + } + + componentWillUnmount() { + this.isActive = false; + } + + updateElements(relatedTo: number[]) { + const { lang } = this.props; + if (relatedTo.length === 0) { + this.setState({ elements: [] }); + } else { + ItemUtil.getList(relatedTo, (results) => { + const elements = results.data.map((item, idx) => { + const evenodd = 0 === idx % 2 ? 'even' : 'odd'; + return ; + }); + if (this.isActive) { + this.setState({ elements }); + } + }); + } + } + + render() { + if (this.state.elements.length === 0) { + return null; + } + return ( + + + {this.state.elements} + +
+ ); + } +} + +export default OpenNeuroRelatedTo; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Preview.module.css b/src/database/item-type/lib/field/Preview.module.css new file mode 100644 index 0000000..7ce5431 --- /dev/null +++ b/src/database/item-type/lib/field/Preview.module.css @@ -0,0 +1,23 @@ +.previewBox { + text-align: center; + margin: 10px; +} + +.previewBox::after { + content: ""; + display: block; + clear: both; +} + +.preview { + width: 200px; + margin: 0 auto; + float: left; + text-align: center; +} + +.preview caption { + width: 200px; + margin: 5px; + font-size: 80%; +} \ No newline at end of file diff --git a/src/database/item-type/lib/field/Preview.tsx b/src/database/item-type/lib/field/Preview.tsx new file mode 100644 index 0000000..86ec5b3 --- /dev/null +++ b/src/database/item-type/lib/field/Preview.tsx @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import Lightbox from 'react-image-lightbox'; +import 'react-image-lightbox/style.css'; +import { MultiLang } from '../../../../config'; +import Functions from '../../../../functions'; +import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil'; +import styles from './Preview.module.css'; + +interface Props { + lang: MultiLang; + file: ItemBasicFile[]; +} + +interface State { + isOpen: boolean; + imageIndex: number; +} + +class Preview extends Component { + + constructor(props: Props) { + super(props); + this.state = { + isOpen: false, + imageIndex: 0, + }; + } + + render() { + const { lang, file } = this.props; + const data = file.filter((value) => { + return value.file_type_name === 'preview'; + }); + if (data.length === 0) { + return null; + } + let imageUrls: string[] = []; + const previews = data.map((value, idx) => { + const fileUrl = ItemUtil.getFileUrl(value); + const previewUrl = ItemUtil.getPreviewFileUrl(value); + const caption = Functions.mlang(value.caption, lang); + imageUrls.push(fileUrl); + return ( +
+ { + e.preventDefault(); + this.setState({ isOpen: true, imageIndex: idx }); + }}> + {caption} + +
{caption}
+
+ ); + }); + const { isOpen, imageIndex } = this.state; + return ( + <> +
+ {previews} +
+ {isOpen && + this.setState({ isOpen: false })} + onMovePrevRequest={() => + this.setState({ + imageIndex: (imageIndex + data.length - 1) % data.length, + }) + } + onMoveNextRequest={() => + this.setState({ + imageIndex: (imageIndex + 1) % data.length, + }) + } + /> + } + + ); + } +} + +export default Preview; \ No newline at end of file diff --git a/src/database/item-type/lib/field/PublicationDate.tsx b/src/database/item-type/lib/field/PublicationDate.tsx new file mode 100644 index 0000000..6da3a62 --- /dev/null +++ b/src/database/item-type/lib/field/PublicationDate.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import DateTime from './DateTime'; + +interface Props { + lang: MultiLang; + year: number; + month: number; + mday: number; +} + +const PublicationDate = (props: Props) => { + const { lang, year, month, mday } = props; + const d = new Date(year + '-' + month + '-' + mday); + const timestamp = Math.floor(d.valueOf() / 1000); + return ; +} + +export default PublicationDate; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Readme.tsx b/src/database/item-type/lib/field/Readme.tsx new file mode 100644 index 0000000..6fbd19b --- /dev/null +++ b/src/database/item-type/lib/field/Readme.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import Description from './Description'; + +interface Props { + lang: MultiLang; + readme: string; +} + +const Readme = (props: Props) => { + const { lang, readme } = props; + return ; +} + +export default Readme; \ No newline at end of file diff --git a/src/database/item-type/lib/field/RelatedTo.tsx b/src/database/item-type/lib/field/RelatedTo.tsx new file mode 100644 index 0000000..87cd518 --- /dev/null +++ b/src/database/item-type/lib/field/RelatedTo.tsx @@ -0,0 +1,74 @@ +import React, { Component } from 'react'; +import { MultiLang } from '../../../../config'; +import ItemType from '../../../item-type'; +import ItemUtil from '../../../lib/ItemUtil'; + +interface Props { + lang: MultiLang + relatedTo: number[]; +} + +interface State { + elements: JSX.Element[]; +} + +class RelatedTo extends Component { + + private isActive: boolean; + + constructor(props: Props) { + super(props); + this.state = { + elements: [], + }; + this.isActive = false; + } + + componentDidMount() { + this.isActive = true; + this.updateElements(this.props.relatedTo); + } + + componentDidUpdate(prevProps: Props) { + if (JSON.stringify(this.props.relatedTo) !== JSON.stringify(prevProps.relatedTo)) { + this.updateElements(this.props.relatedTo); + } + } + + componentWillUnmount() { + this.isActive = false; + } + + updateElements(relatedTo: number[]) { + const { lang } = this.props; + if (relatedTo.length === 0) { + this.setState({ elements: [] }); + } else { + ItemUtil.getList(relatedTo, (results) => { + const elements = results.data.map((item, idx) => { + const evenodd = idx % 0 ? 'even' : 'odd'; + return ; + }); + if (this.isActive) { + this.setState({ elements }); + } + }); + } + } + + render() { + if (this.state.elements.length === 0) { + return null; + } + return ( + + + + {this.state.elements} + +
Item summary
+ ); + } +} + +export default RelatedTo; \ No newline at end of file diff --git a/src/database/item-type/lib/field/Rights.tsx b/src/database/item-type/lib/field/Rights.tsx new file mode 100644 index 0000000..25f65cf --- /dev/null +++ b/src/database/item-type/lib/field/Rights.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import CreativeCommons, { getCreativeCommonsType } from './CreativeCommons'; +import Description from './Description'; + +interface Props { + lang: MultiLang; + rights: string; + useCc: number; + ccCommercialUse: number; + ccModification: number; +} + +const Rights = (props: Props) => { + const { lang, rights, useCc, ccCommercialUse, ccModification } = props; + if (useCc === 0) { + return ; + } + const ccType = getCreativeCommonsType(ccCommercialUse, ccModification); + return ; +} + +export default Rights; \ No newline at end of file diff --git a/src/database/item-type/lib/field/SimPFLinkIcon.tsx b/src/database/item-type/lib/field/SimPFLinkIcon.tsx new file mode 100644 index 0000000..1bfc8cb --- /dev/null +++ b/src/database/item-type/lib/field/SimPFLinkIcon.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { MultiLang } from '../../../../config'; +import imageButton from '../../../assets/images/simpf_button.png'; + +interface Props { + lang: MultiLang; + url: string; + isDetail: boolean; +} + +const SimPFLinkIcon = (props: Props) => { + const { url, isDetail } = props; + const title = 'Online Simulation'; + const size = isDetail ? 64 : 35; + if (url === '') { + return null; + } + return ( + + {title} + + );; +} + +export default SimPFLinkIcon; diff --git a/src/database/item-type/lib/field/index.tsx b/src/database/item-type/lib/field/index.tsx new file mode 100644 index 0000000..12e70e6 --- /dev/null +++ b/src/database/item-type/lib/field/index.tsx @@ -0,0 +1,39 @@ +import Author from './Author'; +import ChangeLog from './ChangeLog'; +import Contributer from './Contributer'; +import CreativeCommons from './CreativeCommons'; +import DateTime from './DateTime'; +import Description from './Description'; +import FileSize from './FileSize'; +import FileDownloadButton from './FileDownloadButton'; +import FreeKeyword from './FreeKeyword'; +import ItemFile from './ItemFile'; +import ItemIndex from './ItemIndex'; +import Language from './Language'; +import Preview from './Preview'; +import PublicationDate from './PublicationDate'; +import Readme from './Readme'; +import RelatedTo from './RelatedTo'; +import Rights from './Rights'; + +const ItemTypeField = { + Author, + ChangeLog, + Contributer, + CreativeCommons, + DateTime, + Description, + FileDownloadButton, + FileSize, + FreeKeyword, + ItemFile, + ItemIndex, + Language, + Preview, + PublicationDate, + Readme, + RelatedTo, + Rights, +}; + +export default ItemTypeField; \ No newline at end of file diff --git a/src/database/item-type/memo/MemoAdvancedSearch.tsx b/src/database/item-type/memo/MemoAdvancedSearch.tsx new file mode 100644 index 0000000..9438658 --- /dev/null +++ b/src/database/item-type/memo/MemoAdvancedSearch.tsx @@ -0,0 +1,28 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class MemoAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'memo'; + this.title = 'Memo'; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['item_link'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Item Link[/en][ja]リンク[/ja]', value: this.renderFieldInputText('item_link', 50) }, + ]; + return rows; + } +} + +export default MemoAdvancedSearch; diff --git a/src/database/item-type/memo/MemoDetail.tsx b/src/database/item-type/memo/MemoDetail.tsx new file mode 100644 index 0000000..100441d --- /dev/null +++ b/src/database/item-type/memo/MemoDetail.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import XoopsCode from '../../../common/lib/XoopsCode'; +import Functions from '../../../functions'; +import { ItemMemo } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; + +class MemoDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemMemo; + const fields = [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Item Link[/en][ja]リンク[/ja]', value: }, + { label: '[en]Memo File[/en][ja]メモファイル[/ja]', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + return fields; + } +} + +export default MemoDetail; diff --git a/src/database/item-type/memo/MemoList.tsx b/src/database/item-type/memo/MemoList.tsx new file mode 100644 index 0000000..1fbd2cd --- /dev/null +++ b/src/database/item-type/memo/MemoList.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import XoopsCode from '../../../common/lib/XoopsCode'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_memo.gif'; +import { ItemMemo } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class MemoList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Memo'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemMemo; + const link = item.item_link !== '' ? : null; + return ( + <> + {Functions.mlang(item.title, lang)}
+ {link} + + ); + } +} + +export default MemoList; diff --git a/src/database/item-type/memo/MemoTop.tsx b/src/database/item-type/memo/MemoTop.tsx new file mode 100644 index 0000000..8245f96 --- /dev/null +++ b/src/database/item-type/memo/MemoTop.tsx @@ -0,0 +1,15 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_memo.gif'; + +class MemoTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'memo'; + this.label = 'Memo'; + this.icon = iconFile; + this.description = '[en]Personal Memo Pad.[/en][ja]汎用メモパッド[/ja]'; + } +} + +export default MemoTop; diff --git a/src/database/item-type/memo/index.tsx b/src/database/item-type/memo/index.tsx new file mode 100644 index 0000000..d5d2951 --- /dev/null +++ b/src/database/item-type/memo/index.tsx @@ -0,0 +1,13 @@ +import MemoTop from './MemoTop'; +import MemoList from './MemoList'; +import MemoDetail from './MemoDetail'; +import MemoAdvancedSearch from './MemoAdvancedSearch'; + +const ItemTypeMemo = { + Top: MemoTop, + List: MemoList, + Detail: MemoDetail, + AdvancedSearch: MemoAdvancedSearch, +}; + +export default ItemTypeMemo; \ No newline at end of file diff --git a/src/database/item-type/model/ModelAdvancedSearch.tsx b/src/database/item-type/model/ModelAdvancedSearch.tsx new file mode 100644 index 0000000..02cfd40 --- /dev/null +++ b/src/database/item-type/model/ModelAdvancedSearch.tsx @@ -0,0 +1,35 @@ +import { ItemModelSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class ModelAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'model'; + this.title = 'Model'; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['model_type'] = ''; + this.state.values['creator'] = ''; + this.state.values['file.preview.caption'] = ''; + this.state.values['file.model_data.original_file_name'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Model Type[/en][ja]モデルタイプ[/ja]', value: this.renderFieldSelect('model_type', ItemModelSubTypes) }, + { label: '[en]Creator[/en][ja]作成者[/ja]', value: this.renderFieldInputText('creator', 50) }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + { label: '[en]Model File[/en][ja]モデルファイル[/ja]', value: this.renderFieldInputText('file.model_data.original_file_name', 50) }, + ]; + return rows; + } +} + +export default ModelAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/model/ModelDetail.tsx b/src/database/item-type/model/ModelDetail.tsx new file mode 100644 index 0000000..837b712 --- /dev/null +++ b/src/database/item-type/model/ModelDetail.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Functions from '../../../functions'; +import ItemUtil, { ItemModel } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import SimPFLinkIcon from '../lib/field/SimPFLinkIcon'; +import ModelUtil from './ModelUtil'; + +class ModelDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemModel; + const fields = [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Model Type[/en][ja]モデルタイプ[/ja]', value: }, + { label: '[en]Creator[/en][ja]作成者[/ja]', value: }, + { label: '[en]Preview[/en][ja]プレビュー[/ja]', value: }, + { label: '[en]Model File[/en][ja]モデルファイル[/ja]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id); + if (simpfLinkUrl !== '') { + const field = { label: 'Online Simulation', value: }; + fields.splice(13, 0, field); + } + return fields; + } +} + +export default ModelDetail; \ No newline at end of file diff --git a/src/database/item-type/model/ModelList.tsx b/src/database/item-type/model/ModelList.tsx new file mode 100644 index 0000000..b709fa2 --- /dev/null +++ b/src/database/item-type/model/ModelList.tsx @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_model.gif'; +import { ItemModel } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class ModelList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Model'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemModel; + const authors = item.creator.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {authors} + + ); + } +} + +export default ModelList; \ No newline at end of file diff --git a/src/database/item-type/model/ModelTop.tsx b/src/database/item-type/model/ModelTop.tsx new file mode 100644 index 0000000..a86c994 --- /dev/null +++ b/src/database/item-type/model/ModelTop.tsx @@ -0,0 +1,18 @@ +import { ItemModelSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_model.gif'; + +class ModelTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'model'; + this.label = 'Model'; + this.icon = iconFile; + // OPENNEURO: + this.description = '[en]Model Program/Script[/en][ja]モデル プログラム/スクリプト[/ja]'; + this.subTypes = ItemModelSubTypes; + } +} + +export default ModelTop; \ No newline at end of file diff --git a/src/database/item-type/model/ModelUtil.tsx b/src/database/item-type/model/ModelUtil.tsx new file mode 100644 index 0000000..43a0c89 --- /dev/null +++ b/src/database/item-type/model/ModelUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemModelSubType, ItemModelSubTypes } from '../../lib/ItemUtil'; + +interface ModelTypeProps { + lang: MultiLang; + type: ItemModelSubType; +} + +const ModelType = (props: ModelTypeProps) => { + const { type } = props; + const subtype = ItemModelSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const ModelUtil = { + ModelType, +} + +export default ModelUtil; \ No newline at end of file diff --git a/src/database/item-type/model/index.tsx b/src/database/item-type/model/index.tsx new file mode 100644 index 0000000..dba8611 --- /dev/null +++ b/src/database/item-type/model/index.tsx @@ -0,0 +1,13 @@ +import ModelTop from './ModelTop'; +import ModelList from './ModelList'; +import ModelDetail from './ModelDetail'; +import ModelAdvancedSearch from './ModelAdvancedSearch'; + +const ItemTypeModel = { + Top: ModelTop, + List: ModelList, + Detail: ModelDetail, + AdvancedSearch: ModelAdvancedSearch, +}; + +export default ItemTypeModel; \ No newline at end of file diff --git a/src/database/item-type/paper/PaperAdvancedSearch.tsx b/src/database/item-type/paper/PaperAdvancedSearch.tsx new file mode 100644 index 0000000..a74e2d4 --- /dev/null +++ b/src/database/item-type/paper/PaperAdvancedSearch.tsx @@ -0,0 +1,39 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class PaperAdvancedSearch extends AdvancedSearchBase { + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'paper'; + this.title = 'Paper'; + this.state.values['pubmed_id'] = ''; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['author'] = ''; + this.state.values['journal'] = ''; + this.state.values['publication_year'] = ''; + this.state.values['volume'] = ''; + this.state.values['number'] = ''; + this.state.values['page'] = ''; + } + + getRows() { + const rows = [ + { label: 'PubMed ID', value: this.renderFieldInputText('pubmed_id', 50) }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Author[/en][ja]著者[/ja]', value: this.renderFieldInputText('author', 50) }, + { label: '[en]Journal[/en][ja]ジャーナル[/ja]', value: this.renderFieldInputText('journal', 50) }, + { label: '[en]Publication Year[/en][ja]出版年[/ja]', value: this.renderFieldInputText('publication_year', 10) }, + { label: '[en]Volume[/en][ja]巻[/ja]', value: this.renderFieldInputText('volume', 50) }, + { label: '[en]Number[/en][ja]号[/ja]', value: this.renderFieldInputText('number', 50) }, + { label: '[en]Page[/en][ja]ページ[/ja]', value: this.renderFieldInputText('page', 50) }, + ]; + return rows; + } +} + +export default PaperAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/paper/PaperDetail.tsx b/src/database/item-type/paper/PaperDetail.tsx new file mode 100644 index 0000000..6cbe55a --- /dev/null +++ b/src/database/item-type/paper/PaperDetail.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemPaper } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import OpenNeuroAuthor from '../lib/field/OpenNeuroAuthor'; +import OpenNeuroFreeKeyword from '../lib/field/OpenNeuroFreeKeyword'; +import OpenNeuroRelatedTo from '../lib/field/OpenNeuroRelatedTo'; +import PaperUtil from './PaperUtil'; + +class PaperDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemPaper; + // OPENNEURO: + return [ + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: 'PubMed ID', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[ja]アブストラクト[/ja][en]Abstract[/en]', value: }, + { label: '[ja]著者[/ja][en]Author[/en]', value: }, + { label: '[ja]ジャーナル名[/ja][en]Journal[/en]', value: Functions.mlang(item.journal, lang) }, + { label: '[ja]出版年[/ja][en]Publication Year[/en]', value: item.publication_year }, + { label: '[ja]巻[/ja][en]Volume[/en]', value: item.volume }, + { label: '[ja]号[/ja][en]Number[/en]', value: item.number }, + { label: '[ja]ページ[/ja][en]Page[/en]', value: item.page }, + { label: '[ja]フリーキーワード[/ja][en]Free keyword[/en]', value: }, + { label: '[ja]最終更新日[/ja][en]Last Update Date[/en]', value: }, + { label: '[ja]作成日[/ja][en]Creation Date[/en]', value: }, + { label: '[ja]所有者[/ja][en]Contributor[/en]', value: }, + { label: '[ja]査読者[/ja][en]Referee[/en]', value: Functions.mlang(item.referee, lang) }, + { label: '[ja]更新履歴[/ja][en]Change Log[/en]', value: }, + { label: '[ja]関連アイテム[/ja][en]Related Item[/en]', value: }, + ]; + } +} + +export default PaperDetail; \ No newline at end of file diff --git a/src/database/item-type/paper/PaperList.tsx b/src/database/item-type/paper/PaperList.tsx new file mode 100644 index 0000000..673d6d3 --- /dev/null +++ b/src/database/item-type/paper/PaperList.tsx @@ -0,0 +1,38 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_paper.gif'; +import { ItemPaper } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; +import PaperUtil from './PaperUtil'; + +class PaperList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Paper'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemPaper; + const authors = item.author.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {authors}
+ {Functions.mlang(item.journal, lang)} + {item.publication_year} + {item.volume !== null && ' ;' + item.volume} + {item.number !== null && ' (' + item.number + ')'} + {item.page !== '' && ' :' + item.page} + {item.pubmed_id !== '' && <> [PMID:]} + + ); + } +} + +export default PaperList; \ No newline at end of file diff --git a/src/database/item-type/paper/PaperTop.tsx b/src/database/item-type/paper/PaperTop.tsx new file mode 100644 index 0000000..2d829f6 --- /dev/null +++ b/src/database/item-type/paper/PaperTop.tsx @@ -0,0 +1,16 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_paper.gif'; + +class PaperTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'paper'; + this.label = 'Paper'; + this.icon = iconFile; + // OPENNEURO: + this.description = '[en]Paper[/en][ja]関連論文[/ja]'; + } +} + +export default PaperTop; \ No newline at end of file diff --git a/src/database/item-type/paper/PaperUtil.tsx b/src/database/item-type/paper/PaperUtil.tsx new file mode 100644 index 0000000..419b6a6 --- /dev/null +++ b/src/database/item-type/paper/PaperUtil.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; + +interface PubmedLinkProps { + lang: MultiLang; + pubmedId: string; +} + +const PubmedLink = (props: PubmedLinkProps) => { + const { pubmedId } = props; + if (pubmedId === '') { + return null; + } + const url = 'https://www.ncbi.nlm.nih.gov/pubmed/' + pubmedId; + return ({pubmedId}); +} + +const PaperUtil = { + PubmedLink, +} + +export default PaperUtil; \ No newline at end of file diff --git a/src/database/item-type/paper/index.tsx b/src/database/item-type/paper/index.tsx new file mode 100644 index 0000000..a781a94 --- /dev/null +++ b/src/database/item-type/paper/index.tsx @@ -0,0 +1,13 @@ +import PaperTop from './PaperTop'; +import PaperList from './PaperList'; +import PaperDetail from './PaperDetail'; +import PaperAdvancedSearch from './PaperAdvancedSearch'; + +const ItemTypePaper = { + Top: PaperTop, + List: PaperList, + Detail: PaperDetail, + AdvancedSearch: PaperAdvancedSearch, +}; + +export default ItemTypePaper; \ No newline at end of file diff --git a/src/database/item-type/presentation/PresentationAdvancedSearch.tsx b/src/database/item-type/presentation/PresentationAdvancedSearch.tsx new file mode 100644 index 0000000..d763def --- /dev/null +++ b/src/database/item-type/presentation/PresentationAdvancedSearch.tsx @@ -0,0 +1,46 @@ +import { ItemPresentationSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class PresentationAdvancedSearch extends AdvancedSearchBase { + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'presentation'; + this.title = 'Presentation'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['meeting_name'] = ''; + this.state.values['presentation_type'] = ''; + this.state.values['creator'] = ''; + this.state.values['publication_year'] = year; + this.state.values['publication_month'] = month; + this.state.values['publication_mday'] = mday; + this.state.values['file.preview.caption'] = ''; + this.state.values['file.presentation_file.original_file_name'] = ''; + this.setIgnoreKey('publication_year'); + this.setIgnoreKey('publication_month'); + this.setIgnoreKey('publication_mday'); + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Presentation Type[/en][ja]ファイル形式[/ja]', value: this.renderFieldSelect('presentation_type', ItemPresentationSubTypes) }, + { label: '[en]Creator[/en][ja]作成者[/ja]', value: this.renderFieldInputText('creator', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + { label: '[en]Presentation File[/en][ja]発表資料[/ja]', value: this.renderFieldInputText('file.presentation_file.original_file_name', 50) }, + ]; + return rows; + } +} + +export default PresentationAdvancedSearch; \ No newline at end of file diff --git a/src/database/item-type/presentation/PresentationDetail.tsx b/src/database/item-type/presentation/PresentationDetail.tsx new file mode 100644 index 0000000..bcb2b0a --- /dev/null +++ b/src/database/item-type/presentation/PresentationDetail.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemPresentation } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import OpenNeuroAuthor from '../lib/field/OpenNeuroAuthor'; +import OpenNeuroFreeKeyword from '../lib/field/OpenNeuroFreeKeyword'; +import OpenNeuroRelatedTo from '../lib/field/OpenNeuroRelatedTo'; +import PresentationUtil from './PresentationUtil'; + +class PresentationDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemPresentation; + return [ + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[ja]概要[/ja][en]Outline[/en]', value: }, + { label: '[ja]プレビュー[/ja][en]Preview[/en]', value: }, + { label: '[ja]発表資料[/ja][en]Presentation File[/en]', value: }, + { label: '[ja]日付[/ja][en]Date[/en]', value: }, + { label: '[ja]ファイル形式[/ja][en]File Format[/en]', value: }, + { label: '[ja]作成者[/ja][en]Creator[/en]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: '[ja]フリーキーワード[/ja][en]Free keyword[/en]', value: }, + { label: '[ja]最終更新日[/ja][en]Last Update Date[/en]', value: }, + { label: '[ja]作成日[/ja][en]Creation Date[/en]', value: }, + { label: '[ja]所有者[/ja][en]Contributor[/en]', value: }, + { label: '[ja]査読者[/ja][en]Referee[/en]', value: Functions.mlang(item.referee, lang) }, + { label: '[ja]更新履歴[/ja][en]Change Log[/en]', value: }, + { label: '[ja]関連アイテム[/ja][en]Related Item[/en]', value: }, + ]; + } +} + +export default PresentationDetail; \ No newline at end of file diff --git a/src/database/item-type/presentation/PresentationList.tsx b/src/database/item-type/presentation/PresentationList.tsx new file mode 100644 index 0000000..a6c8e61 --- /dev/null +++ b/src/database/item-type/presentation/PresentationList.tsx @@ -0,0 +1,33 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_presentation.gif'; +import { ItemPresentation } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; +import PresentationUtil from './PresentationUtil'; + +class PresentationList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Presentation'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemPresentation; + const authors = item.creator.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+
+ {authors} + + ); + } +} + +export default PresentationList; \ No newline at end of file diff --git a/src/database/item-type/presentation/PresentationTop.tsx b/src/database/item-type/presentation/PresentationTop.tsx new file mode 100644 index 0000000..f2492ba --- /dev/null +++ b/src/database/item-type/presentation/PresentationTop.tsx @@ -0,0 +1,18 @@ +import iconFile from '../../assets/images/icon_presentation.gif'; +import { ItemPresentationSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; + +class PresentationTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'presentation'; + this.label = 'Presentation'; + this.icon = iconFile; + // OPENNEURO: + this.description = '[en]Presentation File[/en][ja]プレゼンテーション ファイル[/ja]'; + this.subTypes = ItemPresentationSubTypes; + } +} + +export default PresentationTop; \ No newline at end of file diff --git a/src/database/item-type/presentation/PresentationUtil.tsx b/src/database/item-type/presentation/PresentationUtil.tsx new file mode 100644 index 0000000..e5140bc --- /dev/null +++ b/src/database/item-type/presentation/PresentationUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemPresentationSubType, ItemPresentationSubTypes } from '../../lib/ItemUtil'; + +interface PresentationTypeProps { + lang: MultiLang; + type: ItemPresentationSubType; +} + +const PresentationType = (props: PresentationTypeProps) => { + const { type } = props; + const subtype = ItemPresentationSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const PresentationUtil = { + PresentationType, +} + +export default PresentationUtil; \ No newline at end of file diff --git a/src/database/item-type/presentation/index.tsx b/src/database/item-type/presentation/index.tsx new file mode 100644 index 0000000..13493a3 --- /dev/null +++ b/src/database/item-type/presentation/index.tsx @@ -0,0 +1,13 @@ +import PresentationTop from './PresentationTop'; +import PresentationList from './PresentationList'; +import PresentationDetail from './PresentationDetail'; +import PresentationAdvancedSearch from './PresentationAdvancedSearch'; + +const ItemTypePresentation = { + Top: PresentationTop, + List: PresentationList, + Detail: PresentationDetail, + AdvancedSearch: PresentationAdvancedSearch, +}; + +export default ItemTypePresentation; \ No newline at end of file diff --git a/src/database/item-type/simulator/SimulatorAdvancedSearch.tsx b/src/database/item-type/simulator/SimulatorAdvancedSearch.tsx new file mode 100644 index 0000000..463681a --- /dev/null +++ b/src/database/item-type/simulator/SimulatorAdvancedSearch.tsx @@ -0,0 +1,44 @@ +import { ItemSimulatorSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class SimulatorAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'simulator'; + this.title = 'Simulator'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['simulator_type'] = ''; + this.state.values['developer'] = ''; + this.state.values['publication_year'] = year; + this.state.values['publication_month'] = month; + this.state.values['publication_mday'] = mday; + this.state.values['file.preview.caption'] = ''; + this.setIgnoreKey('publication_year'); + this.setIgnoreKey('publication_month'); + this.setIgnoreKey('publication_mday'); + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Simulator Type[/en][ja]シミュレータータイプ[/ja]', value: this.renderFieldSelect('simulator_type', ItemSimulatorSubTypes) }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: this.renderFieldInputText('developer', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + ]; + return rows; + } +} + +export default SimulatorAdvancedSearch; diff --git a/src/database/item-type/simulator/SimulatorDetail.tsx b/src/database/item-type/simulator/SimulatorDetail.tsx new file mode 100644 index 0000000..d876cb2 --- /dev/null +++ b/src/database/item-type/simulator/SimulatorDetail.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Functions from '../../../functions'; +import ItemUtil, { ItemSimulator } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import SimPFLinkIcon from '../lib/field/SimPFLinkIcon'; +import SimulatorUtil from './SimulatorUtil'; + +class SimulatorDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemSimulator; + const fields = [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Date[/en][ja]日付[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Simulator Type[/en][ja]シミュレータータイプ[/ja]', value: }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: }, + { label: '[en]Preview[/en][ja]プレビュー[/ja]', value: }, + { label: '[en]Simulator File[/en][ja]ファイル[/ja]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id); + if (simpfLinkUrl !== '') { + const field = { label: 'Online Simulation', value: }; + fields.splice(14, 0, field); + } + return fields; + } +} + +export default SimulatorDetail; \ No newline at end of file diff --git a/src/database/item-type/simulator/SimulatorList.tsx b/src/database/item-type/simulator/SimulatorList.tsx new file mode 100644 index 0000000..3b91629 --- /dev/null +++ b/src/database/item-type/simulator/SimulatorList.tsx @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_simulator.gif'; +import { ItemSimulator } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class SimulatorList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Simulator'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemSimulator; + const authors = item.developer.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {authors} + + ); + } +} + +export default SimulatorList; \ No newline at end of file diff --git a/src/database/item-type/simulator/SimulatorTop.tsx b/src/database/item-type/simulator/SimulatorTop.tsx new file mode 100644 index 0000000..95d75da --- /dev/null +++ b/src/database/item-type/simulator/SimulatorTop.tsx @@ -0,0 +1,17 @@ +import { ItemSimulatorSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_simulator.gif'; + +class SimulatorTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'simulator'; + this.label = 'Simulator'; + this.icon = iconFile; + this.description = '[en]Programs/scripts for simulation.[/en][ja]シミュレーション用プログラム/スクリプト[/ja]'; + this.subTypes = ItemSimulatorSubTypes; + } +} + +export default SimulatorTop; \ No newline at end of file diff --git a/src/database/item-type/simulator/SimulatorUtil.tsx b/src/database/item-type/simulator/SimulatorUtil.tsx new file mode 100644 index 0000000..adc52f5 --- /dev/null +++ b/src/database/item-type/simulator/SimulatorUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemSimulatorSubType, ItemSimulatorSubTypes } from '../../lib/ItemUtil'; + +interface SimulatorTypeProps { + lang: MultiLang; + type: ItemSimulatorSubType; +} + +const SimulatorType = (props: SimulatorTypeProps) => { + const { type } = props; + const subtype = ItemSimulatorSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const SimulatorUtil = { + SimulatorType, +} + +export default SimulatorUtil; \ No newline at end of file diff --git a/src/database/item-type/simulator/index.tsx b/src/database/item-type/simulator/index.tsx new file mode 100644 index 0000000..06c082d --- /dev/null +++ b/src/database/item-type/simulator/index.tsx @@ -0,0 +1,13 @@ +import SimulatorTop from './SimulatorTop'; +import SimulatorList from './SimulatorList'; +import SimulatorDetail from './SimulatorDetail'; +import SimulatorAdvancedSearch from './SimulatorAdvancedSearch'; + +const ItemTypeSimulator = { + Top: SimulatorTop, + List: SimulatorList, + Detail: SimulatorDetail, + AdvancedSearch: SimulatorAdvancedSearch, +}; + +export default ItemTypeSimulator; \ No newline at end of file diff --git a/src/database/item-type/stimulus/StimulusAdvancedSearch.tsx b/src/database/item-type/stimulus/StimulusAdvancedSearch.tsx new file mode 100644 index 0000000..d2a0bff --- /dev/null +++ b/src/database/item-type/stimulus/StimulusAdvancedSearch.tsx @@ -0,0 +1,44 @@ +import { ItemStimulusSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class StimulusAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'stimulus'; + this.title = 'Stimulus'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['stimulus_type'] = ''; + this.state.values['developer'] = ''; + this.state.values['publication_year'] = year; + this.state.values['publication_month'] = month; + this.state.values['publication_mday'] = mday; + this.state.values['file.preview.caption'] = ''; + this.setIgnoreKey('publication_year'); + this.setIgnoreKey('publication_month'); + this.setIgnoreKey('publication_mday'); + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Stimulus Type[/en][ja]刺激タイプ[/ja]', value: this.renderFieldSelect('stimulus_type', ItemStimulusSubTypes) }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: this.renderFieldInputText('developer', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + ]; + return rows; + } +} + +export default StimulusAdvancedSearch; diff --git a/src/database/item-type/stimulus/StimulusDetail.tsx b/src/database/item-type/stimulus/StimulusDetail.tsx new file mode 100644 index 0000000..2f16ad4 --- /dev/null +++ b/src/database/item-type/stimulus/StimulusDetail.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Functions from '../../../functions'; +import ItemUtil, { ItemStimulus } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import SimPFLinkIcon from '../lib/field/SimPFLinkIcon'; +import StimulusUtil from './StimulusUtil'; + +class StimulusDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemStimulus; + const fields = [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Date[/en][ja]日付[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Stimulus Type[/en][ja]刺激タイプ[/ja]', value: }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: }, + { label: '[en]Preview[/en][ja]プレビュー[/ja]', value: }, + { label: '[en]Stimulus File[/en][ja]ファイル[/ja]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id); + if (simpfLinkUrl !== '') { + const field = { label: 'Online Simulation', value: }; + fields.splice(14, 0, field); + } + return fields; + } +} + +export default StimulusDetail; diff --git a/src/database/item-type/stimulus/StimulusList.tsx b/src/database/item-type/stimulus/StimulusList.tsx new file mode 100644 index 0000000..baae6b7 --- /dev/null +++ b/src/database/item-type/stimulus/StimulusList.tsx @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_stimulus.gif'; +import { ItemStimulus } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class StimulusList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Stimulus'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemStimulus; + const authors = item.developer.map((author, i) => { + return {i > 0 && ', '}{Functions.mlang(author, lang)} + }); + return ( + <> + {Functions.mlang(item.title, lang)}
+ {authors} + + ); + } +} + +export default StimulusList; \ No newline at end of file diff --git a/src/database/item-type/stimulus/StimulusTop.tsx b/src/database/item-type/stimulus/StimulusTop.tsx new file mode 100644 index 0000000..a58ec2f --- /dev/null +++ b/src/database/item-type/stimulus/StimulusTop.tsx @@ -0,0 +1,17 @@ +import { ItemStimulusSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_stimulus.gif'; + +class StimulusTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'stimulus'; + this.label = 'Stimulus'; + this.icon = iconFile; + this.description = '[en]Picture, movie and program files for experimental stimuli.[/en][ja]実験用刺激プログラム/スクリプト[/ja]'; + this.subTypes = ItemStimulusSubTypes; + } +} + +export default StimulusTop; \ No newline at end of file diff --git a/src/database/item-type/stimulus/StimulusUtil.tsx b/src/database/item-type/stimulus/StimulusUtil.tsx new file mode 100644 index 0000000..3a0f8c8 --- /dev/null +++ b/src/database/item-type/stimulus/StimulusUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemStimulusSubType, ItemStimulusSubTypes } from '../../lib/ItemUtil'; + +interface StimulusTypeProps { + lang: MultiLang; + type: ItemStimulusSubType; +} + +const StimulusType = (props: StimulusTypeProps) => { + const { type } = props; + const subtype = ItemStimulusSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const StimulusUtil = { + StimulusType, +} + +export default StimulusUtil; \ No newline at end of file diff --git a/src/database/item-type/stimulus/index.tsx b/src/database/item-type/stimulus/index.tsx new file mode 100644 index 0000000..b01ff6c --- /dev/null +++ b/src/database/item-type/stimulus/index.tsx @@ -0,0 +1,13 @@ +import StimulusTop from './StimulusTop'; +import StimulusList from './StimulusList'; +import StimulusDetail from './StimulusDetail'; +import StimulusAdvancedSearch from './StimulusAdvancedSearch'; + +const ItemTypeStimulus = { + Top: StimulusTop, + List: StimulusList, + Detail: StimulusDetail, + AdvancedSearch: StimulusAdvancedSearch, +}; + +export default ItemTypeStimulus; \ No newline at end of file diff --git a/src/database/item-type/tool/ToolAdvancedSearch.tsx b/src/database/item-type/tool/ToolAdvancedSearch.tsx new file mode 100644 index 0000000..6c9a8d3 --- /dev/null +++ b/src/database/item-type/tool/ToolAdvancedSearch.tsx @@ -0,0 +1,46 @@ +import { ItemToolSubTypes } from '../../lib/ItemUtil'; +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class ToolAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'tool'; + this.title = 'Tool'; + const now = new Date(); + const year = String(now.getFullYear()); + const month = String(now.getMonth() + 1); + const mday = String(now.getDate()); + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['tool_type'] = ''; + this.state.values['developer'] = ''; + this.state.values['publication_year'] = year; + this.state.values['publication_month'] = month; + this.state.values['publication_mday'] = mday; + this.state.values['file.preview.caption'] = ''; + this.state.values['file.tool_data.original_file_name'] = ''; + this.setIgnoreKey('publication_year'); + this.setIgnoreKey('publication_month'); + this.setIgnoreKey('publication_mday'); + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: '[en]Tool Type[/en][ja]ファイルタイプ[/ja]', value: this.renderFieldSelect('tool_type', ItemToolSubTypes) }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: this.renderFieldInputText('developer', 50) }, + { label: '[en]Date[/en][ja]日付[/ja]', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') }, + { label: '[en]Caption[/en][ja]キャプション[/ja]', value: this.renderFieldInputText('file.preview.caption', 50) }, + { label: '[en]Tool File[/en][ja]ファイル[/ja]', value: this.renderFieldInputText('file.tool_data.original_file_name', 50) }, + ]; + return rows; + } +} + +export default ToolAdvancedSearch; diff --git a/src/database/item-type/tool/ToolDetail.tsx b/src/database/item-type/tool/ToolDetail.tsx new file mode 100644 index 0000000..aa30c02 --- /dev/null +++ b/src/database/item-type/tool/ToolDetail.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Functions from '../../../functions'; +import ItemUtil, { ItemTool } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import SimPFLinkIcon from '../lib/field/SimPFLinkIcon'; +import ToolUtil from './ToolUtil'; + +class ToolDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemTool; + const fields = [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: '[en]Tool Type[/en][ja]ファイルタイプ[/ja]', value: }, + { label: '[en]Developer[/en][ja]開発者[/ja]', value: }, + { label: '[en]Preview[/en][ja]プレビュー[/ja]', value: }, + { label: '[en]Tool File[/en][ja]ファイル[/ja]', value: }, + { label: 'Readme', value: }, + { label: 'Rights', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id); + if (simpfLinkUrl !== '') { + const field = { label: 'Online Simulation', value: }; + fields.splice(13, 0, field); + } + return fields; + } +} + +export default ToolDetail; \ No newline at end of file diff --git a/src/database/item-type/tool/ToolList.tsx b/src/database/item-type/tool/ToolList.tsx new file mode 100644 index 0000000..27ee027 --- /dev/null +++ b/src/database/item-type/tool/ToolList.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_tool.gif'; +import { ItemTool } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; +import ToolUtil from './ToolUtil'; + +class ToolList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Tool'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemTool; + return ( + <> + {Functions.mlang(item.title, lang)}
+ + + ); + } +} + +export default ToolList; \ No newline at end of file diff --git a/src/database/item-type/tool/ToolTop.tsx b/src/database/item-type/tool/ToolTop.tsx new file mode 100644 index 0000000..d1c95b5 --- /dev/null +++ b/src/database/item-type/tool/ToolTop.tsx @@ -0,0 +1,18 @@ +import { ItemToolSubTypes } from '../../lib/ItemUtil'; +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_tool.gif'; + +class ToolTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'tool'; + this.label = 'Tool'; + this.icon = iconFile; + // OPENNEURO: + this.description = '[en]Program/Script for data analysis[/en][ja]データ解析用プログラム/スクリプト[/ja]'; + this.subTypes = ItemToolSubTypes; + } +} + +export default ToolTop; \ No newline at end of file diff --git a/src/database/item-type/tool/ToolUtil.tsx b/src/database/item-type/tool/ToolUtil.tsx new file mode 100644 index 0000000..3b3864c --- /dev/null +++ b/src/database/item-type/tool/ToolUtil.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import { ItemToolSubType, ItemToolSubTypes } from '../../lib/ItemUtil'; + +interface ToolTypeProps { + lang: MultiLang; + type: ItemToolSubType; +} + +const ToolType = (props: ToolTypeProps) => { + const { type } = props; + const subtype = ItemToolSubTypes.find((value) => { return value.type === type; }); + if (typeof subtype === 'undefined') { + return null; + } + return ({subtype.label}); +} + +const ToolUtil = { + ToolType, +} + +export default ToolUtil; \ No newline at end of file diff --git a/src/database/item-type/tool/index.tsx b/src/database/item-type/tool/index.tsx new file mode 100644 index 0000000..23a334c --- /dev/null +++ b/src/database/item-type/tool/index.tsx @@ -0,0 +1,13 @@ +import ToolTop from './ToolTop'; +import ToolList from './ToolList'; +import ToolDetail from './ToolDetail'; +import ToolAdvancedSearch from './ToolAdvancedSearch'; + +const ItemTypeTool = { + Top: ToolTop, + List: ToolList, + Detail: ToolDetail, + AdvancedSearch: ToolAdvancedSearch, +}; + +export default ItemTypeTool; \ No newline at end of file diff --git a/src/database/item-type/url/UrlAdvancedSearch.tsx b/src/database/item-type/url/UrlAdvancedSearch.tsx new file mode 100644 index 0000000..2062516 --- /dev/null +++ b/src/database/item-type/url/UrlAdvancedSearch.tsx @@ -0,0 +1,28 @@ +import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase'; + +class UrlAdvancedSearch extends AdvancedSearchBase { + + constructor(props: AdvancedSearchBaseProps) { + super(props); + this.type = 'url'; + this.title = 'Url'; + this.state.values['title'] = ''; + this.state.values['keyword'] = ''; + this.state.values['description'] = ''; + this.state.values['doi'] = ''; + this.state.values['url'] = ''; + } + + getRows() { + const rows = [ + { label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: this.renderFieldInputText('keyword', 50) }, + { label: '[en]Description[/en][ja]概要[/ja]', value: this.renderFieldInputText('description', 50) }, + { label: 'ID', value: this.renderFieldInputText('doi', 50) }, + { label: 'URL', value: this.renderFieldInputText('url', 50) }, + ]; + return rows; + } +} + +export default UrlAdvancedSearch; diff --git a/src/database/item-type/url/UrlDetail.tsx b/src/database/item-type/url/UrlDetail.tsx new file mode 100644 index 0000000..7637fc6 --- /dev/null +++ b/src/database/item-type/url/UrlDetail.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import Functions from '../../../functions'; +import { ItemUrl } from '../../lib/ItemUtil'; +import DetailBase from '../lib/DetailBase'; +import ItemTypeField from '../lib/field'; +import UrlUtil from './UrlUtil'; + +class UrlDetail extends DetailBase { + + getFields() { + const { lang } = this.props; + const item = this.props.item as ItemUrl; + return [ + { label: 'ID', value: item.doi }, + { label: '[en]Language[/en][ja]言語[/ja]', value: }, + { label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) }, + { label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: }, + { label: '[en]Description[/en][ja]概要[/ja]', value: }, + { label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]', value: }, + { label: '[en]Created Date[/en][ja]作成日[/ja]', value: }, + { label: '[en]Contributor[/en][ja]登録者[/ja]', value: }, + { label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: item.item_type_display_name }, + { label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]', value: }, + { label: 'URL', value: {item.url} }, + { label: '[en]Banner File[/en][ja]バナー[/ja]', value: }, + { label: 'Index', value: }, + { label: '[en]Related to[/en][ja]関連アイテム[/ja]', value: }, + ]; + } +} + +export default UrlDetail; \ No newline at end of file diff --git a/src/database/item-type/url/UrlList.tsx b/src/database/item-type/url/UrlList.tsx new file mode 100644 index 0000000..16905b1 --- /dev/null +++ b/src/database/item-type/url/UrlList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import Functions from '../../../functions'; +import iconFile from '../../assets/images/icon_url.gif'; +import { ItemUrl } from '../../lib/ItemUtil'; +import ListBase, { ListBaseProps } from '../lib/ListBase'; + +class UrlList extends ListBase { + + constructor(props: ListBaseProps) { + super(props); + this.label = 'Url'; + this.icon = iconFile; + } + + renderBody() { + const { lang } = this.props; + const item = this.props.item as ItemUrl; + return ( + <> + {Functions.mlang(item.title, lang)}
+ Link to {item.url} + + ); + } +} + +export default UrlList; \ No newline at end of file diff --git a/src/database/item-type/url/UrlTop.tsx b/src/database/item-type/url/UrlTop.tsx new file mode 100644 index 0000000..ed0c472 --- /dev/null +++ b/src/database/item-type/url/UrlTop.tsx @@ -0,0 +1,15 @@ +import TopBase, { TopBaseProps } from '../lib/TopBase'; +import iconFile from '../../assets/images/icon_url.gif'; + +class UrlTop extends TopBase { + + constructor(props: TopBaseProps) { + super(props); + this.type = 'url'; + this.label = 'Url'; + this.icon = iconFile; + this.description = '[en]Link information.[/en][ja]関連リンク[/ja]'; + } +} + +export default UrlTop; \ No newline at end of file diff --git a/src/database/item-type/url/UrlUtil.tsx b/src/database/item-type/url/UrlUtil.tsx new file mode 100644 index 0000000..1dbbfd2 --- /dev/null +++ b/src/database/item-type/url/UrlUtil.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { MultiLang } from '../../../config'; +import ItemUtil, { ItemBasicFile } from '../../lib/ItemUtil'; + +interface BannerFileProps { + lang: MultiLang; + file: ItemBasicFile[]; +} + +const BannerFile = (props: BannerFileProps) => { + const { file } = props; + const data = file.find((value) => { + return value.file_type_name === 'url_banner_file'; + }); + if (typeof data === 'undefined') { + return null; + } + const url = ItemUtil.getFileUrl(data); + return ( + banner + ); +} + +const UrlUtil = { + BannerFile, +} + +export default UrlUtil; diff --git a/src/database/item-type/url/index.tsx b/src/database/item-type/url/index.tsx new file mode 100644 index 0000000..b359df2 --- /dev/null +++ b/src/database/item-type/url/index.tsx @@ -0,0 +1,13 @@ +import UrlTop from './UrlTop'; +import UrlList from './UrlList'; +import UrlDetail from './UrlDetail'; +import UrlAdvancedSearch from './UrlAdvancedSearch'; + +const ItemTypeUrl = { + Top: UrlTop, + List: UrlList, + Detail: UrlDetail, + AdvancedSearch: UrlAdvancedSearch, +}; + +export default ItemTypeUrl; diff --git a/src/database/lib/AdvancedSearchQuery.ts b/src/database/lib/AdvancedSearchQuery.ts new file mode 100644 index 0000000..2b528f8 --- /dev/null +++ b/src/database/lib/AdvancedSearchQuery.ts @@ -0,0 +1,95 @@ +type AdvancedSearchQueryData = Map; + +class AdvancedSearchQuery { + + private dataset: AdvancedSearchQueryData = new Map(); + + empty() { + let ret = true; + this.dataset.forEach((data) => { + data.forEach((value) => { + value = value.trim(); + if (value.length !== 0) { + ret = false; + } + }); + }); + return ret; + } + + set(type: string, key: string, value: string) { + type = type.trim(); + key = key.trim(); + const data: URLSearchParams = this.dataset.has(type) ? this.dataset.get(type) as URLSearchParams : new URLSearchParams(); + data.set(key, value); + this.dataset.set(type, data); + } + + delete(type: string, key: string) { + type = type.trim(); + key = key.trim(); + const data: URLSearchParams = this.dataset.has(type) ? this.dataset.get(type) as URLSearchParams : new URLSearchParams(); + data.delete(key); + this.dataset.set(type, data); + } + + deleteType(type: string) { + type = type.trim(); + this.dataset.delete(type.trim()); + } + + getQueryParams() { + let ret: URLSearchParams = new URLSearchParams(); + this.dataset.forEach((data, type) => { + data.forEach((value, key) => { + value = value.trim(); + value.length > 0 && ret.set(type + '.' + key, value); + }); + }); + return ret; + } + + setByQueryString(queryString: string) { + const query = new URLSearchParams(queryString); + query.forEach((v, k) => { + const [type, ...key] = k.split('.'); + if (typeof key === 'undefined') { + return; + } + this.set(type, key.join('.'), v); + }); + } + + getSearchFilter() { + let filter: any = []; + this.dataset.forEach((data, type) => { + let filter2: any = []; + filter2.push({ + item_type_name: 'xnp' + type, + }); + data.forEach((value, key) => { + const regex = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const file = key.match(/^file\.([^.]+)\.([^.]+)$/); + if (file !== null) { + const fileType = file[1]; + const subKey = file[2]; + // TODO: wait to support $elemMatch operation on lokijs + // - see: https://github.com/techfort/LokiJS/issues/736 + // const filter3: any = { + // file_type_name: fileType, + // [subKey]: { '$regex': regex } + // }; + // filter2.push({ file: { '$elemMatch': filter3 } }); + filter2.push({ 'file.file_type_name': fileType }); + filter2.push({ ['file.' + subKey]: { '$regex': [regex, 'i'] } }); + } else { + filter2.push({ [key]: { '$regex': [regex, 'i'] } }); + } + }); + filter.push({ '$and': filter2 }); + }); + return { '$or': filter }; + } +} + +export default AdvancedSearchQuery; \ No newline at end of file diff --git a/src/database/lib/DatabaseListIndex.tsx b/src/database/lib/DatabaseListIndex.tsx new file mode 100644 index 0000000..9b4521f --- /dev/null +++ b/src/database/lib/DatabaseListIndex.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { MultiLang } from '../../config'; +import Functions from '../../functions'; +import iconFile from '../assets/images/icon_folder.gif'; +import IndexUtil, { Index } from './IndexUtil'; + +interface Props { + lang: MultiLang; + index: Index; +} + +const ListIndexItem = (props: Props) => { + const { lang, index } = props; + const url = IndexUtil.getUrl(index.id); + const title = Functions.mlang(index.title, lang); + const numOfIndexes = IndexUtil.countChildren(index.id); + return ( + + + + + + + +
+ {title} + + {title} +
+ {numOfIndexes} indexes / {index.numOfItems} items +
+ ); +} + +const DatabaseListIndex = (props: Props) => { + const { lang } = props; + const children = IndexUtil.getChildren(props.index.id); + if (children.length === 0) { + return null; + } + const treeList = children.map((value: Index, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + return ( + + + + + + ); + }); + return ( + + + {treeList} + +
+ ); +} + +export default DatabaseListIndex; diff --git a/src/database/lib/DatabaseListItem.module.css b/src/database/lib/DatabaseListItem.module.css new file mode 100644 index 0000000..a2f6d4f --- /dev/null +++ b/src/database/lib/DatabaseListItem.module.css @@ -0,0 +1,11 @@ +.sortCriteria { + text-align: center; +} + +.pageNavi { + text-align: right; +} + +.pageNaviItem { + margin: 0 5px; +} \ No newline at end of file diff --git a/src/database/lib/DatabaseListItem.tsx b/src/database/lib/DatabaseListItem.tsx new file mode 100644 index 0000000..00c7844 --- /dev/null +++ b/src/database/lib/DatabaseListItem.tsx @@ -0,0 +1,241 @@ +import React, { ChangeEvent, Component } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { Link, withRouter } from 'react-router-dom'; +import Loading from '../../common/lib/Loading'; +import { MultiLang } from '../../config'; +import Functions from '../../functions'; +import ItemType from '../item-type'; +import styles from './DatabaseListItem.module.css'; +import { Item, SearchFunc, SearchResult, SortCondition, SortConditionLimit, SortConditionOrderBy, SortConditionOrderDir } from './ItemUtil'; + +const SORT_CONDITION_DEFAULT: SortCondition = { + limit: 20, + page: 1, + orderBy: 'title', + orderDir: 1 +}; +const SORT_CONDITION_RANGE_ORDER_BY = ['title', 'doi', 'last_update_date', 'creation_date', 'publication_date']; +const SORT_CONDITION_RANGE_ORDER_DIR = ['0', '1']; +const SORT_CONDITION_RANGE_LIMIT = ['20', '50', '100']; + +interface Props extends RouteComponentProps { + lang: MultiLang; + url: string; + search: SearchFunc +} + +interface State { + loading: boolean; + condition: SortCondition; + result: SearchResult; +} + +class DatabaseListItem extends Component { + + private isActive = false; + + constructor(props: Props) { + super(props); + this.state = { + loading: true, + condition: this.getSortConditionByQuery(this.props.location.search), + result: { total: 0, data: [] } + }; + this.handleSelectOrderby = this.handleSelectOrderby.bind(this); + this.handleSelectItemcount = this.handleSelectItemcount.bind(this); + } + + componentDidMount() { + this.isActive = true; + this.props.search(this.state.condition, (result) => { + if (this.isActive) { + this.setState({ loading: false, result }); + } + }); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const condition = this.getSortConditionByQuery(this.props.location.search); + if (prevProps.url !== this.props.url || this.isConditionChanged(prevState.condition, condition)) { + this.props.search(condition, (result) => { + if (this.isActive) { + this.setState({ loading: false, result, condition }); + } + }); + } + } + + componentWillUnmount() { + this.isActive = false; + } + + handleSelectOrderby(event: ChangeEvent) { + const value = event.target.value; + const url = this.getListUrl({ orderby: value }); + this.props.history.push(url); + } + + handleSelectItemcount(event: ChangeEvent) { + const value = parseInt(event.target.value, 10); + const url = this.getListUrl({ limit: value }); + this.props.history.push(url); + } + + isConditionChanged(prev: SortCondition, next: SortCondition) { + return prev.limit !== next.limit || prev.orderBy !== next.orderBy || prev.orderDir !== next.orderDir || prev.page !== next.page; + } + + getListUrl(query: { orderby?: string, order_dir?: number, limit?: number, page?: number }) { + const newOrderBy: string = typeof query.orderby !== 'undefined' ? query.orderby : this.state.condition.orderBy; + const newOrderDir: number = typeof query.order_dir !== 'undefined' ? query.order_dir : this.state.condition.orderDir; + const newLimit: number = typeof query.limit !== 'undefined' ? query.limit : this.state.condition.limit; + let newPage: number = typeof query.page !== 'undefined' ? query.page : this.state.condition.page; + if (newLimit !== this.state.condition.limit || newOrderBy !== this.state.condition.orderBy) { + newPage = SORT_CONDITION_DEFAULT.page; + } + const params = new URLSearchParams({ + orderby: newOrderBy, + order_dir: String(newOrderDir), + itemcount: String(newLimit), + page: String(newPage), + }); + const join = this.props.url.indexOf('?') < 0 ? '?' : '&' + return this.props.url + join + params.toString(); + } + + getDefaultSortCondition() { + return Object.assign({}, SORT_CONDITION_DEFAULT); + }; + + getSortConditionByQuery(queryString: string) { + const query = new URLSearchParams(queryString); + const condition = this.getDefaultSortCondition(); + const orderby = query.get('orderby'); + if (orderby !== null && SORT_CONDITION_RANGE_ORDER_BY.includes(orderby)) { + condition.orderBy = orderby as SortConditionOrderBy; + } + const order_dir = query.get('order_dir'); + if (order_dir !== null && SORT_CONDITION_RANGE_ORDER_DIR.includes(order_dir)) { + condition.orderDir = parseInt(order_dir, 10) as SortConditionOrderDir; + } + const itemcount = query.get('itemcount'); + if (itemcount !== null && SORT_CONDITION_RANGE_LIMIT.includes(itemcount)) { + condition.limit = parseInt(itemcount, 10) as SortConditionLimit; + } + const page = query.get('page'); + if (page !== null && page.match(/^\d+$/) !== null) { + condition.page = parseInt(page, 10); + } + return condition; + } + + renderSortCriteria() { + const { lang } = this.props; + let orderDir; + if (this.state.condition.orderDir !== SortConditionOrderDir.ASC) { + const url = this.getListUrl({ order_dir: SortConditionOrderDir.ASC }); + orderDir = ( + ▼ ▲ + ); + } else { + const url = this.getListUrl({ order_dir: SortConditionOrderDir.DESC }); + orderDir = ( + ▼ ▲ + ); + } + return ( +
+ + + + + + + +
+ Order by  + +  {orderDir} + + No.Item per page  + +
+
+ ); + }; + + renderPageNavi(result: SearchResult) { + const maxPage = Math.floor(result.total / this.state.condition.limit) + (result.total % this.state.condition.limit !== 0 ? 1 : 0); + const link = (title: string, page: number, id: string) => { + if (page === 0 || page === this.state.condition.page) { + return ({title}); + } + const url: string = this.getListUrl({ page: page }); + return ({title}); + } + let startPage = (this.state.condition.page - 4) > 1 ? this.state.condition.page - 4 : 1; + const endPage = (startPage + 9) > maxPage ? maxPage : startPage + 9; + if ((endPage - startPage) < 9) { + startPage = (endPage - 9) > 0 ? endPage - 9 : 1; + } + let pageLinks: JSX.Element[] = []; + pageLinks.push(link('PREV', this.state.condition.page - 1, 'p')); + for (let i = startPage; i <= endPage; i++) { + pageLinks.push(link(String(i), i, String(i))); + } + pageLinks.push(link('NEXT', this.state.condition.page >= maxPage ? 0 : this.state.condition.page + 1, 'n')); + return ( +
+ {pageLinks} +
+ ); + } + + render() { + const { lang } = this.props; + if (this.state.loading) { + return ; + } + const result = this.state.result; + if (result.data.length === 0) { + return ( +

No items found.

+ ); + } + const startNum = 1 + this.state.condition.limit * (this.state.condition.page - 1); + let endNum = this.state.condition.limit * this.state.condition.page; + if (endNum > result.total) { + endNum = result.total; + } + const pageNavi = this.renderPageNavi(result); + const items = result.data.map((item: Item, idx) => { + const evenodd = idx % 2 === 0 ? 'even' : 'odd'; + return (); + }); + return ( + <> + {this.renderSortCriteria()} +

{startNum} - {endNum} of {result.total} Items

+ {pageNavi} + + + {items} + +
+ {pageNavi} + + ); + } +} + +export default withRouter(DatabaseListItem); \ No newline at end of file diff --git a/src/database/lib/IndexUtil.ts b/src/database/lib/IndexUtil.ts new file mode 100644 index 0000000..5664ead --- /dev/null +++ b/src/database/lib/IndexUtil.ts @@ -0,0 +1,95 @@ +import loki from 'lokijs'; +import indexesJson from '../assets/tree.json'; + +export interface Index { + id: number; + title: string; + numOfItems: number; + parentId: number; + weight: number; +} + +export const INDEX_ID_ROOT = 1; +export const INDEX_ID_PUBLIC = 3; + +interface IndexData { + id: number; + title: string; + num_of_items: number; + children: IndexData[]; +} +type IndexesData = IndexData[]; + +class IndexUtil { + + private database: loki; + private indexes: Collection; + + constructor(json: IndexesData) { + this.database = new loki('database'); + this.indexes = this.database.addCollection('indexes'); + this.load(json); + } + + load(json: IndexesData): void { + const store = (indexesData: IndexesData, parentId: number) => { + indexesData.forEach((indexData: IndexData, idx) => { + const entry: Index = { + id: indexData.id, + title: indexData.title, + numOfItems: indexData.num_of_items, + parentId: parentId, + weight: idx, + }; + this.indexes.insert(entry); + store(indexData.children, indexData.id) + }); + } + store(json, INDEX_ID_ROOT); + } + + getUrl(id: number): string { + return '/database/list/' + String(id); + } + + get(indexId: number): Index | null { + const filter = { + 'id': indexId, + } + const res = this.indexes.findOne(filter); + return res; + } + + getChildren(indexId: number): Index[] { + const filter = { + 'parentId': indexId, + } + const res = this.indexes.chain().find(filter).simplesort('weight').data(); + return res; + } + + countChildren(indexId: number): number { + const filter = { + 'parentId': indexId, + } + const res = this.indexes.count(filter); + return res; + } + + getParents(parentId: number): Index[] { + let parents: Index[] = []; + const loop = (parentId: number) => { + if (parentId !== INDEX_ID_ROOT) { + const parent = this.get(parentId); + if (parent !== null) { + loop(parent.parentId); + parents.push(parent); + } + } + } + loop(parentId); + return parents; + } +} + +export default new IndexUtil(indexesJson); \ No newline at end of file diff --git a/src/database/lib/ItemUtil.ts b/src/database/lib/ItemUtil.ts new file mode 100644 index 0000000..3232993 --- /dev/null +++ b/src/database/lib/ItemUtil.ts @@ -0,0 +1,697 @@ +import AsyncLock from 'async-lock'; +import axios from 'axios'; +import loki from 'lokijs'; +import funcs from '../../functions'; +import simpfLinksJson from '../assets/simpf-links.json'; +import AdvancedSearchQuery from './AdvancedSearchQuery'; + +interface SimPFLink { + id: number; + url: string; +} + +interface ItemSubType { + type: T; + label: string; +} + +export type ItemSubTypes = readonly ItemSubType[]; + +export type ItemBasicLang = 'eng' | 'jpn' | 'fra' | 'deu' | 'esl' | 'ita' | 'dut' | 'sve' | 'nor' | 'dan' | 'fin' | 'por' | 'chi' | 'kor'; +export interface ItemBasicIndex { + index_id: number; + title: string; +} +export interface ItemBasicChangeLog { + log_date: number; + log: string; +} +export interface ItemBasicFile { + file_id: number; + original_file_name: string; + mime_type: string; + file_size: number; + caption: string; + timestamp: number; + file_type_name: string; + file_type_display_name: string; +} +export interface ItemCore { + item_id: number; + doi: string; +} +export interface ItemBasic extends ItemCore { + uid: number; + description: string; + last_update_date: number; + creation_date: number; + publication_year: number; + publication_month: number; + publication_mday: number; + lang: ItemBasicLang; + title: string; + item_type_display_name: string; + item_type_name: string; + uname: string; + name: string; + item_url: string; + index: ItemBasicIndex[]; + changelog: ItemBasicChangeLog[]; + related_to: number[]; + keyword: string[]; + file: ItemBasicFile[]; +} + +export interface ItemBinder extends ItemBasic { + extra: string; + item_link: number[]; + // OPENNEURO: + research_area: string; + species: string; + scale: string; + method: string; + agree: string; + referee: string; + guide_to_temporaryuser: string; +} + +export interface ItemBook extends ItemBasic { + classfication: string; + editor: string; + publisher: string; + isbn: string; + url: string; + attachment_dl_limit: number; + attachment_dl_notify: number; + author: string[]; +} + +export type ItemConferenceSubType = 'powerpoint' | 'pdf' | 'illustrator' | 'other'; +export const ItemConferenceSubTypes: ItemSubTypes = [ + { type: 'powerpoint', label: 'PowerPoint' }, + { type: 'pdf', label: 'PDF' }, + { type: 'illustrator', label: 'Illustrator' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemConference extends ItemBasic { + presentation_type: ItemConferenceSubType; + conference_title: string; + place: string; + abstract: string; + conference_from_year: number; + conference_from_month: number; + conference_from_mday: number; + conference_to_year: number; + conference_to_month: number; + conference_to_mday: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + author: string[]; +} + +export type ItemDataSubType = 'excel' | 'movie' | 'text' | 'picture' | 'other'; +export const ItemDataSubTypes: ItemSubTypes = [ + { type: 'excel', label: 'Excel' }, + { type: 'movie', label: 'Movie' }, + { type: 'text', label: 'Text' }, + { type: 'picture', label: 'Picture' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemData extends ItemBasic { + data_type: ItemDataSubType; + rights: string; + readme: string; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + experimenter: string[]; + // OPENNEURO: + referee: string; +} + +export type ItemFilesSubType = 'pdf' | 'doc' | 'xls' | 'ppt' | 'docx' | 'xlsx' | 'pptx' | 'zip' | 'lzh' | 'mov' // CBSN extended +export const ItemFilesSubTypes: ItemSubTypes = [ + { type: 'pdf', label: 'pdf' }, + { type: 'doc', label: 'doc' }, + { type: 'xls', label: 'xls' }, + { type: 'ppt', label: 'ppt' }, + { type: 'docx', label: 'docx' }, + { type: 'xlsx', label: 'xlsx' }, + { type: 'pptx', label: 'pptx' }, + { type: 'zip', label: 'zip' }, + { type: 'lzh', label: 'lzh' }, + { type: 'mov', label: 'mov' }, +]; +export interface ItemFiles extends ItemBasic { + data_file_name: string; + data_file_mimetype: string; + data_file_filetype: string; +} + +export interface ItemMemo extends ItemBasic { + item_link: string; +} + +export type ItemModelSubType = 'matlab' | 'neuron' | 'original_program' | 'satellite' | 'genesis' | 'a_cell' | 'other'; +export const ItemModelSubTypes: ItemSubTypes = [ + { type: 'matlab', label: 'Matlab' }, + { type: 'neuron', label: 'Neuron' }, + { type: 'original_program', label: 'Original Program' }, + { type: 'satellite', label: 'Satellite' }, + { type: 'genesis', label: 'Genesis' }, + { type: 'a_cell', label: 'A-Cell' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemModel extends ItemBasic { + model_type: ItemModelSubType; + readme: string; + rights: string; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + creator: string[]; +} + +export interface ItemPaper extends ItemBasic { + journal: string; + volume: number; + number: number | null; + page: string; + abstract: string; + pubmed_id: string; + author: string[]; + // OPENNEURO: + referee: string; +} + +// OPENNEURO: +export type ItemPresentationSubType = 'powerpoint' | 'illustrator' | 'lotus' | 'justsystem' | 'html' | 'pdf' | 'other'; +// export type ItemPresentationSubType = 'powerpoint' | 'lotus' | 'justsystem' | 'html' | 'pdf' | 'other'; +export const ItemPresentationSubTypes: ItemSubTypes = [ + { type: 'powerpoint', label: 'PowerPoint' }, + { type: 'illustrator', label: 'Illustrator' }, // OPENNEURO: + { type: 'lotus', label: 'Lotus' }, + { type: 'justsystem', label: 'JustSystem' }, + { type: 'html', label: 'HTML' }, + { type: 'pdf', label: 'PDF' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemPresentation extends ItemBasic { + presentation_type: ItemPresentationSubType; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + rights: string; + readme: string; + attachment_dl_limit: number; + attachment_dl_notify: number; + creator: string[]; + // OPENNEURO: + referee: string; +} + +export type ItemSimulatorSubType = 'matlab' | 'mathematica' | 'program' | 'other'; +export const ItemSimulatorSubTypes: ItemSubTypes = [ + { type: 'matlab', label: 'Matlab' }, + { type: 'mathematica', label: 'Mathematica' }, + { type: 'program', label: 'Program' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemSimulator extends ItemBasic { + simulator_type: ItemSimulatorSubType; + readme: string; + rights: string; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + developer: string[]; +} + +export type ItemStimulusSubType = 'picture' | 'movie' | 'program' | 'other'; +export const ItemStimulusSubTypes: ItemSubTypes = [ + { type: 'picture', label: 'Picture' }, + { type: 'movie', label: 'Movie' }, + { type: 'program', label: 'Program' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemStimulus extends ItemBasic { + stimulus_type: ItemStimulusSubType; + readme: string; + rights: string; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + developer: string[]; +} + +export type ItemToolSubType = 'matlab' | 'mathematica' | 'program' | 'other'; +export const ItemToolSubTypes: ItemSubTypes = [ + { type: 'matlab', label: 'Matlab' }, + { type: 'mathematica', label: 'Mathematica' }, + { type: 'program', label: 'Program' }, + { type: 'other', label: 'Other' }, +]; +export interface ItemTool extends ItemBasic { + tool_type: ItemToolSubType; + readme: string; + rights: string; + use_cc: number; + cc_commercial_use: number; + cc_modification: number; + attachment_dl_limit: number; + attachment_dl_notify: number; + developer: string[]; +} + +export interface ItemUrl extends ItemBasic { + url: string; + url_count: number; +} + +export type Item = ItemBinder | ItemBook | ItemConference | ItemData | ItemFiles | ItemMemo | ItemModel | ItemPaper | ItemPresentation | ItemSimulator | ItemStimulus | ItemTool | ItemUrl; + +export type KeywordSearchType = 'all' | 'basic' | 'binder' | 'book' | 'conference' | 'data' | 'files' | 'memo' | 'model' | 'paper' | 'presentation' | 'simulator' | 'stimulus' | 'tool' | 'url'; +export interface KeywordSearchQuery { + type: KeywordSearchType; + keyword: string; +} +const KEYWORD_SEARCH_TYPE_RANGE = ['all', 'basic', 'binder', 'book', 'conference', 'files', 'data', 'memo', 'model', 'paper', 'presentation', 'simulator', 'stimulus', 'tool', 'url']; + +export type SortConditionLimit = 20 | 50 | 100; +export type SortConditionOrderBy = 'title' | 'doi' | 'last_update_date' | 'creation_date' | 'publication_date'; +export enum SortConditionOrderDir { ASC, DESC } + +export interface SortCondition { + limit: SortConditionLimit; + orderBy: SortConditionOrderBy; + orderDir: SortConditionOrderDir; + page: number; +} + +class ItemSorter { + + public orderBy: SortConditionOrderBy; + public orderDir: SortConditionOrderDir; + + constructor(condition: SortCondition) { + this.orderBy = condition.orderBy; + this.orderDir = condition.orderDir; + this.sort = this.sort.bind(this); + } + + sort(a: Item, b: Item) { + let av: string = ''; + let bv: string = ''; + switch (this.orderBy) { + case 'title': + av = a.title.toLocaleUpperCase(); + bv = b.title.toLocaleUpperCase(); + break; + case 'doi': + av = a.doi.toLocaleUpperCase(); + bv = b.doi.toLocaleUpperCase(); + break; + case 'last_update_date': + av = String(a.last_update_date); + bv = String(b.last_update_date); + break; + case 'creation_date': + av = String(a.creation_date); + bv = String(b.creation_date); + break; + case 'publication_date': + av = String(a.publication_year * 10000 + a.publication_month * 100 + a.publication_mday); + bv = String(b.publication_year * 10000 + b.publication_month * 100 + b.publication_mday); + break; + default: + break; + } + if (this.orderDir === SortConditionOrderDir.ASC) { + if (av > bv) return -1; + else if (av < bv) return 1; + } else { + if (av > bv) return 1; + else if (av < bv) return -1; + } + return 0; + } +} + +type GetResult = Item | null; +export interface GetCallbackFunc { (item: GetResult): void } +export interface SearchResult { + total: number; + data: Item[]; +} +export interface SearchCallbackFunc { (results: SearchResult): void } +export interface SearchFunc { (condition: SortCondition, func: SearchCallbackFunc): void } + +interface ItemLoadCallbackFunc { (items: Collection): void } + +class ItemUtil { + + private database: loki; + private items: Collection; + private simpfLinks: Collection; + private loading: boolean; + private callbacks: ItemLoadCallbackFunc[]; + + constructor() { + this.database = new loki('database'); + this.items = this.database.addCollection('items'); + this.simpfLinks = this.database.addCollection('simpf-links'); + this.loading = true; + this.callbacks = []; + this.load(); + } + + load(): void { + axios.get(process.env.PUBLIC_URL + '/database/items.json', { responseType: 'json' }).then((response) => { + const itemsJson = response.data as Item[]; + itemsJson.forEach((json: Item) => { + this.items.insert(json); + }); + const lock = new AsyncLock(); + lock.acquire('items', () => { + this.loading = false; + this.callbacks.forEach((callback) => { + callback(this.items); + }); + this.callbacks = []; + }); + }); + const simpfLinks = simpfLinksJson as SimPFLink[]; + simpfLinks.forEach((simpfLink: SimPFLink) => { + this.simpfLinks.insert(simpfLink); + }); + } + + registerItemLoadCallback(func: ItemLoadCallbackFunc): void { + const lock = new AsyncLock(); + lock.acquire('items', () => { + if (this.loading) { + this.callbacks.push(func); + } else { + func(this.items); + } + }); + } + + getUrl(item: ItemCore): string { + if (item.doi !== '') { + return '/database/item/id/' + funcs.escape(item.doi); + } + return '/database/item/' + String(item.item_id); + } + + getFileUrl(file: ItemBasicFile): string { + return process.env.PUBLIC_URL + '/database/file/' + String(file.file_id) + '/' + funcs.escape(file.original_file_name); + } + + getPreviewFileUrl(file: ItemBasicFile): string { + return process.env.PUBLIC_URL + '/database/file/' + String(file.file_id) + '.png'; + } + + getSearchByKeywordUrl(type: KeywordSearchType, keyword: string): string { + const params = new URLSearchParams({ type, keyword }); + return '/database/search?' + params.toString(); + } + + getItemTypeSearchUrl(type: string): string { + return '/database/search/itemtype/' + funcs.escape(type); + } + + getSearchByAdvancedKeywordsUrl(query: AdvancedSearchQuery): string { + const paramString = query.getQueryParams().toString(); + return '/database/search/advanced' + (paramString.length > 0 ? '?' + paramString : ''); + } + + getSearchKeywordByQuery(queryString: string): KeywordSearchQuery { + const query = new URLSearchParams(queryString); + const qtype = query.get('type'); + const type = qtype !== null && KEYWORD_SEARCH_TYPE_RANGE.includes(qtype) ? qtype as KeywordSearchType : 'all'; + const qkeyword = query.get('keyword'); + const keyword = qkeyword === null ? '' : qkeyword; + return ({ type, keyword }); + } + + getAdvancedSearchQueryByQuery(queryString: string): AdvancedSearchQuery { + const query: AdvancedSearchQuery = new AdvancedSearchQuery(); + query.setByQueryString(queryString); + return query; + } + + get(itemId: number, func: GetCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const filter = { + item_id: itemId + } + const item = items.findOne(filter); + func(item); + }); + } + + getByDoi(doi: string, func: GetCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const filter = { + doi: doi + } + const item = items.findOne(filter); + func(item); + }); + } + + getList(itemIds: number[], func: SearchCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const filter = { + item_id: { + '$in': itemIds, + } + } + const sort = (a: Item, b: Item) => { + const aIdx = itemIds.findIndex((itemId) => { return a.item_id === itemId; }); + const bIdx = itemIds.findIndex((itemId) => { return b.item_id === itemId; }); + if (aIdx > bIdx) { + return 1; + } else if (aIdx < bIdx) { + return -1; + } + return 0; + } + const data = items.chain().find(filter).sort(sort).data(); + const res = { + total: data.length, + data: data, + } + func(res); + }); + } + + getListByIndexId(indexId: number, condition: SortCondition, func: SearchCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const filter: any = { + 'index.index_id': indexId + }; + const offset = condition.limit * (condition.page - 1); + const result = items.chain().find(filter); + const itemSorter = new ItemSorter(condition); + const ret = { + total: result.count(), + data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data() + }; + func(ret); + }); + } + + getListByItemType(itemType: string, subItemType: string, condition: SortCondition, func: SearchCallbackFunc): void { + this.registerItemLoadCallback((items) => { + let filter: any = { + item_type_name: 'xnp' + itemType, + } + if (subItemType !== '') { + switch (itemType) { + case 'conference': + filter.presentation_type = subItemType; + break; + case 'data': + filter.data_type = subItemType; + break; + case 'files': + filter.data_file_filetype = subItemType; + break; + case 'model': + filter.model_type = subItemType; + break; + case 'presentation': + filter.presentation_type = subItemType; + break; + case 'simulator': + filter.simulator_type = subItemType; + break; + case 'stimulus': + filter.stimulus_type = subItemType; + break; + case 'tool': + filter.tool_type = subItemType; + break; + default: + break; + } + } + const offset = condition.limit * (condition.page - 1); + const result = items.chain().find(filter); + const itemSorter = new ItemSorter(condition); + const ret = { + total: result.count(), + data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data() + }; + func(ret); + }); + } + + getListByKeyword(type: KeywordSearchType, keyword: string, condition: SortCondition, func: SearchCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const regex = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const num = keyword.match(/^[0-9]+$/) ? parseInt(keyword, 10) : null; + let filter: any = { '$or': [] }; + const appendToFilter = (type: string, strKeys: string[], numKeys: string[]) => { + const basicStrKeys: string[] = ['title', 'keyword', 'doi', 'description', 'uname', 'name', 'index.title']; + const basicNumKeys: string[] = []; + let filterItemType: any = { + item_type_name: 'xnp' + type, + '$or': [] + }; + let sKeys: string[] = basicStrKeys.concat(strKeys); + let nKeys: string[] = basicNumKeys.concat(numKeys); + sKeys.forEach((key) => { + let criteria: any = {}; + criteria[key] = { '$regex': [regex, 'i'] }; + filterItemType['$or'].push(criteria); + }); + if (num !== null) { + nKeys.forEach((key) => { + let criteria: any = {}; + criteria[key] = { '$eq': num }; + filterItemType['$or'].push(criteria); + }); + } + filter['$or'].push(filterItemType); + } + if (type === 'basic') { + filter['$or'].push({ 'title': { '$regex': [regex, 'i'] } }); + filter['$or'].push({ 'keyword': { '$regex': [regex, 'i'] } }); + } else { + if (type === 'all' || type === 'binder') { + // OPENNEURO: + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'research_area', 'species', 'scale', 'method']; + // const strKeys: string[] = ['doi', 'title', 'keyword', 'description']; + const numKeys: string[] = []; + appendToFilter('binder', strKeys, numKeys); + } + if (type === 'all' || type === 'book') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'classification', 'editor', 'publisher', 'isbn', 'url', 'author', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = []; + appendToFilter('book', strKeys, numKeys); + } + if (type === 'all' || type === 'conference') { + const strKeys: string[] = ['doi', 'title', 'conference_title', 'place', 'abstract', 'author', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = ['conference_from_year', 'conference_from_month', 'conference_from_mday', 'conference_to_year', 'conference_to_month', 'conference_to_mday']; + appendToFilter('conference', strKeys, numKeys); + } + if (type === 'all' || type === 'data') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'experimenter', 'data_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = ['publication_year', 'publication_month', 'publication_mday']; + appendToFilter('data', strKeys, numKeys); + } + if (type === 'all' || type === 'files') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'data_file_name', 'data_file_mimetype', 'data_file_filetype']; + const numKeys: string[] = []; + appendToFilter('files', strKeys, numKeys); + } + if (type === 'all' || type === 'memo') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'item_link', 'file.original_file_name']; + const numKeys: string[] = []; + appendToFilter('memo', strKeys, numKeys); + } + if (type === 'all' || type === 'model') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'creator', 'model_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = []; + appendToFilter('model', strKeys, numKeys); + } + if (type === 'all' || type === 'paper') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'journal', 'page', 'pubmed_id', 'author']; + const numKeys: string[] = ['publication_year', 'volume', 'number']; + appendToFilter('paper', strKeys, numKeys); + } + if (type === 'all' || type === 'presentation') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'creator', 'presentation_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = ['publication_year', 'publication_month', 'publication_mday']; + appendToFilter('presentation', strKeys, numKeys); + } + if (type === 'all' || type === 'simulator') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'developer', 'simulator_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = ['publication_year', 'publication_month', 'publication_mday']; + appendToFilter('simulator', strKeys, numKeys); + } + if (type === 'all' || type === 'stimulus') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'developer', 'stimulus_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = ['publication_year', 'publication_month', 'publication_mday']; + appendToFilter('stimulus', strKeys, numKeys); + } + if (type === 'all' || type === 'tool') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'description', 'readme', 'rights', 'developer', 'tool_type', 'file.caption', 'file.original_file_name']; + const numKeys: string[] = []; + appendToFilter('tool', strKeys, numKeys); + } + if (type === 'all' || type === 'url') { + const strKeys: string[] = ['doi', 'title', 'keyword', 'url', 'file.original_file_name']; + const numKeys: string[] = []; + appendToFilter('url', strKeys, numKeys); + } + if (type !== 'all') { + filter['item_type_name'] = 'xnp' + type; + } + } + const offset = condition.limit * (condition.page - 1); + const result = items.chain().find(filter); + const itemSorter = new ItemSorter(condition); + const ret = { + total: result.count(), + data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data() + }; + func(ret); + }); + } + + getListByAdvancedSearchQuery(query: AdvancedSearchQuery, condition: SortCondition, func: SearchCallbackFunc): void { + this.registerItemLoadCallback((items) => { + const filter: any = query.getSearchFilter(); + const offset = condition.limit * (condition.page - 1); + const result = items.chain().find(filter); + const itemSorter = new ItemSorter(condition); + const ret = { + total: result.count(), + data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data() + }; + func(ret); + }); + } + + getSimPFLinkUrl(itemId: number) { + const simpfLink = this.simpfLinks.findOne({ id: itemId }); + if (simpfLink === null) { + return ''; + } + return simpfLink.url; + } +} + +export default new ItemUtil(); \ No newline at end of file diff --git a/src/functions.ts b/src/functions.ts new file mode 100644 index 0000000..72a1c0c --- /dev/null +++ b/src/functions.ts @@ -0,0 +1,130 @@ +import XRegExp from 'xregexp'; +import Config, { MultiLang } from './config'; + +const escape = (str: string) => { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { + return '%' + c.charCodeAt(0).toString(16); + }); +} + +const unescape = (str: string) => { + return decodeURIComponent(str); +} + +const htmlspecialchars = (str: string) => { + return str.replace(/(<|>|&|'|")/g, (match) => { + switch (match) { + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; + case '\'': + return '''; + case '"': + return '"'; + } + return ''; + }); +} + +const camelCase = (str: string) => { + return str.replace(/[-_](.)/g, (...matches) => { + return matches[1].toUpperCase(); + }); +} + +const snakeCase = (str: string) => { + var camel = camelCase(str); + return camel.replace(/[A-Z]/g, (...matches) => { + return "_" + matches[0].charAt(0).toLowerCase(); + }); +} + +const pascalCase = (str: string) => { + var camel = camelCase(str); + return camel.charAt(0).toUpperCase() + camel.slice(1); +} + +const base64Encode = (str: string) => { + return btoa(unescape(encodeURIComponent(str))); +} + +const base64Decode = (str: string) => { + return decodeURIComponent(escape(atob(str))); +} + +const ordinal = (num: number) => { + 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) => { + const mlLangs = ['en', 'ja']; + // escape brackets inside of + let text = str.replace(/(]*)(>)/isg, (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>)/isg, (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>)/isg, (whole, m1, m2, m3) => { + return m1 + m2.replace(/__ml\[ml__/g, '[') + m3; + }); + // unescape brackets inside of + text = text.replace(/(]*)(>)/isg, (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) => { + return mlang(Config.SITE_TITLE, lang); +} + +const siteSlogan = (lang: MultiLang) => { + return mlang(Config.SITE_SLOGAN, lang); +} + +const Functions = { + escape, + unescape, + htmlspecialchars, + camelCase, + snakeCase, + pascalCase, + base64Encode, + base64Decode, + ordinal, + mlang, + siteTitle, + siteSlogan, +}; + +export default Functions; \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..2daa7e9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@import-normalize; diff --git a/src/index.tsx b/src/index.tsx index 87d1be5..bd9907b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,14 @@ +import 'react-app-polyfill/ie11'; +import 'react-app-polyfill/stable'; import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; -import * as serviceWorker from './serviceWorker'; +import './index.css'; +// import * as serviceWorker from './serviceWorker'; ReactDOM.render(, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); +// serviceWorker.unregister(); \ No newline at end of file diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 2e5df0d..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pico/Pico.tsx b/src/pico/Pico.tsx new file mode 100644 index 0000000..6ce402d --- /dev/null +++ b/src/pico/Pico.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import './assets/pico_main.css'; +import PicoUtils from './lib/PicoUtils'; +import PicoCategory from './PicoCategory'; +import PicoContent from './PicoContent'; +import PicoIndex from './PicoIndex'; +import PicoMenu from './PicoMenu'; + +interface Props { + lang: MultiLang; + name: string; +} + +const Pico = (props: Props) => { + const { lang, name } = props; + return ( + <> + + } /> + ) => { + const { location } = props; + const { pathname, search } = location; + const path = pathname.replace('/' + name + '/', ''); + if (path === '' || path === 'index.php') { + const params = new URLSearchParams(search); + const paramCatId = params.get('cat_id'); + if (paramCatId !== null && paramCatId.match(/^\d+$/)) { + const catId = parseInt(paramCatId, 10); + return ; + } + const paramContentId = params.get('content_id'); + if (paramContentId !== null && paramContentId.match(/^\d+$/)) { + const contentId = parseInt(paramContentId, 10); + return ; + } + const paramPage = params.get('page'); + if (paramPage !== null && paramPage === 'menu') { + return ; + } + return ; + } + const category = PicoUtils.getCategoryByPath(name, path); + if (category !== null) { + return ; + } + const content = PicoUtils.getContentByPath(name, path); + if (content !== null) { + return ; + } + return ; + }} /> + + + + ); +} + +export default Pico; diff --git a/src/pico/PicoCategory.tsx b/src/pico/PicoCategory.tsx new file mode 100644 index 0000000..224fd0b --- /dev/null +++ b/src/pico/PicoCategory.tsx @@ -0,0 +1,84 @@ +import React, { Fragment } from 'react'; +import Helmet from 'react-helmet'; +import { Link } from 'react-router-dom'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import PicoUtils, { PicoCategoryData, PicoContentData } from './lib/PicoUtils'; + +interface Props { + lang: MultiLang; + name: string; + catId: number; +} + +const PicoCategory = (props: Props) => { + const { lang, name, catId } = props; + const pico = PicoUtils.getModule(name); + if (pico === null) { + return ; + } + const category = PicoUtils.getCategory(name, catId); + if (category === null) { + return ; + } + const parentCategories = PicoUtils.getParentCategories(name, category.pid); + const subCategories = PicoUtils.getSubCategories(name, catId); + const contents = PicoUtils.getCategoryContents(name, catId); + return ( +
+ + {Functions.mlang(category.title, lang)} - {Functions.mlang(pico.name, lang)} - {Functions.siteTitle(lang)} + + {pico.show_breadcrumbs !== 0 && ( +
+ {parentCategories.map((parentCategory: PicoCategoryData, idx: number) => { + const link = '/' + name + '/' + parentCategory.link; + return ( + + {idx > 0 && <> > } + {Functions.mlang(parentCategory.title, lang)} + + ); + })} + {parentCategories.length > 0 && <> > } + {Functions.mlang(category.title, lang)} +
+ )} + {catId === 0 && pico.message !== '' && ( +

{Functions.mlang(pico.message, lang)}

+ )} +

{Functions.mlang(category.title, lang)}

+ {category.desc !== '' && ( +

{Functions.mlang(category.desc, lang)}

+ )} + {subCategories.length > 0 && ( + <> +

{Functions.mlang('[en]Subcageroies[/en][ja]サブカテゴリー[/ja]', lang)}

+ {subCategories.map((subCategory: PicoCategoryData) => { + const url = '/' + name + '/' + subCategory.link; + return ( +
+
{Functions.mlang(subCategory.title, lang)}
+
{Functions.mlang(subCategory.desc, lang)}
+
+ ); + })} + + )} + {contents.length > 0 && ( + <> +

{Functions.mlang('[en]Contents[/en][ja]コンテンツ[/ja]', lang)}

+
    + {contents.map((content: PicoContentData) => { + const url = '/' + name + '/' + content.link; + return
  • {Functions.mlang(content.title, lang)}
  • ; + })} +
+ + )} +
+ ); +}; + +export default PicoCategory; \ No newline at end of file diff --git a/src/pico/PicoContent.tsx b/src/pico/PicoContent.tsx new file mode 100644 index 0000000..2b473ad --- /dev/null +++ b/src/pico/PicoContent.tsx @@ -0,0 +1,112 @@ +import React, { Component, Fragment } from 'react'; +import Helmet from 'react-helmet'; +import { Link } from 'react-router-dom'; +import { HashLink } from 'react-router-hash-link'; +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 PicoUtils, { PicoCategoryData, PicoPageData } from './lib/PicoUtils'; + +interface Props { + lang: MultiLang; + name: string; + contentId: number; +} + +interface State { + loading: boolean; + page: PicoPageData | null; +} + +class PicoContent extends Component { + + private isActive: boolean; + + constructor(props: Props) { + super(props); + this.state = { + loading: true, + page: null, + }; + this.isActive = false; + } + + componentDidMount() { + const { name, contentId } = this.props; + this.isActive = true; + this.updatePage(name, contentId); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const { name, contentId } = this.props; + const prevName = prevProps.name; + const prevContentId = prevProps.contentId; + if (name !== prevName || contentId !== prevContentId) { + this.setState({ loading: true, page: null }); + this.updatePage(name, contentId); + } + } + + componentWillUnmount() { + this.isActive = false; + } + + updatePage(name: string, contentId: number) { + this.isActive = true; + PicoUtils.getPage(name, contentId, (page: PicoPageData | null) => { + if (this.isActive) { + this.setState({ loading: false, page }); + } + }) + } + + render() { + const { lang, name, contentId } = this.props; + const { loading, page } = this.state; + if (loading) { + return ; + } + if (page === null) { + return ; + } + const pico = PicoUtils.getModule(name); + if (pico === null) { + return ; + } + const categories = PicoUtils.getContentCategery(name, contentId); + return ( +
+ + {Functions.mlang(page.title, lang)} - {Functions.mlang(pico.name, lang)} - {Functions.siteTitle(lang)} + +

{Functions.mlang(pico.name, lang)}

+ + {pico.show_breadcrumbs !== 0 && ( +
+ {categories.map((category: PicoCategoryData, idx: number) => { + const link = '/' + name + '/' + category.link; + return ( + + {idx > 0 && <> > } + {Functions.mlang(category.title, lang)} + + ); + })} +  > {Functions.mlang(page.title, lang)} +
+ )} +
+ +
+
+ {Functions.mlang('[en]Jump to the top[/en][ja]この記事の1行目に飛ぶ[/ja]', lang)} +
+
+ ); + } +} + +export default PicoContent; diff --git a/src/pico/PicoIndex.tsx b/src/pico/PicoIndex.tsx new file mode 100644 index 0000000..2f8578d --- /dev/null +++ b/src/pico/PicoIndex.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import PicoUtils from './lib/PicoUtils'; +import PicoCategory from './PicoCategory'; +import PicoContent from './PicoContent'; +import PicoMenu from './PicoMenu'; + +interface Props { + lang: MultiLang; + name: string; +} + +const PicoIndex = (props: Props) => { + const { lang, name } = props; + const pico = PicoUtils.getModule(name); + if (pico === null) { + return ; + } + if (pico.show_menuinmoduletop !== 0) { + return + } + if (pico.show_listasindex !== 0) { + return + } + const content = PicoUtils.getFirstContent(name, 0); + if (content === null) { + return ; + } + return ; + +} + +export default PicoIndex; diff --git a/src/pico/PicoMenu.tsx b/src/pico/PicoMenu.tsx new file mode 100644 index 0000000..05afc0c --- /dev/null +++ b/src/pico/PicoMenu.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import { Link } from 'react-router-dom'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; +import Functions from '../functions'; +import PicoUtils, { PicoCategoryData, PicoContentData } from './lib/PicoUtils'; + +interface Props { + lang: MultiLang; + name: string; +} + +interface PicoMenuCategoryProps { + lang: MultiLang; + name: string; + category: PicoCategoryData; + depth: number; +} + +const PicoMenuCategory = (props: PicoMenuCategoryProps) => { + const { lang, name, category, depth } = props; + const subCategories = PicoUtils.getSubCategories(name, category.id); + const contents = PicoUtils.getCategoryContents(name, category.id); + const level = 'level' + depth; + const link = {Functions.mlang(category.title, lang)}; + let title =
{link}
; + switch (depth) { + case 1: + title =

{link}

; + break; + case 2: + title =

{link}

; + break; + case 3: + title =

{link}

; + break; + case 4: + title =

{link}

; + break; + case 5: + title =
{link}
; + break; + } + return ( + <> + {title} + {contents.length > 0 && ( +
+
    + {contents.map((content: PicoContentData) => { + const url = '/' + name + '/' + content.link; + return
  • {Functions.mlang(content.title, lang)}
  • ; + })} +
+
+ )} + {subCategories.length > 0 && ( + <> + {subCategories.map((subCategory: PicoCategoryData) => { + return ; + })} + + )} + + ); +} + +const PicoMenu = (props: Props) => { + const { lang, name } = props; + const pico = PicoUtils.getModule(name); + if (pico === null) { + return ; + } + const category = PicoUtils.getCategory(name, 0); + if (category === null) { + return ; + } + return ( +
+ + {Functions.mlang('[en]Menu[/en][ja]メニュー[/ja]', lang)} - {Functions.mlang(pico.name, lang)} - {Functions.siteTitle(lang)} + + {pico.message !== '' && ( +

{Functions.mlang(pico.message, lang)}

+ )} + +
+ ); +}; + +export default PicoMenu; \ No newline at end of file diff --git a/src/pico/PicoXoopsPathRedirect.tsx b/src/pico/PicoXoopsPathRedirect.tsx new file mode 100644 index 0000000..e39d888 --- /dev/null +++ b/src/pico/PicoXoopsPathRedirect.tsx @@ -0,0 +1,43 @@ +import React, { Component } from 'react'; +import { Redirect, RouteComponentProps, withRouter } from 'react-router'; +import PageNotFound from '../common/lib/PageNotFound'; +import { MultiLang } from '../config'; + +interface Props extends RouteComponentProps { + lang: MultiLang; + name: string; +} + +class PicoXoopsPathRedirect extends Component { + + getRedirectUrl(): string { + const { name, location } = this.props; + const pathname = location.pathname || ''; + const search = new RegExp(`^/modules/${name}(?:/+(.*))?$`); + const matches = pathname.match(search); + if (matches === null) { + return ''; + } + const path = matches[1] || ''; + switch (path) { + case '': + case 'index.php': { + const params = new URLSearchParams(location.search); + const paramString = params.toString(); + return '/' + name + '/' + (paramString !== '' ? '?' + paramString : ''); + } + } + return '/' + name + '/' + path; + } + + render() { + const { lang } = this.props; + const url = this.getRedirectUrl(); + if (url === '') { + return ; + } + return ; + } +} + +export default withRouter(PicoXoopsPathRedirect); diff --git a/src/pico/assets/pico_main.css b/src/pico/assets/pico_main.css new file mode 100644 index 0000000..9413c22 --- /dev/null +++ b/src/pico/assets/pico_main.css @@ -0,0 +1,140 @@ +div.pico_breadcrumbs { + font-size: 95%; + padding: 0 0 3px; + border-bottom: 1px #aaa solid; +} + +.pico_body { + margin: 0 -2px; + padding: 15px 8px; +} + +div.pico_menu h1 { + margin: 18px 0 0 0; + padding: 3px; + background-color: #eee; +} +div.pico_menu h2 { + margin: 18px 0 0 10px; + padding: 3px; + background-color: #eee; +} +div.pico_menu h3 { + margin: 18px 0 0 20px; + padding: 3px; + background-color: #eee; +} +div.pico_menu h4 { + margin: 18px 0 0 30px; + padding: 3px; + background-color: #eee; +} +div.pico_menu h5 { + margin: 18px 0 0 40px; + padding: 3px; + background-color: #eee; +} +div.pico_menu div.level1 { + margin-left: 0; +} +div.pico_menu div.level2 { + margin-left: 10px; +} +div.pico_menu div.level3 { + margin-left: 20px; +} +div.pico_menu div.level4 { + margin-left: 30px; +} +div.pico_menu div.level5 { + margin-left: 40px; +} + +em.pico_notice { + font-weight: bold; + font-style: normal; + color: #ff0000; +} + +div.pico_controllers_in_menu { + float: right; + position: relative; + top: -1.5em; +} + +div.bottom_of_content_body { + clear: right; +} + +ul.pico_list_contents li { + list-style: none outside; +} +ul.pico_list_contents_in_menu li { + list-style: none outside; +} + +div.pico_print_icon { + float: right; + width: 40px; + height: 40px; +} +div.pico_tellafriend_icon { + float: right; + width: 40px; + height: 40px; +} +div.pico_vote form { + display: inline; +} + +table.pico_pagenavigation { + border-top: 1px #aaa solid; + margin-top: 20px; +} + +table.pico_form_table th.pico_waiting { + color: red; +} +table.pico_form_table td.pico_waiting { + color: red; + background-color: white; +} + +table.pico_form_table td, +table.pico_form_table th { + color: black; + vertical-align: top !important; + text-align: left; +} +p.pico_submit { + text-align: center; + margin: 0; + padding: 10px; +} + +input.pico_ascii_only { + ime-mode: disabled; +} +input.pico_number_only { + ime-mode: disabled; + text-align: right; +} + +pre.pico_history_diff del { + color: red; +} +pre.pico_history_diff ins { + color: blue; +} + +div.pico_pagebreak { + margin: 10px; +} +div.pico_pagebreak span { + border: 1px solid black; + padding: 2px; + margin: 2px; +} +div.pico_pagebreak span.selected { + border: 0px; +} diff --git a/src/pico/lib/PicoUtils.ts b/src/pico/lib/PicoUtils.ts new file mode 100644 index 0000000..c33f876 --- /dev/null +++ b/src/pico/lib/PicoUtils.ts @@ -0,0 +1,180 @@ +import axios from 'axios'; +import loki from 'lokijs'; +import configJson from '../assets/config.json'; + +export interface PicoModuleData { + name: string; + dirname: string; + message: string; + show_menuinmoduletop: number; + show_listasindex: number; + show_breadcrumbs: number; + show_pagenavi: number; +} + +export interface PicoCategoryData { + id: number; + title: string; + desc: string; + pid: number; + weight: number; + link: string; +} + +export interface PicoContentData { + id: number; + title: string; + cat_id: number; + weight: number; + link: string; +} + +interface PicoConfigData { + module: PicoModuleData; + categories: PicoCategoryData[]; + contents: PicoContentData[]; +} + +interface PicoData { + module: PicoModuleData; + categories: Collection; + contents: Collection; +} + +export interface PicoPageData { + id: number; + title: string; + content: string; +} + +export const PICO_CATEGORY_ID_ROOT = 65535; +export type GetPageCallback = (page: PicoPageData | null) => void; + +class PicoUtils { + + private database: loki; + private modules: Map; + + constructor(json: PicoConfigData[]) { + this.database = new loki('pico'); + this.modules = new Map(); + json.forEach((data) => { + const name = data.module.dirname; + const pico = { + module: data.module, + categories: this.database.addCollection(name + '_categories'), + contents: this.database.addCollection(name + '_contents'), + }; + data.categories.forEach(category => { + pico.categories.insert(category); + }); + data.contents.forEach(content => { + pico.contents.insert(content); + }); + this.modules.set(name, pico); + }) + } + + getModule(name: string): PicoModuleData | null { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return null; + } + return pico.module; + } + + getCategoryByPath(name: string, link: string): PicoCategoryData | null { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return null; + } + const content = pico.categories.findOne({ link: link }); + return content; + } + + getCategory(name: string, catId: number): PicoCategoryData | null { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return null; + } + const category = pico.categories.findOne({ id: catId }); + return category; + } + + getParentCategories(name: string, catId: number): PicoCategoryData[] { + const categories: PicoCategoryData[] = []; + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return categories; + } + while (catId !== PICO_CATEGORY_ID_ROOT) { + const category = pico.categories.findOne({ id: catId }); + if (category === null) { + break; + } + categories.unshift(category); + catId = category.pid; + } + return categories; + } + + getSubCategories(name: string, catId: number): PicoCategoryData[] { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return []; + } + return pico.categories.chain().find({ pid: catId }).simplesort('weight').data(); + } + + getCategoryContents(name: string, catId: number): PicoContentData[] { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return []; + } + return pico.contents.chain().find({ cat_id: catId }).simplesort('weight').data(); + } + + getContentCategery(name: string, contentId: number): PicoCategoryData[] { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return []; + } + const content = pico.contents.findOne({ id: contentId }); + if (content === null) { + return []; + } + return this.getParentCategories(name, content.cat_id); + } + + getFirstContent(name: string, catId: number): PicoContentData | null { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return null; + } + const contents = pico.contents.chain().find({ cat_id: catId }).simplesort('weight').limit(1).data(); + if (contents.length !== 1) { + return null; + } + return contents[0]; + } + + getContentByPath(name: string, link: string): PicoContentData | null { + const pico = this.modules.get(name); + if (typeof pico === 'undefined') { + return null; + } + const content = pico.contents.findOne({ link: link }); + return content as PicoContentData | null; + } + + getPage(name: string, contentId: number, callback: GetPageCallback): void { + axios.get('/' + name + '/' + contentId + '.json').then((response) => { + const page = response.data as PicoPageData; + callback(page); + }).catch(e => { + callback(null); + }); + } +} + +export default new PicoUtils(configJson); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d110846..22f62cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -839,6 +839,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.6.0" +"@babel/runtime-corejs2@^7.2.0": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.7.2.tgz#5a8c4e2f8688ce58adc9eb1d8320b6e7341f96ce" + integrity sha512-GfVnHchOBvIMsweQ13l4jd9lT4brkevnavnVOej5g2y7PpTRY+R4pcQlCjWMZoUla5rMLFzaS/Ll2s59cB1TqQ== + dependencies: + core-js "^2.6.5" + regenerator-runtime "^0.13.2" + "@babel/runtime@7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205" @@ -853,6 +861,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a" + integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" @@ -904,6 +919,32 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-9.0.1.tgz#c27b391d8457d1e893f1eddeaf5e5412d12ffbb5" integrity sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA== +"@fortawesome/fontawesome-common-types@^0.2.25": + version "0.2.25" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.25.tgz#6df015905081f2762e5cfddeb7a20d2e9b16c786" + integrity sha512-3RuZPDuuPELd7RXtUqTCfed14fcny9UiPOkdr2i+cYxBoTOfQgxcDoq77fHiiHcgWuo1LoBUpvGxFF1H/y7s3Q== + +"@fortawesome/fontawesome-svg-core@^1.2.25": + version "1.2.25" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.25.tgz#24b03391d14f0c6171e8cad7057c687b74049790" + integrity sha512-MotKnn53JKqbkLQiwcZSBJVYtTgIKFbh7B8+kd05TSnfKYPFmjKKI59o2fpz5t0Hzl35vVGU6+N4twoOpZUrqA== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.25" + +"@fortawesome/free-solid-svg-icons@^5.11.2": + version "5.11.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.11.2.tgz#2f2f1459743a27902b76655a0d0bc5ec4d945631" + integrity sha512-zBue4i0PAZJUXOmLBBvM7L0O7wmsDC8dFv9IhpW5QL4kT9xhhVUsYg/LX1+5KaukWq4/cbDcKT+RT1aRe543sg== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.25" + +"@fortawesome/react-fontawesome@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.7.tgz#c004ca75c15c5a1218101e8f042b8da8dec0c4b5" + integrity sha512-AHWSzOsHBe5vqOkrvs+CKw+8eLl+0XZsVixOWhTPpGpOA8WQUbVU6J9cmtAvTaxUU5OIf+rgxxF8ZKc3BVldxg== + dependencies: + prop-types "^15.5.10" + "@hapi/address@2.x.x": version "2.1.2" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.2.tgz#1c794cd6dbf2354d1eb1ef10e0303f573e1c7222" @@ -1200,6 +1241,11 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" +"@types/async-lock@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.1.tgz#81f218213bebcc5f740efe9648272c774a2e4b4b" + integrity sha512-TU1X8jmAU2BjwKryBFV/GDezz7Ge0xu9ZuYC7dy6wKj4hnL0JcxeseCOr/G2JkGylff6hdUBrR+Ee5ApAQeU5g== + "@types/babel__core@^7.1.0": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30" @@ -1233,11 +1279,50 @@ dependencies: "@babel/types" "^7.3.0" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + +"@types/domhandler@*": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/domhandler/-/domhandler-2.4.1.tgz#7b3b347f7762180fbcb1ece1ce3dd0ebbb8c64cf" + integrity sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA== + +"@types/domutils@*": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/domutils/-/domutils-1.7.2.tgz#89422e579c165994ad5c09ce90325da596cc105d" + integrity sha512-Nnwy1Ztwq42SSNSZSh9EXBJGrOZPR+PQ2sRT4VZy8hnsFXfCil7YlKO2hd2360HyrtFz2qwnKQ13ENrgXNxJbw== + dependencies: + "@types/domhandler" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/history@*": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a" + integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw== + +"@types/hoist-non-react-statics@^3.0.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/htmlparser2@*": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.10.1.tgz#1e65ba81401d53f425c1e2ba5a3d05c90ab742c7" + integrity sha512-fCxmHS4ryCUCfV9+CJZY1UjkbR+6Al/EQdX5Jh03qBj9gdlPG5q+7uNoDgE/ZNXb3XNWSAQgqKIWnbRCbOyyWA== + dependencies: + "@types/domhandler" "*" + "@types/domutils" "*" + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1275,11 +1360,21 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== -"@types/node@12.12.6": +"@types/lokijs@^1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/lokijs/-/lokijs-1.5.2.tgz#ed228f080033ce1fb16eff4acde65cb9ae0f1bf2" + integrity sha512-ZF14v1P1Bjbw8VJRu+p4WS9V926CAOjWF4yq23QmSBWRPe0/GXlUKzSxjP1fi/xi8nrq6zr9ECo8Z/8KsRqroQ== + +"@types/node@*", "@types/node@12.12.6": version "12.12.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.6.tgz#a47240c10d86a9a57bb0c633f0b2e0aea9ce9253" integrity sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA== +"@types/object-assign@^4.0.30": + version "4.0.30" + resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.30.tgz#8949371d5a99f4381ee0f1df0a9b7a187e07e652" + integrity sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI= + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1297,6 +1392,61 @@ dependencies: "@types/react" "*" +"@types/react-helmet@^5.0.14": + version "5.0.14" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.14.tgz#a07979ccb2cee088e74e735c84058fc8607d32e4" + integrity sha512-Q73FFg7+LjblfSQUNbnjrwy2T1avBP8yevEgNrkDjyz1rBbnXkuOQcEV7I5wvmAic9FLUk0CnkLieEDej84Zkw== + dependencies: + "@types/react" "*" + +"@types/react-html-parser@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/react-html-parser/-/react-html-parser-2.0.1.tgz#2d9002ac5bf1adf9aff8eae77ace5488bd78c98d" + integrity sha512-Lyw0AtG3gahw78CX2pzmzhKaoZCfJNzzuhhPsFVhzFrylMv8NaCmzYaPKglMv3RRHpwBbHuMOkVx0HiwGZKgSA== + dependencies: + "@types/htmlparser2" "*" + "@types/react" "*" + +"@types/react-overlays@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/react-overlays/-/react-overlays-1.1.3.tgz#fb97081cbf506917e358f0523b34fed14e2de2f7" + integrity sha512-oOq5NWbyfNz2w2sKvjkHdvGQSMA+VDVfI5UOfGPR0wkik2welad1RDVnVgH15jKf58jrZNBa1Ee4SVBgCGFxCg== + dependencies: + "@types/react" "*" + "@types/react-transition-group" "*" + +"@types/react-router-dom@*", "@types/react-router-dom@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.2.tgz#853f229f1f297513c0be84f7c914a08b778cfdf5" + integrity sha512-kRx8hoBflE4Dp7uus+j/0uMHR5uGTAvQtc4A3vOTWKS+epe0leCuxEx7HNT7XGUd1lH53/moWM51MV2YUyhzAg== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router-hash-link@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/react-router-hash-link/-/react-router-hash-link-1.2.1.tgz#fba7dc351cef2985791023018b7a5dbd0653c843" + integrity sha512-jdzPGE8jFGq7fHUpPaKrJvLW1Yhoe5MQCrmgeesC+eSLseMj3cGCTYMDA4BNWG8JQmwO8NTYt/oT3uBZ77pmBA== + dependencies: + "@types/react" "*" + "@types/react-router-dom" "*" + +"@types/react-router@*": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.2.tgz#41e5e6aa333a7b9a2bfdac753c04e1ca4b3e0d21" + integrity sha512-euC3SiwDg3NcjFdNmFL8uVuAFTpZJm0WMFUw+4eXMUnxa7M9RGFEG0szt0z+/Zgk4G2k9JBFhaEnY64RBiFmuw== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-transition-group@*": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.2.3.tgz#4924133f7268694058e415bf7aea2d4c21131470" + integrity sha512-Hk8jiuT7iLOHrcjKP/ZVSyCNXK73wJAUz60xm0mVhiRujrdiI++j4duLiL282VGxwAgxetHQFfqA29LgEeSkFA== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@16.9.11": version "16.9.11" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.11.tgz#70e0b7ad79058a7842f25ccf2999807076ada120" @@ -1310,6 +1460,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/xregexp@^3.0.30": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@types/xregexp/-/xregexp-3.0.30.tgz#333d550467dd27ef989f375629f8f279a97cee39" + integrity sha512-u1dpabg81Rd660bYebOqMXO0+E63H1hxunPAWGebNb7TpxqZYe9YaVLgkkj6ZnzLs3yLumtVB956o8u8OZdhXw== + "@types/yargs-parser@*": version "13.1.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228" @@ -1569,6 +1724,13 @@ acorn@^7.0.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== +add-dom-event-listener@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== + dependencies: + object-assign "4.x" + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -1826,6 +1988,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-lock@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.2.tgz#480bd51e4b7ffd4debbd4973763718ec9acb9a9e" + integrity sha512-uczz62z2fMWOFbyo6rG4NlV2SdxugJT6sZA2QcfB1XaSjEiOh8CuOb/TttyMnYQCda6nkWecJe465tGQDPJiKw== + async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1864,6 +2031,14 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" + integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axobject-query@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" @@ -2008,7 +2183,7 @@ babel-preset-react-app@^9.0.2: babel-plugin-macros "2.6.1" babel-plugin-transform-react-remove-prop-types "0.4.24" -babel-runtime@^6.26.0: +babel-runtime@6.x, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -2463,6 +2638,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@2.x, classnames@^2.2.5, classnames@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + clean-css@4.2.x: version "4.2.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" @@ -2612,11 +2792,23 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +component-classes@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691" + integrity sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE= + dependencies: + component-indexof "0.0.3" + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +component-indexof@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24" + integrity sha1-EdCRMSI5648yyPJa6csAL/6NPCQ= + compose-function@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" @@ -2720,7 +2912,7 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.0: +cookie@0.4.0, cookie@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== @@ -2760,6 +2952,11 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== +core-js@^2.6.5: + version "2.6.10" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f" + integrity sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2834,6 +3031,14 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-animation@^1.3.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.6.1.tgz#162064a3b0d51f958b7ff37b3d6d4de18e17039e" + integrity sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog== + dependencies: + babel-runtime "6.x" + component-classes "^1.2.5" + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -3100,6 +3305,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.0.0, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -3324,6 +3536,13 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^3.2.1, dom-helpers@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + dom-serializer@0: version "0.2.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb" @@ -3823,6 +4042,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4112,6 +4336,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + follow-redirects@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f" @@ -4396,6 +4627,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -4524,6 +4760,18 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4533,6 +4781,13 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.4" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.4.tgz#44119abaf4bc64692a16ace34700fed9c03e2546" @@ -4600,7 +4855,7 @@ html-webpack-plugin@4.0.0-beta.5: tapable "^1.1.0" util.promisify "1.0.0" -htmlparser2@^3.3.0: +htmlparser2@^3.3.0, htmlparser2@^3.9.0: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4950,6 +5205,11 @@ is-buffer@^1.0.2, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" @@ -5168,6 +5428,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5940,11 +6205,35 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= + +lodash.keys@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5990,7 +6279,12 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +lokijs@^1.5.8: + version "1.5.8" + resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.8.tgz#9296f288edb2147389ec692fc972c428c59179d1" + integrity sha512-D8E3TBrY35o1ELnonp2MF8b3wKu2tVNl2TqRjvS+95oPMMe7OoIAxNY1qr+5BEZwnWn2V4ErAjVt000DonM+FA== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6187,6 +6481,15 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + mini-css-extract-plugin@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" @@ -6283,6 +6586,11 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -6576,7 +6884,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@4.1.1, object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -6981,6 +7289,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + dependencies: + isarray "0.0.1" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -7832,7 +8147,15 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types-extra@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.0.tgz#32609910ea2dcf190366bacd3490d5a6412a605f" + integrity sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg== + dependencies: + react-is "^16.3.2" + warning "^3.0.0" + +prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7949,7 +8272,7 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== -raf@3.4.1: +raf@3.4.1, raf@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -7986,6 +8309,50 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +rc-animate@^2.9.2: + version "2.10.1" + resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.10.1.tgz#c0c4faab50e55535e903e66f207e49891ba86d94" + integrity sha512-yfP3g5fNf8wB5eh85nim2IGrqNu5u7TKrrSh710+1vlUqZvnI2R5YHK99IBCQNgkLCAWjT0sHtkcYdynjly39w== + dependencies: + babel-runtime "6.x" + classnames "^2.2.6" + css-animation "^1.3.2" + prop-types "15.x" + raf "^3.4.0" + rc-util "^4.8.0" + react-lifecycles-compat "^3.0.4" + +rc-tree@3.0.0-alpha.37: + version "3.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-3.0.0-alpha.37.tgz#7a70b6358d7836be032ef9c9adc0f8f36c4c9f96" + integrity sha512-HYKA1dpKJLzCnivOg2urjQ2JKrx6lZkARvJBxB4rd/SvyCzBWubjTzFeI1xN0Ywr4VuYAc9rjwO+e9MsKhJ/fA== + dependencies: + classnames "2.x" + prop-types "^15.5.8" + rc-animate "^2.9.2" + rc-util "^4.11.0" + rc-virtual-list "^0.0.0-alpha.28" + react-lifecycles-compat "^3.0.4" + +rc-util@^4.11.0, rc-util@^4.8.0: + version "4.14.4" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.14.4.tgz#e7e009559e93bba022c49b0d0aec62665d6a0e7f" + integrity sha512-GQgEn6ywJYZq1NEoZ6NZzeaE2U6mT6DhdqrtRV5IBNM3yTZZW8HRjIiMOpXOhTEUj10bnHnKWKZpC36RoNmS9Q== + dependencies: + add-dom-event-listener "^1.1.0" + babel-runtime "6.x" + prop-types "^15.5.10" + react-lifecycles-compat "^3.0.4" + shallowequal "^0.2.2" + +rc-virtual-list@^0.0.0-alpha.28: + version "0.0.0-alpha.28" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-0.0.0-alpha.28.tgz#35ec58bdee8410948813b6a9f848001122daaad6" + integrity sha512-NILroEucVeZOY0hIhihPAxZ08QQqsi40PGf0R9xTSsSwCNhorBIb9DmmrpufqduABZwnv7G6V13Ij4zOQxRKDw== + dependencies: + classnames "^2.2.6" + rc-util "^4.8.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -8008,6 +8375,15 @@ react-app-polyfill@^1.0.4: regenerator-runtime "0.13.3" whatwg-fetch "3.0.0" +react-cookie@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-4.0.1.tgz#c21b2c30a5cacf320b45338fa3dcaf76d9fe9c2c" + integrity sha512-h61qAtSXvfjNa81h3XCFdFoyFaF+nb7gjK0cxQuTiCPMPAe50D950FjLCFhaIfSpAesQFAmkxf5XFpWoEVBDAA== + dependencies: + "@types/hoist-non-react-statics" "^3.0.1" + hoist-non-react-statics "^3.0.0" + universal-cookie "^4.0.0" + react-dev-utils@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.1.0.tgz#3ad2bb8848a32319d760d0a84c56c14bdaae5e81" @@ -8039,7 +8415,7 @@ react-dev-utils@^9.1.0: strip-ansi "5.2.0" text-table "0.2.0" -react-dom@16.11.0: +react-dom@^16.11.0: version "16.11.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.11.0.tgz#7e7c4a5a85a569d565c2462f5d345da2dd849af5" integrity sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA== @@ -8054,11 +8430,114 @@ react-error-overlay@^6.0.3: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.3.tgz#c378c4b0a21e88b2e159a3e62b2f531fd63bf60d" integrity sha512-bOUvMWFQVk5oz8Ded9Xb7WVdEi3QGLC8tH7HmYP0Fdp4Bn3qw0tRFmr5TW6mvahzvmrK4a6bqWGfCevBflP+Xw== +react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-ga@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c" + integrity sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA== + +react-helmet@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa" + integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA== + dependencies: + object-assign "^4.1.1" + prop-types "^15.5.4" + react-fast-compare "^2.0.2" + react-side-effect "^1.1.0" + +react-html-parser@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/react-html-parser/-/react-html-parser-2.0.2.tgz#6dbe1ddd2cebc1b34ca15215158021db5fc5685e" + integrity sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g== + dependencies: + htmlparser2 "^3.9.0" + +react-image-lightbox@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/react-image-lightbox/-/react-image-lightbox-5.1.0.tgz#b47b904b027c918acdd9efa94dc0a0ddd8125c86" + integrity sha512-R46QvffoDBscLQgTl4s3kFxVbnP7a+nIh7AXJNS0EXVeDaa6zKDKtIT+jFeEvs+F9oUHtZfenG1NHhTkO4hEOA== + dependencies: + prop-types "^15.6.2" + react-modal "^3.6.1" + +react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0: + version "16.11.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" + integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== + react-is@^16.8.1, react-is@^16.8.4: version "16.10.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.1.tgz#0612786bf19df406502d935494f0450b40b8294f" integrity sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw== +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-modal@^3.6.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.1.tgz#2a0d6877c9e98f123939ea92d2bb4ad7fa5a17f9" + integrity sha512-8uN744Yq0X2lbfSLxsEEc2UV3RjSRb4yDVxRQ1aGzPo86QjNOwhQSukDb8U8kR+636TRTvfMren10fgOjAy9eA== + dependencies: + exenv "^1.2.0" + prop-types "^15.5.10" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + +react-overlays@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.1.tgz#d4702bfe5b5e9335b676ff5a940253771fdeed12" + integrity sha512-b0asy/zHtRd0i2+2/uNxe3YVprF3bRT1guyr791DORjCzE/HSBMog+ul83CdtKQ1kZ+pLnxWCu5W3BMysFhHdQ== + dependencies: + classnames "^2.2.5" + dom-helpers "^3.2.1" + prop-types "^15.5.10" + prop-types-extra "^1.0.1" + react-transition-group "^2.2.1" + warning "^3.0.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router-hash-link@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-1.2.2.tgz#7a0ad5e925d49596d19554de8bc6c554ce4f8099" + integrity sha512-LBthLVHdqPeKDVt3+cFRhy15Z7veikOvdKRZRfyBR2vjqIE7rxn+tKLjb6DOmLm6JpoQVemVDnxQ35RVnEHdQA== + dependencies: + prop-types "^15.6.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-scripts@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.2.0.tgz#58ccd6b4ffa27f1b4d2986cbdcaa916660e9e33c" @@ -8120,7 +8599,29 @@ react-scripts@3.2.0: optionalDependencies: fsevents "2.0.7" -react@16.11.0: +react-side-effect@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" + integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== + dependencies: + shallowequal "^1.0.1" + +react-spinner-material@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/react-spinner-material/-/react-spinner-material-1.1.3.tgz#bdf375089f038028250762007323f026de5f57d5" + integrity sha512-sa9ESbt9/V79jzdxYGfd0i84JG5Nuz6q2wmxqS0Jc8M+J/Rk/aHiA/R2X1ewAwRxysMMDhFG7Altf7FEea1vkg== + +react-transition-group@^2.2.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + +react@^16.11.0: version "16.11.0" resolved "https://registry.yarnpkg.com/react/-/react-16.11.0.tgz#d294545fe62299ccee83363599bf904e4a07fdbb" integrity sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g== @@ -8396,6 +8897,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + resolve-url-loader@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.0.tgz#54d8181d33cd1b66a59544d05cadf8e4aa7d37cc" @@ -8722,6 +9228,18 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e" + integrity sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4= + dependencies: + lodash.keys "^3.1.2" + +shallowequal@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -9338,6 +9856,16 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-invariant@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" + integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9555,6 +10083,16 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +universal-cookie@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.2.tgz#c3398a64c72ff0c31fecb1ac4966c424e8669c6d" + integrity sha512-n14lhA//lQeYRweP9j9uXsshN9Cs4LunVSnvAGmnA69SofwsjpUU03geaCaPC9LlsH2rkBy99o3zxQyVOldGvA== + dependencies: + "@types/cookie" "^0.3.3" + "@types/object-assign" "^4.0.30" + cookie "^0.4.0" + object-assign "^4.1.1" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -9685,6 +10223,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -9732,6 +10275,20 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" @@ -10149,6 +10706,13 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== +xregexp@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.2.4.tgz#02a4aea056d65a42632c02f0233eab8e4d7e57ed" + integrity sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA== + dependencies: + "@babel/runtime-corejs2" "^7.2.0" + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"