├── .babelrc ├── .gitignore ├── .huskyrc ├── README.md ├── jest.config.js ├── netlify.toml ├── package.json ├── preact.config.js ├── scripts ├── ParseCharacters.js ├── ParseItemTable.js ├── ParseLevelList.js ├── ParseSideProducts.js ├── bump.js ├── copy.js ├── handbook │ ├── handbook_force_map_table.json │ ├── handbookcard_table.json │ ├── handbookline_table.json │ ├── handbookpos_table.json │ └── handbookteamicon_table.json ├── i18n │ ├── CheckOperatorsDiff.js │ ├── ParseEquipmentsTable.js │ ├── ParseItemTable.js │ ├── ParseLevelList.js │ ├── ParseOperatorTable.js │ └── diff │ │ └── operators.json ├── mapping.json ├── materials_base.json ├── migration_check.json ├── penguin-stat │ ├── ParseLevels.js │ ├── levels.json │ ├── penguin_items.json │ └── penguin_levels.json ├── postBuild.js └── update.js ├── src ├── __mocks__ │ └── styleMock.js ├── app.js ├── assets │ ├── cards │ │ ├── T1.png │ │ ├── T2.png │ │ ├── T3.png │ │ ├── T4.png │ │ ├── T5.png │ │ └── T6.png │ ├── favicon.ico │ ├── icons │ │ ├── clear_selection.png │ │ ├── close.png │ │ ├── close_white.png │ │ ├── external.png │ │ ├── hidden.png │ │ ├── hidden_all.png │ │ ├── move_down.png │ │ ├── move_to_first.png │ │ ├── move_to_last.png │ │ ├── move_up.png │ │ ├── paypal.png │ │ ├── penguin.png │ │ ├── penguin_black.png │ │ ├── select.png │ │ ├── tick.png │ │ ├── tick_invert.png │ │ └── tick_white.png │ ├── materials │ │ ├── 404.png │ │ ├── AP.png │ │ ├── B-2-1.png │ │ ├── B-3-1.png │ │ ├── B-4-1.png │ │ ├── BC-2-1.png │ │ ├── BC-3-1.png │ │ ├── BC-4-1.png │ │ ├── C-3-1.png │ │ ├── C-3-2.png │ │ ├── C-3-3.png │ │ ├── C-3-4.png │ │ ├── C-3-5.png │ │ ├── C-3-6.png │ │ ├── C-3-7.png │ │ ├── C-3-8.png │ │ ├── C-4-1.png │ │ ├── C-4-2.png │ │ ├── C-4-3.png │ │ ├── C-4-4.png │ │ ├── C-4-5.png │ │ ├── C-4-6.png │ │ ├── C-4-7.png │ │ ├── C-4-8.png │ │ ├── C-5-1.png │ │ ├── C-5-2.png │ │ ├── C-5-3.png │ │ ├── C-5-4.png │ │ ├── C-5-5.png │ │ ├── C-5-6.png │ │ ├── C-5-7.png │ │ ├── C-5-8.png │ │ ├── D-5-1.png │ │ ├── D-6-1.png │ │ ├── E-2-1.png │ │ ├── E-3-1.png │ │ ├── E-4-1.png │ │ ├── E-5-1.png │ │ ├── EO-4-1.png │ │ ├── FC-4-1.png │ │ ├── G-4-1.png │ │ ├── M-1-1.png │ │ ├── M-1-2.png │ │ ├── M-1-3.png │ │ ├── M-1-4.png │ │ ├── M-1-5.png │ │ ├── M-1-6.png │ │ ├── M-2-1.png │ │ ├── M-2-2.png │ │ ├── M-2-3.png │ │ ├── M-2-4.png │ │ ├── M-2-5.png │ │ ├── M-2-6.png │ │ ├── M-3-1.png │ │ ├── M-3-10.png │ │ ├── M-3-11.png │ │ ├── M-3-12.png │ │ ├── M-3-13.png │ │ ├── M-3-14.png │ │ ├── M-3-15.png │ │ ├── M-3-16.png │ │ ├── M-3-17.png │ │ ├── M-3-18.png │ │ ├── M-3-19.png │ │ ├── M-3-2.png │ │ ├── M-3-3.png │ │ ├── M-3-4.png │ │ ├── M-3-5.png │ │ ├── M-3-6.png │ │ ├── M-3-7.png │ │ ├── M-3-8.png │ │ ├── M-3-9.png │ │ ├── M-4-1.png │ │ ├── M-4-10.png │ │ ├── M-4-11.png │ │ ├── M-4-12.png │ │ ├── M-4-13.png │ │ ├── M-4-14.png │ │ ├── M-4-15.png │ │ ├── M-4-16.png │ │ ├── M-4-17.png │ │ ├── M-4-18.png │ │ ├── M-4-19.png │ │ ├── M-4-2.png │ │ ├── M-4-3.png │ │ ├── M-4-4.png │ │ ├── M-4-5.png │ │ ├── M-4-6.png │ │ ├── M-4-7.png │ │ ├── M-4-8.png │ │ ├── M-4-9.png │ │ ├── M-5-1.png │ │ ├── M-5-2.png │ │ ├── M-5-3.png │ │ ├── M-5-4.png │ │ ├── M-5-5.png │ │ ├── M-5-6.png │ │ ├── O-4-1.png │ │ ├── PC-3-1.png │ │ ├── S-2-1.png │ │ ├── S-3-1.png │ │ ├── S-4-1.png │ │ ├── W-5-1.png │ │ ├── W-5-2.png │ │ └── W-5-3.png │ ├── tip_ali.png │ └── tip_wechat.png ├── components │ ├── button │ │ ├── index.js │ │ └── style.css │ ├── cell │ │ ├── index.js │ │ └── style.css │ ├── dropdown │ │ ├── index.js │ │ └── style.css │ ├── fuseInputCell │ │ ├── __tests__ │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── style.css │ ├── header │ │ ├── index.js │ │ ├── style.css │ │ └── toggle.js │ ├── inputCell │ │ ├── index.js │ │ └── style.css │ ├── item │ │ ├── index.js │ │ └── style.css │ ├── levelInfo │ │ ├── index.js │ │ └── style.css │ ├── materialCard │ │ ├── index.js │ │ └── style.css │ ├── materialsGroup │ │ ├── index.js │ │ └── style.css │ ├── penguinLink │ │ ├── index.js │ │ └── style.css │ └── row │ │ ├── index.js │ │ └── style.css ├── config │ ├── useConfig.js │ └── version.json ├── i18n │ ├── __tests__ │ │ └── render.spec.js │ ├── equipments.json │ ├── items.json │ ├── levels.json │ ├── locale │ │ ├── en_US.json │ │ ├── index.js │ │ ├── ja_JP.json │ │ ├── ko_KR.json │ │ ├── template.json │ │ └── zh_CN.json │ ├── operators.json │ └── render.js ├── index.js ├── manifest.json ├── models │ ├── Attributes.js │ ├── Compounds.js │ ├── Equipments.js │ ├── Levels.js │ ├── Operators.js │ ├── Resources │ │ ├── exp_tapes.json │ │ ├── index.js │ │ └── materials.json │ ├── Upgrade.js │ ├── aggregateMaterialRequirement.js │ ├── checkFulFillment.js │ ├── compounds.json │ ├── exp.json │ ├── getShortageFocusMaterials.js │ ├── levels.json │ ├── operators.json │ ├── processRecord.js │ ├── sumLevelUpRequirement.js │ ├── sumRequirements.js │ ├── sumShortage.js │ ├── useData.js │ ├── useFilterSetting.js │ └── useRecord.js ├── routes │ ├── backup │ │ ├── index.js │ │ └── style.css │ ├── farming │ │ ├── components │ │ │ └── LevelInput.js │ │ ├── getLevelMaterials.js │ │ ├── index.js │ │ ├── options │ │ │ └── index.js │ │ ├── sections │ │ │ └── ArkPlanner │ │ │ │ ├── index.js │ │ │ │ └── style.css │ │ └── style.css │ ├── info │ │ ├── autoPrint.js │ │ ├── auto_announcements.json │ │ ├── history.js │ │ ├── index.js │ │ └── style.css │ ├── materials │ │ ├── components │ │ │ ├── CompoundRequirementsRow.js │ │ │ ├── CompoundRow.js │ │ │ ├── CompoundSideProductsRow.js │ │ │ ├── DropRow.js │ │ │ ├── MaterialInput.js │ │ │ └── RequirementRow.js │ │ ├── index.js │ │ └── style.css │ ├── operator │ │ ├── components │ │ │ ├── OperatorInput.js │ │ │ ├── TableHeader.js │ │ │ ├── UpgradeInputRow.js │ │ │ └── UpgradeRow.js │ │ ├── hooks │ │ │ ├── processUpgrade.js │ │ │ └── useOperatorUpgrade.js │ │ ├── index.js │ │ └── style.css │ ├── stock │ │ ├── index.js │ │ ├── options │ │ │ └── index.js │ │ └── style.css │ └── table │ │ ├── components │ │ ├── NewUpgradeRow.js │ │ ├── ShortageRow.js │ │ ├── StockRow.js │ │ ├── SummaryRow.js │ │ ├── TableHeader.js │ │ └── UpgradeInputRow.js │ │ ├── index.js │ │ ├── sections │ │ ├── FilterSettings │ │ │ ├── index.js │ │ │ └── style.css │ │ ├── FocusMaterials │ │ │ ├── index.js │ │ │ └── style.css │ │ ├── SortingPanel │ │ │ ├── index.js │ │ │ └── style.css │ │ └── UpgradeRowHeaders │ │ │ ├── Row.js │ │ │ └── index.js │ │ └── style.css ├── services │ ├── arkplanner │ │ ├── generatePayload.js │ │ ├── index.js │ │ └── parse.js │ └── penguinstats.js ├── style │ └── index.css └── template.html └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | ["preact-cli/babel", { "modules": "commonjs" }] 6 | ] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | /*.log 4 | src/assets/wip 5 | scripts/excels/* 6 | scripts/i18n/data 7 | scripts/i18n/data-i18n 8 | scripts/copy.js 9 | 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-push": "yarn test" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arkgraph 2 | 3 | ## CLI Commands 4 | 5 | ``` bash 6 | # install dependencies 7 | npm install 8 | 9 | # serve with hot reload at localhost:8080 10 | npm run dev 11 | 12 | # build for production with minification 13 | npm run build 14 | 15 | # test the production build locally 16 | npm run serve 17 | 18 | # run tests with jest and preact-render-spy 19 | npm run test 20 | ``` 21 | 22 | For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). 23 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDir: 'src', 3 | testPathIgnorePatterns: ['__fixtures__', 'test.js'], 4 | testURL: 'http://localhost', 5 | testEnvironment: 'node', 6 | moduleFileExtensions: ['js', 'json', 'node'], 7 | moduleDirectories: ['node_modules', ''], 8 | moduleNameMapper: { 9 | '(style)$': '/__mocks__/styleMock.js', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/table" 3 | to = "/index.html" 4 | status = 200 5 | 6 | [[redirects]] 7 | from = "/operator/*" 8 | to = "/index.html" 9 | status = 200 10 | 11 | [[redirects]] 12 | from = "/materials/*" 13 | to = "/index.html" 14 | status = 200 15 | 16 | [[redirects]] 17 | from = "/farming/*" 18 | to = "/index.html" 19 | status = 200 20 | 21 | [[redirects]] 22 | from = "/stock" 23 | to = "/index.html" 24 | status = 200 25 | 26 | [[redirects]] 27 | from = "/backup" 28 | to = "/index.html" 29 | status = 200 30 | 31 | [[redirects]] 32 | from = "/settings" 33 | to = "/index.html" 34 | status = 200 35 | 36 | [[redirects]] 37 | from = "https://serene-mestorf-73c07a.netlify.com/*" 38 | to = "https://ark-nights.com/:splat" 39 | status = 301 40 | force = true 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "arkgraph", 4 | "version": "0.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "start": "per-env", 8 | "start:production": "npm run -s serve", 9 | "start:development": "npm run -s dev", 10 | "pull-data": "cd scripts/i18n/data && git pull origin master --rebase", 11 | "pull-data-i18n": "cd scripts/i18n/data-i18n && git pull origin main --rebase", 12 | "prepare-data": "node scripts/copy.js", 13 | "parse-data": "node scripts/update.js", 14 | "generate-announcement": "node scripts/i18n/CheckOperatorsDiff.js", 15 | "bump": "yarn generate-announcement && node scripts/bump.js", 16 | "update": "yarn pull-data && yarn pull-data-i18n && yarn prepare-data && yarn parse-data", 17 | "build": "preact build --no-prerender --template src/template.html --production && node scripts/postBuild.js", 18 | "serve": "preact build --no-prerender && preact serve --port 3000", 19 | "dev": "preact watch --port 3000 --template src/template.html", 20 | "lint": "eslint src", 21 | "test": "jest" 22 | }, 23 | "eslintConfig": { 24 | "extends": "eslint-config-synacor", 25 | "rules": { 26 | "brace-style": [ 27 | "error", 28 | "1tbs" 29 | ], 30 | "camelcase": "off", 31 | "comma-dangle": [ 32 | "error", 33 | "always-multiline" 34 | ], 35 | "react/jsx-no-bind": "disable", 36 | "react/display-name": "disable" 37 | } 38 | }, 39 | "eslintIgnore": [ 40 | "build/*" 41 | ], 42 | "devDependencies": { 43 | "cheerio": "^1.0.0-rc.3", 44 | "copy-dir": "^1.3.0", 45 | "eslint": "^4.9.0", 46 | "eslint-config-synacor": "^2.0.2", 47 | "husky": "^3.0.0", 48 | "identity-obj-proxy": "^3.0.0", 49 | "jaco": "^4.0.0", 50 | "jest": "^24.8.0", 51 | "per-env": "^1.0.2", 52 | "pinyin": "^2.9.0", 53 | "preact-cli": "^3.0.0-next.19", 54 | "preact-cli-workbox-plugin": "^1.1.1", 55 | "preact-render-spy": "^1.2.1" 56 | }, 57 | "dependencies": { 58 | "classnames": "^2.2.6", 59 | "fuse.js": "^3.4.4", 60 | "preact": "^10.0.0-beta.1", 61 | "preact-router": "^3.0.0" 62 | }, 63 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 64 | } 65 | -------------------------------------------------------------------------------- /preact.config.js: -------------------------------------------------------------------------------- 1 | const { generateSw } = require('preact-cli-workbox-plugin'); 2 | export default function(config, env, helpers) { 3 | if (env.isProd) { 4 | config.devtool = false; 5 | } 6 | return generateSw(config, helpers, { 7 | include: [/\.jpg$/, /\.png$/], 8 | exclude: [/\.js$/, /\.css/, /plan$/], 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /scripts/ParseItemTable.js: -------------------------------------------------------------------------------- 1 | const { items } = require('./excels/item_table.json'); 2 | const { stages } = require('./excels/stage_table.json'); 3 | 4 | const pinyin = require('pinyin'); 5 | 6 | const MapItem = require('./mapping.json'); 7 | const MapOcc = { 8 | 0: '固定', 9 | 1: '大概率', 10 | 2: '中概率', 11 | 3: '小概率', 12 | 4: '罕见', 13 | }; 14 | 15 | const DropTypeMapping = { 16 | ALWAYS: '固定', 17 | ALMOST: '大概率', 18 | USUAL: '中概率', 19 | OFTEN: '小概率', 20 | SOMETIMES: '罕见', 21 | }; 22 | 23 | const ParseItem = (data) => { 24 | const item_id = MapItem[data.itemId]; 25 | if (!item_id || !item_id.startsWith('M')) { 26 | return null; 27 | } 28 | 29 | const source = { 30 | 31 | }; 32 | 33 | data.stageDropList.forEach(({ 34 | stageId, 35 | occPer, 36 | }) => { 37 | const stage_code = stages[stageId].code; 38 | if (!stageId.startsWith('main') && !stageId.startsWith('sub') && !stageId.startsWith('easy') && !stageId.startsWith('tough')) { 39 | return; 40 | } 41 | source[stage_code] = DropTypeMapping[occPer]; 42 | }); 43 | 44 | const item = { 45 | id: MapItem[data.itemId], 46 | source, 47 | sort_id: data.sortId 48 | }; 49 | 50 | 51 | return item; 52 | }; 53 | 54 | const materials_dict = {}; 55 | 56 | const MATERIAL_LIST = Object.entries(items) 57 | .sort(([_, { sortId: sa }], [__, { sortId: sb }]) => sb - sa) 58 | .map(([item_key, item_data]) => ParseItem(item_data)) 59 | .filter(Boolean) 60 | .forEach((item) => materials_dict[item.id] = item); 61 | 62 | const materials_base = require('./materials_base.json'); 63 | const materials_full = materials_base.map(mat => ({ 64 | ...mat, 65 | ...materials_dict[mat.id], 66 | })).sort((a, b) => a.sort_id - b.sort_id); 67 | 68 | // require('fs').writeFileSync(require('path').resolve(__dirname, '../', 'materials.json'), JSON.stringify(materials_full, null, 2)); 69 | 70 | const fs = require('fs'); 71 | const path = require('path'); 72 | fs.writeFileSync(path.resolve(__dirname, '../src/models/Resources', 'materials.json'), JSON.stringify(materials_full, null, 2)); 73 | -------------------------------------------------------------------------------- /scripts/ParseLevelList.js: -------------------------------------------------------------------------------- 1 | const { stages } = require('./excels/stage_table.json'); 2 | const { stageList: retro_stages } = require('./excels/retro_table.json'); 3 | 4 | const MapItem = require('./mapping.json'); 5 | const MapOcc = { 6 | 0: '固定', 7 | 1: '大概率', 8 | 2: '中概率', 9 | 3: '小概率', 10 | 4: '罕见', 11 | }; 12 | 13 | const DROP_TYPES = { 14 | "NONE": 0, 15 | "ONCE": 1, 16 | "NORMAL": 2, 17 | "SPECIAL": 3, 18 | "ADDITIONAL": 4, 19 | "APRETURN": 5, 20 | "DIAMOND_MATERIAL": 6, 21 | "FUNITURE_DROP": 7, 22 | "COMPLETE": 8, 23 | "CHARM_DROP": 9, 24 | "OVERRIDE_DROP": 10, 25 | "ITEM_RETURN": 11, 26 | } 27 | 28 | const MapDropType = dropType => { 29 | if (Object.keys(DROP_TYPES).includes(dropType)) return DROP_TYPES[dropType]; 30 | return dropType; 31 | } 32 | 33 | const ParseLevel = (data) => { 34 | const retro_data = retro_stages[data.stageId]; 35 | const is_perm = Boolean(retro_data); 36 | 37 | const level_data = is_perm ? retro_data : data; 38 | 39 | const level = { 40 | level: data.code, 41 | energy: level_data.apCost, 42 | unique_id: data.stageId, 43 | zone_id: data.zoneId, 44 | is_perm, 45 | normal_drop: [], 46 | special_drop: [], 47 | extra_drop: [], 48 | }; 49 | 50 | level_data.stageDropInfo.displayDetailRewards.forEach(({ 51 | occPercent, 52 | id, 53 | dropType: dropTypeRaw, 54 | }) => { 55 | const dropType = MapDropType(dropTypeRaw) 56 | 57 | if (!MapItem[id]) { // Not supported 58 | return; 59 | } 60 | if ( 61 | !MapItem[id].startsWith('M') 62 | && !MapItem[id].startsWith('E') 63 | && !MapItem[id].startsWith('S') 64 | && !MapItem[id].startsWith('C') 65 | && !MapItem[id].startsWith('O') 66 | && !MapItem[id].startsWith('PC') 67 | ) { 68 | return; 69 | } 70 | const item = { 71 | resource: MapItem[id], 72 | probability: MapOcc[occPercent], 73 | }; 74 | switch (dropType) { 75 | case 2: // Normal 76 | level.normal_drop.push(item); 77 | break; 78 | case 3: // Special 79 | level.special_drop.push(item); 80 | break; 81 | case 4: // Extra 82 | level.extra_drop.push({ 83 | ...item, 84 | probability: MapOcc[3], 85 | }); 86 | break; 87 | default: 88 | case 8: // First time 89 | break; 90 | 91 | } 92 | }); 93 | 94 | return level; 95 | }; 96 | 97 | const level_list = []; 98 | 99 | Object.entries(stages).forEach(([level_key, level_data]) => { 100 | const level = ParseLevel(level_data); 101 | 102 | if (!level) { 103 | return; 104 | } 105 | 106 | if (level.normal_drop.length + level.special_drop.length + level.extra_drop.length === 0) { 107 | return; 108 | } 109 | 110 | level_list.push(level) 111 | }); 112 | 113 | const sorted_level_list = level_list.sort( 114 | (a, b) => b.normal_drop.length - a.normal_drop.length 115 | ); 116 | 117 | require('fs').writeFileSync(require('path').resolve(__dirname, '../src/models', 'levels.json'), JSON.stringify(sorted_level_list, null, 2)); -------------------------------------------------------------------------------- /scripts/ParseSideProducts.js: -------------------------------------------------------------------------------- 1 | const pinyin = require('pinyin'); 2 | const { workshopFormulas } = require('./excels/building_data.json'); 3 | const MapItem = require('./mapping.json'); 4 | const game_data = require('./excels/gamedata_const.json'); 5 | 6 | const FormulaTypes = [ 7 | 'F_BUILDING', // Building material 8 | 'F_EVOLVE', // Materials 9 | 'F_SKILL', // Skill books 10 | 'F_ASC', // Chips 11 | ]; 12 | 13 | const parseCosts = costs => costs 14 | .map(({ id, count }) => ({ 15 | resource: MapItem[id], 16 | quantity: count, 17 | })); 18 | 19 | const parseExtraOutcomeGroup = (extraOutcomeGroup, baseRate = 0.1) => { 20 | const sum_weight = extraOutcomeGroup 21 | .map(({ weight }) => weight) 22 | .reduce((a, b) => a + b, 0); 23 | const side_products = extraOutcomeGroup 24 | .map(({ weight, itemId, itemCount }) => ({ 25 | probability: Math.round(baseRate * weight / sum_weight * 100000) / 1000, 26 | resource: MapItem[itemId], 27 | quantity: itemCount, 28 | })); 29 | return side_products; 30 | }; 31 | 32 | const parseFormula = (formula) => { 33 | const { 34 | formulaType, 35 | itemId, 36 | apCost, 37 | costs, 38 | extraOutcomeRate, 39 | extraOutcomeGroup, 40 | } = formula; 41 | if (formulaType !== 'F_EVOLVE') { 42 | return null; 43 | } 44 | 45 | const material_id = MapItem[itemId]; 46 | const compound_formula = parseCosts(costs); 47 | const side_products = parseExtraOutcomeGroup(extraOutcomeGroup, extraOutcomeRate); 48 | 49 | const compound_data = { 50 | material_id, 51 | formula: compound_formula, 52 | side_products, 53 | ap_cost: apCost / 360000, 54 | }; 55 | 56 | return compound_data; 57 | }; 58 | 59 | const SIDE_PRODUCTS = Object.entries(workshopFormulas) 60 | .map(([id, value], index) => parseFormula(value)) 61 | .filter(Boolean); 62 | 63 | require('fs').writeFileSync(require('path').resolve(__dirname, '../src/models', 'compounds.json'), JSON.stringify(SIDE_PRODUCTS, null, 2)); 64 | -------------------------------------------------------------------------------- /scripts/bump.js: -------------------------------------------------------------------------------- 1 | const { storage } = require('../src/config/version.json'); 2 | 3 | const [major, minor, patch] = storage.split('.').map(Number); 4 | 5 | const version = { 6 | storage: `${major}.${minor}.${patch + 1}`, 7 | }; 8 | 9 | require('fs').writeFileSync(require('path').resolve(__dirname, '../src/config', 'version.json'), JSON.stringify(version, null, 2)); 10 | -------------------------------------------------------------------------------- /scripts/copy.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const copydir = require('copy-dir'); 3 | 4 | copydir.sync(path.resolve(__dirname, './i18n/data/zh_CN/gamedata/excel'), path.resolve(__dirname, './excels'), { 5 | utimes: true, 6 | mode: true, 7 | cover: true, 8 | }); 9 | 10 | copydir.sync(path.resolve(__dirname, './i18n/data/zh_CN/gamedata/art'), path.resolve(__dirname, './handbook'), { 11 | utimes: true, 12 | mode: true, 13 | cover: true, 14 | }); -------------------------------------------------------------------------------- /scripts/i18n/CheckOperatorsDiff.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const prev_operators = require('./diff/operators.json'); 4 | const new_operators = require('../../src/i18n/operators.json'); 5 | 6 | const OPERATORS = require('../../src/models/operators.json'); 7 | 8 | const I18N_LANG = [ 9 | 'zh_TW', 10 | 'en_US', 11 | 'ja_JP', 12 | 'ko_KR', 13 | ]; 14 | 15 | const now = new Date(); 16 | const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; 17 | 18 | const cn_prev_operators_list = Object.keys(prev_operators); 19 | const cn_new_operator_list = Object.keys(new_operators); 20 | 21 | const added_operators = cn_new_operator_list.filter(unique_id => !cn_prev_operators_list.includes(unique_id)).filter(unique_id => unique_id.startsWith('char_')); 22 | const i18n_added_oprators = Object.entries(new_operators).map(([unique_id, value]) => { 23 | if (!unique_id.startsWith('char_')) { 24 | return null; 25 | } 26 | 27 | const prev_status = prev_operators[unique_id]; 28 | if (!prev_status) { 29 | return null; 30 | } 31 | 32 | const new_status = value; 33 | 34 | const added_locale = I18N_LANG.filter(locale => !prev_status[locale] && new_status[locale]); 35 | 36 | if (added_locale.length <= 0) { 37 | return null; 38 | } 39 | 40 | return { 41 | locale: added_locale, 42 | operator: unique_id, 43 | }; 44 | }).filter(Boolean); 45 | 46 | // write to announcement 47 | const cn_announcement = added_operators.length > 0 && { 48 | server: [ 49 | 'zh_CN', 50 | ], 51 | date, 52 | type: 'new_operators', 53 | new_operators: added_operators, 54 | }; 55 | 56 | const i18n_announcements = i18n_added_oprators.map(item => ({ 57 | server: item.locale, 58 | date, 59 | type: 'new_operators', 60 | new_operators: [item.operator], 61 | })); 62 | 63 | const current_announcement_items = require('../../src/routes/info/auto_announcements.json'); 64 | 65 | const announcement_items = [ 66 | cn_announcement, 67 | ...i18n_announcements, 68 | ].filter(Boolean).reduce((acc, next) => { 69 | const merge_server_index = acc.findIndex(item => ( 70 | item.date === next.date && 71 | item.type === next.type && 72 | item.server.length === next.server.length && 73 | item.server.every(server => next.server.includes(server)) 74 | )); 75 | if (merge_server_index >= 0) { 76 | const merge_item = acc[merge_server_index]; 77 | return acc.map((item, index) => index === merge_server_index ? { 78 | ...merge_item, 79 | new_operators: [ 80 | ...merge_item.new_operators, 81 | ...next.new_operators, 82 | ], 83 | } : item); 84 | } 85 | 86 | const merge_operators_index = acc.findIndex(item => ( 87 | item.date === next.date && 88 | item.type === next.type && 89 | item.new_operators.length === next.new_operators.length && 90 | item.new_operators.every(unique_id => next.new_operators.includes(unique_id)) 91 | )); 92 | if (merge_operators_index >= 0) { 93 | const merge_item = acc[merge_operators_index]; 94 | return acc.map((item, index) => index === merge_operators_index ? { 95 | ...merge_item, 96 | server: [ 97 | ...merge_item.server, 98 | ...next.server, 99 | ], 100 | } : item); 101 | } 102 | 103 | return [next ,...acc]; 104 | }, current_announcement_items).map(item => { 105 | if (item.type === 'new_operators') { 106 | return { 107 | ...item, 108 | new_operators: item.new_operators.sort((a, b) => ( 109 | (OPERATORS.find(({ unique_id }) => unique_id === b) || { rarity: 0 }).rarity 110 | - (OPERATORS.find(({ unique_id }) => unique_id === a) || { rarity: 0 }).rarity 111 | )), 112 | }; 113 | } 114 | 115 | return item; 116 | }).sort((a, b) => b.date.localeCompare(a.date)).filter((_, i) => i < 10); 117 | 118 | // Update diff file 119 | fs.writeFileSync(path.resolve(__dirname, './diff/operators.json'), JSON.stringify(new_operators, null, 2)); 120 | 121 | // Update announcements 122 | fs.writeFileSync(path.resolve(__dirname, '../../src/routes/info/auto_announcements.json'), JSON.stringify(announcement_items, null, 2)); 123 | -------------------------------------------------------------------------------- /scripts/i18n/ParseEquipmentsTable.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const LANG = [ 4 | 'en_US', 5 | 'ja_JP', 6 | 'ko_KR', 7 | 'zh_CN', 8 | // 'zh_TW', 9 | ]; 10 | 11 | const equipments_data = {}; 12 | 13 | const readTable = (lang) => { 14 | const repo = lang === 'zh_CN' ? 'data' : 'data-i18n'; 15 | 16 | try { 17 | const { equipDict } = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/uniequip_table.json`)); 18 | Object.entries(equipDict) 19 | .filter(([key, value]) => value.type !== 'INITIAL') 20 | .forEach(([ 21 | key, value, 22 | ]) => { 23 | const { 24 | uniEquipName, 25 | charId, 26 | } = value; 27 | 28 | equipments_data[key] = equipments_data[key] || {}; 29 | equipments_data[key].char_id = charId; 30 | 31 | equipments_data[key][lang] = equipments_data[key][lang] || {}; 32 | equipments_data[key][lang].enabled = true; 33 | equipments_data[key][lang].name = uniEquipName; 34 | }); 35 | } catch (err) { 36 | 37 | // Not enabled for non CN server 38 | } 39 | 40 | }; 41 | 42 | LANG.forEach(lang => { 43 | readTable(lang); 44 | }); 45 | 46 | require('fs').writeFileSync(require('path').resolve(__dirname, '../../src/i18n', 'equipments.json'), JSON.stringify(equipments_data, null, 2)); 47 | -------------------------------------------------------------------------------- /scripts/i18n/ParseItemTable.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pinyin = require('pinyin'); 3 | const { default: toHiragana } = require('jaco/fn/toHiragana'); 4 | 5 | const ItemMapping = require('../mapping.json'); 6 | 7 | const LANG = [ 8 | 'en_US', 9 | 'ja_JP', 10 | 'ko_KR', 11 | 'zh_CN', 12 | // 'zh_TW', 13 | ]; 14 | 15 | const item_data = {}; 16 | Object.entries(ItemMapping).forEach(([key, value]) => { 17 | item_data[value] = {}; 18 | LANG.forEach(lang => { 19 | item_data[value][lang] = { 20 | enabled: false, 21 | name: '', 22 | alias: [], 23 | }; 24 | }); 25 | }); 26 | 27 | const readTable = (lang) => { 28 | const repo = lang === 'zh_CN' ? 'data' : 'data-i18n'; 29 | 30 | const { items } = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/item_table.json`)); 31 | Object.entries(items) 32 | .filter(([unique_id, value]) => ItemMapping[unique_id]) 33 | .forEach(([unique_id, value]) => { 34 | const item_id = ItemMapping[unique_id]; 35 | const { 36 | name: name_raw, 37 | sortId, 38 | } = value; 39 | 40 | const name = String(name_raw).trim(); 41 | item_data[item_id][lang].enabled = true; 42 | item_data[item_id][lang].name = name; 43 | item_data[item_id][lang].alias = []; 44 | item_data[item_id][lang].sortId = sortId; 45 | if (lang === 'zh_CN') { 46 | const item_pinyin = [].concat(...pinyin(name, { 47 | style: pinyin.STYLE_NORMAL, 48 | })); 49 | item_data[item_id][lang].alias.push(item_pinyin.join(' ')); 50 | item_data[item_id][lang].alias.push(item_pinyin.join('')); 51 | } 52 | if (lang === 'ja_JP' && item_data[item_id][lang]) { 53 | const item_hiragana = toHiragana(name); 54 | item_data[item_id][lang].alias.push(item_hiragana); 55 | } 56 | }); 57 | }; 58 | 59 | LANG.forEach(lang => { 60 | readTable(lang); 61 | }); 62 | 63 | require('fs').writeFileSync(require('path').resolve(__dirname, '../../src/i18n', 'items.json'), JSON.stringify(item_data, null, 2)); 64 | 65 | 66 | -------------------------------------------------------------------------------- /scripts/i18n/ParseLevelList.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const LANG = [ 4 | 'en_US', 5 | 'ja_JP', 6 | 'ko_KR', 7 | 'zh_CN', 8 | // 'zh_TW', 9 | ]; 10 | 11 | const stages_data = {}; 12 | 13 | const readTable = (lang) => { 14 | const repo = lang === 'zh_CN' ? 'data' : 'data-i18n'; 15 | 16 | const { stages } = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/stage_table.json`)); 17 | Object.entries(stages) 18 | .forEach(([ 19 | key, value, 20 | ]) => { 21 | const { 22 | code, 23 | } = value; 24 | 25 | stages_data[key] = stages_data[key] || {}; 26 | stages_data[key].code = code; 27 | 28 | stages_data[key][lang] = stages_data[key][lang] || {}; 29 | stages_data[key][lang].enabled = true; 30 | }); 31 | }; 32 | 33 | LANG.forEach(lang => { 34 | readTable(lang); 35 | }); 36 | 37 | require('fs').writeFileSync(require('path').resolve(__dirname, '../../src/i18n', 'levels.json'), JSON.stringify(stages_data, null, 2)); 38 | -------------------------------------------------------------------------------- /scripts/i18n/ParseOperatorTable.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pinyin = require('pinyin'); 3 | const { default: toHiragana } = require('jaco/fn/toHiragana'); 4 | 5 | const LANG = [ 6 | 'en_US', 7 | 'ja_JP', 8 | 'ko_KR', 9 | 'zh_CN', 10 | // 'zh_TW', 11 | ]; 12 | 13 | const profession_map = { 14 | "zh_CN": { 15 | "PIONEER": "先锋", 16 | "WARRIOR": "近卫", 17 | "TANK": "重装", 18 | "SNIPER": "狙击", 19 | "CASTER": "术师", 20 | "MEDIC": "医疗", 21 | "SUPPORT": "辅助", 22 | "SPECIAL": "特种", 23 | }, 24 | "en_US": { 25 | "PIONEER": "Pioneer", 26 | "WARRIOR": "Warrior", 27 | "TANK": "Tank", 28 | "SNIPER": "Sniper", 29 | "CASTER": "Caster", 30 | "MEDIC": "Medic", 31 | "SUPPORT": "Support", 32 | "SPECIAL": "Special", 33 | }, 34 | "ja_JP": { 35 | "PIONEER": "先鋒", 36 | "WARRIOR": "前衛", 37 | "TANK": "重装", 38 | "SNIPER": "狙撃", 39 | "CASTER": "術師", 40 | "MEDIC": "医療", 41 | "SUPPORT": "補助", 42 | "SPECIAL": "特殊", 43 | }, 44 | "ko_KR": { 45 | "PIONEER": "뱅가드", 46 | "WARRIOR": "가드", 47 | "TANK": "디펜더", 48 | "SNIPER": "스나이퍼", 49 | "CASTER": "캐스터", 50 | "MEDIC": "메딕", 51 | "SUPPORT": "서포터", 52 | "SPECIAL": "스페셜리스트", 53 | }, 54 | } 55 | 56 | const operator_data = {}; 57 | 58 | const readTable = (lang) => { 59 | const repo = lang === 'zh_CN' ? 'data' : 'data-i18n'; 60 | 61 | const characters = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/character_table.json`)); 62 | const skills = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/skill_table.json`)); 63 | 64 | const patch_characters = require(path.resolve(__dirname, `./${repo}/${lang}/gamedata/excel/char_patch_table.json`)); 65 | 66 | Object.entries(patch_characters.patchChars).forEach(([unique_id, value]) => { 67 | characters[unique_id] = { 68 | ...value, 69 | name: (() => { 70 | switch (lang) { 71 | case 'zh_CN': 72 | return `升变${value.name}${profession_map[lang][value.profession]}`; 73 | case 'ja_JP': 74 | return `昇格${value.name}${profession_map[lang][value.profession]}`; 75 | case 'en_US': 76 | case 'ko_KR': 77 | return `Promoted ${value.name} ${profession_map['en_US'][value.profession]}`; 78 | } 79 | })(), 80 | }; 81 | }); 82 | 83 | Object.entries(characters) 84 | .filter( 85 | // Duplicated rogue characters 86 | ([unique_id]) => unique_id !== 'char_512_aprot' // char_4025_aprot2 87 | ) 88 | .filter( 89 | ([, value]) => { 90 | return !["TRAP", "TOKEN"].includes(value.profession); 91 | } 92 | ) 93 | .forEach(([unique_id, value]) => { 94 | operator_data[unique_id] = operator_data[unique_id] || {}; 95 | operator_data[unique_id][lang] = { 96 | enabled: true, 97 | name: value.name, 98 | skills: value.skills.map(({ skillId }) => skills[skillId] ? skills[skillId].levels[0].name : ''), 99 | alias: [], 100 | }; 101 | if (lang === 'zh_CN') { 102 | const operator_pinyin = [].concat(...pinyin(value.name, { 103 | style: pinyin.STYLE_NORMAL, 104 | })); 105 | operator_data[unique_id][lang].alias.push(operator_pinyin.join(' ')); 106 | operator_data[unique_id][lang].alias.push(operator_pinyin.join('')); 107 | operator_data[unique_id][lang].appellation = value.appellation; 108 | } 109 | if (lang === 'ja_JP') { 110 | const operator_hiragana = toHiragana(value.name); 111 | operator_data[unique_id][lang].alias.push(operator_hiragana); 112 | } 113 | }); 114 | }; 115 | 116 | LANG.forEach(lang => { 117 | readTable(lang); 118 | }); 119 | 120 | Object.entries(operator_data).forEach(([key, value]) => { 121 | operator_data[key].code = value.en_US && value.en_US.name && value.en_US.name.toLowerCase() || null; 122 | operator_data[key].code_name = value.zh_CN && value.zh_CN.appellation || null; 123 | }); 124 | 125 | require('fs').writeFileSync(require('path').resolve(__dirname, '../../src/i18n', 'operators.json'), JSON.stringify(operator_data, null, 2)); 126 | 127 | 128 | -------------------------------------------------------------------------------- /scripts/mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "2001": "E-2-1", 3 | "2002": "E-3-1", 4 | "2003": "E-4-1", 5 | "2004": "E-5-1", 6 | "3211": "C-3-1", 7 | "3212": "C-4-1", 8 | "3213": "C-5-1", 9 | "3221": "C-3-2", 10 | "3222": "C-4-2", 11 | "3223": "C-5-2", 12 | "3231": "C-3-3", 13 | "3232": "C-4-3", 14 | "3233": "C-5-3", 15 | "3241": "C-3-4", 16 | "3242": "C-4-4", 17 | "3243": "C-5-4", 18 | "3251": "C-3-5", 19 | "3252": "C-4-5", 20 | "3253": "C-5-5", 21 | "3261": "C-3-6", 22 | "3262": "C-4-6", 23 | "3263": "C-5-6", 24 | "3271": "C-3-7", 25 | "3272": "C-4-7", 26 | "3273": "C-5-7", 27 | "3281": "C-3-8", 28 | "3282": "C-4-8", 29 | "3283": "C-5-8", 30 | "3301": "S-2-1", 31 | "3302": "S-3-1", 32 | "3303": "S-4-1", 33 | "4001": "G-4-1", 34 | "4006": "PC-3-1", 35 | "30011": "M-1-1", 36 | "30012": "M-2-1", 37 | "30013": "M-3-1", 38 | "30014": "M-4-1", 39 | "30021": "M-1-4", 40 | "30022": "M-2-4", 41 | "30023": "M-3-4", 42 | "30024": "M-4-4", 43 | "30031": "M-1-3", 44 | "30032": "M-2-3", 45 | "30033": "M-3-3", 46 | "30034": "M-4-3", 47 | "30041": "M-1-5", 48 | "30042": "M-2-5", 49 | "30043": "M-3-5", 50 | "30044": "M-4-5", 51 | "30051": "M-1-6", 52 | "30052": "M-2-6", 53 | "30053": "M-3-6", 54 | "30054": "M-4-6", 55 | "30061": "M-1-2", 56 | "30062": "M-2-2", 57 | "30063": "M-3-2", 58 | "30064": "M-4-2", 59 | "30073": "M-3-7", 60 | "30074": "M-4-7", 61 | "30083": "M-3-8", 62 | "30084": "M-4-8", 63 | "30093": "M-3-9", 64 | "30094": "M-4-9", 65 | "30103": "M-3-10", 66 | "30104": "M-4-10", 67 | "30115": "M-5-3", 68 | "30125": "M-5-2", 69 | "30135": "M-5-1", 70 | "31013": "M-3-11", 71 | "31014": "M-4-11", 72 | "31023": "M-3-12", 73 | "31024": "M-4-12", 74 | "31043": "M-3-14", 75 | "31044": "M-4-14", 76 | "31053": "M-3-15", 77 | "31054": "M-4-15", 78 | "32001": "O-4-1", 79 | "31033": "M-3-13", 80 | "31034": "M-4-13", 81 | "30145": "M-5-4", 82 | "31063": "M-3-16", 83 | "31064": "M-4-16", 84 | "30155": "M-5-5", 85 | "31073": "M-3-17", 86 | "31074": "M-4-17", 87 | "31083": "M-3-18", 88 | "31084": "M-4-18", 89 | "31093": "M-3-19", 90 | "31094": "M-4-19", 91 | "30165": "M-5-6", 92 | "mod_unlock_token": "W-5-1", 93 | "mod_update_token_1": "W-5-2", 94 | "mod_update_token_2": "W-5-3" 95 | } -------------------------------------------------------------------------------- /scripts/migration_check.json: -------------------------------------------------------------------------------- 1 | {"records":[{"operator":"雪雉","attribute":"技能1专精","current":1,"target":2,"hidden":false},{"operator":"雪雉","attribute":"精0等级","current":1,"target":2,"hidden":false}],"stock":{"M-3-2":2,"M-2-2":10,"M-3-9":2,"M-3-7":0},"focus_materials":[],"compound_materials":[{"id":"M-5-1","options":{}},{"id":"M-5-2","options":{}},{"id":"M-5-3","options":{}},{"id":"M-4-7","options":{}},{"id":"M-4-8","options":{}},{"id":"M-4-9","options":{}},{"id":"M-4-10","options":{}},{"id":"M-4-1","options":{}},{"id":"M-4-2","options":{}},{"id":"M-4-3","options":{}},{"id":"M-4-4","options":{}},{"id":"M-4-5","options":{}},{"id":"M-4-6","options":{}},{"id":"M-4-11","options":{}},{"id":"M-4-12","options":{}}],"version":"1.0.3","language":"en-US"} -------------------------------------------------------------------------------- /scripts/penguin-stat/ParseLevels.js: -------------------------------------------------------------------------------- 1 | const { RESOURCES } = require('../common/resources'); 2 | const stages = require('./penguin_levels.json'); 3 | 4 | const MapItem = require('../common/mapping.json'); 5 | 6 | const level_energy = { 7 | '0-1': 6, 8 | '0-2': 6, 9 | '0-3': 6, 10 | '0-4': 6, 11 | '0-5': 6, 12 | '0-6': 6, 13 | '0-7': 6, 14 | '0-8': 6, 15 | '0-9': 6, 16 | '0-10': 6, 17 | '0-11': 6, 18 | '1-1': 6, 19 | '1-2': 0, 20 | '1-3': 6, 21 | '1-4': 6, 22 | '1-5': 6, 23 | '1-6': 6, 24 | '1-7': 6, 25 | '1-8': 9, 26 | '1-9': 9, 27 | '1-10': 9, 28 | '1-11': 0, 29 | '1-12': 9, 30 | '2-1': 9, 31 | 'S2-1': 9, 32 | '2-2': 9, 33 | 'S2-2': 9, 34 | 'S2-3': 9, 35 | 'S2-4': 9, 36 | '2-3': 12, 37 | '2-4': 12, 38 | 'S2-5': 12, 39 | 'S2-6': 12, 40 | 'S2-7': 12, 41 | '2-5': 12, 42 | '2-6': 12, 43 | '2-7': 12, 44 | 'S2-8': 12, 45 | 'S2-9': 12, 46 | '2-8': 12, 47 | '2-9': 12, 48 | 'S2-10': 12, 49 | 'S2-11': 12, 50 | 'S2-12': 15, 51 | '2-10': 15, 52 | '3-1': 15, 53 | '3-2': 15, 54 | '3-3': 15, 55 | 'S3-1': 15, 56 | 'S3-2': 15, 57 | '3-4': 15, 58 | '3-5': 15, 59 | '3-6': 15, 60 | '3-7': 15, 61 | 'S3-3': 15, 62 | 'S3-4': 15, 63 | 'S3-5': 15, 64 | '3-8': 18, 65 | '4-1': 18, 66 | '4-2': 18, 67 | '4-3': 18, 68 | 'S4-1': 18, 69 | 'S4-2': 18, 70 | 'S4-3': 18, 71 | '4-4': 18, 72 | '4-5': 18, 73 | '4-6': 18, 74 | 'S4-4': 18, 75 | 'S4-5': 18, 76 | 'S4-6': 18, 77 | '4-7': 18, 78 | '4-8': 21, 79 | '4-9': 21, 80 | 'S4-7': 18, 81 | 'S4-8': 18, 82 | 'S4-9': 21, 83 | '4-10': 21, 84 | '5-1': 18, 85 | '5-2': 18, 86 | '5-3': 18, 87 | '5-4': 18, 88 | '5-5': 18, 89 | '5-6': 18, 90 | '5-7': 21, 91 | '5-8': 18, 92 | '5-9': 18, 93 | '5-10': 21, 94 | '5-11': 0, 95 | 'S5-1': 18, 96 | 'S5-2': 18, 97 | 'S5-3': 18, 98 | 'S5-4': 18, 99 | 'S5-5': 18, 100 | 'S5-6': 18, 101 | 'LS-1': 10, 102 | 'LS-2': 15, 103 | 'LS-3': 20, 104 | 'LS-4': 25, 105 | 'LS-5': 30, 106 | 'AP-1': 10, 107 | 'AP-2': 15, 108 | 'AP-3': 20, 109 | 'AP-4': 25, 110 | 'AP-5': 30, 111 | 'CA-1': 10, 112 | 'CA-2': 15, 113 | 'CA-3': 20, 114 | 'CA-4': 25, 115 | 'CA-5': 30, 116 | 'CE-1': 10, 117 | 'CE-2': 15, 118 | 'CE-3': 20, 119 | 'CE-4': 25, 120 | 'CE-5': 30, 121 | 'SK-1': 10, 122 | 'SK-2': 15, 123 | 'SK-3': 20, 124 | 'SK-4': 25, 125 | 'SK-5': 30, 126 | 'PR-A-1': 18, 127 | 'PR-B-1': 18, 128 | 'PR-C-1': 18, 129 | 'PR-D-1': 18, 130 | 'PR-A-2': 36, 131 | 'PR-B-2': 36, 132 | 'PR-C-2': 36, 133 | 'PR-D-2': 36, 134 | }; 135 | 136 | const levels = Object.entries(level_energy) 137 | .map(([level, energy]) => ({ 138 | level, 139 | energy, 140 | normal_drop: [], 141 | special_drop: [], 142 | extra_drop: [], 143 | })); 144 | 145 | const mapExtraDrop = (material) => { 146 | const resource = Object.entries(RESOURCES).find(([k, v]) => v.id === material); 147 | if (resource) { 148 | return ({ 149 | resource: resource[1].id, 150 | probability: '小概率', 151 | }); 152 | } 153 | }; 154 | 155 | const parseJson = (record) => { 156 | const { 157 | extraDrop, 158 | code, 159 | stageId, 160 | zoneId, 161 | } = record; 162 | 163 | const level = levels.find(l => l.level === code); 164 | if (!level) { 165 | console.log(code); 166 | if (code.startsWith('GT')) return; 167 | } 168 | console.log(extraDrop); 169 | level.extra_drop = extraDrop.map(item => mapExtraDrop(MapItem[item])).filter(Boolean); 170 | level.unique_id = stageId; 171 | level.zone_id = zoneId; 172 | console.log(level); 173 | }; 174 | 175 | stages.forEach(parseJson); 176 | 177 | const parseProbability = (probability) => { 178 | const matching = /^(.+)\[([\d~]+)\]$/.exec(probability); 179 | if (matching) { 180 | const [, prob, quantity] = matching; 181 | return { 182 | probability: prob, 183 | quantity, 184 | }; 185 | } 186 | return { probability }; 187 | }; 188 | const sumSource = (material) => { 189 | Object.entries(material.source).forEach(([level, probability]) => { 190 | if (probability.startsWith('罕见')){ 191 | levels.find(l => l.level === level).special_drop.push({ 192 | resource: material.id, 193 | ...parseProbability(probability), 194 | }); 195 | } else { 196 | levels.find(l => l.level === level).normal_drop.push({ 197 | resource: material.id, 198 | ...parseProbability(probability), 199 | }); 200 | } 201 | }); 202 | }; 203 | 204 | Object.entries(RESOURCES).forEach(([k, v]) => { 205 | sumSource(v); 206 | }); 207 | 208 | 209 | require('fs').writeFileSync( 210 | require('path').resolve(__dirname, '../../src/models/levels.json'), 211 | JSON.stringify( 212 | levels, 213 | null, 214 | 2 215 | ) 216 | ); 217 | -------------------------------------------------------------------------------- /scripts/postBuild.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | fs.copyFileSync( 5 | path.join(__dirname, '../build/index.html'), 6 | path.join(__dirname, '../build/404.html'), 7 | ); 8 | -------------------------------------------------------------------------------- /scripts/update.js: -------------------------------------------------------------------------------- 1 | require('./ParseItemTable'); 2 | require('./ParseSideProducts'); 3 | require('./ParseCharacters'); 4 | require('./ParseLevelList'); 5 | require('./i18n/ParseItemTable'); 6 | require('./i18n/ParseOperatorTable'); 7 | require('./i18n/ParseEquipmentsTable'); 8 | require('./i18n/ParseLevelList'); -------------------------------------------------------------------------------- /src/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { Router } from 'preact-router'; 3 | import { useState, useEffect } from 'preact/hooks'; 4 | 5 | import useConfig from './config/useConfig'; 6 | 7 | import Header from './components/header'; 8 | 9 | import ArkInfo from './routes/info'; 10 | import ArkTable from './routes/table'; 11 | import ArkOperator from './routes/operator'; 12 | import ArkMaterials from './routes/materials'; 13 | import ArkStock from './routes/stock'; 14 | import ArkFarming from './routes/farming'; 15 | import ArkBackup from './routes/backup'; 16 | 17 | import useData from './models/useData'; 18 | 19 | import LocaleRender from './i18n/render'; 20 | import { locale } from './i18n/locale'; 21 | const LANG = Object.keys(locale); 22 | 23 | import fetchStatMatrix from './services/penguinstats'; 24 | 25 | const App = (props) => { 26 | const [currentUrl, setCurrentUrl] = useState('/'); 27 | const { 28 | config, 29 | load, 30 | setLanguage, 31 | toggleShowAllResources, 32 | toggleShowFocusMaterials, 33 | toggleShowFilter, 34 | toggleTableRowHeader, 35 | toggleGroupByOperator, 36 | toggleShowExp, 37 | toggleShowExtendedData, 38 | toggleShowAnnouncementCodeOnce, 39 | setFilters, 40 | } = useConfig(); 41 | const query = (window.location.search || '').substr(1); 42 | const qs = query.split('='); 43 | if (qs && qs.length === 2 && qs[0] === 'locale' && LANG.includes(qs[1])) { 44 | if (!global.locale) { 45 | global.locale = qs[1]; 46 | setLanguage(global.locale); 47 | } else if (global.locale !== qs[1]) { 48 | global.locale = qs[1]; 49 | setLanguage(qs[1]); 50 | } 51 | } 52 | 53 | const ir = LocaleRender(config.locale); 54 | 55 | const [drops, setDrops] = useState([]); 56 | 57 | const data = useData(); 58 | 59 | useEffect(() => { 60 | load(); 61 | fetchStatMatrix().then(matrix => { 62 | setDrops(matrix); 63 | }); 64 | }, []); 65 | 66 | return ( 67 |
68 |
79 | { 80 | global.ga('set', 'page', e.url); 81 | global.ga('send', 'pageview'); 82 | setCurrentUrl(e.url); 83 | }} 84 | > 85 | 92 | 100 | 101 | 102 | 103 | 104 | 115 | 116 |
117 | ); 118 | }; 119 | 120 | export default App; -------------------------------------------------------------------------------- /src/assets/cards/T1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T1.png -------------------------------------------------------------------------------- /src/assets/cards/T2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T2.png -------------------------------------------------------------------------------- /src/assets/cards/T3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T3.png -------------------------------------------------------------------------------- /src/assets/cards/T4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T4.png -------------------------------------------------------------------------------- /src/assets/cards/T5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T5.png -------------------------------------------------------------------------------- /src/assets/cards/T6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/cards/T6.png -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/clear_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/clear_selection.png -------------------------------------------------------------------------------- /src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/close.png -------------------------------------------------------------------------------- /src/assets/icons/close_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/close_white.png -------------------------------------------------------------------------------- /src/assets/icons/external.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/external.png -------------------------------------------------------------------------------- /src/assets/icons/hidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/hidden.png -------------------------------------------------------------------------------- /src/assets/icons/hidden_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/hidden_all.png -------------------------------------------------------------------------------- /src/assets/icons/move_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/move_down.png -------------------------------------------------------------------------------- /src/assets/icons/move_to_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/move_to_first.png -------------------------------------------------------------------------------- /src/assets/icons/move_to_last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/move_to_last.png -------------------------------------------------------------------------------- /src/assets/icons/move_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/move_up.png -------------------------------------------------------------------------------- /src/assets/icons/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/paypal.png -------------------------------------------------------------------------------- /src/assets/icons/penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/penguin.png -------------------------------------------------------------------------------- /src/assets/icons/penguin_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/penguin_black.png -------------------------------------------------------------------------------- /src/assets/icons/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/select.png -------------------------------------------------------------------------------- /src/assets/icons/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/tick.png -------------------------------------------------------------------------------- /src/assets/icons/tick_invert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/tick_invert.png -------------------------------------------------------------------------------- /src/assets/icons/tick_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/icons/tick_white.png -------------------------------------------------------------------------------- /src/assets/materials/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/404.png -------------------------------------------------------------------------------- /src/assets/materials/AP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/AP.png -------------------------------------------------------------------------------- /src/assets/materials/B-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/B-2-1.png -------------------------------------------------------------------------------- /src/assets/materials/B-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/B-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/B-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/B-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/BC-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/BC-2-1.png -------------------------------------------------------------------------------- /src/assets/materials/BC-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/BC-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/BC-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/BC-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-2.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-3.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-4.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-5.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-6.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-7.png -------------------------------------------------------------------------------- /src/assets/materials/C-3-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-3-8.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-2.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-3.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-4.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-5.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-6.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-7.png -------------------------------------------------------------------------------- /src/assets/materials/C-4-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-4-8.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-1.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-2.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-3.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-4.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-5.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-6.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-7.png -------------------------------------------------------------------------------- /src/assets/materials/C-5-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/C-5-8.png -------------------------------------------------------------------------------- /src/assets/materials/D-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/D-5-1.png -------------------------------------------------------------------------------- /src/assets/materials/D-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/D-6-1.png -------------------------------------------------------------------------------- /src/assets/materials/E-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/E-2-1.png -------------------------------------------------------------------------------- /src/assets/materials/E-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/E-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/E-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/E-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/E-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/E-5-1.png -------------------------------------------------------------------------------- /src/assets/materials/EO-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/EO-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/FC-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/FC-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/G-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/G-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-2.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-3.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-4.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-5.png -------------------------------------------------------------------------------- /src/assets/materials/M-1-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-1-6.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-2.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-3.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-4.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-5.png -------------------------------------------------------------------------------- /src/assets/materials/M-2-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-2-6.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-10.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-11.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-12.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-13.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-14.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-15.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-16.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-17.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-18.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-19.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-2.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-3.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-4.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-5.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-6.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-7.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-8.png -------------------------------------------------------------------------------- /src/assets/materials/M-3-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-3-9.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-10.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-11.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-12.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-13.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-14.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-15.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-16.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-17.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-18.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-19.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-2.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-3.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-4.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-5.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-6.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-7.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-8.png -------------------------------------------------------------------------------- /src/assets/materials/M-4-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-4-9.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-1.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-2.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-3.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-4.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-5.png -------------------------------------------------------------------------------- /src/assets/materials/M-5-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/M-5-6.png -------------------------------------------------------------------------------- /src/assets/materials/O-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/O-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/PC-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/PC-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/S-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/S-2-1.png -------------------------------------------------------------------------------- /src/assets/materials/S-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/S-3-1.png -------------------------------------------------------------------------------- /src/assets/materials/S-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/S-4-1.png -------------------------------------------------------------------------------- /src/assets/materials/W-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/W-5-1.png -------------------------------------------------------------------------------- /src/assets/materials/W-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/W-5-2.png -------------------------------------------------------------------------------- /src/assets/materials/W-5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/materials/W-5-3.png -------------------------------------------------------------------------------- /src/assets/tip_ali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/tip_ali.png -------------------------------------------------------------------------------- /src/assets/tip_wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/assets/tip_wechat.png -------------------------------------------------------------------------------- /src/components/button/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | const ArkCell = (props) => ( 6 | 30 | ); 31 | 32 | export default ArkCell; 33 | -------------------------------------------------------------------------------- /src/components/button/style.css: -------------------------------------------------------------------------------- 1 | .button { 2 | -webkit-appearance: none; 3 | border: none; 4 | background: none; 5 | outline: none; 6 | 7 | width: 100%; 8 | height: 100%; 9 | 10 | color: #EEE; 11 | 12 | font-size: 16px; 13 | font-family: inherit; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | } -------------------------------------------------------------------------------- /src/components/cell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | import { Link } from 'preact-router/match'; 5 | 6 | const ArkCell = (props) => ( 7 |
props.onClick && props.onClick(e)} 31 | > 32 | { props.children || ( 33 | props.href ? ( 34 | 35 |

{props.content}

36 | 37 | ) : ( 38 |

{props.content}

39 | ) 40 | )} 41 |
42 | ); 43 | 44 | export default ArkCell; 45 | -------------------------------------------------------------------------------- /src/components/cell/style.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | text-align: center; 6 | 7 | height: 32px; 8 | min-width: 100px; 9 | width: 100px; 10 | border-right: 0.8px #666 solid; 11 | border-bottom: 0.8px #666 solid; 12 | color: #EEE; 13 | transition: background-color 0.1s ease; 14 | } 15 | .cell.header { 16 | height: 40px; 17 | font-weight: bold; 18 | background: #888; 19 | text-shadow: 0px 1px 5px rgba(48, 48, 48, 0.8); 20 | border-color: transparent; 21 | } 22 | .cell.header:not(.icons_header).T0 { 23 | background-color: transparent; 24 | } 25 | .cell.header:not(.icons_header).T1 { 26 | background-color: #9F9FA2; 27 | } 28 | .cell.header:not(.icons_header).T2 { 29 | background-color: #DADC39; 30 | } 31 | .cell.header:not(.icons_header).T3 { 32 | background-color: #16AFF0; 33 | } 34 | .cell.header:not(.icons_header).T4 { 35 | background-color: #D3C3D4; 36 | } 37 | .cell.header:not(.icons_header).T5 { 38 | background-color: #FCC90E; 39 | } 40 | .cell.header:not(.icons_header).T6 { 41 | background-color: #FFF2BE; 42 | } 43 | .cell.fullheight { 44 | height: 100%; 45 | } 46 | .cell.stretch { 47 | align-self: stretch; 48 | height: auto; 49 | } 50 | .cell.fullwidth { 51 | width: 100%; 52 | min-width: inherit; 53 | border-right: none; 54 | } 55 | .cell.halfwidth { 56 | width: 48px; 57 | min-width: 48px; 58 | word-break: break-all; 59 | } 60 | .cell.dim { 61 | color: #666; 62 | } 63 | .cell.long_text { 64 | font-size: 13px; 65 | white-space: normal; 66 | } 67 | .cell.icons_header { 68 | height: 76px; 69 | flex-flow: column; 70 | justify-content: center; 71 | border: none; 72 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 93%, #FEFEFE 100%); 73 | position: relative; 74 | } 75 | .cell.icons_header.T1 { 76 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 93%, #9F9FA2 98%, #9F9FA2 100%); 77 | } 78 | .cell.icons_header.T2 { 79 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 93%, #DADC39 98%, #DADC39 100%); 80 | } 81 | .cell.icons_header.T3 { 82 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 93%, #16AFF0 98%, #16AFF0 100%); 83 | } 84 | .cell.icons_header.T4 { 85 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 92%, #D3C3D4 98%, #D3C3D4 100%); 86 | } 87 | .cell.icons_header.T5 { 88 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 91%, #FCC90E 98%, #FCC90E 100%); 89 | } 90 | .cell.icons_header.T6 { 91 | background: linear-gradient(180deg, #DDD 0%, #FEFEFE 88%, #FFF2BE 98%, #FFF2BE 100%); 92 | } 93 | .cell.icons_header > p { 94 | font-size: 18px; 95 | text-shadow: 1px 1px 1px rgba(24, 24, 24, 0.2); 96 | color: black; 97 | } 98 | .cell.icons_header.long_text > p { 99 | font-size: 15px; 100 | } 101 | .cell.icons_header span { 102 | font-size: 12px; 103 | text-shadow: 0px 0px 2px rgba(240, 240, 240, 0.2); 104 | color: #EEEEEE; 105 | background-color: #111; 106 | padding: 2px 5px; 107 | border-radius: 1px; 108 | z-index: 20; 109 | } 110 | .cell.is_focus_material:before { 111 | content: ''; 112 | width: 100%; 113 | height: 100%; 114 | position: absolute; 115 | top: -28; 116 | background: linear-gradient(180deg, #CCC 0%, rgba(170, 170, 170, 0.2) 93%); 117 | } 118 | .cell span { 119 | text-align: center 120 | } 121 | 122 | :global(.row:not(.disable_hover):hover) .cell { 123 | background-color: #4C4C4C; 124 | } 125 | :global(.row:not(.disable_hover)) .cell.selected { 126 | background-color: #104050; 127 | } 128 | 129 | @media screen and (max-width: 600px) { 130 | .cell.icons_header { 131 | height: 56px; 132 | } 133 | 134 | .cell.icons_header > p { 135 | font-size: 14px; 136 | } 137 | 138 | .cell.icons_header span { 139 | font-size: 8px; 140 | margin-top: -12px; 141 | } 142 | 143 | .cell:not(.force_no_shrink):not(.fullwidth) { 144 | width: 60px; 145 | min-width: 60px; 146 | } 147 | 148 | .cell.long_text { 149 | font-size: 10px; 150 | white-space: normal; 151 | } 152 | 153 | .cell.mobile_long_text { 154 | font-size: 13px; 155 | white-space: normal; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/components/dropdown/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | const ArkDropdownCell = (props) => ( 6 |
5, 16 | } 17 | ) 18 | } 19 | > 20 | 35 |
36 | ); 37 | 38 | export default ArkDropdownCell; 39 | -------------------------------------------------------------------------------- /src/components/dropdown/style.css: -------------------------------------------------------------------------------- 1 | .cell select { 2 | -webkit-appearance: none; 3 | border: none; 4 | background: none; 5 | text-align-last: center; 6 | } 7 | 8 | .cell, 9 | .cell select { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | text-align: center; 14 | font-family: 'Microsoft YaHei', 'Helvetica Neue', arial, sans-serif; 15 | font-weight: 400; 16 | font-size: 16px; 17 | 18 | height: 32px; 19 | min-width: 100px; 20 | width: 100px; 21 | background-color: rgba(238, 238, 238, 0.07); 22 | border-right: 0.5px #666 solid; 23 | border-bottom: 0.5px #666 solid; 24 | color: #EEE; 25 | position: relative; 26 | } 27 | .cell option { 28 | background-color: #444; 29 | } 30 | 31 | .long_text select { 32 | font-size: 12px; 33 | } 34 | 35 | .cell.select:after { 36 | content: '▼'; 37 | font-size: 8px; 38 | position: absolute; 39 | right: 6px; 40 | } 41 | 42 | .cell.selected { 43 | background-color: #104050; 44 | } -------------------------------------------------------------------------------- /src/components/fuseInputCell/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | import { search } from '../index.js'; 2 | 3 | describe('ArkFuseInputCell', () => { 4 | describe('fuse search should produce correct result', () => { 5 | it('should map hong to 红', () => { 6 | expect(search('hong', 'zh_CN').unique_id).toBe('char_144_red'); 7 | }); 8 | 9 | it('should map hongd to 红豆', () => { 10 | expect(search('hongd', 'zh_CN').unique_id).toBe('char_290_vigna'); 11 | }); 12 | 13 | it('should map hongdou to 红豆', () => { 14 | expect(search('hongdou', 'zh_CN').unique_id).toBe('char_290_vigna'); 15 | }); 16 | 17 | it('should map kong to 空', () => { 18 | expect(search('kong', 'zh_CN').unique_id).toBe('char_101_sora'); 19 | }); 20 | 21 | it('should map kongbao to 空爆', () => { 22 | expect(search('kongbao', 'zh_CN').unique_id).toBe('char_282_catap'); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/fuseInputCell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | import Fuse from 'fuse.js'; 5 | 6 | import { OPERATORS } from '../../models/Operators'; 7 | import operator_i18n from '../../i18n/operators.json'; 8 | const OPERATORS_LANG = {}; 9 | 10 | [ 11 | 'zh_CN', 12 | 'en_US', 13 | 'ja_JP', 14 | 'ko_KR', 15 | ].forEach(lang => { 16 | OPERATORS_LANG[lang] = []; 17 | OPERATORS.forEach((operator) => { 18 | if (operator_i18n[operator.unique_id]) { 19 | const operator_lang = operator_i18n[operator.unique_id][lang]; 20 | if (operator_lang && operator_lang.enabled) { 21 | OPERATORS_LANG[lang].push({ 22 | unique_id: operator.unique_id, 23 | code: operator_i18n[operator.unique_id].code, 24 | ...operator_lang, 25 | }); 26 | } 27 | } 28 | }); 29 | }); 30 | 31 | [ 32 | 'en_US', 33 | 'ja_JP', 34 | 'ko_KR', 35 | ].forEach(lang => { 36 | const lang_extended = `${lang}_extended`; 37 | OPERATORS_LANG[lang_extended] = []; 38 | OPERATORS.forEach((operator) => { 39 | if (operator_i18n[operator.unique_id]) { 40 | const operator_lang = operator_i18n[operator.unique_id][lang]; 41 | if (operator_lang && operator_lang.enabled) { 42 | OPERATORS_LANG[lang_extended].push({ 43 | unique_id: operator.unique_id, 44 | code: operator_i18n[operator.unique_id].code, 45 | ...operator_lang, 46 | }); 47 | } else { 48 | const operator_lang_extended = operator_i18n[operator.unique_id].zh_CN; 49 | OPERATORS_LANG[lang_extended].push({ 50 | unique_id: operator.unique_id, 51 | code_name: operator_i18n[operator.unique_id].code_name, 52 | ...operator_lang_extended, 53 | }); 54 | } 55 | } 56 | }); 57 | }); 58 | 59 | const options = { 60 | shouldSort: true, 61 | threshold: 0.6, 62 | location: 0, 63 | distance: 100, 64 | maxPatternLength: 32, 65 | minMatchCharLength: 1, 66 | keys: [{ 67 | name: 'name', 68 | weight: 0.7, 69 | }, { 70 | name: 'code', 71 | weight: 0.3, 72 | }], 73 | }; 74 | 75 | const extended_options = { 76 | ...options, 77 | keys: [{ 78 | name: 'name', 79 | weight: 0.7, 80 | }, { 81 | name: 'code_name', 82 | weight: 0.3, 83 | }], 84 | }; 85 | 86 | const fuse_i18n = { 87 | zh_CN: new Fuse(OPERATORS_LANG.zh_CN, { 88 | ...options, 89 | keys: [{ 90 | name: 'name', 91 | weight: 0.7, 92 | }, { 93 | name: 'alias', 94 | weight: 0.3, 95 | }], 96 | }), 97 | en_US: new Fuse(OPERATORS_LANG.en_US, options), 98 | ja_JP: new Fuse(OPERATORS_LANG.ja_JP, options), 99 | ko_KR: new Fuse(OPERATORS_LANG.ko_KR, options), 100 | en_US_extended: new Fuse(OPERATORS_LANG.en_US_extended, extended_options), 101 | ja_JP_extended: new Fuse(OPERATORS_LANG.ja_JP_extended, extended_options), 102 | ko_KR_extended: new Fuse(OPERATORS_LANG.ko_KR_extended, extended_options), 103 | }; 104 | 105 | export const search = (query, lang) => { 106 | const results = (fuse_i18n[lang] || fuse_i18n.zh_CN).search(query); 107 | if (results.length) { 108 | const exact_match = results.find(({ name, code, alias }) => name === query || code === query || alias.includes(query)); 109 | if (exact_match) { 110 | return { 111 | unique_id: exact_match.unique_id, 112 | name: exact_match.name, 113 | }; 114 | } 115 | 116 | const [{ unique_id, name }] = results; 117 | return { unique_id, name }; 118 | } 119 | return null; 120 | }; 121 | 122 | export const searchAll = (query, lang) => { 123 | const results = (fuse_i18n[lang] || fuse_i18n.zh_CN).search(query); 124 | return results; 125 | }; 126 | 127 | const ArkFuseInputCell = (props) => ( 128 |
143 | { 148 | const query = e.target.value; 149 | const match = search(query, props.locale || 'zh_CN'); 150 | if (match) { 151 | props.onChange && props.onChange(match); 152 | } 153 | props.onChangeRaw && props.onChangeRaw(query); 154 | }} 155 | onClick={e => { 156 | props.onClick && props.onClick(e); 157 | }} 158 | /> 159 |
160 | ); 161 | 162 | export default ArkFuseInputCell; 163 | -------------------------------------------------------------------------------- /src/components/fuseInputCell/style.css: -------------------------------------------------------------------------------- 1 | .cell input { 2 | border: none; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | .cell, 8 | .cell input { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | text-align: center; 13 | font-family: 'Microsoft YaHei', 'Helvetica Neue', arial, sans-serif; 14 | font-weight: 400; 15 | font-size: 16px; 16 | 17 | height: 32px; 18 | min-width: 100px; 19 | width: 100px; 20 | background-color: rgba(238, 238, 238, 0.07); 21 | border-right: 0.5px #666 solid; 22 | border-bottom: 0.5px #666 solid; 23 | color: #EEE; 24 | position: relative; 25 | } 26 | 27 | .cell > input:empty:after { 28 | content: ''; 29 | position: absolute; 30 | height: 1px; 31 | width: auto; 32 | left: 8px; 33 | right: 8px; 34 | bottom: 4px; 35 | background-color: #EEE; 36 | } 37 | 38 | .cell.selected { 39 | background-color: #104050; 40 | } -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { Link } from 'preact-router/match'; 3 | import style from './style'; 4 | import { STORAGE_VERSION } from '../../config/useConfig'; 5 | 6 | import Toggle from './toggle'; 7 | 8 | const Header = ({ 9 | ir, 10 | currentUrl, 11 | config, 12 | toggleShowAllResources, 13 | toggleShowFocusMaterials, 14 | toggleTableRowHeader, 15 | toggleShowFilter, 16 | toggleShowExp, 17 | }) => ( 18 |
19 | 27 |
28 | 43 |
44 | { 45 | currentUrl === '/table' && ( 46 |
47 | 0} 49 | toggle={value => { 50 | toggleShowFilter(!config.showFilter || value); 51 | }} 52 | content={ir('table-options-filters', 'Filters')} 53 | /> 54 | 59 | 64 | 69 |
70 | ) 71 | } 72 |
73 | ); 74 | 75 | export default Header; 76 | -------------------------------------------------------------------------------- /src/components/header/style.css: -------------------------------------------------------------------------------- 1 | .deco { 2 | font-family: 'san-serif'; 3 | font-weight: 100; 4 | } 5 | .version { 6 | margin-left: 8px; 7 | font-size: 12px; 8 | vertical-align: baseline; 9 | } 10 | 11 | .header { 12 | position: fixed; 13 | left: 0; 14 | top: 0; 15 | width: 100%; 16 | height: 56px; 17 | padding: 0; 18 | background: linear-gradient(295deg, #0CC3E7 0%, #0D2A36 90%); 19 | box-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 20 | z-index: 99; 21 | 22 | display: flex; 23 | flex-flow: row; 24 | justify-content: space-between; 25 | 26 | } 27 | .header .title { 28 | user-select: none; 29 | 30 | vertical-align: baseline; 31 | display: flex; 32 | flex-flow: row; 33 | flex-wrap: nowrap; 34 | white-space: nowrap; 35 | justify-content: flex-start; 36 | } 37 | .header .nav_group { 38 | width: 100%; 39 | } 40 | .header h1 a { 41 | color: inherit; 42 | text-decoration: none; 43 | } 44 | 45 | .header h1 { 46 | vertical-align: baseline; 47 | 48 | margin: 0; 49 | padding: 0 15px; 50 | font-size: 24px; 51 | line-height: 56px; 52 | font-weight: 400; 53 | color: #FFF; 54 | text-shadow: 0px 1px 6px #DDDDDD66; 55 | } 56 | 57 | .header nav { 58 | display: flex; 59 | justify-content: flex-start; 60 | align-items: center; 61 | flex-wrap: nowrap; 62 | font-size: 100%; 63 | } 64 | 65 | .header nav a { 66 | display: inline-block; 67 | overflow: hidden; 68 | display: flex; 69 | justify-content: center; 70 | align-items: center; 71 | height: 56px; 72 | padding: 0 15px; 73 | width: auto; 74 | min-width: 56px; 75 | flex-shrink: 0; 76 | text-align: center; 77 | background: rgba(255,255,255,0); 78 | text-decoration: none; 79 | text-shadow: 0px 0px 6px #333333; 80 | color: #FFF; 81 | will-change: background-color; 82 | transition: background-color 0.4s ease; 83 | } 84 | 85 | .header nav a:hover, 86 | .header nav a:active { 87 | background: rgba(0,0,0,0.2); 88 | } 89 | 90 | .header nav a.active { 91 | background: rgba(0,0,0,0.4); 92 | } 93 | 94 | .toggles { 95 | flex-grow: 0; 96 | padding: 0 8px; 97 | display: flex; 98 | flex-flow: row-reverse; 99 | justify-content: flex-start; 100 | align-items: center; 101 | width: auto; 102 | } 103 | 104 | .toggle { 105 | padding: 8px 16px; 106 | margin-right: 10px; 107 | color: #BBB; 108 | border-radius: 1px; 109 | 110 | font-size: 11px; 111 | font-weight: bold; 112 | text-shadow: 0px 1px 3px rgba(200, 200, 200, 0.3); 113 | display: flex; 114 | flex-flow: row-reverse; 115 | justify-content: flex-start; 116 | white-space: nowrap; 117 | align-items: center; 118 | user-select: none; 119 | background: #666; 120 | box-shadow: 0px 3px 3px 0 #33333333; 121 | transition: background 0.6s ease, color 0.6s ease; 122 | } 123 | .toggle:first-of-type { 124 | margin-right: 0; 125 | } 126 | .toggle.enable { 127 | background: #111; 128 | box-shadow: 0px 3px 4px 0 #33333366; 129 | color: #EEE; 130 | } 131 | 132 | @media screen and (max-width: 600px) { 133 | .header .title { 134 | display: none; 135 | } 136 | .header nav { 137 | font-size: 12px; 138 | } 139 | .header nav a { 140 | padding: 0 5px; 141 | border-right: rgba(204, 204, 204, 0.3) 1px solid; 142 | white-space: nowrap; 143 | } 144 | 145 | .header .nav_group { 146 | width: 100%; 147 | overflow-x: scroll; 148 | overflow-y: visible; 149 | } 150 | .header .toggles { 151 | background-color: #333333; 152 | min-width: 120px; 153 | } 154 | 155 | .toggle { 156 | padding: 3px; 157 | font-size: 10px; 158 | margin-right: 4px; 159 | white-space: normal; 160 | text-align: center; 161 | } 162 | .toggle:first-of-type { 163 | margin-right: 0; 164 | } 165 | } 166 | 167 | @media screen and (max-width: 1200px) { 168 | .header h1 { 169 | font-size: 16px; 170 | } 171 | .header nav { 172 | font-size: 12px; 173 | } 174 | .header nav a { 175 | padding: 0 5px; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/components/header/toggle.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | const Toggle = (props) => ( 6 |
{ 17 | props.toggle(!props.value); 18 | } 19 | } 20 | > 21 | { 22 | props.content 23 | } 24 |
25 | ); 26 | 27 | export default Toggle; 28 | -------------------------------------------------------------------------------- /src/components/inputCell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | const ArkInputCell = (props) => { 6 | const handleNext = (e) => { 7 | const nextTabIndex = Math.max(props.tabIndex + (e.shiftKey ? -1 : 1), 0); 8 | setTimeout(() => { 9 | const next = document.getElementById(`input_${nextTabIndex}`); 10 | if (next && next.focus && next.select) { 11 | next.focus(); 12 | next.select(); 13 | } 14 | }); 15 | }; 16 | 17 | return ( 18 |
30 | { 37 | const value = Math.max(Number(e.target.value), 0); 38 | props.onChange && props.onChange(value); 39 | } 40 | } 41 | onKeyDown={ 42 | e => { 43 | if (e.key === 'Tab') { 44 | handleNext(e); 45 | } 46 | } 47 | } 48 | onClick={e => { 49 | e.target.select(); 50 | }} 51 | /> 52 |
53 | ); 54 | }; 55 | 56 | export default ArkInputCell; 57 | -------------------------------------------------------------------------------- /src/components/inputCell/style.css: -------------------------------------------------------------------------------- 1 | .cell input { 2 | border: none; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | .cell, 8 | .cell input { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | text-align: center; 13 | font-family: 'Microsoft YaHei', 'Helvetica Neue', arial, sans-serif; 14 | font-weight: 400; 15 | font-size: 16px; 16 | 17 | height: 32px; 18 | min-width: 100px; 19 | width: 100px; 20 | background-color: rgba(238, 238, 238, 0.07); 21 | border-right: 0.5px #666 solid; 22 | border-bottom: 0.5px #666 solid; 23 | color: #EEE; 24 | position: relative; 25 | } 26 | 27 | .cell > input:empty:after { 28 | content: ''; 29 | position: absolute; 30 | height: 1px; 31 | width: auto; 32 | left: 8px; 33 | right: 8px; 34 | bottom: 4px; 35 | background-color: #EEE; 36 | } 37 | 38 | @media screen and (max-width: 600px) { 39 | .cell, .cell > input { 40 | width: 60px; 41 | min-width: 60px; 42 | } 43 | } 44 | 45 | .cell.selected { 46 | background-color: #104050; 47 | } -------------------------------------------------------------------------------- /src/components/item/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | import { Link } from 'preact-router/match'; 5 | 6 | import { RESOURCES, parseQuantity } from '../../models/Resources'; 7 | const resources_available = Object.keys(RESOURCES); 8 | 9 | const ArkItem = (props) => { 10 | const material_id = resources_available.includes(props.id) ? props.id : '404'; 11 | const card_tier = /^T[123456]$/.test(props.tier) ? props.tier : 'T1'; 12 | const shortage = (props.requirement) ? Math.max(props.requirement - (props.quantity || 0), 0) : null; 13 | const exceeded = props.show_exceeded ? Math.max((props.quantity || 0) - (props.requirement || 0), 0) : null; 14 | return ( 15 |
props.onClick && props.onClick(e)} 29 | > 30 |
31 |
37 | material_id 38 | card_tier 39 |
40 | { 41 | !props.hide_quantity && props.quantity && props.quantity !== 0 && ( 42 |
43 | {parseQuantity(props.quantity)} 44 |
45 | ) || null 46 | } 47 | { 48 | shortage && shortage !== 0 && ( 49 |
50 | {parseQuantity(shortage)} 51 |
52 | ) || null 53 | } 54 | { 55 | exceeded && exceeded !== 0 && ( 56 |
57 | {parseQuantity(exceeded)} 58 |
59 | ) || null 60 | } 61 |
62 | {(props.force_link || !props.disable_link) && ( 63 |
71 | { 72 | 73 | external 74 | 75 | } 76 |
77 | )} 78 |
79 |
80 |
81 | ); 82 | }; 83 | 84 | export default ArkItem; 85 | -------------------------------------------------------------------------------- /src/components/item/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 56px; 3 | height: 40px; 4 | user-select: none; 5 | } 6 | 7 | .composer { 8 | width: 100%; 9 | height: 48%; 10 | display: flex; 11 | justify-content: space-around; 12 | align-items: center; 13 | transform: scale(0.3); 14 | position: relative; 15 | } 16 | 17 | .item { 18 | z-index: 9901; 19 | } 20 | .card { 21 | z-index: 9900; 22 | } 23 | .numbers { 24 | box-shadow: 1px 2px 2px rgba(36, 36, 36, 0.6); 25 | z-index: 9902; 26 | position: absolute; 27 | font-size: 28px; 28 | right: -50px; 29 | bottom: -80px; 30 | display: flex; 31 | flex-flow: column-reverse; 32 | } 33 | .link { 34 | z-index: 9903; 35 | position: absolute; 36 | font-size: 28px; 37 | right: -50px; 38 | top: -80px; 39 | display: flex; 40 | flex-flow: column-reverse; 41 | transition: opacity 0.4s ease; 42 | opacity: 0; 43 | background-color: rgba(0, 0, 0, 0.7); 44 | padding: 4px 4px 0; 45 | } 46 | .wrapper:hover .link, .link.force_link { 47 | opacity: 1; 48 | } 49 | .quantity { 50 | background: rgba(0, 0, 0, 0.90); 51 | color: #DDD; 52 | padding: 5px 15px; 53 | z-index: 9902; 54 | } 55 | .shortage { 56 | background: rgba(180, 0, 0, 0.90); 57 | color: #DDD; 58 | padding: 5px 15px; 59 | z-index: 9902; 60 | } 61 | .exceeded { 62 | background: rgba(0, 180, 180, 0.90); 63 | color: #DDD; 64 | padding: 5px 15px; 65 | z-index: 9902; 66 | } 67 | .quantity, .requirement { 68 | text-align: center; 69 | white-space: nowrap; 70 | } 71 | 72 | .item, .card { 73 | position: absolute; 74 | } 75 | 76 | @media screen and (max-width: 600px) { 77 | .wrapper .link { 78 | opacity: 0.9; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/levelInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | import { LEVELS } from '../../models/Levels'; 6 | 7 | const parseProbability = (probability) => { 8 | const matching = /^(.+)\[([\d~]+)\]$/.exec(probability); 9 | if (matching) { 10 | const [, prob, quantity] = matching; 11 | return { 12 | probability: prob, 13 | quantity, 14 | }; 15 | } 16 | return { probability }; 17 | }; 18 | 19 | const ArkLevelInfo = ({ 20 | level, 21 | drop_probability, 22 | drops = [], 23 | }) => { 24 | const { energy, unique_id } = LEVELS.find(l => l.level === level) || { energy: '---' }; 25 | 26 | const drop = drops.find(({ stageId }) => stageId === String(unique_id)); 27 | 28 | const { probability, quantity } = parseProbability(drop_probability); 29 | 30 | 31 | return ( 32 |
33 | 34 | {level} 35 | 36 | 47 | { 48 | drop 49 | ? (`${~~(drop.quantity / drop.times * 1000) / 10}%`) 50 | : (quantity || probability) 51 | } 52 | 53 | 54 | AP 55 | {energy} 56 | { 57 | drop && ( 58 | 59 | {~~(energy / (drop.quantity / drop.times) * 10) / 10} 60 | 61 | ) 62 | } 63 | 64 |
65 | ); 66 | }; 67 | 68 | export default ArkLevelInfo; 69 | -------------------------------------------------------------------------------- /src/components/levelInfo/style.css: -------------------------------------------------------------------------------- 1 | .black, .dark_grey, .grey, .white, .red { 2 | display: inline-block; 3 | padding: 2px 8px; 4 | border-radius: 1px; 5 | } 6 | .black { 7 | color: #DDDDDD; 8 | background-color: #111; 9 | } 10 | .dark_grey { 11 | color: #DDDDDD; 12 | background-color: #333; 13 | } 14 | .grey { 15 | color: #DDDDDD; 16 | background-color: #666; 17 | } 18 | .white { 19 | color: #333; 20 | background-color: #CCC; 21 | } 22 | .red { 23 | background-color: #990001; 24 | color: #DDDDDD; 25 | } 26 | 27 | a.level_link { 28 | color: #333; 29 | } 30 | .source_level { 31 | display: flex; 32 | flex-flow: row; 33 | justify-content: flex-end; 34 | align-items: center; 35 | margin-top: 3px; 36 | } 37 | .source_level span { 38 | margin-left: 8px; 39 | border-radius: 1px; 40 | } 41 | 42 | .level { 43 | height: 18px; 44 | min-width: 30px; 45 | text-align: right; 46 | } 47 | .energy { 48 | overflow: hidden; 49 | position: relative; 50 | min-width: 68px; 51 | text-align: right; 52 | } 53 | .energy span { 54 | margin-left: 28px; 55 | } 56 | .energy img { 57 | position: absolute; 58 | left: 4px; 59 | bottom: -4px; 60 | height: 28px; 61 | vertical-align: baseline; 62 | } 63 | 64 | .energy_expectaion { 65 | position: absolute; 66 | width: 100%; 67 | height: 100%; 68 | right: 0; 69 | top: 0; 70 | z-index: 999; 71 | opacity: 0; 72 | text-align: center; 73 | transition: opacity 0.4s ease; 74 | } 75 | 76 | :global(.drop_source):hover .energy_expectaion { 77 | opacity: 1; 78 | } 79 | -------------------------------------------------------------------------------- /src/components/materialsGroup/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | 4 | import ArkItem from '../item'; 5 | import ArkInputCell from '../inputCell'; 6 | 7 | const ArkMaterialGroup = ({ 8 | ir, 9 | stock = {}, 10 | summary = {}, 11 | groups, 12 | resources, 13 | adjustStockItem, 14 | setStockItem, 15 | item_scale, 16 | filter, 17 | filter_type, 18 | }) => { 19 | if (Object.keys(groups).length === 0) { 20 | groups.default = { 21 | render: 'material_group-stock_order', 22 | list: Array(resources.length).fill().map((_, index) => index), 23 | }; 24 | } 25 | return ( 26 |
27 | { 28 | resources && Object.entries(groups).map(([group_key, { render, list }]) => ( 29 |
30 | {ir(render)} 31 |
32 | { 33 | list.map((index) => ( 34 | Boolean(filter ? filter(resources[index]) : true) && ( 35 |
36 |
{ 40 | e.preventDefault(); 41 | e.stopPropagation(); 42 | adjustStockItem(resources[index].id, e.shiftKey ? 10 : 1); 43 | }} 44 | onContextMenu={e => { 45 | e.preventDefault(); 46 | e.stopPropagation(); 47 | adjustStockItem(resources[index].id, e.shiftKey ? -10 : -1); 48 | }} 49 | > 50 | 59 |
60 | setStockItem(resources[index].id, quantity)} 64 | tabIndex={index} 65 | /> 66 |
67 | ))) 68 | } 69 |
70 |
71 | )) 72 | } 73 |
74 | ); 75 | }; 76 | 77 | export default ArkMaterialGroup; 78 | -------------------------------------------------------------------------------- /src/components/materialsGroup/style.css: -------------------------------------------------------------------------------- 1 | .material_group_name { 2 | width: 100px; 3 | max-width: 100px; 4 | white-space: nowrap; 5 | padding: 8px 24px 8px 28px; 6 | font-size: 16px; 7 | background-color: #222; 8 | margin-bottom: 12px; 9 | position: relative; 10 | left: -36px; 11 | } 12 | 13 | .material_group_name:after { 14 | content: ''; 15 | position: absolute; 16 | width: 64px; 17 | height: 100%; 18 | background: linear-gradient(to right, rgba(34, 34, 34, 1) 0%, rgba(34, 34, 34, 0.6) 40%, rgba(34, 34, 34, 0) 100%); 19 | top: 0; 20 | right: -64px; 21 | } 22 | 23 | .material_group_materials { 24 | margin-top: 18px; 25 | display: flex; 26 | flex-flow: row; 27 | flex-wrap: wrap; 28 | } 29 | 30 | .material_group_item { 31 | width: 106px; 32 | height: 128px; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | flex-direction: column; 37 | margin-top: 6px; 38 | } 39 | 40 | .material_group_item_icon { 41 | margin-bottom: 12px; 42 | } 43 | 44 | @media screen and (max-width: 600px) { 45 | .material_group_name { 46 | left: -8px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/penguinLink/index.js: -------------------------------------------------------------------------------- 1 | import style from './style'; 2 | import cn from 'classnames'; 3 | 4 | const PenguinLink = ({ 5 | id, 6 | category, 7 | render = '查看掉落概率', 8 | color = 'grey', 9 | }) => ( 10 | 11 | penguin 15 | {render} 24 | 25 | ); 26 | 27 | export default PenguinLink; 28 | -------------------------------------------------------------------------------- /src/components/penguinLink/style.css: -------------------------------------------------------------------------------- 1 | .penguin_span { 2 | width: auto; 3 | height: 100%; 4 | display: flex; 5 | flex-flow: row; 6 | justify-content: space-between; 7 | align-items: center; 8 | } 9 | 10 | img.icon { 11 | max-height: 100%; 12 | max-width: 100%; 13 | } 14 | 15 | .penguin_link { 16 | margin-left: 3px; 17 | } 18 | .grey.penguin_link { 19 | color: #EEE; 20 | } 21 | .white.penguin_link { 22 | color: #333; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/row/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import cn from 'classnames'; 3 | import ArkCell from '../cell'; 4 | import ArkInputCell from '../inputCell'; 5 | 6 | const ArkRow = (props) => { 7 | const cell_props = { 8 | header: props.header, 9 | icons_header: props.icons_header, 10 | fullheight: props.fullheight, 11 | stretch: props.stretch, 12 | selected: props.selected, 13 | }; 14 | 15 | return ( 16 |
29 | { 30 | props.cells 31 | .filter(e => e !== undefined) 32 | .map(cell => { 33 | if (cell instanceof Function) { 34 | const Component = cell; 35 | return (); 36 | } 37 | return cell.input ? ( 38 | 39 | ) : ( 40 | 41 | ); 42 | }).filter((e, i) => props.resources_filter ? props.resources_filter(i) : true) 43 | } 44 |
45 | ); 46 | }; 47 | 48 | export default ArkRow; 49 | -------------------------------------------------------------------------------- /src/components/row/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Houdou/arkgraph/8735265af3b4104d37275665d04fa0e4b48d59cc/src/components/row/style.css -------------------------------------------------------------------------------- /src/config/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "storage": "2.22.2" 3 | } -------------------------------------------------------------------------------- /src/i18n/__tests__/render.spec.js: -------------------------------------------------------------------------------- 1 | import locale from '../locale'; 2 | 3 | const { 4 | template, 5 | ...translations 6 | } = locale; 7 | 8 | describe('i18n locale render', () => { 9 | it('all locale should cover all the keys in template', () => { 10 | const keys = Object.keys(template); 11 | const check = Object.entries(translations) 12 | .every(([language, translation]) => { 13 | const failed_key = keys.find(locale_key => !translation.hasOwnProperty(locale_key)); 14 | if (failed_key) { 15 | console.error(`key '${failed_key}' is missing in language '${language}'`); 16 | return false; 17 | } 18 | return true; 19 | }); 20 | expect(check).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/i18n/locale/index.js: -------------------------------------------------------------------------------- 1 | import template from './template'; 2 | import zhCN from './zh_CN'; 3 | import enUs from './en_US'; 4 | import jaJP from './ja_JP'; 5 | import koKR from './ko_KR'; 6 | 7 | export default { 8 | template, 9 | zh_CN: zhCN, 10 | zh_TW: zhCN, 11 | en_US: enUs, 12 | ja_JP: jaJP, 13 | ko_KR: koKR, 14 | }; 15 | 16 | const locale = { 17 | zh_CN: '中文', 18 | // zh_TW: '繁體中文', 19 | en_US: 'English', 20 | ja_JP: '日本語 [β]', 21 | ko_KR: '한국어 [β]', 22 | }; 23 | 24 | export { 25 | locale, 26 | }; -------------------------------------------------------------------------------- /src/i18n/render.js: -------------------------------------------------------------------------------- 1 | import LOCALE from './locale'; 2 | 3 | const LocaleRender = locale => { 4 | const renderer = (id, fallback = '-') => LOCALE[locale] && LOCALE[locale][id] || fallback; 5 | renderer.locale = locale; 6 | return renderer; 7 | }; 8 | 9 | export default LocaleRender; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './app'; 3 | 4 | export default App; 5 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "明日方舟 - 干员培养表", 3 | "short_name": "ARK-NIGHTS.com", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#EEE", 8 | "theme_color": "#0CC3E7", 9 | "icons": [] 10 | } 11 | -------------------------------------------------------------------------------- /src/models/Attributes.js: -------------------------------------------------------------------------------- 1 | export const ATTRIBUTES = { 2 | LEVEL_ELITE_0: 'LEVEL_ELITE_0', 3 | LEVEL_ELITE_1: 'LEVEL_ELITE_1', 4 | LEVEL_ELITE_2: 'LEVEL_ELITE_2', 5 | ELITE_RANK: 'ELITE_RANK', 6 | SKILL_LEVEL: 'SKILL_LEVEL', 7 | MASTER_SKILL_1: 'MASTER_SKILL_1', 8 | MASTER_SKILL_2: 'MASTER_SKILL_2', 9 | MASTER_SKILL_3: 'MASTER_SKILL_3', 10 | ADVANCED_EQUIPMENT_1: 'ADVANCED_EQUIPMENT_1', 11 | ADVANCED_EQUIPMENT_2: 'ADVANCED_EQUIPMENT_2', 12 | ADVANCED_EQUIPMENT_3: 'ADVANCED_EQUIPMENT_3', 13 | }; 14 | -------------------------------------------------------------------------------- /src/models/Compounds.js: -------------------------------------------------------------------------------- 1 | import compounds from './compounds.json'; 2 | 3 | export const COMPOUNDS = compounds; 4 | -------------------------------------------------------------------------------- /src/models/Equipments.js: -------------------------------------------------------------------------------- 1 | import equipment_i18n from '../i18n/equipments.json'; 2 | import { locale as available_locale } from '../i18n/locale'; 3 | 4 | const LANG = Object.keys(available_locale); 5 | 6 | export default class Equipment { 7 | constructor({ equipment_id }) { 8 | this.equipment_id = equipment_id; 9 | } 10 | } 11 | 12 | const findEquipmentByName = (name) => { 13 | for (const equipment of Object.entries(equipment_i18n)) { 14 | const candidate = Object.entries(equipment[1]) 15 | .filter(([lang]) => LANG.includes(lang)) 16 | .find(([lang, equipment_lang]) => equipment_lang && equipment_lang.enabled && equipment_lang.name === name); 17 | if (candidate) { 18 | return { 19 | locale: candidate[0], 20 | unique_id: equipment[0], 21 | }; 22 | } 23 | } 24 | }; 25 | 26 | const getEquipmentName = ({ id, locale, fallback, showExtendedData = false }) => { 27 | const equipment = equipment_i18n[id]; 28 | 29 | if (equipment) { 30 | const locale_equipment = equipment[locale]; 31 | if (locale_equipment && locale_equipment.enabled) { 32 | const name = locale_equipment.name; 33 | return name; 34 | } 35 | if (showExtendedData) { 36 | const extended_name = equipment.code_name; 37 | return extended_name; 38 | } 39 | } 40 | return fallback; 41 | }; 42 | 43 | export { 44 | getEquipmentName, 45 | findEquipmentByName, 46 | }; 47 | -------------------------------------------------------------------------------- /src/models/Levels.js: -------------------------------------------------------------------------------- 1 | import levels from './levels.json'; 2 | 3 | import levels_i18n from '../i18n/levels.json'; 4 | 5 | 6 | const LEVELS = levels; 7 | 8 | const UNAVAILABLE_LEVELS = levels.map(level => { 9 | const i18n_data = levels_i18n[level.unique_id]; 10 | if (i18n_data.zh_CN && i18n_data.zh_CN.enabled) { 11 | if (!i18n_data.ja_JP) { 12 | return i18n_data.code; 13 | } 14 | } 15 | }).filter(Boolean); 16 | 17 | export { 18 | LEVELS, 19 | UNAVAILABLE_LEVELS, 20 | }; 21 | -------------------------------------------------------------------------------- /src/models/Operators.js: -------------------------------------------------------------------------------- 1 | import operators from './operators.json'; 2 | import operator_i18n from '../i18n/operators.json'; 3 | import { locale as available_locale } from '../i18n/locale'; 4 | 5 | import { getEquipmentName } from './Equipments'; 6 | 7 | const LANG = Object.keys(available_locale); 8 | 9 | const OPERATORS = operators.map(o => o); 10 | 11 | export default class Operator { 12 | constructor(name, attributes) { 13 | this.name = name; 14 | this.attributes = attributes; 15 | } 16 | } 17 | 18 | const findOperatorByName = (name) => { 19 | for (const operator of Object.entries(operator_i18n)) { 20 | const candidate = Object.entries(operator[1]) 21 | .filter(([lang]) => LANG.includes(lang)) 22 | .find(([lang, operator_lang]) => operator_lang && operator_lang.enabled && operator_lang.name === name); 23 | if (candidate) { 24 | return { 25 | locale: candidate[0], 26 | unique_id: operator[0], 27 | }; 28 | } 29 | } 30 | }; 31 | 32 | const getOperatorName = ({ id, locale, fallback, showExtendedData = false }) => { 33 | const operator = operator_i18n[id]; 34 | if (operator) { 35 | const locale_operator = operator[locale]; 36 | if (locale_operator && locale_operator.enabled) { 37 | const name = locale_operator.name; 38 | return name; 39 | } 40 | if (showExtendedData) { 41 | const extended_name = operator.code_name; 42 | return extended_name; 43 | } 44 | } 45 | return fallback; 46 | }; 47 | 48 | const getSkillNames = ({ id, locale, showExtendedData = false }) => { 49 | const operator = operator_i18n[id]; 50 | if (operator) { 51 | const locale_operator = operator[locale]; 52 | if (locale_operator && locale_operator.enabled) { 53 | const skills = locale_operator.skills; 54 | return skills; 55 | } 56 | if (showExtendedData) { 57 | const extended_data = operator.zh_CN; 58 | if (extended_data) { 59 | const extended_skills = extended_data.skills; 60 | return extended_skills; 61 | } 62 | } 63 | } 64 | return []; 65 | }; 66 | 67 | const getEquipmentNames = ({ id, locale, showExtendedData = false }) => { 68 | const operator = operators.find(({ unique_id }) => unique_id === id); 69 | 70 | if (operator) { 71 | return operator.equipments.map(( 72 | equipment 73 | ) => { 74 | const { 75 | equipment_id, 76 | } = equipment[0]; 77 | 78 | return getEquipmentName({ id: equipment_id, locale, showExtendedData }) 79 | }); 80 | } 81 | return []; 82 | }; 83 | 84 | export { 85 | OPERATORS, 86 | getOperatorName, 87 | getSkillNames, 88 | getEquipmentNames, 89 | findOperatorByName, 90 | }; 91 | -------------------------------------------------------------------------------- /src/models/Resources/exp_tapes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "E-5-1", 4 | "unique_id": 2004, 5 | "name": "高级作战记录", 6 | "pinyin": "gao ji zuo zhan ji lu", 7 | "tier": "T5", 8 | "value": 2000, 9 | "type": "tape", 10 | "source": { 11 | "LS-4": "固定[1]", 12 | "LS-5": "固定[3]" 13 | }, 14 | "formula": { 15 | "E-4-1": 2 16 | } 17 | }, { 18 | "id": "E-4-1", 19 | "unique_id": 2003, 20 | "name": "中级作战记录", 21 | "pinyin": "zhong ji zuo zhan ji lu", 22 | "tier": "T4", 23 | "value": 1000, 24 | "type": "tape", 25 | "source": { 26 | "LS-3": "固定[3]", 27 | "LS-4": "固定[1~3]", 28 | "LS-5": "固定[1]" 29 | }, 30 | "formula": { 31 | "E-3-1": 2, 32 | "E-2-1": 1 33 | } 34 | }, { 35 | "id": "E-3-1", 36 | "unique_id": 2002, 37 | "name": "初级作战记录", 38 | "pinyin": "chu ji zuo zhan ji lu", 39 | "tier": "T3", 40 | "value": 400, 41 | "type": "tape", 42 | "source": { 43 | "LS-1": "固定[3]", 44 | "LS-2": "固定[5]", 45 | "LS-3": "固定[1~3]", 46 | "LS-4": "固定[1~4]", 47 | "LS-5": "固定[1]" 48 | }, 49 | "formula": { 50 | "E-2-1": 2 51 | } 52 | }, { 53 | "id": "E-2-1", 54 | "unique_id": 2001, 55 | "name": "基础作战记录", 56 | "pinyin": "ji chu zuo zhan ji lu", 57 | "tier": "T2", 58 | "value": 200, 59 | "type": "tape", 60 | "source": { 61 | "LS-1": "固定[1~3]", 62 | "LS-2": "固定[3~5]", 63 | "LS-3": "固定[1~3]", 64 | "LS-4": "固定[1~3]" 65 | }, 66 | "formula": {} 67 | } 68 | ] -------------------------------------------------------------------------------- /src/models/Upgrade.js: -------------------------------------------------------------------------------- 1 | export default class Upgrade { 2 | constructor({ operator_id, attribute, current, target, hidden, selected }) { 3 | this.operator_id = operator_id; 4 | this.attribute = attribute; 5 | this.current = current; 6 | this.target = target; 7 | this.hidden = Boolean(hidden); 8 | this.selected = Boolean(selected); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/models/aggregateMaterialRequirement.js: -------------------------------------------------------------------------------- 1 | import { ATTRIBUTES } from './Attributes'; 2 | import { OPERATORS, getSkillNames } from './Operators'; 3 | 4 | import operator_i18n from '../i18n/operators.json'; 5 | import { RESOURCES } from './Resources'; 6 | 7 | const aggregateMaterialRequirement = ({ 8 | locale, 9 | showExtendedData = false, 10 | }) => { 11 | const material_requirements = {}; 12 | 13 | Object.entries(RESOURCES).forEach(([id, material]) => { 14 | material_requirements[id] = {}; 15 | }); 16 | 17 | Object.entries(RESOURCES).forEach(([id, material]) => { 18 | const compound_materials = Object.entries(material.formula); 19 | compound_materials.forEach(([resource, quantity]) => { 20 | material_requirements[resource].compound = material_requirements[resource].compound || []; 21 | material_requirements[resource].compound.push({ 22 | result: id, 23 | required: quantity, 24 | }); 25 | }); 26 | }); 27 | 28 | OPERATORS 29 | .filter(operator => { 30 | const operator_lang = operator_i18n[operator.unique_id][locale]; 31 | return showExtendedData || operator_lang && operator_lang.enabled; 32 | }) 33 | .forEach((operator, index) => { 34 | operator.skills && operator.skills.forEach((skill, index) => { 35 | skill.materials.forEach(({ resource, quantity }) => { 36 | material_requirements[resource].operator = material_requirements[resource].operator || []; 37 | material_requirements[resource].operator.push({ 38 | operator_id: operator.unique_id, 39 | profession: operator.profession, 40 | rarity: operator.rarity, 41 | attribute: ATTRIBUTES.SKILL_LEVEL, 42 | current: index + 1, 43 | target: index + 2, 44 | quantity, 45 | }); 46 | }); 47 | }); 48 | 49 | operator.elites && operator.elites.forEach((elite, index) => { 50 | elite.materials && elite.materials.forEach(({ resource, quantity }) => { 51 | material_requirements[resource].operator = material_requirements[resource].operator || []; 52 | material_requirements[resource].operator.push({ 53 | operator_id: operator.unique_id, 54 | profession: operator.profession, 55 | rarity: operator.rarity, 56 | attribute: ATTRIBUTES.ELITE_RANK, 57 | current: index, 58 | target: index + 1, 59 | quantity, 60 | }); 61 | }); 62 | }); 63 | 64 | const skill_names = getSkillNames({ id: operator.unique_id, locale, showExtendedData }); 65 | operator.master_skills.forEach((master_skill, skill_index) => { 66 | master_skill.upgrades.forEach((upgrade, level) => { 67 | upgrade.materials.forEach(({ resource, quantity }) => { 68 | material_requirements[resource].operator = material_requirements[resource].operator || []; 69 | material_requirements[resource].operator.push({ 70 | operator_id: operator.unique_id, 71 | profession: operator.profession, 72 | rarity: operator.rarity, 73 | attribute: ATTRIBUTES[`MASTER_SKILL_${skill_index + 1}`], 74 | render: skill_names[skill_index], 75 | current: level, 76 | target: level + 1, 77 | quantity, 78 | }); 79 | }); 80 | }); 81 | }); 82 | }); 83 | 84 | material_requirements['G-4-1'] = { 85 | operator: [], 86 | compound: [], 87 | }; 88 | 89 | return material_requirements; 90 | }; 91 | 92 | export default aggregateMaterialRequirement; 93 | -------------------------------------------------------------------------------- /src/models/checkFulFillment.js: -------------------------------------------------------------------------------- 1 | import { EXP, RESOURCES } from './Resources'; 2 | 3 | export const FULFILLMENT_STATUS = { 4 | FULFILLED: 'FULFILLED', 5 | COMPOUND: 'COMPOUND', 6 | UNSATISFIED: 'UNSATISFIED', 7 | }; 8 | 9 | const checkResourceEnough = ( 10 | stock, 11 | resource, 12 | quantity, 13 | ) => (stock[resource] || 0) >= quantity; 14 | 15 | const checkFulFillment = ({ 16 | records, 17 | stock, 18 | compound_materials, 19 | }) => { 20 | const fulfilled_records = records.map(record => { 21 | if (record.requirements && record.requirements.length > 0) { 22 | if (record.requirements.every( 23 | ({ resource, quantity }) => resource === EXP.id || checkResourceEnough(stock, resource, quantity) 24 | )) { 25 | return FULFILLMENT_STATUS.FULFILLED; 26 | } 27 | const checklist = []; 28 | const accumulated_requirements = {}; 29 | record.requirements.forEach( 30 | ({ resource, quantity }) => { 31 | checklist.push({ 32 | resource, 33 | quantity, 34 | }); 35 | accumulated_requirements[resource] = quantity; 36 | } 37 | ); 38 | 39 | let compoundable = true; 40 | for (const item of checklist) { 41 | const { 42 | resource, 43 | quantity, 44 | } = item; 45 | if (resource === EXP.id || checkResourceEnough(stock, resource, accumulated_requirements[resource])) { 46 | continue; 47 | } else if (compound_materials.find(({ id }) => id === resource)) { 48 | Object.entries(RESOURCES[resource].formula).forEach(([ingredient_resource, ingredient_quantity]) => { 49 | checklist.push({ 50 | resource: ingredient_resource, 51 | quantity: (quantity - (stock[resource] || 0)) * ingredient_quantity, 52 | }); 53 | accumulated_requirements[ingredient_resource] = accumulated_requirements[ingredient_resource] || 0; 54 | accumulated_requirements[ingredient_resource] += (quantity - (stock[resource] || 0)) * ingredient_quantity; 55 | }); 56 | continue; 57 | } else { 58 | compoundable = false; 59 | break; 60 | } 61 | } 62 | if (compoundable) { 63 | return FULFILLMENT_STATUS.COMPOUND; 64 | } 65 | } 66 | return FULFILLMENT_STATUS.UNSATISFIED; 67 | }); 68 | return fulfilled_records; 69 | }; 70 | 71 | export default checkFulFillment; -------------------------------------------------------------------------------- /src/models/getShortageFocusMaterials.js: -------------------------------------------------------------------------------- 1 | import { EXP, EXP_TAPES } from './Resources'; 2 | 3 | const excluding_list = [ 4 | EXP, 5 | ...EXP_TAPES, 6 | ].map(({ id }) => id); 7 | 8 | const getShortageFocusMaterials = (shortage) => { 9 | let focus_materials = []; 10 | 11 | Object.entries(shortage).forEach(([resource, quantity]) => { 12 | if (Number(quantity) > 0 && !excluding_list.includes(resource)) { 13 | focus_materials.push({ 14 | id: resource, 15 | options: {}, 16 | }); 17 | } 18 | }); 19 | 20 | return focus_materials; 21 | }; 22 | 23 | export default getShortageFocusMaterials; 24 | -------------------------------------------------------------------------------- /src/models/sumLevelUpRequirement.js: -------------------------------------------------------------------------------- 1 | import exp from './exp.json'; 2 | import { MONEY, EXP, EXP_TAPES } from './Resources'; 3 | 4 | const sumTapeRequirements = (required_exp) => { 5 | const summary = []; 6 | 7 | if (required_exp <= 0) { 8 | return []; 9 | } 10 | const rounded_exp = Math.ceil(required_exp/200)*200; 11 | let remain_exp = rounded_exp; 12 | EXP_TAPES.forEach(tape => { 13 | const requirement = { 14 | resource: tape.id, 15 | quantity: 0, 16 | }; 17 | while (remain_exp >= tape.value) { 18 | requirement.quantity += 1; 19 | remain_exp -= tape.value; 20 | } 21 | if (requirement.quantity > 0) { 22 | summary.push(requirement); 23 | } 24 | }); 25 | 26 | return summary; 27 | }; 28 | 29 | const sumLevelUpRequirements = (rarity, elite_rank, current, target) => { 30 | const summary = []; 31 | if (current === target) { 32 | return summary; 33 | } 34 | 35 | try { 36 | // Lungmen coin 37 | let sum_coin = 0; 38 | for (let i = current - 1, step = 0; i < target - 1 && step < 90; i++, step++) { 39 | sum_coin += exp.upgradeCoinRequirement[elite_rank][i]; 40 | } 41 | summary.push({ 42 | resource: MONEY.id, 43 | quantity: sum_coin, 44 | }); 45 | 46 | // Exp cost 47 | let sum_exp = 0; 48 | for (let i = current - 1, step = 0; i < target - 1 && step < 90; i++, step++) { 49 | sum_exp += exp.upgradeExpRequirement[elite_rank][i]; 50 | } 51 | summary.push(...sumTapeRequirements(sum_exp)); 52 | summary.push({ 53 | resource: EXP.id, 54 | quantity: sum_exp, 55 | }); 56 | 57 | return summary; 58 | } catch (err) { 59 | console.error(err); 60 | return []; 61 | } 62 | }; 63 | 64 | export default sumLevelUpRequirements; 65 | -------------------------------------------------------------------------------- /src/models/sumRequirements.js: -------------------------------------------------------------------------------- 1 | import processRecord from './processRecord'; 2 | import { RESOURCES } from './Resources'; 3 | 4 | const sumRequirements = (records, stock, compound_materials) => { 5 | const summary = [] 6 | .concat( 7 | ...records 8 | .filter(({ hidden }) => !hidden) 9 | .map(record => processRecord(record).requirements) 10 | ) 11 | .reduce((prev, next) => { 12 | prev[next.resource] = prev[next.resource] || 0; 13 | prev[next.resource] += next.quantity; 14 | return prev; 15 | }, {}); 16 | 17 | compound_materials 18 | .map(({ id: material_id }) => RESOURCES[material_id] || null) 19 | .filter(Boolean) 20 | .sort((prev, next) => prev.tier < next.tier ? 1 : -1) 21 | .forEach(({ id: material_id, formula }) => { 22 | Object.entries(formula).forEach(([ingredient_material_id, ingredient_quantity]) => { 23 | summary[ingredient_material_id] = (summary[ingredient_material_id] || 0) + (Math.max((summary[material_id] || 0) - (stock[material_id] || 0), 0)) * ingredient_quantity; 24 | }); 25 | }); 26 | 27 | return summary; 28 | }; 29 | 30 | export default sumRequirements; 31 | -------------------------------------------------------------------------------- /src/models/sumShortage.js: -------------------------------------------------------------------------------- 1 | import { EXP, EXP_TAPES } from './Resources'; 2 | 3 | const sumShortage = (stock, summary, compound_materials, ir) => { 4 | const shortage = {}; 5 | 6 | Object.entries(summary).forEach(([material_id, requirement]) => { 7 | shortage[material_id] = Math.max(requirement - (stock[material_id] || 0), 0); 8 | }); 9 | 10 | const exp_stock = EXP_TAPES 11 | .map(tape => (stock[tape.id] || 0) * tape.value) 12 | .reduce((a, b) => a + b, 0); 13 | shortage[EXP.id] = Math.max(summary[EXP.id] - exp_stock, 0); 14 | 15 | compound_materials.forEach(({ id }) => { 16 | shortage[id] = shortage[id] ? `${ 17 | ir('table-shortage-compound-prefix', '') 18 | }${shortage[id]}${ 19 | ir('table-shortage-compound-suffix', '') 20 | }` : 0; 21 | }); 22 | 23 | return shortage; 24 | }; 25 | 26 | export default sumShortage; 27 | -------------------------------------------------------------------------------- /src/models/useFilterSetting.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'preact/hooks'; 2 | 3 | const buildFlags = (options, values = []) => { 4 | let flags = {}; 5 | options.forEach((v, i) => { 6 | flags[v.value] = values[i] || false; 7 | }); 8 | return flags; 9 | }; 10 | 11 | const useFilterSetting = ({ options, field }, initial_value = {}) => { 12 | if (Object.entries(initial_value).every(([k, v]) => v)) { 13 | initial_value = {}; 14 | } 15 | 16 | const [setting, setSetting] = useState( 17 | options.map((option, index) => Boolean(false || initial_value[option.value])) 18 | ); 19 | 20 | const getOption = () => { 21 | if (setting.length > 1 && setting.some(Boolean)) { 22 | return setting; 23 | } 24 | return [...setting].map(e => true); 25 | }; 26 | 27 | const getFilter = () => ({ 28 | field, 29 | flags: buildFlags(options, getOption()), 30 | }); 31 | 32 | const toggle = (index) => { 33 | const new_setting = [...setting]; 34 | new_setting[index] = !new_setting[index]; 35 | setSetting(new_setting); 36 | }; 37 | 38 | const resetAll = () => { 39 | setSetting([...setting].map(e => false)); 40 | }; 41 | 42 | const enableAll = () => { 43 | setSetting([...setting].map(e => true)); 44 | }; 45 | 46 | return { 47 | setting, 48 | toggle, 49 | getOption, 50 | getFilter, 51 | resetAll, 52 | enableAll, 53 | }; 54 | }; 55 | 56 | export default useFilterSetting; 57 | -------------------------------------------------------------------------------- /src/models/useRecord.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'preact/hooks'; 2 | 3 | import processRecord from './processRecord'; 4 | 5 | const useRecord = (init_values) => { 6 | const init_record = Object.assign( 7 | { 8 | operator_id: null, 9 | attribute: null, 10 | current: 0, 11 | target: 1, 12 | requirements: [], 13 | hidden: false, 14 | selected: false 15 | }, 16 | init_values, 17 | { requirements: processRecord(init_values).requirements } 18 | ); 19 | const [record, setRecord_raw] = useState(init_record); 20 | 21 | const setRecord = (to_update) => { 22 | const processed_record = processRecord(to_update); 23 | setRecord_raw(processed_record); 24 | return processed_record; 25 | }; 26 | 27 | const setOperatorId = (operator_id) => setRecord({ 28 | ...record, 29 | operator_id, 30 | }); 31 | const setAttribute = (attribute) => setRecord({ 32 | ...record, 33 | attribute, 34 | }); 35 | const setCurrent = (current) => setRecord({ 36 | ...record, 37 | current, 38 | }); 39 | const setTarget = (target) => setRecord({ 40 | ...record, 41 | target, 42 | }); 43 | const setHidden = (hidden) => setRecord({ 44 | ...record, 45 | hidden: Boolean(hidden), 46 | }); 47 | const setSelected = (selected) => setRecord({ 48 | ...record, 49 | selected: Boolean(selected), 50 | }); 51 | 52 | return { 53 | record, 54 | setOperatorId, 55 | setAttribute, 56 | setCurrent, 57 | setTarget, 58 | setHidden, 59 | setSelected, 60 | }; 61 | }; 62 | 63 | export default useRecord; 64 | -------------------------------------------------------------------------------- /src/routes/backup/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 100%; 4 | background: #333; 5 | } 6 | 7 | .info { 8 | padding: 40px 20px; 9 | margin: 56px auto 0; 10 | width: 100%; 11 | max-width: 1000px; 12 | min-height: 100%; 13 | 14 | text-shadow: 0px 0px 10px #111; 15 | 16 | color: #EEE; 17 | background-color: #666; 18 | } 19 | 20 | .info a { 21 | color: #EEE; 22 | } 23 | 24 | .lang_options { 25 | display: flex; 26 | justify-content: left; 27 | flex-wrap: wrap; 28 | padding-bottom: 12px; 29 | } 30 | .lang_option { 31 | color: #CCC; 32 | background-color: #444; 33 | padding: 8px 20px; 34 | margin-top: 12px; 35 | text-align: center; 36 | user-select: none; 37 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 38 | font-size: 16px; 39 | font-weight: bold; 40 | margin-left: 0; 41 | } 42 | .lang_option_active { 43 | background-color: #222; 44 | } 45 | .lang_option:not(:first-of-type) { 46 | margin-left: 8px; 47 | } 48 | 49 | .data_area { 50 | margin-top: 12px; 51 | display: flex; 52 | justify-content: space-between; 53 | } 54 | .data_area textarea { 55 | flex-grow: 9; 56 | } 57 | .buttons { 58 | display: flex; 59 | width: 10%; 60 | flex-grow: 1; 61 | margin-left: 12px; 62 | flex-flow: column; 63 | justify-content: space-between; 64 | align-items: center; 65 | } 66 | .buttons span { 67 | padding: 8px; 68 | width: 100%; 69 | height: auto; 70 | text-align: center; 71 | color: #DDD; 72 | background-color: #333; 73 | } 74 | .buttons span:not(:first-of-type) { 75 | margin-top: 8px; 76 | } 77 | .save_data { 78 | width: 90%; 79 | background: #444; 80 | color: #EEE; 81 | padding: 12px; 82 | } 83 | .clear_data { 84 | color: #CCC; 85 | background-color: #311; 86 | padding: 8px; 87 | margin-top: 12px; 88 | text-align: center; 89 | user-select: none; 90 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 91 | font-size: 20px; 92 | font-weight: bold; 93 | } 94 | .load_error { 95 | padding: 8px; 96 | margin-top: 12px; 97 | text-align: center; 98 | user-select: none; 99 | background: linear-gradient(to bottom, #890000 0%, #A90000 30%); 100 | color: #EEE; 101 | } 102 | -------------------------------------------------------------------------------- /src/routes/farming/components/LevelInput.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useEffect, useRef } from 'preact/hooks'; 3 | import Fuse from 'fuse.js'; 4 | import style from '../style'; 5 | 6 | import { LEVELS } from '../../../models/Levels'; 7 | 8 | const options = { 9 | shouldSort: true, 10 | threshold: 0.9, 11 | location: 0, 12 | distance: 100, 13 | maxPatternLength: 32, 14 | minMatchCharLength: 1, 15 | keys: [{ 16 | name: 'level', 17 | weight: 0.8, 18 | }, { 19 | name: 'unique_id', 20 | weight: 0.2, 21 | }], 22 | }; 23 | 24 | const fuse = new Fuse(LEVELS, options); 25 | 26 | const ArkStockInput = ({ 27 | level_id, 28 | setLevelId, 29 | }) => { 30 | const level_id_input_ref = useRef(null); 31 | 32 | useEffect(() => { 33 | if (!level_id) { 34 | level_id_input_ref.current && level_id_input_ref.current.focus(); 35 | } 36 | }, []); 37 | 38 | return ( 39 |
40 | { 45 | const results = fuse.search(e.target.value); 46 | if (results.length > 0) { 47 | const exact = results.find( 48 | ({ level }) => level.toLowerCase().replace(/-/g, '') === (e.target.value || '').toLowerCase() 49 | ); 50 | if (exact) { 51 | setLevelId(exact.level); 52 | return; 53 | } 54 | 55 | const [query] = results; 56 | setLevelId(query.level); 57 | } else { 58 | console.log(results); 59 | } 60 | }} 61 | onClick={() => level_id_input_ref.current && level_id_input_ref.current.select()} 62 | /> 63 |
64 | ); 65 | }; 66 | 67 | export default ArkStockInput; 68 | -------------------------------------------------------------------------------- /src/routes/farming/getLevelMaterials.js: -------------------------------------------------------------------------------- 1 | import { RESOURCES } from '../../models/Resources'; 2 | export const getLevelMaterials = (level) => { 3 | if (!level) { 4 | return { 5 | resources: [], 6 | normal_drop: [], 7 | special_drop: [], 8 | extra_drop: [], 9 | }; 10 | } 11 | 12 | let index = 0; 13 | const resources = []; 14 | const normal_drop = []; 15 | const special_drop = []; 16 | const extra_drop = []; 17 | level.normal_drop.forEach(m => { 18 | resources.push(RESOURCES[m.resource]); 19 | normal_drop.push(index++); 20 | }); 21 | level.special_drop.forEach(m => { 22 | resources.push(RESOURCES[m.resource]); 23 | special_drop.push(index++); 24 | }); 25 | level.extra_drop.forEach(m => { 26 | resources.push(RESOURCES[m.resource]); 27 | extra_drop.push(index++); 28 | }); 29 | 30 | return { 31 | resources, 32 | normal_drop, 33 | special_drop, 34 | extra_drop, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/routes/farming/options/index.js: -------------------------------------------------------------------------------- 1 | import { MONEY, PURCHASE_CREDIT, EXP_TAPES, MATERIALS, SKILL_BOOKS, CHIPS } from '../../../models/Resources'; 2 | 3 | const material_grouping_options = { 4 | default: { 5 | render: 'material_grouping_options-all', 6 | field: null, 7 | options: [], 8 | groups: {}, 9 | }, 10 | type: { 11 | render: 'material_grouping_options-type', 12 | field: 'type', 13 | options: [ 14 | { value: 'money', render: 'material_grouping_options-type-money' }, 15 | { value: 'tape', render: 'material_grouping_options-type-tape' }, 16 | { value: 'rare', render: 'material_grouping_options-type-rare' }, 17 | { value: 'alcohol', render: 'material_grouping_options-type-alcohol' }, 18 | { value: 'manganese', render: 'material_grouping_options-type-manganese' }, 19 | { value: 'grind', render: 'material_grouping_options-type-grind' }, 20 | { value: 'rma', render: 'material_grouping_options-type-rma' }, 21 | { value: 'stone', render: 'material_grouping_options-type-stone' }, 22 | { value: 'device', render: 'material_grouping_options-type-device' }, 23 | { value: 'ester', render: 'material_grouping_options-type-ester' }, 24 | { value: 'sugar', render: 'material_grouping_options-type-sugar' }, 25 | { value: 'iron', render: 'material_grouping_options-type-iron' }, 26 | { value: 'ketone', render: 'material_grouping_options-type-ketone' }, 27 | { value: 'gel', render: 'material_grouping_options-type-gel' }, 28 | { value: 'alloy', render: 'material_grouping_options-type-alloy' }, 29 | { value: 'skill', render: 'material_grouping_options-type-skill' }, 30 | { value: 'chip', render: 'material_grouping_options-type-chip' }, 31 | ], 32 | groups: {}, 33 | }, 34 | tier: { 35 | render: 'material_grouping_options-tier', 36 | field: 'tier', 37 | options: [ 38 | { value: 'T5', render: 'material_grouping_options-tier-T5' }, 39 | { value: 'T4', render: 'material_grouping_options-tier-T4' }, 40 | { value: 'T3', render: 'material_grouping_options-tier-T3' }, 41 | { value: 'T2', render: 'material_grouping_options-tier-T2' }, 42 | { value: 'T1', render: 'material_grouping_options-tier-T1' }, 43 | ], 44 | groups: {}, 45 | }, 46 | }; 47 | 48 | const material_filter_options = { 49 | default: { 50 | render: 'material_filter_options-all', 51 | }, 52 | required: { 53 | render: 'material_filter_options-required', 54 | }, 55 | shortage: { 56 | render: 'material_filter_options-shortage', 57 | }, 58 | exceeded: { 59 | render: 'material_filter_options-exceeded', 60 | }, 61 | }; 62 | 63 | const material_list = [ 64 | MONEY, 65 | PURCHASE_CREDIT, 66 | ...EXP_TAPES, 67 | ...MATERIALS, 68 | ...SKILL_BOOKS, 69 | ...CHIPS, 70 | ]; 71 | 72 | const indexed_material_list = material_list.map((m, index) => ({ 73 | material: m, 74 | index, 75 | })); 76 | 77 | Object.entries(material_grouping_options) 78 | .forEach(([key, data]) => { 79 | if (!data.field) { 80 | return; 81 | } 82 | data.options.forEach(({ value, render }) => { 83 | material_grouping_options[key].groups[value] = 84 | { 85 | render, 86 | list: indexed_material_list 87 | .filter(({ material }) => material[data.field] === value) 88 | .map(({ index }) => index), 89 | }; 90 | }); 91 | }); 92 | 93 | // Special handling for chip 94 | material_grouping_options.type.groups.chip.render = 'material_grouping_options-type-catalyst'; 95 | const profession_chip_index = material_grouping_options.type.groups.chip.list.splice(1, 3 * 8); 96 | const professions = [ 97 | { value: 'pioneer', render: 'material_grouping_options-type-pioneer' }, 98 | { value: 'warrior', render: 'material_grouping_options-type-warrior' }, 99 | { value: 'tank', render: 'material_grouping_options-type-tank' }, 100 | { value: 'sniper', render: 'material_grouping_options-type-sniper' }, 101 | { value: 'caster', render: 'material_grouping_options-type-caster' }, 102 | { value: 'medic', render: 'material_grouping_options-type-medic' }, 103 | { value: 'support', render: 'material_grouping_options-type-support' }, 104 | { value: 'special', render: 'material_grouping_options-type-special' }, 105 | ]; 106 | professions.forEach(({ value, render }, index) => { 107 | material_grouping_options.type.groups[`chip_${value}`] = { 108 | render: `${render}_chip`, 109 | list: profession_chip_index.splice(0, 3), 110 | }; 111 | }); 112 | 113 | export { 114 | material_grouping_options, 115 | material_filter_options, 116 | material_list, 117 | }; 118 | -------------------------------------------------------------------------------- /src/routes/farming/sections/ArkPlanner/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | } 4 | .plan_result { 5 | width: 100%; 6 | } 7 | 8 | .sub_header { 9 | width: 100px; 10 | max-width: 100px; 11 | white-space: nowrap; 12 | padding: 8px 24px 8px 28px; 13 | background-color: #222; 14 | margin-bottom: 12px; 15 | position: relative; 16 | left: -36px; 17 | font-size: 18px; 18 | font-weight: bold; 19 | color: #CCC; 20 | } 21 | 22 | .sub_header:after { 23 | content: ''; 24 | position: absolute; 25 | width: 64px; 26 | height: 100%; 27 | background: linear-gradient(to right, rgba(34, 34, 34, 1) 0%, rgba(34, 34, 34, 0.6) 40%, rgba(34, 34, 34, 0) 100%); 28 | top: 0; 29 | right: -64px; 30 | } 31 | 32 | .helper_block { 33 | padding: 12px 20px; 34 | } 35 | 36 | .request_options { 37 | padding: 12px 20px 8px; 38 | display: flex; 39 | width: 100%; 40 | } 41 | .request_option { 42 | margin-right: 20px; 43 | padding: 8px 32px; 44 | text-align: center; 45 | user-select: none; 46 | background-color: #333; 47 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 48 | font-size: 16px; 49 | font-weight: 400; 50 | transition: background-color 0.3s ease; 51 | } 52 | .request_option:last-of-type { 53 | margin-right: 0; 54 | } 55 | 56 | .request_option_active { 57 | background-color: #111; 58 | transform: translate(0, 1px); 59 | } 60 | 61 | .request_button { 62 | color: #CCC; 63 | background-color: rgb(17, 28, 50); 64 | padding: 8px 0; 65 | margin-top: 12px; 66 | margin-bottom: 12px; 67 | text-align: center; 68 | user-select: none; 69 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 70 | font-size: 20px; 71 | font-weight: bold; 72 | } 73 | 74 | .plan_section { 75 | padding: 20px 8px; 76 | } 77 | 78 | .requirement_cell { 79 | min-width: 80px; 80 | height: 56px; 81 | padding: 18px 0; 82 | display: flex; 83 | flex-flow: row; 84 | } 85 | .requirement_cell span, 86 | .requirement_cell .requirement_quantity { 87 | align-self: flex-end; 88 | 89 | } 90 | 91 | @media screen and (max-width: 600px) { 92 | .sub_header { 93 | left: -8px; 94 | } 95 | } -------------------------------------------------------------------------------- /src/routes/farming/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .page { 7 | padding: 88px 0px 80px; 8 | margin: 0 auto; 9 | width: 100%; 10 | max-width: 1000px; 11 | text-shadow: 0px 0px 10px #111; 12 | 13 | color: #EEE; 14 | background-color: #444; 15 | } 16 | 17 | .section { 18 | padding: 52px 40px 8px; 19 | position: relative; 20 | } 21 | .section_no_header { 22 | padding-top: 32px; 23 | } 24 | .section_header { 25 | width: auto; 26 | position: absolute; 27 | top: 0px; 28 | left: -20px; 29 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 30 | display: flex; 31 | justify-content: flex-start; 32 | padding: 8px 32px; 33 | font-size: 20px; 34 | font-weight: bold; 35 | background-color: #111; 36 | color: #DDD; 37 | text-shadow: 0px 0px 2px rgba(180, 180, 180, 0.6); 38 | } 39 | 40 | .level_id_input { 41 | width: 360px; 42 | height: 80px; 43 | background: rgba(238, 238, 238, 0.13); 44 | border: none; 45 | } 46 | 47 | .level_id_input input { 48 | color: #EEE; 49 | text-align: left; 50 | padding: 0 24px; 51 | width: 100%; 52 | height: 100%; 53 | font-family: 'Microsoft YaHei', 'Helvetica Neue', arial, sans-serif; 54 | font-size: 48px; 55 | font-weight: bold; 56 | background: linear-gradient(to right, #EEEEEE11 80%, #444 100%); 57 | border: none; 58 | text-shadow: 59 | 0px 0px 6px rgba(24, 24, 24, 0.8), 60 | 0px 0px 2px rgba(180, 180, 180, 0.8), 61 | -2px -1px 0px rgba(170, 34, 34, 0.4), 62 | 2px -1px 0px rgba(34, 34, 170, 0.4), 63 | -2px 1px 0px rgba(170, 34, 34, 0.4), 64 | 2px 1px 0px rgba(34, 34, 170, 0.4); 65 | } 66 | 67 | .material_grouping_options { 68 | display: flex; 69 | flex-flow: row; 70 | } 71 | 72 | .material_grouping_option, 73 | .material_filter_option { 74 | width: 100px; 75 | margin-right: 20px; 76 | padding: 8px 12px; 77 | text-align: center; 78 | user-select: none; 79 | background-color: #333; 80 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 81 | font-size: 16px; 82 | font-weight: 400; 83 | transition: background-color 0.3s ease; 84 | display: flex; 85 | justify-content: center; 86 | } 87 | 88 | .material_grouping_option_active, 89 | .material_filter_option_active { 90 | background-color: #111; 91 | transform: translate(0, 1px); 92 | } 93 | 94 | .drops, .stock, .stock_options, .planner { 95 | background: linear-gradient(to right, #EEEEEE11 80%, #444 100%); 96 | padding: 20px 24px; 97 | 98 | display: flex; 99 | flex-flow: row; 100 | flex-wrap: wrap; 101 | justify-content: flex-start; 102 | } 103 | 104 | .drops { 105 | position: relative; 106 | } 107 | 108 | .penguin_link { 109 | position: absolute; 110 | top: -48px; 111 | right: 12px; 112 | height: 36px; 113 | padding: 8px 16px 8px 12px; 114 | font-size: 16px; 115 | background-color: #222; 116 | margin-bottom: 12px; 117 | } 118 | 119 | .stock_options { 120 | padding: 12px 24px; 121 | flex-flow: column; 122 | } 123 | 124 | .stock_options_header { 125 | width: 100px; 126 | max-width: 100px; 127 | white-space: nowrap; 128 | padding: 8px 24px 8px 28px; 129 | background-color: #222; 130 | margin-bottom: 12px; 131 | position: relative; 132 | left: -36px; 133 | font-size: 18px; 134 | font-weight: bold; 135 | color: #CCC; 136 | } 137 | 138 | .stock_options_header:after { 139 | content: ''; 140 | position: absolute; 141 | width: 64px; 142 | height: 100%; 143 | background: linear-gradient(to right, rgba(34, 34, 34, 1) 0%, rgba(34, 34, 34, 0.6) 40%, rgba(34, 34, 34, 0) 100%); 144 | top: 0; 145 | right: -64px; 146 | } 147 | 148 | @media screen and (max-width: 600px) { 149 | .page, .wrapper { 150 | max-width: unset; 151 | width: 100%; 152 | } 153 | 154 | .section { 155 | padding: 12px 0px 8px; 156 | font-size: 12px; 157 | } 158 | 159 | .section_header { 160 | padding: 8px 20px; 161 | position: relative; 162 | left: 0; 163 | width: 100%; 164 | font-size: 16px; 165 | } 166 | 167 | .material_grouping_option, 168 | .material_filter_option { 169 | padding: 8px 12px; 170 | } 171 | 172 | .drops, .stock, .stock_options, .planner { 173 | padding: 8px; 174 | width: 100%; 175 | min-width: unset; 176 | background: rgba(238, 238, 238, 0.07); 177 | padding-right: unset; 178 | flex-wrap: wrap; 179 | width: 100%; 180 | } 181 | 182 | .penguin_link { 183 | top: -37px; 184 | right: 0; 185 | background: none; 186 | margin: 0; 187 | } 188 | 189 | .stock_options { 190 | padding: 0 8px 8px; 191 | } 192 | .stock_options_header { 193 | left: -8px; 194 | } 195 | 196 | .level_id_input { 197 | width: 100%; 198 | } 199 | .level_id_input input { 200 | font-size: 36px; 201 | width: 100%; 202 | background: rgba(238, 238, 238, 0.07); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/routes/info/autoPrint.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useEffect, useState } from 'preact/hooks'; 3 | 4 | const clampRange = (value, min, max) => Math.max(Math.min(value || 0, max), min); 5 | 6 | const AutoPrint = ({ 7 | data = [], 8 | speed, 9 | show, 10 | setShow, 11 | }) => { 12 | const [print, setPrint] = useState(['']); 13 | 14 | useEffect(() => { 15 | if (!data || data.length === 0) return; 16 | 17 | let content = ''; 18 | let lines = ['']; 19 | let rowIndex = 0; 20 | let charIndex = 0; 21 | setPrint(['']); 22 | const interval = setInterval(() => { 23 | const str = data[clampRange(rowIndex, 0, data.length - 1)][clampRange(charIndex, 0, data[rowIndex].length - 1)]; 24 | content += str; 25 | charIndex++; 26 | lines.splice(lines.length - 1, 1, content); 27 | if (charIndex === data[rowIndex].length) { 28 | rowIndex++; 29 | charIndex = 0; 30 | lines.push(''); 31 | content = ''; 32 | } 33 | if (rowIndex === data.length) { 34 | lines.pop(); 35 | clearInterval(interval); 36 | setShow(true); 37 | } 38 | setPrint([...lines]); 39 | }, speed); 40 | 41 | return () => { 42 | clearInterval(interval); 43 | }; 44 | }, [show]); 45 | 46 | return ( 47 | 48 | { show ? ( 49 | print.map(line => ( 50 | {line} 51 | )) 52 | ) : ( 53 | data.map(line => ( 54 | {line.join('')} 55 | )) 56 | )} 57 | 58 | ); 59 | }; 60 | 61 | export default AutoPrint; 62 | -------------------------------------------------------------------------------- /src/routes/info/auto_announcements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "server": [ 4 | "en_US", 5 | "ja_JP", 6 | "ko_KR" 7 | ], 8 | "date": "2025-05-01", 9 | "type": "new_operators", 10 | "new_operators": [ 11 | "char_4026_vulpis", 12 | "char_1038_whitw2", 13 | "char_1502_crosly", 14 | "char_4155_talr", 15 | "char_4148_philae", 16 | "char_4165_ctrail" 17 | ] 18 | }, 19 | { 20 | "server": [ 21 | "zh_CN" 22 | ], 23 | "date": "2025-05-01", 24 | "type": "new_operators", 25 | "new_operators": [ 26 | "char_4193_lemuen", 27 | "char_1041_angel2", 28 | "char_4194_rmixer", 29 | "char_4187_graceb", 30 | "char_4188_confes" 31 | ] 32 | }, 33 | { 34 | "server": [ 35 | "en_US", 36 | "ja_JP", 37 | "ko_KR" 38 | ], 39 | "date": "2025-04-10", 40 | "type": "new_operators", 41 | "new_operators": [ 42 | "char_1019_siege2", 43 | "char_4162_cathy", 44 | "char_487_bobb" 45 | ] 46 | }, 47 | { 48 | "server": [ 49 | "zh_CN" 50 | ], 51 | "date": "2025-04-10", 52 | "type": "new_operators", 53 | "new_operators": [ 54 | "char_4179_monstr", 55 | "char_4178_alanna", 56 | "char_445_wscoot" 57 | ] 58 | }, 59 | { 60 | "server": [ 61 | "en_US", 62 | "ja_JP", 63 | "ko_KR" 64 | ], 65 | "date": "2025-03-14", 66 | "type": "new_operators", 67 | "new_operators": [ 68 | "char_4141_marcil", 69 | "char_4144_chilc", 70 | "char_4142_laios", 71 | "char_4143_sensi" 72 | ] 73 | }, 74 | { 75 | "server": [ 76 | "zh_CN" 77 | ], 78 | "date": "2025-03-07", 79 | "type": "new_operators", 80 | "new_operators": [ 81 | "char_450_necras", 82 | "char_4177_brigid", 83 | "char_4171_wulfen" 84 | ] 85 | }, 86 | { 87 | "server": [ 88 | "en_US", 89 | "ja_JP", 90 | "ko_KR" 91 | ], 92 | "date": "2025-02-14", 93 | "type": "new_operators", 94 | "new_operators": [ 95 | "char_4146_nymph", 96 | "char_4147_mitm", 97 | "char_4151_tinman" 98 | ] 99 | }, 100 | { 101 | "server": [ 102 | "zh_CN" 103 | ], 104 | "date": "2025-02-14", 105 | "type": "new_operators", 106 | "new_operators": [ 107 | "char_4010_etlchi", 108 | "char_4173_nowell" 109 | ] 110 | }, 111 | { 112 | "server": [ 113 | "en_US", 114 | "ja_JP", 115 | "ko_KR" 116 | ], 117 | "date": "2025-01-22", 118 | "type": "new_operators", 119 | "new_operators": [ 120 | "char_4138_narant", 121 | "char_4058_pepe", 122 | "char_4140_lasher", 123 | "char_4139_papyrs" 124 | ] 125 | }, 126 | { 127 | "server": [ 128 | "zh_CN" 129 | ], 130 | "date": "2025-01-22", 131 | "type": "new_operators", 132 | "new_operators": [ 133 | "char_1040_blaze2", 134 | "char_2026_yu", 135 | "char_4052_surfer", 136 | "char_4172_xingzh" 137 | ] 138 | } 139 | ] -------------------------------------------------------------------------------- /src/routes/info/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | 4 | background: #333; 5 | } 6 | 7 | .info { 8 | padding: 40px 20px; 9 | margin: 56px auto 0; 10 | width: 100%; 11 | max-width: 1000px; 12 | height: auto; 13 | 14 | text-shadow: 0px 0px 10px #111; 15 | 16 | color: #EEE; 17 | background-color: #666; 18 | } 19 | 20 | .version { 21 | margin-left: 8px; 22 | font-size: 12px; 23 | vertical-align: baseline; 24 | } 25 | 26 | .info a { 27 | color: #EEE; 28 | } 29 | .info code { 30 | display: inline-block; 31 | border: 1px #888 solid; 32 | border-radius: 3px; 33 | color: #EEE; 34 | background-color: #222; 35 | padding: 1px 5px; 36 | margin: 0 3px; 37 | } 38 | .info ul { 39 | list-style-type: none; 40 | padding: 0 20px; 41 | } 42 | .info li:before { 43 | content: '- ' 44 | } 45 | .info blockquote { 46 | margin: 8px 20px 0; 47 | padding: 10px; 48 | border-radius: 4px; 49 | height: auto; 50 | background: #FFFFFF11; 51 | } 52 | .info blockquote p { 53 | margin: 0 54 | } 55 | 56 | .tips { 57 | margin: 16px auto; 58 | display: flex; 59 | justify-content: center; 60 | } 61 | .tip { 62 | margin: 0 10px; 63 | width: 160px; 64 | height: 200px; 65 | } 66 | .tip_donate, .tip_donate_button { 67 | display: flex; 68 | justify-content: space-around; 69 | align-items: center; 70 | } 71 | .tip_donate_button { 72 | padding: 12px; 73 | border-radius: 3px; 74 | background: #036ab7; 75 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.3); 76 | } 77 | .tip_donate_button a { 78 | text-decoration: none; 79 | } 80 | .tip_donate_button .donate_icon { 81 | height: 24px; 82 | margin-right: 8px; 83 | } 84 | 85 | @media screen and (max-width: 601px) { 86 | h1, h3 { 87 | margin: 0; 88 | } 89 | } 90 | @media screen and (min-width: 601px) { 91 | .mobile_title { 92 | display: none; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/routes/materials/components/CompoundRequirementsRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from '../style'; 3 | import cn from 'classnames'; 4 | import { Link } from 'preact-router/match'; 5 | 6 | import ArkItem from '../../../components/item'; 7 | import ArkCell from '../../../components/cell'; 8 | import ArkRow from '../../../components/row'; 9 | 10 | const ArkCompoundRequirementsRow= ({ 11 | compound, 12 | formula, 13 | sum, 14 | material_query, 15 | }) => { 16 | const { 17 | result, 18 | } = compound; 19 | 20 | const CompoundItem = (props) => ( 21 | 28 |
29 | 30 | 36 | 37 |
38 |
39 | ); 40 | 41 | const CompoundFormula = (props) => ( 42 | 50 | { 51 | Object.entries(formula).map(([resource, quantity]) => ( 52 |
53 | 54 | 60 | 61 | x 62 | 2, 67 | } 68 | )} 69 | >{quantity} 70 |
71 | )) 72 | } 73 |
74 | ); 75 | 76 | return ( 77 | 88 | ); 89 | }; 90 | 91 | export default ArkCompoundRequirementsRow; 92 | -------------------------------------------------------------------------------- /src/routes/materials/components/CompoundRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from '../style'; 3 | import cn from 'classnames'; 4 | 5 | import ArkItem from '../../../components/item'; 6 | import ArkCell from '../../../components/cell'; 7 | import ArkRow from '../../../components/row'; 8 | 9 | const ArkCompoundRow = ({ 10 | compound, 11 | material_id, 12 | formula, 13 | sum, 14 | material_query, 15 | stock, 16 | adjustStockItem, 17 | }) => { 18 | const CompoundItem = (props) => ( 19 | 26 |
{ 28 | e.preventDefault(); 29 | e.stopPropagation(); 30 | adjustStockItem(material_id, e.shiftKey ? 10 : 1); 31 | }} 32 | onContextMenu={e => { 33 | e.preventDefault(); 34 | e.stopPropagation(); 35 | adjustStockItem(material_id, e.shiftKey ? -10 : -1); 36 | }} 37 | > 38 | 44 |
45 |
46 | ); 47 | 48 | const CompoundFormula = (props) => ( 49 | 57 | { 58 | Object.entries(formula).map(([resource, quantity]) => ( 59 |
{ 62 | e.preventDefault(); 63 | e.stopPropagation(); 64 | adjustStockItem(resource, e.shiftKey ? 10 : 1); 65 | }} 66 | onContextMenu={e => { 67 | e.preventDefault(); 68 | e.stopPropagation(); 69 | adjustStockItem(resource, e.shiftKey ? -10 : -1); 70 | }} 71 | > 72 | 78 | 79 | x 80 | 2, 85 | } 86 | )} 87 | >{quantity} 88 | 89 |
90 | )) 91 | } 92 |
93 | ); 94 | 95 | return ( 96 | 106 | ); 107 | }; 108 | 109 | export default ArkCompoundRow; 110 | -------------------------------------------------------------------------------- /src/routes/materials/components/CompoundSideProductsRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from '../style'; 3 | 4 | import ArkItem from '../../../components/item'; 5 | 6 | const ArkCompoundSideProductsRow= ({ 7 | compound_data, 8 | stock, 9 | adjustStockItem, 10 | }) => { 11 | const { 12 | side_products, 13 | } = compound_data; 14 | 15 | return ( 16 |
17 | { 18 | side_products.map(({ resource, quantity }) => ( 19 |
{ 22 | e.preventDefault(); 23 | e.stopPropagation(); 24 | adjustStockItem(resource, e.shiftKey ? 10 : 1); 25 | }} 26 | onContextMenu={e => { 27 | e.preventDefault(); 28 | e.stopPropagation(); 29 | adjustStockItem(resource, e.shiftKey ? -10 : -1); 30 | }} 31 | > 32 | 38 |
39 | )) 40 | } 41 |
42 | ); 43 | }; 44 | 45 | export default ArkCompoundSideProductsRow; 46 | -------------------------------------------------------------------------------- /src/routes/materials/components/DropRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { Link } from 'preact-router/match'; 3 | 4 | import ArkCell from '../../../components/cell'; 5 | import ArkRow from '../../../components/row'; 6 | 7 | const DropRow= ({ 8 | level, 9 | times, 10 | quantity, 11 | energy, 12 | }) => { 13 | const LevelCell = () => ( 14 | 15 | 16 | {level} 17 | 18 | 19 | ); 20 | return ( 21 | 31 | ); 32 | }; 33 | 34 | export default DropRow; 35 | -------------------------------------------------------------------------------- /src/routes/materials/components/MaterialInput.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useEffect, useRef } from 'preact/hooks'; 3 | import Fuse from 'fuse.js'; 4 | import style from '../style'; 5 | 6 | import { MONEY, EXP_TAPES, MATERIALS, SKILL_BOOKS, CHIPS } from '../../../models/Resources'; 7 | import item_i18n from '../../../i18n/items.json'; 8 | 9 | const materials = [ 10 | ...MATERIALS, 11 | ...SKILL_BOOKS, 12 | ...CHIPS, 13 | ...EXP_TAPES, 14 | MONEY, 15 | ]; 16 | 17 | const RESOURCE_LANG = {}; 18 | 19 | [ 20 | 'zh_CN', 21 | 'en_US', 22 | 'ja_JP', 23 | 'ko_KR', 24 | ].forEach(lang => { 25 | RESOURCE_LANG[lang] = []; 26 | materials.forEach((material) => { 27 | if (item_i18n[material.id]) { 28 | const material_lang = item_i18n[material.id][lang]; 29 | if (material_lang && material_lang.enabled) { 30 | RESOURCE_LANG[lang].push({ 31 | unique_id: String(material.unique_id), 32 | id: material.id, 33 | ...material_lang, 34 | }); 35 | } 36 | } 37 | }); 38 | }); 39 | 40 | const options = { 41 | shouldSort: true, 42 | threshold: 0.8, 43 | location: 0, 44 | distance: 100, 45 | maxPatternLength: 32, 46 | minMatchCharLength: 1, 47 | keys: [ 48 | 'name', 49 | 'alias', 50 | 'id', 51 | 'unique_id', 52 | ], 53 | }; 54 | 55 | 56 | const fuse_i18n = { 57 | zh_CN: new Fuse(RESOURCE_LANG.zh_CN, options), 58 | en_US: new Fuse(RESOURCE_LANG.en_US, options), 59 | ja_JP: new Fuse(RESOURCE_LANG.ja_JP, options), 60 | ko_KR: new Fuse(RESOURCE_LANG.ko_KR, options), 61 | }; 62 | 63 | export const search = (query, lang) => { 64 | const results = fuse_i18n[lang].search(query); 65 | if (results.length) { 66 | const exact_match = results.find( 67 | ({ name, id, unique_id, alias }) => 68 | name === query || id === query || unique_id === query || alias.includes(query) 69 | ); 70 | if (exact_match) { 71 | return { 72 | unique_id: exact_match.unique_id, 73 | id: exact_match.id, 74 | name: exact_match.name, 75 | }; 76 | } 77 | 78 | const [{ unique_id, id, name }] = results; 79 | return { unique_id, id, name }; 80 | } 81 | return null; 82 | }; 83 | 84 | const ArkMaterialInput = ({ 85 | locale, 86 | material, 87 | setMaterialQuery, 88 | }) => { 89 | const material_input_ref = useRef(null); 90 | 91 | useEffect(() => { 92 | if (!material) { 93 | material_input_ref.current && material_input_ref.current.focus(); 94 | } 95 | }, []); 96 | 97 | return ( 98 |
99 | { 104 | const match = search(e.target.value, locale || 'zh_CN'); 105 | if (match) { 106 | setMaterialQuery(match.id); 107 | } 108 | }} 109 | onClick={() => material_input_ref.current && material_input_ref.current.select()} 110 | /> 111 |
112 | ); 113 | }; 114 | 115 | export default ArkMaterialInput; 116 | -------------------------------------------------------------------------------- /src/routes/materials/components/RequirementRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from '../style'; 3 | import cn from 'classnames'; 4 | import { Link } from 'preact-router/match'; 5 | 6 | import ArkItem from '../../../components/item'; 7 | import ArkCell from '../../../components/cell'; 8 | import ArkRow from '../../../components/row'; 9 | 10 | import { getOperatorName } from '../../../models/Operators'; 11 | import processRecord from '../../../models/processRecord'; 12 | 13 | const ArkRequirementRow = ({ 14 | ir, 15 | showExtendedData, 16 | upgrade: init_record, 17 | upgrade_index, 18 | material_query, 19 | skill_render_map, 20 | }) => { 21 | const { 22 | operator_id, 23 | attribute, 24 | render, 25 | current, 26 | target, 27 | } = init_record; 28 | 29 | const { requirements } = processRecord(init_record); 30 | 31 | const RequirementItems = (props) => ( 32 | 40 | { 41 | requirements 42 | .sort((a, b) => { 43 | if (b.resource === 'G-4-1') return -1; 44 | if (a.resource === material_query) return -1; 45 | return 0; 46 | }) 47 | .map(({ resource, quantity }) => ( 48 |
49 | 50 | 56 | 57 | x 58 | 2, 63 | } 64 | )} 65 | >{quantity} 66 |
67 | )) 68 | } 69 |
70 | ); 71 | 72 | const attribute_text = render || ir(`attribute-${attribute.toLowerCase()}`); 73 | const operator_name = getOperatorName({ id: operator_id, locale: ir.locale }); 74 | const operator_name_extended = operator_name || getOperatorName({ id: operator_id, locale: ir.locale, showExtendedData }); 75 | const is_extended_data = !operator_name; 76 | 77 | return ( 78 | 4 }, 83 | { content: current }, 84 | { content: target }, 85 | RequirementItems, 86 | ] 87 | } 88 | style={{ 89 | height: '56px', 90 | backgroundColor: showExtendedData && is_extended_data ? 'rgba(235, 71, 71, 0.2)' : 'none', 91 | opacity: showExtendedData && is_extended_data ? 0.7 : 1, 92 | }} 93 | fullheight 94 | /> 95 | ); 96 | }; 97 | 98 | export default ArkRequirementRow; 99 | -------------------------------------------------------------------------------- /src/routes/operator/components/OperatorInput.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useEffect, useRef } from 'preact/hooks'; 3 | import style from '../style'; 4 | 5 | import ArkFuseInputCell, { searchAll } from '../../../components/fuseInputCell'; 6 | 7 | const ArkOperatorInput = ({ 8 | locale, 9 | showExtendedData, 10 | operator, 11 | setOperatorId, 12 | setQueryList, 13 | }) => { 14 | const operatorInputRef = useRef(null); 15 | 16 | useEffect(() => { 17 | if (!operator) { 18 | operatorInputRef.current && operatorInputRef.current.focus(); 19 | } 20 | }, []); 21 | 22 | return ( 23 |
24 | setOperatorId(unique_id)} 30 | onChangeRaw={query => { setQueryList && setQueryList(searchAll(query, locale)) }} 31 | onClick={() => operatorInputRef.current && operatorInputRef.current.select()} 32 | /> 33 |
34 | ); 35 | }; 36 | 37 | export default ArkOperatorInput; 38 | -------------------------------------------------------------------------------- /src/routes/operator/components/TableHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import ArkRow from '../../../components/row'; 3 | 4 | const ArkTableHeader = ({ 5 | ir, 6 | config, 7 | operator, 8 | skill_names, 9 | }) => ( 10 | ({ 18 | content: skill_names[index] 19 | ? `${skill_names[index]}` 20 | : `${ 21 | ir('attribute-skill_mastering_prefix', 'Skill ') 22 | }${index + 1}${ 23 | ir('attribute-skill_mastering_suffix', ' Mastering') 24 | }` , 25 | header_level: 'T1', 26 | long_text: skill_names[index] && skill_names[index].length > 5 27 | || ir('attribute-skill_mastering_prefix', 'Skill ').length > 5, 28 | })).filter((e, i) => skill_names.length === 0 || i < skill_names.length), 29 | ] 30 | } 31 | disable_hover 32 | /> 33 | ); 34 | 35 | export default ArkTableHeader; 36 | -------------------------------------------------------------------------------- /src/routes/operator/components/UpgradeInputRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | 3 | import ArkInputCell from '../../../components/inputCell'; 4 | import ArkRow from '../../../components/row'; 5 | 6 | const ArkUpgradeInputRow = ({ 7 | prefix, 8 | attributes, 9 | tab_index_offset, 10 | }) => { 11 | const AttributeInput = ({ attribute, setAttribute }, index) => (props) => ( 12 | setAttribute(value)} 16 | /> 17 | ); 18 | 19 | return ( 20 | { 25 | if (typeof field.override === 'string') { 26 | return ({ content: field.override }); 27 | } 28 | return AttributeInput(field, index); 29 | }), 30 | ] 31 | } 32 | disable_hover 33 | /> 34 | ); 35 | }; 36 | 37 | export default ArkUpgradeInputRow; 38 | -------------------------------------------------------------------------------- /src/routes/operator/components/UpgradeRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from '../style'; 3 | import cn from 'classnames'; 4 | import { Link } from 'preact-router/match'; 5 | 6 | import ArkItem from '../../../components/item'; 7 | import ArkCell from '../../../components/cell'; 8 | import ArkRow from '../../../components/row'; 9 | 10 | const ArkUpgradeRow = ({ 11 | ir, 12 | upgrade, 13 | upgrade_index, 14 | skill_render_map, 15 | fulfilled, 16 | }) => { 17 | const { 18 | attribute, 19 | current, 20 | target, 21 | requirements, 22 | } = upgrade; 23 | 24 | const StockIndicator = (props) => ( 25 | 26 | {fulfilled && ( 27 | tick 33 | )} 34 | 35 | ); 36 | 37 | const RequirementItems = (props) => ( 38 | 46 | { 47 | requirements 48 | .sort((prev, next) => next.resource === 'G-4-1' ? - 1: 0) 49 | .map(({ resource, quantity }) => ( 50 |
51 | 52 | 58 | 59 | x 60 | 2, 65 | } 66 | )} 67 | >{quantity} 68 |
69 | )) 70 | } 71 |
72 | ); 73 | 74 | const attribute_render = skill_render_map[attribute] ? `${skill_render_map[attribute]}` : ir(`attribute-${attribute.toLowerCase()}`); 75 | const attribute_long_text = attribute_render.length > 5; 76 | 77 | return ( 78 | 91 | ); 92 | }; 93 | 94 | export default ArkUpgradeRow; 95 | -------------------------------------------------------------------------------- /src/routes/operator/hooks/useOperatorUpgrade.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'preact/hooks'; 2 | 3 | import processUpgrade from './processUpgrade'; 4 | 5 | const useOperatorUpgrade = (init_values) => { 6 | const init_record = Object.assign( 7 | { 8 | operator_id: null, 9 | current_elite: 0, 10 | target_elite: 0, 11 | current_level: 1, 12 | target_level: 1, 13 | current_all_skill: 1, 14 | target_all_skill: 1, 15 | current_master_skill_1: 0, 16 | target_master_skill_1: 0, 17 | current_master_skill_2: 0, 18 | target_master_skill_2: 0, 19 | current_master_skill_3: 0, 20 | target_master_skill_3: 0, 21 | current_advanced_equipment_1: 0, 22 | target_advanced_equipment_1: 0, 23 | current_advanced_equipment_2: 0, 24 | target_advanced_equipment_2: 0, 25 | current_advanced_equipment_3: 0, 26 | target_advanced_equipment_3: 0, 27 | upgrades: [], 28 | }, 29 | init_values, 30 | ); 31 | const [operatorUpgrade, setOperatorUpgrade_raw] = useState(init_record); 32 | 33 | const setOperatorUpgrade = (to_update) => { 34 | const processed_upgrade = processUpgrade(to_update); 35 | setOperatorUpgrade_raw(processed_upgrade); 36 | global.operator_upgrade = processed_upgrade; 37 | return processed_upgrade; 38 | }; 39 | 40 | const setAttribute = (attribute, value) => setOperatorUpgrade({ 41 | ...operatorUpgrade, 42 | [attribute]: value, 43 | }); 44 | 45 | return { 46 | operatorUpgrade, 47 | setOperatorUpgrade, 48 | setOperatorId: value => setAttribute('operator_id', value), 49 | setCurrentElite: value => setAttribute('current_elite', value), 50 | setTargetElite: value => setAttribute('target_elite', value), 51 | setCurrentLevel: value => setAttribute('current_level', value), 52 | setTargetLevel: value => setAttribute('target_level', value), 53 | setCurrentAllSkill: value => setAttribute('current_all_skill', value), 54 | setTargetAllSkill: value => setAttribute('target_all_skill', value), 55 | setCurrentMasterSkill_1: value => setAttribute('current_master_skill_1', value), 56 | setTargetMasterSkill_1: value => setAttribute('target_master_skill_1', value), 57 | setCurrentMasterSkill_2: value => setAttribute('current_master_skill_2', value), 58 | setTargetMasterSkill_2: value => setAttribute('target_master_skill_2', value), 59 | setCurrentMasterSkill_3: value => setAttribute('current_master_skill_3', value), 60 | setTargetMasterSkill_3: value => setAttribute('target_master_skill_3', value), 61 | setCurrentAdvancedEquipment: (index, value) => setAttribute(`current_advanced_equipment_${index+1}`, value), 62 | setTargetAdvancedEquipment: (index, value) => setAttribute(`target_advanced_equipment_${index+1}`, value), 63 | }; 64 | }; 65 | 66 | export default useOperatorUpgrade; 67 | -------------------------------------------------------------------------------- /src/routes/stock/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useState, useEffect } from 'preact/hooks'; 3 | import style from './style'; 4 | import cn from 'classnames'; 5 | 6 | import ArkMaterialsGroup from '../../components/materialsGroup'; 7 | 8 | import { material_grouping_options, material_list, sorted_material_list_by_locale, material_grouping_options_by_locale } from './options'; 9 | 10 | const item_scale = 0.42; 11 | 12 | const ArkStockView = ({ 13 | ir, 14 | config, 15 | data, 16 | level_id: level_query, 17 | }) => { 18 | const { 19 | state: { stock }, 20 | load, 21 | adjustStockItem, 22 | setStockItem, 23 | } = data; 24 | 25 | const [grouping_type, setGroupingType_raw] = useState(global.grouping_type || 'default'); 26 | 27 | const setGroupingType = (type) => { 28 | if (type && typeof type === 'string') { 29 | try { 30 | global.ga('send', { 31 | hitType: 'event', 32 | eventCategory: 'stock_grouping_type', 33 | eventAction: 'set', 34 | eventLabel: type, 35 | }); 36 | } catch (err) {} 37 | } 38 | setGroupingType_raw(type); 39 | global.grouping_type = type; 40 | }; 41 | 42 | useEffect(() => { 43 | load(); 44 | }, []); 45 | 46 | return ( 47 |
48 |
49 |
50 |
51 | {ir('stock-stock-stock', 'Stock')} 52 |
53 |
54 | {ir('stock-stock-display', 'Display')} 59 |
60 | { 61 | Object.entries(material_grouping_options) 62 | .map(([grouping_option_type, grouping_option]) => ( 63 |
{ 71 | setGroupingType(grouping_option_type); 72 | }} 73 | > 74 | {ir(grouping_option.render)} 75 |
76 | )) 77 | } 78 |
79 |
80 |
81 | 90 |
91 |
92 |
93 |
94 | ); 95 | }; 96 | 97 | export default ArkStockView; 98 | -------------------------------------------------------------------------------- /src/routes/stock/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .page { 7 | padding: 88px 0px 80px; 8 | margin: 0 auto; 9 | width: 100%; 10 | max-width: 1000px; 11 | text-shadow: 0px 0px 10px #111; 12 | 13 | color: #EEE; 14 | background-color: #444; 15 | } 16 | 17 | .section { 18 | padding: 52px 40px 8px; 19 | position: relative; 20 | } 21 | .section_no_header { 22 | padding-top: 32px; 23 | } 24 | .section_header { 25 | width: auto; 26 | position: absolute; 27 | top: 0px; 28 | left: -20px; 29 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 30 | display: flex; 31 | justify-content: flex-start; 32 | padding: 8px 32px; 33 | font-size: 20px; 34 | font-weight: bold; 35 | background-color: #111; 36 | color: #DDD; 37 | text-shadow: 0px 0px 2px rgba(180, 180, 180, 0.6); 38 | } 39 | 40 | .material_grouping_options { 41 | display: flex; 42 | flex-flow: row; 43 | } 44 | 45 | .material_grouping_option { 46 | width: 100px; 47 | margin-right: 20px; 48 | padding: 8px 12px; 49 | text-align: center; 50 | user-select: none; 51 | background-color: #333; 52 | box-shadow: 1px 2px 3px rgba(24, 24, 24, 0.8); 53 | font-size: 16px; 54 | font-weight: 400; 55 | transition: background-color 0.3s ease; 56 | justify-content: center; 57 | } 58 | .material_grouping_option_active { 59 | background-color: #111; 60 | transform: translate(0, 1px); 61 | } 62 | 63 | .stock, .stock_options { 64 | background: linear-gradient(to right, #EEEEEE11 80%, #444 100%); 65 | padding: 20px 24px; 66 | 67 | display: flex; 68 | flex-flow: row; 69 | flex-wrap: wrap; 70 | justify-content: flex-start; 71 | } 72 | 73 | .stock_options { 74 | padding: 12px 24px; 75 | flex-flow: column; 76 | } 77 | 78 | .stock_item { 79 | width: 84px; 80 | height: 96px; 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | } 85 | 86 | .stock_options_header { 87 | width: 100px; 88 | max-width: 100px; 89 | white-space: nowrap; 90 | padding: 8px 24px 8px 28px; 91 | background-color: #222; 92 | margin-bottom: 12px; 93 | position: relative; 94 | left: -36px; 95 | font-size: 18px; 96 | font-weight: bold; 97 | color: #CCC; 98 | } 99 | 100 | .stock_options_header:after { 101 | content: ''; 102 | position: absolute; 103 | width: 64px; 104 | height: 100%; 105 | background: linear-gradient(to right, rgba(34, 34, 34, 1) 0%, rgba(34, 34, 34, 0.6) 40%, rgba(34, 34, 34, 0) 100%); 106 | top: 0; 107 | right: -64px; 108 | } 109 | 110 | @media screen and (max-width: 600px) { 111 | .page, .wrapper { 112 | max-width: unset; 113 | width: 100%; 114 | } 115 | 116 | .section { 117 | padding: 12px 0px 8px; 118 | font-size: 12px; 119 | } 120 | 121 | .section_header { 122 | padding: 8px 20px; 123 | position: relative; 124 | left: 0; 125 | width: 100%; 126 | font-size: 16px; 127 | } 128 | 129 | .material_grouping_option { 130 | padding: 8px 12px; 131 | } 132 | 133 | .stock, .stock_options { 134 | padding: 8px; 135 | width: 100%; 136 | min-width: unset; 137 | background: rgba(238, 238, 238, 0.07); 138 | padding-right: unset; 139 | flex-wrap: wrap; 140 | width: 100%; 141 | } 142 | 143 | .stock_options { 144 | padding: 0 8px 8px; 145 | } 146 | .stock_options_header { 147 | left: -8px; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/routes/table/components/NewUpgradeRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | 3 | import ArkCell from '../../../components/cell'; 4 | import ArkButton from '../../../components/button'; 5 | import ArkRow from '../../../components/row'; 6 | 7 | const ArkNewUpgradeRow = ({ 8 | addEmptyRow, 9 | addLastRow, 10 | resources_filter, 11 | }) => { 12 | const new_upgrade_input = (props) => ( 13 | 14 | { 17 | addEmptyRow(); 18 | }} 19 | /> 20 | 21 | ); 22 | const new_copy_input = (props) => ( 23 | 24 | { 27 | addLastRow(); 28 | }} 29 | /> 30 | 31 | ); 32 | 33 | return ( 34 | 50 | ); 51 | }; 52 | 53 | export default ArkNewUpgradeRow; 54 | -------------------------------------------------------------------------------- /src/routes/table/components/ShortageRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | 3 | import ArkRow from '../../../components/row'; 4 | 5 | const ArkShortageRow = ({ 6 | ir, 7 | shortage, 8 | header_list, 9 | header_skip, 10 | resources_filter, 11 | }) => { 12 | const shortage_row = Array.from(header_list) 13 | .splice(header_skip, header_list.length - header_skip) 14 | .map(e => ({ 15 | content: shortage[e.id] || '', 16 | long_text: ir('table-shortage-compound-prefix', 'Compound').length > 6, 17 | mobile_long_text: shortage[e.id] > 99999 || `${shortage[e.id]}`.startsWith(ir('table-shortage-compound-prefix', 'Compound')), 18 | })); 19 | 20 | return ( 21 | 37 | ); 38 | }; 39 | 40 | export default ArkShortageRow; 41 | -------------------------------------------------------------------------------- /src/routes/table/components/StockRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | 3 | import ArkInputCell from '../../../components/inputCell'; 4 | import ArkRow from '../../../components/row'; 5 | import ArkCell from '../../../components/cell'; 6 | import ArkButton from '../../../components/button'; 7 | 8 | import { EXP, EXP_TAPES } from '../../../models/Resources'; 9 | 10 | const ArkStockRow = ({ 11 | ir, 12 | stock, 13 | setStockItem, 14 | header_list, 15 | header_skip, 16 | resources_filter, 17 | }) => { 18 | const StockInput = ({ resource, tabIndex }) => ( 19 | { 23 | setStockItem(resource.id, quantity); 24 | }} 25 | tabIndex={tabIndex} 26 | /> 27 | ); 28 | 29 | let tab_index_count = 1; 30 | const stock_inputs = Array.from(header_list) 31 | .splice(header_skip, header_list.length - header_skip) 32 | .map((e, i) => { 33 | let tab_index = -1; 34 | if (resources_filter(i + header_skip) && e.id !== EXP.id) { 35 | tab_index = tab_index_count++; 36 | } 37 | return (props) => { 38 | if (e.id === EXP.id) { 39 | const exp_sum = EXP_TAPES 40 | .map(tape => (stock[tape.id] || 0) * tape.value) 41 | .reduce((a, b) => a + b, 0); 42 | return ( 43 | 99999} /> 44 | ); 45 | } 46 | return (); 47 | }; 48 | }); 49 | 50 | return ( 51 | 67 | ); 68 | }; 69 | 70 | export default ArkStockRow; 71 | -------------------------------------------------------------------------------- /src/routes/table/components/SummaryRow.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useState } from 'preact/hooks'; 3 | 4 | import ArkRow from '../../../components/row'; 5 | import ArkCell from '../../../components/cell'; 6 | import ArkButton from '../../../components/button'; 7 | 8 | const sort_by_name = (order) => (a, b) => a.operator > b.operator ? (order ? 0 : -1) : (order ? -1 : 0); 9 | const sort_by_attribute = (order) => (a, b) => a.attibute > b.attibute ? (order ? 0 : -1) : (order ? -1 : 0); 10 | 11 | const ArkSummaryRow = ({ 12 | ir, 13 | summary, 14 | toggleHiddenAll, 15 | sortRecords, 16 | header_list, 17 | header_skip, 18 | resources_filter, 19 | }) => { 20 | const [hidden_all, setHiddenAll] = useState(true); 21 | const [sorting_order_name_asc, setSortingOrderNameAsc] = useState(false); 22 | const [sorting_order_attribute_asc, setSortingOrderAttributeAsc] = useState(false); 23 | 24 | const summary_row = Array.from(header_list) 25 | .splice(header_skip, header_list.length - header_skip) 26 | .map(e => ({ 27 | content: summary[e.id] || '', 28 | mobile_long_text: summary[e.id] > 99999, 29 | })); 30 | 31 | 32 | const AllVisibilityButton = (props) => ( 33 | 34 | { 36 | setHiddenAll(prev => { 37 | toggleHiddenAll(hidden_all); 38 | return !prev; 39 | }); 40 | }} 41 | > 42 | tick 47 | 48 | 49 | ); 50 | 51 | const SortByNameButton = (props) => ( 52 | 53 | { 55 | setSortingOrderNameAsc((prev) => { 56 | sortRecords(sort_by_name(sorting_order_name_asc)); 57 | return !prev; 58 | }); 59 | }} 60 | > 61 | 排序 62 | 63 | 64 | ); 65 | 66 | const SortByAttributeButton = (props) => ( 67 | 68 | { 70 | setSortingOrderAttributeAsc((prev) => { 71 | sortRecords(sort_by_attribute(sorting_order_attribute_asc)); 72 | return !prev; 73 | }); 74 | }} 75 | > 76 | 排序 77 | 78 | 79 | ); 80 | 81 | return ( 82 | 101 | ); 102 | }; 103 | 104 | export default ArkSummaryRow; 105 | -------------------------------------------------------------------------------- /src/routes/table/components/TableHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | 3 | import ArkRow from '../../../components/row'; 4 | import ArkCell from '../../../components/cell'; 5 | import ArkItem from '../../../components/item'; 6 | 7 | import item_i18n from '../../../i18n/items.json'; 8 | 9 | const getItemName = ({ id, locale, fallback }) => { 10 | const item = item_i18n[id]; 11 | if (item) { 12 | const locale_item = item[locale]; 13 | if (locale_item && locale_item.enabled) { 14 | const name = locale_item.name; 15 | return name; 16 | } 17 | } 18 | return fallback; 19 | }; 20 | 21 | const ArkTableHeader = ({ 22 | ir, 23 | config, 24 | focus_materials, 25 | header_list, 26 | header_skip, 27 | resources_filter, 28 | toggleFocusMaterial, 29 | }) => { 30 | const ArkIconCell = (e) => (props) => ( 31 | id === e.id)} 35 | onClick={() => toggleFocusMaterial(e.id)} 36 | header 37 | {...props} 38 | > 39 | 40 | {getItemName({ id: e.id, locale: config.locale, fallback: e.name })} 41 | 42 | ); 43 | 44 | return ( 45 | 5, 56 | force_no_shrink: true, 57 | }, 58 | { content: ir('table-header-attribute', 'Attribute'), header_level: 'T1', force_no_shrink: true }, 59 | { content: ir('table-header-current', 'Current'), header_level: 'T1' }, 60 | { content: ir('table-header-target', 'Target'), header_level: 'T1' }, 61 | ...Array.from(header_list) 62 | .splice(header_skip, header_list.length - header_skip) 63 | .map(e => ArkIconCell(e)), 64 | ] 65 | } 66 | disable_hover 67 | resources_filter={resources_filter} 68 | icons_header 69 | header 70 | /> 71 | ); 72 | }; 73 | 74 | export default ArkTableHeader; 75 | -------------------------------------------------------------------------------- /src/routes/table/sections/FocusMaterials/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import style from './style'; 3 | import cn from 'classnames'; 4 | 5 | import { RESOURCES } from '../../../../models/Resources'; 6 | import getShortageFocusMaterials from '../../../../models/getShortageFocusMaterials'; 7 | import ArkMaterialCard from '../../../../components/materialCard'; 8 | 9 | const ArkFocusMaterials = ({ 10 | ir, 11 | config, 12 | focus_materials, 13 | toggleFocusMaterial, 14 | addFocusMaterials, 15 | setFocusMaterials, 16 | clearFocusMaterials, 17 | compound_materials, 18 | toggleCompoundMaterial, 19 | compoundMaterial, 20 | stock, 21 | adjustStockItem, 22 | summary, 23 | shortage, 24 | drops, 25 | }) => { 26 | const material_groups = focus_materials 27 | .filter(({ id: material_id, options }) => RESOURCES[material_id] && options && !options.hidden); 28 | 29 | return ( 30 |
36 |

{ir('focus_materials-track_materials', 'Focusing materials')}

37 | { 38 | config.showFocusMaterials && material_groups.length !== 0 && ( 39 |
clearFocusMaterials(e)} 42 | /> 43 | ) 44 | } 45 | { 46 | config.showFocusMaterials && material_groups.length === 0 && ( 47 |
setFocusMaterials(getShortageFocusMaterials(shortage))} 50 | > 51 | {ir('focus_materials-smart_track', 'Auto')} 52 |
53 | ) 54 | } 55 |
56 |
57 | { 58 | material_groups.map(({ id: material_id, options }, index) => ( 59 | id === material_id)} 73 | toggleCompoundMaterial={toggleCompoundMaterial} 74 | compoundMaterial={compoundMaterial} 75 | drops={drops} 76 | {...options} 77 | /> 78 | )) 79 | } 80 | { 81 | material_groups.length === 0 && ( 82 |

{ir('focus_materials-placeholder_hint', 'Click table header or [+] button to focus on certain materials')}

83 | ) 84 | } 85 |
86 |
87 |
88 | ); 89 | }; 90 | 91 | export default ArkFocusMaterials; 92 | -------------------------------------------------------------------------------- /src/routes/table/sections/FocusMaterials/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | user-select: none; 4 | position: fixed; 5 | bottom: 0; 6 | max-height: 450px; 7 | background-color: #333; 8 | } 9 | .wrapper > h2 { 10 | position: absolute; 11 | left: 64px; 12 | top: -40px; 13 | padding: 8px 32px; 14 | box-shadow: 0px 2px 3px rgba(24, 24, 24, 0.8); 15 | z-index: 9920; 16 | font-size: 20px; 17 | 18 | color: #DDDDDD; 19 | background-color: #111; 20 | text-shadow: 0px 0px 2px rgba(240, 240, 240, 0.2); 21 | transform: translate(0, 0); 22 | transition: transform 0.4s ease; 23 | } 24 | .wrapper .hint { 25 | color: #DDD; 26 | line-height: 80px; 27 | } 28 | .wrapper.hide_focus_materials, .wrapper.hide_focus_materials .scroll, 29 | .wrapper.hide_focus_materials:hover, .wrapper.hide_focus_materials:hover .scroll { 30 | max-height: 20px; 31 | } 32 | 33 | .wrapper::before { 34 | content: ''; 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 10%; 40 | z-index: 9918; 41 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4) 10%, rgba(0, 0, 0, 0) 100%); 42 | user-select: none; 43 | } 44 | 45 | .scroll { 46 | max-height: 450px; 47 | overflow-y: scroll; 48 | } 49 | .scroll::-webkit-scrollbar { 50 | height: 0px; 51 | width: 0px; 52 | background: transparent; 53 | } 54 | 55 | .clear, .auto { 56 | position: absolute; 57 | background-color: #111; 58 | box-shadow: 0px 2px 3px rgba(24, 24, 24, 0.8); 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | transition: transform 0.4s ease; 63 | transform: translate(0px, 2px); 64 | z-index: 9999; 65 | } 66 | .clear { 67 | right: 30px; 68 | bottom: 40px; 69 | height: 36px; 70 | width: 72px; 71 | } 72 | .auto { 73 | right: 20px; 74 | top: 20px; 75 | height: 36px; 76 | width: 72px; 77 | } 78 | .clear:hover, .auto:hover { 79 | transform: translate(0px, 0px); 80 | } 81 | .clear:before, .clear:after, .auto:before, .auto:after { 82 | position: absolute; 83 | content: ''; 84 | width: 3px; 85 | height: 20px; 86 | background-color: #CCC; 87 | transition: transform 0.3s ease 0.1s, background-color 0.3s ease 0.1s; 88 | } 89 | .auto span { 90 | color: #CCC; 91 | font-size: 12px; 92 | transition: opacity 0.3s ease; 93 | opacity: 0; 94 | } 95 | .clear:before { 96 | transform: rotate(45deg); 97 | } 98 | .clear:after { 99 | transform: rotate(-45deg); 100 | } 101 | .clear:hover:before { 102 | transform: rotate(270deg); 103 | } 104 | .clear:hover:after { 105 | transform: rotate(-90deg); 106 | } 107 | .auto:before { 108 | transform: rotate(0deg); 109 | } 110 | .auto:after { 111 | transform: rotate(-90deg); 112 | } 113 | .auto:hover:before { 114 | transform: translate(28px, 0px) rotate(180deg); 115 | } 116 | .auto:hover:after { 117 | transform: translate(-28px, 0px) rotate(180deg); 118 | } 119 | .auto:hover span { 120 | opacity: 1; 121 | } 122 | .clear:hover:before, .clear:hover:after, 123 | .auto:hover:before, .auto:hover:after { 124 | background-color: #CCC; 125 | } 126 | 127 | .materials { 128 | max-width: 1600px; 129 | margin: 0 auto; 130 | padding: 80px 0 40px; 131 | display: flex; 132 | flex-flow: row; 133 | flex-wrap: wrap; 134 | align-content: flex-start; 135 | justify-content: center; 136 | } 137 | .wrapper, .scroll { 138 | transition: max-height 0.4s ease; 139 | } 140 | 141 | @media screen and (min-width: 601px) { 142 | .wrapper:not(.hide_focus_materials):hover h2 { 143 | transform: translate(0, 54px); 144 | } 145 | .wrapper:hover, .wrapper:hover .scroll { 146 | max-height: 650px; 147 | } 148 | } 149 | @media screen and (max-width: 600px) { 150 | .wrapper, .wrapper .scroll { 151 | max-height: 250px; 152 | } 153 | .wrapper > h2 { 154 | font-size: 14px; 155 | left: 8px; 156 | top: -28px; 157 | } 158 | .clear, .auto { 159 | transform: scale(0.8); 160 | right: 8px; 161 | } 162 | .materials { 163 | padding: 40px 0 20px; 164 | } 165 | } 166 | @media screen and (max-height: 400px) { 167 | .wrapper, .wrapper .scroll { 168 | max-height: 20px; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/routes/table/sections/SortingPanel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useRef, useEffect } from 'preact/hooks'; 3 | import cn from 'classnames'; 4 | 5 | import panel_style from './style'; 6 | 7 | let debounce = -1; 8 | 9 | const ArkSortingPanel = ({ 10 | ir, 11 | config, 12 | records, 13 | moveRows, 14 | clearSelection, 15 | }) => { 16 | const headerRef = useRef(null); 17 | 18 | useEffect(() => { 19 | const handleScroll = () => { 20 | if (debounce) { 21 | clearTimeout(debounce); 22 | } 23 | 24 | debounce = setTimeout(() => { 25 | if (headerRef && headerRef.current) { 26 | const should_fixed = config.tableRowHeader; 27 | headerRef.current.style.transform = should_fixed 28 | ? `translate(${window.scrollX}px, ${window.scrollY}px)` 29 | : `translate(0, ${window.scrollY}px)`; 30 | } 31 | }, 100); 32 | }; 33 | window.addEventListener('scroll', handleScroll); 34 | handleScroll() 35 | 36 | return () => { 37 | window.removeEventListener('scroll', handleScroll); 38 | }; 39 | }, [config.tableRowHeader]); 40 | 41 | const selected_count = records.filter(record => record.selected).length; 42 | 43 | return ( 44 |
0 ? 1 : 0, 50 | pointerEvents: selected_count > 0 ? 'initial' : 'none' 51 | }} 52 | ref={headerRef} 53 | > 54 |
55 |
59 | {selected_count}{' '}{ir('table-sorting_panel-selected_count', 'selected')} 60 |
61 |
{ moveRows('to_first'); }} 63 | > 64 | move-first 67 |
68 |
{ moveRows('up'); }} 70 | > 71 | move-up 74 |
75 |
{ moveRows('down'); }} 77 | > 78 | move-down 81 |
82 |
{ moveRows('to_last'); }} 84 | > 85 | move-last 88 |
89 |
{ clearSelection(); }} 91 | > 92 | move-last 95 |
96 |
97 |
98 | ); 99 | }; 100 | 101 | export default ArkSortingPanel; -------------------------------------------------------------------------------- /src/routes/table/sections/SortingPanel/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: absolute; 3 | top: 132px; 4 | background: linear-gradient(to bottom, #15849f, transparent); 5 | width: 491px; 6 | height: 54px; 7 | opacity: 0; 8 | transition: transform 0.45s ease, opacity 0.3s ease; 9 | } 10 | 11 | .buttons { 12 | height: 100%; 13 | width: 100%; 14 | display: flex; 15 | flex-flow: row; 16 | flex-wrap: nowrap; 17 | justify-content: flex-start; 18 | align-items: center; 19 | padding: 8px 16px; 20 | } 21 | .button { 22 | padding: 8px 16px; 23 | margin-right: 10px; 24 | border-radius: 1px; 25 | 26 | font-size: 11px; 27 | font-weight: bold; 28 | text-shadow: 0px 1px 3px rgba(200, 200, 200, 0.3); 29 | display: flex; 30 | flex-flow: row-reverse; 31 | justify-content: flex-start; 32 | white-space: nowrap; 33 | align-items: center; 34 | user-select: none; 35 | transition: background 0.6s ease, color 0.6s ease; 36 | 37 | background: #111; 38 | box-shadow: 0px 3px 4px 0 #33333366; 39 | color: #EEE; 40 | } 41 | .button:last-of-type { 42 | margin-right: 0; 43 | } 44 | 45 | .image_button { 46 | padding: 4px; 47 | min-width: 30px; 48 | min-height: 30px; 49 | } 50 | 51 | @media screen and (max-width: 540px) { 52 | .wrapper { 53 | top: 112px; 54 | } 55 | } -------------------------------------------------------------------------------- /src/routes/table/sections/UpgradeRowHeaders/index.js: -------------------------------------------------------------------------------- 1 | import React from 'preact'; 2 | import { useRef, useEffect } from 'preact/hooks'; 3 | 4 | import style from '../../style'; 5 | 6 | import ArkRow from '../../../../components/row'; 7 | import ArkUpgradeInputRow from '../../components/UpgradeInputRow'; 8 | import ArkNewUpgradeRow from '../../components/NewUpgradeRow'; 9 | import ArkSummaryRow from '../../components/SummaryRow'; 10 | 11 | let debounce = -1; 12 | 13 | const ArkUpgradeRowHeaders = ({ 14 | ir, 15 | config, 16 | records, 17 | addLastRow, 18 | addEmptyRow, 19 | updateRow, 20 | removeRow, 21 | completeRow, 22 | fulfillment_statuses, 23 | summary, 24 | toggleHiddenAll, 25 | sortRecords, 26 | header_list, 27 | header_skip, 28 | resources_filter, 29 | }) => { 30 | const headerRef = useRef(null); 31 | 32 | useEffect(() => { 33 | const handleScroll = () => { 34 | if (debounce) { 35 | clearTimeout(debounce); 36 | } 37 | 38 | debounce = setTimeout(() => { 39 | if (headerRef && headerRef.current) { 40 | const should_hide = window.scrollX <= 400; 41 | headerRef.current.style.opacity = should_hide ? 0 : 1; 42 | headerRef.current.style.pointerEvents = should_hide ? 'none' : 'initial'; 43 | headerRef.current.style.transform = `translateX(${window.scrollX}px)`; 44 | } 45 | }, 100); 46 | }; 47 | window.addEventListener('scroll', handleScroll); 48 | handleScroll(); 49 | 50 | return () => { 51 | window.removeEventListener('scroll', handleScroll); 52 | }; 53 | }, [config.table_row_header]); 54 | 55 | return ( 56 |
60 | 74 | 88 | i < header_skip} 96 | /> 97 | { 98 | records.map((record, index) => ( 99 | i < header_skip} 112 | /> 113 | )) 114 | } 115 | 116 | 120 |
121 | ); 122 | }; 123 | 124 | export default ArkUpgradeRowHeaders; -------------------------------------------------------------------------------- /src/routes/table/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | width: 100%; 4 | height: 100%; 5 | flex-flow: column; 6 | align-content: stretch; 7 | } 8 | 9 | .table { 10 | padding: 56px 0 560px; 11 | } 12 | .table.hide_focus_materials { 13 | padding-bottom: 160px; 14 | } 15 | 16 | @media screen and (max-width: 600px) { 17 | .table { 18 | overflow: scroll visible; 19 | } 20 | } 21 | 22 | .row_header { 23 | position: absolute; 24 | top: 132px; 25 | background: #333; 26 | transform: translateX(0); 27 | opacity: 1; 28 | transition: transform 0.45s ease, opacity 0.3s ease; 29 | } 30 | 31 | @media screen and (max-width: 540px) { 32 | .row_header { 33 | display: none; 34 | } 35 | } -------------------------------------------------------------------------------- /src/services/arkplanner/generatePayload.js: -------------------------------------------------------------------------------- 1 | import { MATERIALS } from '../../models/Resources'; 2 | 3 | const generateArkPlannerData = (summary, stock) => MATERIALS.map( 4 | ({ id, unique_id, name }) => ({ 5 | id: String(unique_id), 6 | name, 7 | need: summary[id] || 0, 8 | have: stock[id] || 0, 9 | }) 10 | ); 11 | 12 | export default generateArkPlannerData; -------------------------------------------------------------------------------- /src/services/arkplanner/index.js: -------------------------------------------------------------------------------- 1 | const URL = 'https://planner.penguin-stats.io/plan'; 2 | 3 | const callPlanner = async ({ required, owned }, options = {}) => { 4 | let result = null; 5 | try { 6 | const response = await fetch( 7 | URL, 8 | { 9 | method: 'POST', 10 | mode: 'cors', 11 | cache: 'no-cache', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | }, 15 | body: JSON.stringify({ 16 | required, 17 | owned, 18 | ...options, 19 | }), 20 | }, 21 | ); 22 | result = await response.json(); 23 | } catch (err) { 24 | return null; 25 | } 26 | return result; 27 | }; 28 | 29 | export default callPlanner; 30 | -------------------------------------------------------------------------------- /src/services/arkplanner/parse.js: -------------------------------------------------------------------------------- 1 | import { RESOURCES } from '../../models/Resources'; 2 | 3 | const convert = (items) => { 4 | const stock = {}; 5 | items.forEach(r => { 6 | const { 7 | id, have, name 8 | } = r; 9 | const item = Object.entries(RESOURCES).find( 10 | ([rid, m]) => String(m.unique_id) === String(id) || m.name === name || rid === String(id) 11 | ); 12 | if (item && have > 0) { 13 | stock[item[1].id] = Number(have); 14 | } 15 | }); 16 | 17 | return stock; 18 | } 19 | 20 | const parse = (raw) => { 21 | try { 22 | const data = JSON.parse(raw); 23 | if (data.items) { 24 | return convert(data.items); 25 | } 26 | if (data && data.length > 0) { 27 | return convert(data); 28 | } 29 | return {} 30 | } catch (err) { 31 | console.warn(err); 32 | } 33 | }; 34 | 35 | export default parse; -------------------------------------------------------------------------------- /src/services/penguinstats.js: -------------------------------------------------------------------------------- 1 | const URL = 'https://penguin-stats.io/PenguinStats/api/v2/result/matrix'; 2 | 3 | const fetchStatMatrix = async () => { 4 | let matrix = []; 5 | try { 6 | const response = await fetch( 7 | URL, 8 | { 9 | method: 'GET', 10 | mode: 'cors', 11 | } 12 | ); 13 | ({ matrix } = await response.json()); 14 | } catch (err) { 15 | return []; 16 | } 17 | return matrix; 18 | }; 19 | 20 | export default fetchStatMatrix; 21 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: calc(100% - 28px); 3 | width: 100%; 4 | padding: 0; 5 | margin: 0; 6 | background: #333; 7 | font-family: 'Microsoft YaHei', 'Helvetica Neue', arial, sans-serif; 8 | font-weight: 400; 9 | color: #444; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | #app { 19 | height: 100%; 20 | } 21 | 22 | input { 23 | -webkit-appearance: none; 24 | outline: none; 25 | } 26 | input[type="number"]::-webkit-outer-spin-button, 27 | input[type="number"]::-webkit-inner-spin-button { 28 | -webkit-appearance: none; 29 | margin: 0; 30 | } 31 | a { 32 | color: #EEE; 33 | } 34 | 35 | .row { 36 | width: auto; 37 | display: flex; 38 | flex-direction: row; 39 | flex-wrap: nowrap; 40 | justify-content: flex-start; 41 | align-items: center; 42 | position: relative; 43 | } 44 | 45 | .row.sticky { 46 | position: sticky; 47 | } 48 | 49 | .row.header { 50 | position: sticky; 51 | top: 56px; 52 | z-index: 999; 53 | box-shadow: 1px 2px 3px 0px #33333333; 54 | } 55 | 56 | @media screen and (max-width: 600px) { 57 | .row.header { 58 | top: 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 22 | 23 | <%= htmlWebpackPlugin.options.title %> 24 | 25 | 26 | 27 | 28 | <% if (htmlWebpackPlugin.options.manifest.theme_color) { %> 29 | 30 | <% } %> 31 | <% const loadManifest = htmlWebpackPlugin.options.createLoadManifest(compilation.assets, webpack.namedChunkGroups);%> 32 | <% const filesRegexp = htmlWebpackPlugin.options.inlineCss ? /\.(chunk\.\w{5}\.css|js)$/ : /\.(css|js)$/;%> 33 | <% for (const file in loadManifest[htmlWebpackPlugin.options.url]) { %> 34 | <% if (htmlWebpackPlugin.options.preload && file && file.match(filesRegexp)) { %> 35 | <% /* crossorigin for main bundle as that is loaded from ` 45 | 46 | <% 47 | /*Fetch and Promise polyfills are not needed for browsers that support type=module 48 | Please re-evaluate below line if adding more polyfills.*/ 49 | %> 50 | 51 | 52 | <% } else { %> 53 | 54 | 55 | <% } %> 56 | 57 | 58 | --------------------------------------------------------------------------------