import { NodeIDStrict, AddressNode, AddressTree, AddressNodeRoot, NodeID, LabelChineseSimplified, LabelEnglish, AddressNodeInternal, AddressNodeLeaf, } from './Types'; import { ADDRTREE_ROOT_NAME, ADDRTREE_ROOT_LEVEL_KEY, PANE_LEVEL_KEYS, } from './util'; import { ADDRTREE_ROOT_KEY } from './Constant'; import axios from 'axios'; const URL_CNUSER_GLOBALADDR = '/access/AjaxGlobalAddress.do?method=globalCountries'; const URL_CNUSER_LOCALADDR = '/access/AjaxGlobalAddress.do?method=getDivisionByName'; type RespCNUserDivision = { divisionId: IDType; nameCN: LabelChineseSimplified; nameEN: LabelEnglish; }; type RespCNUserDivisionGlobal = RespCNUserDivision; type RespCNUserDivisionLocal = { childDivision: RespCNUserDivisionLocal[]; divisionId: NodeIDStrict; divisionName: string; parentId: NodeIDStrict; }; interface IAddressDataConnect { // getChildrenOfId(id: NodeIDStrict): Promise; // getParentChainOfId(id: NodeIDStrict): Promise; // getParentChainOfIdSync(id: NodeIDStrict): AddressNode[] | null; getNodeById( id: NodeIDStrict, fetchChildren?: boolean ): Promise; getNodeByIdSync(id: NodeIDStrict): AddressNode | null; getRootNode(): AddressNodeRoot; } class AddressDataConnectStatic {} class AddressDataConnectCNUser implements IAddressDataConnect { constructor() { this.getRootNode = this.getRootNode.bind(this); this.getNodeById = this.getNodeById.bind(this); this.getNodeByIdSync = this.getNodeByIdSync.bind(this); this.allNodesSet.set('0', this.addressTree); } addressTree: AddressNodeRoot = { id: ADDRTREE_ROOT_KEY, parent: null, parentNode: null, children: null, isLeaf: false, ofCountry: '0', nameZh: ADDRTREE_ROOT_NAME, namePy: '', namePyJoined: '', namePyAbbr: '', nameEn: ADDRTREE_ROOT_NAME, level: -1, levelKey: ADDRTREE_ROOT_LEVEL_KEY, value: ADDRTREE_ROOT_KEY, label: ADDRTREE_ROOT_NAME, }; allNodesSet: Map = new Map(); private globalInited = false; // attachNode(node: AddressNodeInternal | AddressNodeLeaf, parent: AddressNode): // AddressNodeInternal | AddressNodeLeaf { // node.parentNode = parent; // node.parent = parent.id; // parent // return node; // } async initGlobal(): Promise { if (!this.globalInited) { const resp = await axios.get(URL_CNUSER_GLOBALADDR, { withCredentials: true, }); const data: AddressNodeInternal[] = resp.data.data.country.map( (item: RespCNUserDivisionGlobal): AddressNodeInternal => ({ id: item.divisionId.toString(), nameZh: item.nameCN, nameEn: item.nameEN, namePy: '', namePyJoined: '', namePyAbbr: '', level: 0, levelKey: PANE_LEVEL_KEYS[0], ofCountry: '0', value: item.divisionId.toString(), label: item.nameEN.toString(), isLeaf: false, parent: ADDRTREE_ROOT_KEY, parentNode: this.getRootNode(), children: null, }) ); data.forEach((item: AddressNodeInternal) => this.allNodesSet.set(item.id, item) ); data.sort( (lhs, rhs) => parseInt(lhs.id.toString()) - parseInt(rhs.id.toString()) ); this.getRootNode().children = data; this.globalInited = true; } return this.getRootNode(); } async ensureCountry(id: NodeIDStrict): Promise { const rootNode = await this.initGlobal(); const countryNode = this.allNodesSet.get(id)! as AddressNodeInternal; if (countryNode.isLeaf || countryNode.children) { return countryNode; } else { const resp = await axios.get(URL_CNUSER_LOCALADDR, { params: { name: countryNode.nameZh }, withCredentials: true, }); const data: RespCNUserDivisionLocal[] = resp.data.data.globalDivision; const mapDivision = ( item: RespCNUserDivisionLocal ): AddressNodeInternal | AddressNodeLeaf => { // seems we do not need level anymore ... // however, safety is compromised here // since some assumption on level/levelKey is broken here const ret: AddressNodeInternal | AddressNodeLeaf = { id: item.divisionId, isLeaf: item.childDivision.length == 0, parent: item.parentId, parentNode: this.getNodeByIdSync(item.parentId)!, ofCountry: id, value: item.divisionId, label: item.divisionName, children: null, level: 65535, levelKey: 'NO_LEVEL', } as any; this.allNodesSet.set(item.divisionId, ret); if (!ret.isLeaf) { ret.children = item.childDivision.map(mapDivision); if (id != '1') { ret.children.sort((lhs, rhs) => lhs.label.localeCompare(rhs.label)); } } return ret; }; const nodes = data.map(mapDivision); if (id != '1') { nodes.sort((lhs, rhs) => lhs.label.localeCompare(rhs.label)); } this.allNodesSet.get(id)!.isLeaf = nodes.length == 0; this.allNodesSet.get(id)!.children = nodes; return countryNode; } } // interface - IAddressDataConnect getRootNode(): AddressNodeRoot { return this.addressTree; } async getNodeById( id: NodeIDStrict, fetchChildren: boolean = false ): Promise { const rootNode = await this.initGlobal(); if (id == ADDRTREE_ROOT_KEY) { return rootNode; } else { let country: NodeIDStrict | null = null; const len = id.toString().length; switch (len) { case 6: // province/city/area, Greater China case 9: // town, Greater China country = '1'; break; case 18: // all sub-sovereign-state-division except Greater China country = parseInt(id.toString().substr(1, 3)).toString(); break; default: if (len <= 3 && fetchChildren) { country = id.toString(); } } if (country) { await this.ensureCountry(country); } return this.allNodesSet.get(id) || null; } } getNodeByIdSync(id: NodeIDStrict): AddressNode | null { return this.allNodesSet.get(id) || null; } } export class AddressStore { impl: IAddressDataConnect; constructor(impl: IAddressDataConnect) { this.impl = impl; } } export const defaultAddressDataConnectStatic = new AddressDataConnectStatic(); // export const defaultAddressStoreStatic = new AddressStore(defaultAddressDataConnectStatic); export const defaultAddressDataConnectCNUser = new AddressDataConnectCNUser(); export const defaultAddressStoreCNUser = new AddressStore( defaultAddressDataConnectCNUser );