Files
mikaboshi/docs/examples/FengShui.js
2022-07-21 20:02:33 +09:00

604 lines
17 KiB
JavaScript

/**
* A context provider to load a WASM app ("voi-feng-shui" in our case).
* Once `FengShuiContext` is set in the parent component, you can use
* `useFengShui` in child components to access the given properties.
* To start with, it requies data in `profile`, however, `profile`
* is managed in `ProfilesProvider`. For two providers cannot
* communicate to each other, we have `useFengShuiSync`
* to serve the purpose. For any components handling `profile`,
* we run `useFengShuiSync` in there, and it will automatically
* @namespace FengShui
*/
import React, {
useContext,
createContext,
useEffect,
useState,
useCallback,
useRef,
} from 'react';
import moment from 'moment';
import init, {
// 八卦 (Ba-Gua)
get_gua_compass_order as wasm_get_gua_compass_order,
// 二十四山向 (Er-Shi Si-Shan Xiang)
get_twentyfour_direction_from_index as wasm_get_twentyfour_direction_from_index,
get_twentyfour_direction_from_degrees as wasm_get_twentyfour_direction_from_degrees,
get_twentyfour_data_from_index as wasm_get_twentyfour_data_from_index,
get_twentyfour_data_from_direction as wasm_get_twentyfour_data_from_direction,
// 干支 (Gan-Zhi)
get_bazi as wasm_get_bazi,
get_lichun as wasm_get_lichun,
// 九星 (Jiu-Xings)
get_jiuxing_from_index as wasm_get_jiuxing_from_index,
get_unpan_xing_index as wasm_get_unpan_xing_index,
get_xiaguatu_from_unpan_index as wasm_get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction as wasm_get_jiuxing_dipan_positions_from_direction,
// 生死衰旺 (Sheng-Si Shuai-Wang)
get_shengsi_mapping as wasm_get_shengsi_mapping,
} from 'voi-feng-shui';
import { DATETIME_FORMAT, BAZI_TYPE_KEYS, GANZHI_TYPE_KEYS } from '@/constants';
import { int, noop, get_utc_offset_in_hours } from '@/lib/utils';
import {
useProfiles,
get_first_valid_profile_from_profiles,
} from '@/contexts/Profiles';
const WASM_PATH =
NODE_ENV === 'production'
? 'wasm/voi-feng-shui/voi-feng-shui_bg.wasm'
: void 0;
const DATETIME_KEYS = Object.keys(DATETIME_FORMAT);
const DATE_KEYS = DATETIME_KEYS.filter(
key =>
key.indexOf('year') > -1 ||
key.indexOf('month') > -1 ||
key.indexOf('day') > -1
);
/** @public */
export const has_valid_profile = (p = {}) =>
p.localtime && p.direction && p.sector;
// ----------------------------------------------------------------
// FengShuiContext
// ----------------------------------------------------------------
/**
* @typedef FengShui.FengShuiContext
* @property {boolean} ready
* @property {Object} profile
* @property {Object} bazi
* @property {Object} lichun
* @property {Object} unpan_xing
* @property {Object} shan_xing
* @property {Object} xiang_xing
* @property {Function} update
* @property {FengShui.FengShuiContext.get_gua_compass_order}
* @property {FengShui.FengShuiContext.get_direction_positions_in_chart} - NOT IN USE
* @property {FengShui.FengShuiContext.get_opposite_direction} - NOT IN USE
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_index}
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
* @property {FengShui.FengShuiContext.get_bazi} get_bazi
* @property {FengShui.FengShuiContext.get_lichun} get_lichun
* @property {FengShui.FengShuiContext.get_unpan_xing_index}
* @property {FengShui.FengShuiContext.get_jiuxing_from_index}
* @property {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index}
* @property {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
* @property {FengShui.FengShuiContext.get_shengsi_mapping}
*/
const FengShuiContext = createContext({
ready: false,
profile: null, // localtime, direction, sector
bazi: null,
lichun: null,
unpan_xing: null, // 運盤星
shan_xing: null, // 山星
xiang_xing: null, // 向星
update: noop,
// 八卦 (Ba-Gua)
get_gua_compass_order: noop,
// 二十四山向 (Er-Shi Si-Shan Xiang)
get_twentyfour_direction_from_index: noop,
get_twentyfour_direction_from_degrees: noop,
get_twentyfour_data_from_index: noop,
get_twentyfour_data_from_direction: noop,
// 干支 (Gan-Zhi)
get_bazi: noop,
get_lichun: noop,
// 九星 (Jiu-Xings)
unpan_xing_index: noop,
get_xiaguatu_from_unpan_index: noop,
get_jiuxing_dipan_positions_from_direction: noop,
// 生死衰旺 (Sheng-Si Shuai-Wang)
get_shengsi_mapping: noop,
});
// ----------------------------------------------------------------
// FengShuiProvider
// ----------------------------------------------------------------
/**
* @typedef FengShui.FengShuiProvider
* @function
*/
export const FengShuiProvider = props => {
const [ready, setReady] = useState(false);
const [profile, setProfile] = useState(null);
const [bazi, setBazi] = useState(null);
const [lichun, setLiChun] = useState(null);
const [unpan_xing, setUnPanXing] = useState(null);
const [shan_xing, setShanXing] = useState(null);
const [xiang_xing, setXiangXing] = useState(null);
// ================================================================
// 八卦 (Bagua)
// ================================================================
/**
* A simple accessor for values stored in `BAGUA_START_NORTH`.
* @typedef FengShui.FengShuiContext.get_gua_compass_order
* @function
*/
/**
* @type {FengShui.FengShuiContext.get_gua_compass_order}
*/
const get_gua_compass_order = useCallback(
dir => {
return ready && wasm_get_gua_compass_order(dir);
},
[ready]
);
// ================================================================
// 二十四山向 (Er-Shi Si-Shan Xiang)
// ================================================================
/**
* From the index, returns `Direction`.
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_index
* @function
* @param {number} [index]
* @returns {Object} - { direction, sector }
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
*/
const get_twentyfour_direction_from_index = useCallback(
index => {
return ready && wasm_get_twentyfour_direction_from_index(index);
},
[ready]
);
/**
* From `degrees`, returns `Direction` (for 二十四山向; Er-Shi Si-Shan Xiang).
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_degrees
* @function
* @param {number} [degrees=0]
* @returns {Object} - { direction, sector }
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
*/
const get_twentyfour_direction_from_degrees = useCallback(
degrees => {
return ready && wasm_get_twentyfour_direction_from_degrees(degrees);
},
[ready]
);
/**
* From index, returns `TwentyFourType`.
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_index
* @function
* @param {number} [index]
* @returns {Bagua|Stem|Branch} - TwentyFourType
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_index}
*/
const get_twentyfour_data_from_index = useCallback(
index => {
return ready && wasm_get_twentyfour_data_from_index(index);
},
[ready]
);
/**
* From `direction`, returns `TwentyFourType`.
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_direction
* @function
* @param {number} [index]
* @returns {Bagua|Stem|Branch} - TwentyFourType
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
*/
const get_twentyfour_data_from_direction = useCallback(
(dir, sec) => {
return ready && wasm_get_twentyfour_data_from_direction(dir, sec);
},
[ready]
);
// ================================================================
// 干支 (Gan-Zhi)
// ================================================================
/**
* From `t` (localtime), calculates for 八字 (Bazi).
* Internally mapped to: get_bazi
* @typedef FengShui.FengShuiContext.get_bazi
* @function
* @param {Object} [t] - localtime (as a momemt object)
* @returns {Object} - Bazi
*/
/** @type {FengShui.FengShuiContext.get_bazi} */
const get_bazi = useCallback(
t => {
return (
ready &&
normalize_bazi_data(wasm_get_bazi(datetime_params_from_localtime(t)))
);
},
[ready]
);
/**
* From `year`, calculates for 立春 (Li-Chun).
* Internally mapped to: get_lichun
* @typedef FengShui.FengShuiContext.get_lichun
* @function
* @param {number} [y] - year
* @returns {string} - Li-Chun
*/
/** @type {FengShui.FengShuiContext.get_lichun} */
const get_lichun = useCallback(
y => {
return ready && y && wasm_get_lichun(~~y);
},
[ready]
);
// ================================================================
// 九星 (Jiu-Xing)
// ================================================================
/**
* A simple accessor for values stored in `JIU_XING`.
* @typedef FengShui.FengShuiContext.get_jiuxing_from_index
* @function
* @param {Object} [index] - Jiu-Xing index
* @returns {Object} - JiuXing
*/
/** @type {FengShui.FengShuiContext.get_jiuxing_from_index} */
const get_jiuxing_from_index = useCallback(
index => {
return ready && wasm_get_jiuxing_from_index(index);
},
[ready]
);
/**
* From: (1) `current` (current localtime), and
* (2) `lichun` (立春 (Li-Chun) for the year),
* returns 運盤星 (Un-Pan Xing) index.
* @typedef FengShui.FengShuiContext.get_unpan_xing_index
* @function
* @param {Object} [current] - Current localtime
* @param {Object} [lichun] - Li-Chun (for the year)
* @returns {number} - Un-Pan Xing index
*/
/** @type {FengShui.FengShuiContext.get_unpan_xing_index} */
const get_unpan_xing_index = useCallback(
params => {
const { current, lichun } = params;
return (
ready &&
wasm_get_unpan_xing_index(
date_params_from_localtime(current),
date_params_from_localtime(lichun)
)
);
},
[ready]
);
/**
* Takes 運盤星 (Un-Pan Xing) information and 向星 (Xiang-Xing)
* information, and returns 下卦図 (Xia-Gua-Tu).
* For 運盤星 (Un-Pan Xing), we need 2 kinds: 運盤 (Un-Pan)
* which is currently in the center, and 洛書 (Lo-Shu) order
* in its re-arranged form. For 向星 (Xiang-Xing),
* we want `direction` and `sector`.
* @typedef FengShui.FengShuiContext.get_xiaguatu_from_unpan_index
* @function
* @param {Object} arg
* @param {number} arg.unpan_xing_center - 運盤星 (Un-Pan Xing) index
* @param {Array} arg.unpan_xing_order - Jiu-Xing Order
* @param {string} arg.xiang_xing_direction - Direction for 向星 (Xiang-Xing)
* @param {number} arg.xiang_xing_sector - Sector for both 山星 (Shan-Xing) and 向星 (Xiang-Xing)
* @returns {Object}
*/
/** @type {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index} */
const get_xiaguatu_from_unpan_index = useCallback(
params => {
return (
ready &&
wasm_get_xiaguatu_from_unpan_index({
...params,
unpan_xing_order: params.unpan_xing_order || [
5, 0, 7, 6, 4, 2, 1, 8, 3,
],
})
);
},
[ready]
);
/**
* A simple accessor for values stored in `JIU_XING_DI_PAN_POSITIONS`.
* @type {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
*/
const get_jiuxing_dipan_positions_from_direction = useCallback(
dir => {
return ready && wasm_get_jiuxing_dipan_positions_from_direction(dir);
},
[ready]
);
// ================================================================
// 生死衰旺 (Sheng-Si Shuai-Wang)
// ================================================================
/**
* When provided with 運盤 (Un-Pan) index and the current chart,
* returns the corresponding 生死衰旺 (Sheng-Si Shuai-Wang).
* @typedef FengShui.FengShuiContext.get_shengsi_mapping
* @function
* @param {Object} arg
* @param {number} arg.unpan_id - 運盤星 (Un-Pan Xing) index
* @param {Array} arg.unpan_xing_chart - 運盤星 (Un-Pan Xing) index
* @returns {Object}
*/
/** @type {FengShui.FengShuiContext.get_shengsi_mapping} */
const get_shengsi_mapping = useCallback(
params => {
return (
ready &&
wasm_get_shengsi_mapping({
...params,
unpan_xing_chart: params.unpan_xing_chart || [
5, 0, 7, 6, 4, 2, 1, 8, 3,
],
})
);
},
[ready]
);
// ================================================================
// MAIN FOR THE CONTEXT PROVIDER
// ================================================================
/** @private */
const _set = useCallback(
(prof = {}) => {
const { localtime: current, direction, sector } = prof;
if (ready && current && direction) {
const lichun_0 = get_lichun(current.year());
const lichun = moment(lichun_0);
const center = get_unpan_xing_index({ current, lichun });
const xgtu = get_xiaguatu_from_unpan_index({
unpan_xing_center: center,
xiang_xing_direction: direction,
xiang_xing_sector: sector,
});
setBazi(
normalize_bazi_data(
wasm_get_bazi(datetime_params_from_localtime(current))
)
);
setLiChun(lichun);
setUnPanXing(xgtu.unpan_xing);
setShanXing(xgtu.shan_xing);
setXiangXing(xgtu.xiang_xing);
}
},
[ready, profile?.locatltime, profile?.direction]
);
/**
* @typedef FengShui.FengShuiContext.update
* @function
*/
/** @type {FengShui.FengShuiContext.update} */
const update = useCallback(
prof => {
if (ready) {
if (!has_valid_profile(prof)) {
throw new Error(
'[FengShuiContext] Need a valid profile for the argument'
);
}
setProfile(prof);
_set(prof);
}
},
[ready, _set]
);
useEffect(() => {
if (ready !== true) {
init(WASM_PATH)
.then(() => {
setReady(true);
})
.catch(err => {
throw err;
});
}
}, [props?.localtime, props?.direction]);
return (
<FengShuiContext.Provider
value={{
ready,
profile,
bazi,
lichun,
unpan_xing,
shan_xing,
xiang_xing,
update,
get_gua_compass_order,
get_twentyfour_direction_from_index,
get_twentyfour_direction_from_degrees,
get_twentyfour_data_from_index,
get_twentyfour_data_from_direction,
get_bazi,
get_lichun,
get_jiuxing_from_index,
get_unpan_xing_index,
get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction,
get_shengsi_mapping,
}}
{...props}
/>
);
};
// ----------------------------------------------------------------
// useFengShui
// ----------------------------------------------------------------
/**
* @typedef FengShui.useFengShui
* @function
*/
export const useFengShui = () => useContext(FengShuiContext);
// ----------------------------------------------------------------
// useFengShuiSync (hook)
// ----------------------------------------------------------------
/**
* Syncing the latest primary profile to FengShuiContext.profile
* @typedef FengShui.useFengShuiSync
* @function
*/
export const useFengShuiSync = () => {
const { profiles } = useProfiles();
const { ready, update } = useFengShui();
const [hasProfile, setHasProfile] = useState(false);
useEffect(() => {
const p = get_first_valid_profile_from_profiles(profiles);
if (p) {
const t = p.localtime.format('YYYY-MM-DD');
const dir = `${p.direction}${p.sector}`;
setHasProfile(true);
update(p);
} else {
setHasProfile(false);
}
}, [ready, profiles]);
return hasProfile;
};
// ----------------------------------------------------------------
// All the others
// ----------------------------------------------------------------
/**
* @private
* @param {Object} t - Moment object in localtime + explicit UTC offset
*/
function date_params_from_localtime(t) {
if (!t) throw new Error("No 'localtime' specified for FengShui");
return {
...DATE_KEYS.reduce((acc, key) => {
acc[key] = int(t.format(DATETIME_FORMAT[key]));
return acc;
}, {}),
};
}
/**
* @private
* @param {Object} t - Moment object in localtime + explicit UTC offset
*/
function datetime_params_from_localtime(t) {
if (!t) throw new Error("No 'localtime' specified for FengShui");
return {
...DATETIME_KEYS.reduce((acc, key) => {
acc[key] = int(t.format(DATETIME_FORMAT[key]));
return acc;
}, {}),
zone: get_utc_offset_in_hours(t),
};
}
/**
* @private
*/
function normalize_bazi_data(orig) {
const hash = { __orig: { ...orig } };
// 'year', 'month', 'hour', 'day'
BAZI_TYPE_KEYS.forEach(bazi_key => {
hash[bazi_key] = {
__obj: orig[bazi_key][bazi_key],
};
// 'stem', 'branch'
GANZHI_TYPE_KEYS.forEach(ganzhi_key => {
hash[bazi_key][ganzhi_key] = {
kanji: orig[bazi_key][ganzhi_key].name.zh_cn.alphabet,
yomi: orig[bazi_key][ganzhi_key].name.ja.alphabet,
};
});
hash[
bazi_key
].kanji = `${hash[bazi_key].stem.kanji}${hash[bazi_key].branch.kanji}`;
hash[
bazi_key
].yomi = `${hash[bazi_key].stem.yomi}${hash[bazi_key].branch.yomi}`;
});
return hash;
}