///
import {
PANE_LEVELS,
PANE_ATTRIBUTES,
PANE_LEVEL_KEYS,
loadAddressData,
loadTownData,
traceTownParents,
defaultTextSerializer,
standardValueEqual,
addressTreeOfSet,
delay,
} from './util';
import React from 'react';
import {
NodeID,
AddressTree,
AddressNode,
AddressNodeSet,
AddressDataset,
NodeSelection,
NodeIDStrict,
NodeChain,
NodeAnyValue,
SelectionChain,
NodeSelectionInternalData,
AddressRequestParams,
} from './Types';
import { last } from './lodash-alt';
// import last = require("lodash/fp/last")
// import * as last from "lodash/fp/last"
// import { Dictionary } from "lodash/fp";
import { ConfigProvider } from '@alifd/next';
// import LocaleProvider from "@alifd/next/lib/locale-provider";
import { ADDRTREE_ROOT_KEY } from './Constant';
export interface AddressCompProps {
className?: string;
style?: object;
requestAddressUrl: string;
requestTownUrl: string;
requestAddressLevelUrl: string;
level: number;
hiddenData: NodeIDStrict[];
ignored: NodeIDStrict[];
disabled: boolean;
dataOverride: { [key: string]: string[] };
preprocessor: (dataRaw: object) => { [key: string]: string[] };
}
export interface AddressCompState {
paneRoot: AddressNode | null;
addressTree: AddressTree;
}
type AddressComp = React.Component;
// type RequestParam = {
// addressURL: string
// townURL: string
// parentURL: string
// ignored: NodeIDStrict[];
// dataOverride: { [key: string]: string[] };
// preprocessor: (dataRaw: { [key: string]: string[] }) => { [key: string]: string[] };
// }
export interface AddressCompE extends AddressComp {
allNodesSet: AddressNodeSet;
futureDatasetTem?: Promise;
}
export function getRequestParams(
props: AddressCompProps
): AddressRequestParams {
return {
addressURL: props.requestAddressUrl,
townURL: props.requestTownUrl,
parentURL: props.requestAddressLevelUrl,
ignored: props.ignored,
dataOverride: props.dataOverride,
preprocessor: props.preprocessor,
};
}
export function setStateAsync(self: AddressComp, updater: any) {
return new Promise((resolve, reject) => {
// @ts-ignore
self.setState(updater, () => resolve());
});
}
export async function loadAndStoreDataset(
self: AddressComp,
params = getRequestParams(self.props)
) {
const {
countryList,
allNodesSet,
addressTree,
} = await (loadAddressData as Function)(params);
const selfE = self as AddressCompE;
selfE.allNodesSet = allNodesSet;
await setStateAsync(self, {
countryList,
addressTree,
paneRoot: allNodesSet['1'],
});
return {
addressTree,
allNodesSet,
};
}
export async function dataset(
self: AddressComp,
params: AddressRequestParams = getRequestParams(self.props)
): Promise {
const selfE = self as AddressCompE;
if (self.state.paneRoot) {
return {
addressTree: self.state.addressTree,
allNodesSet: (self as AddressCompE).allNodesSet,
};
} else {
if (!selfE.futureDatasetTem) {
selfE.futureDatasetTem = loadAndStoreDataset(self, params);
}
return await selfE.futureDatasetTem;
}
}
export function selectNodeFromChain(
node: AddressNode,
chain: NodeID[]
): AddressNode[] {
const ret = [node];
if (!chain.length) {
return ret;
}
if (!node.children || node.isLeaf) {
throw new Error(`children expected for node ${node.id}, chain ${chain}`);
}
const nextRoot = node.children!.find(node => node.id == chain[0]);
if (!nextRoot) {
throw new Error(
`children ${chain[0]} expected for node ${node.id}, chain ${chain}`
);
}
return ret.concat(
selectNodeFromChain(nextRoot, chain.slice(1, chain.length))
);
}
export function nodeChainFromNode(node: AddressNode): NodeChain {
return (!node || !node.parentNode
? []
: nodeChainFromNode(node.parentNode)
).concat(node);
}
export function foldNodeChainToSelection(
nodeChain: AddressNode[]
): NodeSelection {
const ret: any = {};
nodeChain.forEach(node => {
if (node) {
ret[node.levelKey] = node.id;
}
});
return ret;
}
export function selectionToChain(value: NodeSelection): SelectionChain {
// TODO: fix the dictionary type
return (
PANE_LEVEL_KEYS.map(key => (value as any)[key]) // keys array, must be in order
// necessary because there is cases that one or more
// intermediate levels with no value
.filter(v => !!v)
);
}
export function anyValueToSelectionId(val: NodeAnyValue): NodeIDStrict {
switch (typeof val) {
case 'string':
case 'number':
return val.toString();
default:
// didn't used switch case
// because TS's inference is kind of dumb ...
if (typeof val == 'object') {
const ret = last(selectionToChain(val as NodeSelection));
if (ret) {
return ret;
}
}
throw `invalid address anyValue ${val}`;
}
}
export const addressEqual = (lhs: NodeAnyValue, rhs: NodeAnyValue) =>
anyValueToSelectionId(lhs) == anyValueToSelectionId(rhs);
export const CANNOT_REACH: any = 'CANNOT_REACH';
export function maybeGet(src: T | null | undefined): T {
return src as T;
}
export function selectionChainLastNonstrict(chain: SelectionChain): NodeID {
if (chain.length) {
return last(chain);
} else {
return '';
}
}
export function nodeChainLastIDNonstrict(chain: NodeChain): NodeID {
if (chain.length) {
return last(chain).id;
} else {
return '';
}
}
// export function decorateValueInternal(
// value: NodeSelection, nodeChain: NodeChain): NodeSelection {
// let lastName = "";
// let id = "";
// if (nodeChain.length) {
// const l = last(nodeChain);
// id = l.id;
// lastName = l.nameZh;
// }
// Object.defineProperty(value, "__addressValueInternalData", {
// get () {
// return { nodeChain, lastName, id };
// }
// });
// return value;
// }
// export function valueInternalDecorated(value: NodeSelection, id: string): boolean {
// if (!value || typeof value != "object") { return false; }
// const v = ((value as any)["__addressValueInternalData"]) as
// NodeSelectionInternalData;
// return (!!v) && (v.id == id);
// }
// export function valueInternalDecorated2(value: number | string | NodeSelection): boolean {
// if (typeof value == "number" || typeof value == "string") { return false; }
// const v = ((value as any)["__addressValueInternalData"]) as
// NodeSelectionInternalData;
// if (!v) { return false; }
// const id = selectionToChain(value);
// if (id.toString() != v.id) { return false; }
// return true;
// }
export const id = (v: any) => v;
export function getParentSetOf(node: AddressNode): Set {
const nodeChain = nodeChainFromNode(node);
const ret = new Set();
nodeChain.forEach(v => ret.add(v.id));
// @ts-ignore
return ret;
}
export function isDescendantOfOne(
allNodesSet: AddressNodeSet,
childId: NodeIDStrict,
parentIds: NodeIDStrict[]
) {
// TODO: use LCA-like alg. to opt.
const childNode = allNodesSet[childId.toString()];
const parents = getParentSetOf(childNode);
for (const parentId of parentIds) {
if (parentId != childId && parents.has(parentId)) {
return true;
}
}
return false;
}
export const PROVINCE_LETTERS = ['A–G', 'H–K', 'L–S', 'T–Z'];
export function provinceLetterParititon(namePy: string): number {
const c = namePy[0];
if (c <= 'g') {
return 0;
} else if (c <= 'k') {
return 1;
} else if (c <= 's') {
return 2;
} else {
return 3;
}
}
export function lastOr(src: T[], def: T): T {
if (src.length) {
return src[src.length - 1];
} else {
return def;
}
}
export function selectionToId(src: SelectionChain): NodeIDStrict {
return lastOr(src, ADDRTREE_ROOT_KEY);
}
export function getLabelIntl(node: AddressNode): string {
if (node.parent == '0') {
// return node.label;
return ((ConfigProvider as any).getLanguage() == 'zh-cn'
? node.nameZh!
: node.label) as any;
} else {
return node.label;
}
}
export function getNodeLevelInTree(node: AddressNode): number {
if ('_cachedLevelInTree' in node) {
return (node)._cachedLevelInTree;
}
function f(node: AddressNode, i: number): number {
return node.parentNode ? f(node.parentNode, i + 1) : i;
}
const ret = f(node, 0);
(node)._cachedLevelInTree = ret;
return ret;
}