118 lines
5.0 KiB
TypeScript
118 lines
5.0 KiB
TypeScript
import { extractPlaceAboutData } from './placeUtils';
|
|
import type { GooglePlaceContactFields } from '../types';
|
|
import { pickFormattedPhoneNumber, pickPlaceWebsite } from '../types';
|
|
import type { OfflinePlaceRow } from './offlinePlaceStore';
|
|
import type { Locale } from '../../../os/locale';
|
|
import { makeLatLngLike } from './latLng';
|
|
|
|
/** 内部双语文本对 */
|
|
type BiText = { zh: string; en: string };
|
|
|
|
function pickText(field: string | BiText | undefined | null, locale: Locale): string {
|
|
if (!field) return '';
|
|
if (typeof field === 'string') return field;
|
|
return locale === 'en' ? (field.en || field.zh || '') : (field.zh || field.en || '');
|
|
}
|
|
|
|
function pickTextArr(field: string[] | { zh: string[]; en: string[] } | undefined | null, locale: Locale): string[] {
|
|
if (!field) return [];
|
|
if (Array.isArray(field)) return field;
|
|
return locale === 'en' ? (field.en?.length ? field.en : field.zh || []) : (field.zh?.length ? field.zh : field.en || []);
|
|
}
|
|
|
|
function localizedText(v: unknown, locale: Locale): string | undefined {
|
|
if (v == null) return undefined;
|
|
if (typeof v === 'string') return v;
|
|
if (typeof v === 'object' && v !== null && 'zh' in v && 'en' in v) {
|
|
return pickText(v as BiText, locale);
|
|
}
|
|
if (typeof v === 'object' && v !== null && 'text' in v) {
|
|
return String((v as { text?: string }).text ?? '');
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* 将 REST Places details 与离线行合并为可供 UI 使用的 PlaceResult 形状。
|
|
* locale 参数控制 displayName / formattedAddress / editorialSummary 等字段的语言。
|
|
*/
|
|
export function buildPlaceDetailResultFromOfflineRow(
|
|
row: OfflinePlaceRow,
|
|
googleNs: typeof google | null | undefined,
|
|
locale: Locale,
|
|
): (google.maps.places.PlaceResult & Record<string, unknown>) | null {
|
|
const d = row.details ?? {};
|
|
|
|
const displayName =
|
|
localizedText(d.displayName, locale) ||
|
|
pickText(row.name as string | BiText, locale);
|
|
const formattedAddress = localizedText(d.formattedAddress, locale) || pickText(row.formattedAddress as string | BiText, locale);
|
|
const loc = googleNs ? new googleNs.maps.LatLng(row.lat, row.lng) : makeLatLngLike(row.lat, row.lng);
|
|
|
|
const editorialRaw = d.editorialSummary;
|
|
let editorialSummary: string | undefined;
|
|
if (editorialRaw) {
|
|
editorialSummary = localizedText(editorialRaw, locale);
|
|
}
|
|
|
|
const plusRaw = d.plusCode as { compoundCode?: string } | undefined;
|
|
const plusCode = plusRaw?.compoundCode ?? null;
|
|
|
|
const weekdayDescs = d.regularOpeningHours && typeof d.regularOpeningHours === 'object'
|
|
? (d.regularOpeningHours as Record<string, unknown>).weekdayDescriptions
|
|
: undefined;
|
|
const resolvedWeekdayDescriptions = weekdayDescs
|
|
? pickTextArr(weekdayDescs as string[] | { zh: string[]; en: string[] }, locale)
|
|
: undefined;
|
|
|
|
const openingHoursRaw = d.regularOpeningHours || d.currentOpeningHours || null;
|
|
let regularOpeningHours: unknown = openingHoursRaw;
|
|
if (resolvedWeekdayDescriptions && openingHoursRaw && typeof openingHoursRaw === 'object') {
|
|
regularOpeningHours = { ...openingHoursRaw as object, weekdayDescriptions: resolvedWeekdayDescriptions };
|
|
}
|
|
|
|
const raw = d as Record<string, unknown>;
|
|
const aboutSource = {
|
|
...d,
|
|
displayName,
|
|
websiteURI: raw.websiteURI ?? raw.websiteUri,
|
|
hasDineIn: raw.hasDineIn ?? raw.dineIn,
|
|
hasTakeout: raw.hasTakeout ?? raw.takeout,
|
|
hasDelivery: raw.hasDelivery ?? raw.delivery,
|
|
isReservable: raw.isReservable ?? raw.reservable,
|
|
hasRestroom: raw.hasRestroom ?? raw.restroom,
|
|
hasOutdoorSeating: raw.hasOutdoorSeating ?? raw.outdoorSeating,
|
|
hasLiveMusic: raw.hasLiveMusic ?? raw.liveMusic,
|
|
hasMenuForChildren: raw.hasMenuForChildren ?? raw.menuForChildren,
|
|
isGoodForChildren: raw.isGoodForChildren ?? raw.goodForChildren,
|
|
isGoodForGroups: raw.isGoodForGroups ?? raw.goodForGroups,
|
|
isGoodForWatchingSports: raw.isGoodForWatchingSports ?? raw.goodForWatchingSports,
|
|
accessibilityOptions: d.accessibilityOptions,
|
|
paymentOptions: d.paymentOptions,
|
|
parkingOptions: d.parkingOptions,
|
|
};
|
|
|
|
return {
|
|
place_id: row.placeId,
|
|
name: displayName,
|
|
formatted_address: formattedAddress,
|
|
geometry: { location: loc },
|
|
rating: (d.rating as number | undefined) ?? row.rating,
|
|
user_ratings_total: (d.userRatingCount as number | undefined) ?? row.userRatingCount,
|
|
business_status: d.businessStatus as google.maps.places.BusinessStatus | undefined,
|
|
types: (d.types as string[] | undefined) || row.types,
|
|
primaryType: (d.primaryType as string | undefined) || row.primaryType,
|
|
primaryTypeDisplayName: localizedText(d.primaryTypeDisplayName, locale),
|
|
formatted_phone_number:
|
|
(d.nationalPhoneNumber as string | undefined) ||
|
|
pickFormattedPhoneNumber({ ...d, ...row } as GooglePlaceContactFields),
|
|
websiteURI: pickPlaceWebsite({ ...d, ...row } as GooglePlaceContactFields),
|
|
regularOpeningHours: regularOpeningHours as unknown as
|
|
| google.maps.places.PlaceOpeningHours
|
|
| null,
|
|
editorialSummary,
|
|
plusCode,
|
|
_aboutData: extractPlaceAboutData(aboutSource),
|
|
};
|
|
}
|