Compare commits

..

14 Commits

Author SHA1 Message Date
harukin-expo-dev-env 907ddf9dae Merge commit '929c444474a121b15a8f995351387646fdb35e88' into develop 2026-07-15 08:04:16 +00:00
harukin-expo-dev-env 929c444474 fix: 通過駅のフィルタリングロジックを改善し、列車データの取得処理を最適化 2026-07-15 08:04:05 +00:00
harukin-expo-dev-env b6800d6566 fix: 列車情報の時間フィルタリングロジックを改善し、再生中の基準時刻を追加 2026-07-14 10:49:31 +00:00
harukin-expo-dev-env b0a0d62344 fix: 列車情報の表示ロジックを改善し、条件付きHTML生成を追加 2026-07-14 09:11:23 +00:00
harukin-expo-dev-env bfd90b50e4 feat: 列車タイプのラッピング処理を追加し、表示ロジックを改善 2026-07-14 05:57:05 +00:00
harukin-expo-dev-env 553735ac1d feat: JR-WEST-PLUSフォントの調整と表示領域の正規化を実施 2026-07-14 04:26:38 +00:00
harukin-expo-dev-env 5f1641ff90 feat: JR-WEST-PLUSフォントを追加し、フォント設定を最適化 2026-07-14 02:51:41 +00:00
harukin-expo-dev-env 351854f128 fix: 修正された列車データの取得と表示ロジックを改善し、未登録列車の処理を追加 2026-07-14 00:46:35 +00:00
harukin-expo-dev-env e6f0391484 feat: Implement X投稿向け運行情報画像セット generation in ndView.tsx
- Added functionality to generate a set of images for posting to X (formerly Twitter) with multiple images.
- Introduced a new button for creating X投稿向け画像, which generates up to 4 PNG images.
- The first image serves as a cover with a route map and summary, while subsequent images contain detailed operation information.
- Ensured images maintain a fixed size of 1080 x 1440 px and follow specific naming conventions.
- Implemented error handling for image generation failures and ensured compatibility with existing sharing features.
- Updated UI elements and styles for the new functionality, including button states and layout adjustments.
- Added necessary TypeScript type checks and validation for the new features.
2026-07-12 19:01:51 +00:00
harukin-expo-dev-env b1cfad4528 Merge commit '76b13a70a9e8f791bc8551107b7609581e92e8d5' into develop 2026-07-12 15:40:52 +00:00
harukin-expo-dev-env 76b13a70a9 型定義を改善し、originalStationListの使用を最適化 2026-07-12 15:35:00 +00:00
harukin-expo-dev-env eec2fd2bb7 Refactor MenuPage component and update dependencies
- Removed unused navigation logic from MenuPage.tsx.
- Added postinstall script for patch-package in package.json.
- Introduced a new patch for @react-navigation/bottom-tabs to fix zIndex and opacity issues on Android.
- Updated yarn.lock with new dependencies and versions, including patch-package and others.
2026-07-12 10:59:40 +00:00
harukin-expo-dev-env 8c2930c814 ネイティブスクリーンの無効化を条件化し、スクリーンの非アクティブ状態を保持する設定を追加 2026-07-11 04:27:16 +00:00
harukin-expo-dev-env c7fdf6f0b1 Merge commit '9746a4d04168112371c44a7a1afd72cd8dd04e4f' into develop 2026-07-11 00:14:03 +00:00
38 changed files with 2736 additions and 709 deletions
+12 -3
View File
@@ -74,7 +74,10 @@ LogBox.ignoreLogs([
"ColorPropType will be removed", "ColorPropType will be removed",
]); ]);
if (Platform.OS === "ios") { const nativeScreensDisabled =
Platform.OS === "ios" || Platform.OS === "android";
if (nativeScreensDisabled) {
enableFreeze(false); enableFreeze(false);
enableScreens(false); enableScreens(false);
} }
@@ -87,12 +90,17 @@ if (Platform.OS === "android") {
export default Sentry.wrap(function App() { export default Sentry.wrap(function App() {
useEffect(() => { useEffect(() => {
const nativeScreensMode = const nativeScreensMode = nativeScreensDisabled
Platform.OS === "ios" ? "screens-disabled" : "default"; ? "screens-disabled"
: "default";
const rootTabSceneMode =
Platform.OS === "android" ? "opacity-fixed-z-v1" : "default";
Sentry.setTag("native_screens_mode", nativeScreensMode); Sentry.setTag("native_screens_mode", nativeScreensMode);
Sentry.setTag("root_tab_scene_mode", rootTabSceneMode);
Sentry.setContext("runtime_navigation_flags", { Sentry.setContext("runtime_navigation_flags", {
platform: Platform.OS, platform: Platform.OS,
nativeScreensMode, nativeScreensMode,
rootTabSceneMode,
}); });
Sentry.addBreadcrumb({ Sentry.addBreadcrumb({
category: "runtime.flags", category: "runtime.flags",
@@ -101,6 +109,7 @@ export default Sentry.wrap(function App() {
data: { data: {
platform: Platform.OS, platform: Platform.OS,
nativeScreensMode, nativeScreensMode,
rootTabSceneMode,
}, },
}); });
void startAppLifecycleCrashSentinel({ nativeScreensMode }); void startAppLifecycleCrashSentinel({ nativeScreensMode });
+67 -6
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { NavigationContainer, DarkTheme, DefaultTheme } from "@react-navigation/native"; import { NavigationContainer, DarkTheme, DefaultTheme, type EventArg } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { Animated, Platform, ActivityIndicator, View, StyleSheet, StatusBar, useWindowDimensions, InteractionManager } from "react-native"; import { Animated, Platform, ActivityIndicator, View, StyleSheet, StatusBar, useWindowDimensions, InteractionManager } from "react-native";
import { useNavigationState } from "@react-navigation/native"; import { useNavigationState } from "@react-navigation/native";
@@ -15,10 +15,16 @@ import lineColorList from "./assets/originData/lineColorList";
import { stationIDPair } from "./lib/getStationList"; import { stationIDPair } from "./lib/getStationList";
import "./components/ActionSheetComponents/sheets"; import "./components/ActionSheetComponents/sheets";
import { lastObservedRootRouteRef, positionsLifecycleRef, rootNavigationRef } from "./lib/rootNavigation"; import { lastObservedRootRouteRef, positionsLifecycleRef, rootNavigationRef } from "./lib/rootNavigation";
import {
startupExplicitTargetRef,
waitForStartupNavigationResolution,
} from "./lib/rootNavigation";
import { fixedColors } from "./lib/theme/colors"; import { fixedColors } from "./lib/theme/colors";
import { useThemeColors } from "./lib/theme"; import { useThemeColors } from "./lib/theme";
import * as Sentry from "@sentry/react-native"; import * as Sentry from "@sentry/react-native";
import WebView from "react-native-webview"; import WebView from "react-native-webview";
import { AS } from "./storageControl";
import { STORAGE_KEYS } from "./constants/storage";
import { import {
recordAppLifecycleRootNavigation, recordAppLifecycleRootNavigation,
setAppLifecycleWebViewActive, setAppLifecycleWebViewActive,
@@ -247,6 +253,7 @@ export function AppContainer() {
const { selectedLine } = useTrainMenu(); const { selectedLine } = useTrainMenu();
const { width, height } = useWindowDimensions(); const { width, height } = useWindowDimensions();
const [isExtraWindowOpen, setIsExtraWindowOpen] = React.useState(false); const [isExtraWindowOpen, setIsExtraWindowOpen] = React.useState(false);
const [startupInitialRoute, setStartupInitialRoute] = React.useState<keyof RootTabParamList | null>(null);
const [hasVisitedPositions, setHasVisitedPositions] = React.useState(false); const [hasVisitedPositions, setHasVisitedPositions] = React.useState(false);
const [hasVisitedInformation, setHasVisitedInformation] = React.useState(false); const [hasVisitedInformation, setHasVisitedInformation] = React.useState(false);
const [currentRootRoute, setCurrentRootRoute] = React.useState<keyof RootTabParamList | null>(null); const [currentRootRoute, setCurrentRootRoute] = React.useState<keyof RootTabParamList | null>(null);
@@ -278,6 +285,45 @@ export function AppContainer() {
const { isDark } = useThemeColors(); const { isDark } = useThemeColors();
const lastRootNavStateRef = React.useRef(""); const lastRootNavStateRef = React.useRef("");
const lineColorDark = lineColor ? darkenHex(lineColor, 0.78) : null; const lineColorDark = lineColor ? darkenHex(lineColor, 0.78) : null;
React.useEffect(() => {
let cancelled = false;
void (async () => {
await waitForStartupNavigationResolution();
if (cancelled) return;
let nextInitialRoute: keyof RootTabParamList = "topMenu";
if (!startupExplicitTargetRef.current) {
try {
const startPage = await AS.getItem(STORAGE_KEYS.START_PAGE);
if (startPage === "true") {
nextInitialRoute = "positions";
}
} catch {
// Keep the default route when the setting is unset or unreadable.
}
}
if (cancelled) return;
setStartupInitialRoute(nextInitialRoute);
setHasVisitedPositions(nextInitialRoute === "positions");
Sentry.addBreadcrumb({
category: "nav.root",
level: "info",
message: "startup initial root route resolved",
data: {
startupExplicitTarget: startupExplicitTargetRef.current,
initialRoute: nextInitialRoute,
},
});
})();
return () => {
cancelled = true;
};
}, []);
const linking = { const linking = {
prefixes: ["jrshikoku://"], prefixes: ["jrshikoku://"],
config: { config: {
@@ -376,6 +422,7 @@ export function AppContainer() {
const [fontLoaded, error] = useFonts({ const [fontLoaded, error] = useFonts({
"JR-Nishi": require("./assets/fonts/jr-nishi.otf"), "JR-Nishi": require("./assets/fonts/jr-nishi.otf"),
"JR-WEST-PLUS": require("./assets/fonts/JR-WEST-PLUS.otf"),
Zou: require("./assets/fonts/DelaGothicOne-Regular.ttf"), Zou: require("./assets/fonts/DelaGothicOne-Regular.ttf"),
"JNR-font": require("./assets/fonts/JNRfont_pict.ttf"), "JNR-font": require("./assets/fonts/JNRfont_pict.ttf"),
"DiaPro": require("./assets/fonts/DiaPro-Regular.otf"), "DiaPro": require("./assets/fonts/DiaPro-Regular.otf"),
@@ -458,7 +505,7 @@ export function AppContainer() {
}; };
}, [currentRootRoute, fontLoaded, hasVisitedInformation, navigationReady]); }, [currentRootRoute, fontLoaded, hasVisitedInformation, navigationReady]);
if (!fontLoaded) { if (!fontLoaded || !startupInitialRoute) {
return ( return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
@@ -498,9 +545,9 @@ export function AppContainer() {
<Tab.Navigator <Tab.Navigator
id="rootTabs" id="rootTabs"
detachInactiveScreens={false} detachInactiveScreens={false}
initialRouteName="topMenu" initialRouteName={startupInitialRoute}
screenListeners={({ route }) => ({ screenListeners={({ route }) => ({
tabPress: (event) => { tabPress: (event: EventArg<"tabPress", true>) => {
const rootState = rootNavigationRef.getState(); const rootState = rootNavigationRef.getState();
const activeRootRouteName = const activeRootRouteName =
(rootState?.routes?.[rootState.index ?? 0]?.name as keyof RootTabParamList | undefined) ?? (rootState?.routes?.[rootState.index ?? 0]?.name as keyof RootTabParamList | undefined) ??
@@ -532,16 +579,30 @@ export function AppContainer() {
if (blockingUnstableExit) { if (blockingUnstableExit) {
event.preventDefault(); event.preventDefault();
positionsLifecycleRef.current.resetBeforeLeave?.();
requestAnimationFrame(() => {
setTimeout(() => {
rootNavigationRef.navigate(route.name as never);
}, 0);
});
return; return;
} }
if (leavingUnstablePositions) { if (leavingUnstablePositions) {
event.preventDefault(); event.preventDefault();
positionsLifecycleRef.current.resetBeforeLeave?.();
requestAnimationFrame(() => {
setTimeout(() => {
rootNavigationRef.navigate(route.name as never);
}, 0);
});
Sentry.addBreadcrumb({ Sentry.addBreadcrumb({
category: "positions.root", category: "positions.root",
level: "info", level: "info",
message: "positions root exit blocked while session unstable", message: "positions root exit reset and continued while session unstable",
data: { route: route.name }, data: {
route: route.name,
},
}); });
} }
}, },
+2 -24
View File
@@ -19,10 +19,6 @@ import { useFavoriteStation } from "@/stateBox/useFavoriteStation";
import { optionData } from "@/lib/stackOption"; import { optionData } from "@/lib/stackOption";
import { AllTrainDiagramView } from "@/components/AllTrainDiagramView"; import { AllTrainDiagramView } from "@/components/AllTrainDiagramView";
import { useNavigation, useIsFocused } from "@react-navigation/native"; import { useNavigation, useIsFocused } from "@react-navigation/native";
import {
startupExplicitTargetRef,
waitForStartupNavigationResolution,
} from "@/lib/rootNavigation";
import { news } from "@/config/newsUpdate"; import { news } from "@/config/newsUpdate";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"; import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import GeneralWebView from "@/GeneralWebView"; import GeneralWebView from "@/GeneralWebView";
@@ -69,23 +65,6 @@ export function MenuPage() {
}, [height, isFocused, width]); }, [height, isFocused, width]);
useEffect(() => { useEffect(() => {
let cancelled = false;
void (async () => {
try {
await waitForStartupNavigationResolution();
if (cancelled) return;
const res = await AS.getItem(STORAGE_KEYS.START_PAGE);
if (res !== "true" || startupExplicitTargetRef.current) return;
navigation.navigate("positions");
} catch (e) {
//6.0以降false
AS.setItem(STORAGE_KEYS.START_PAGE, "false");
}
})();
//ニュース表示 //ニュース表示
AS.getItem(STORAGE_KEYS.NEWS_STATUS) AS.getItem(STORAGE_KEYS.NEWS_STATUS)
.then((d) => { .then((d) => {
@@ -98,9 +77,7 @@ export function MenuPage() {
}) })
.catch((error) => logger.error("Error fetching icon setting:", error)); .catch((error) => logger.error("Error fetching icon setting:", error));
return () => { return undefined;
cancelled = true;
};
}, []); }, []);
const scrollRef = useRef(null); const scrollRef = useRef(null);
@@ -182,6 +159,7 @@ export function MenuPage() {
return ( return (
<Stack.Navigator <Stack.Navigator
id={null} id={null}
detachInactiveScreens={false}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }} screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
screenListeners={({ route, navigation: stackNav }) => { screenListeners={({ route, navigation: stackNav }) => {
stackNavRef.current = stackNav; stackNavRef.current = stackNav;
+1
View File
@@ -188,6 +188,7 @@ export const Top = () => {
return ( return (
<Stack.Navigator <Stack.Navigator
id={null} id={null}
detachInactiveScreens={false}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }} screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
screenListeners={({ route, navigation: stackNav }) => { screenListeners={({ route, navigation: stackNav }) => {
stackNavRef.current = stackNav; stackNavRef.current = stackNav;
+1
View File
@@ -117,6 +117,7 @@
{ {
"fonts": [ "fonts": [
"./assets/fonts/jr-nishi.otf", "./assets/fonts/jr-nishi.otf",
"./assets/fonts/JR-WEST-PLUS.otf",
"./assets/fonts/DelaGothicOne-Regular.ttf", "./assets/fonts/DelaGothicOne-Regular.ttf",
"./assets/fonts/JNRfont_pict.ttf", "./assets/fonts/JNRfont_pict.ttf",
"./assets/fonts/DiaPro-Regular.otf" "./assets/fonts/DiaPro-Regular.otf"
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
西日本方向ロゴ拡張「JR-WEST-PLUS」
2026/07/11 by Mameh1nata
「回送」とか専用の書体の方がかっこよくね?と思い制作したフォントです
現時点で対応している種別は以下の通りです
回送 試運転 貸切 修学旅行 救援 配給 訓練 検測 競技 検定 教習
ver1.0 プライベート公開
ver1.01 修学旅行を追加
ver1.1 救援 配給 訓練の追加
ver1.2 競技 検定 教習の追加
-注意-
・このフォントは西日本方向幕ロゴの制作者とは何の関係もありません
・常識の範囲内で使うことをお勧めします(常識の範囲外でも自己責任でお願いします)
・文字サイズが多少違ったりするかもしれませんがご了承ください
・フォント初制作なのであまりにも致命的なミス以外は許してください
配布元: https://uu.getuploader.com/mameh1nata_dustbox/download/1
アプリ同梱版の調整:
・JR-Nishi と字面を揃えるため、各グリフの表示領域を 1040 units に正規化
・送り幅は 930 units とし、広げた字形の右側はアプリ側で小幅の DiaPro NBSP を付加して保護
・縦の表示領域を 836 units、ベースラインを -86〜750 に正規化
・フォント全体の ascender / descender を JR-Nishi と同じ 781 / -118 に調整
・文字コードと内部フォント名は元ファイルから維持
@@ -1,4 +1,4 @@
import React, { FC, useMemo, useState } from "react"; import React, { FC, useMemo } from "react";
import { Text, View, TextStyle, TouchableOpacity } from "react-native"; import { Text, View, TextStyle, TouchableOpacity } from "react-native";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { migrateTrainName } from "../../../lib/eachTrainInfoCoreLib/migrateTrainName"; import { migrateTrainName } from "../../../lib/eachTrainInfoCoreLib/migrateTrainName";
@@ -16,6 +16,7 @@ import type { NavigateFunction } from "@/types";
import { useUnyohub } from "@/stateBox/useUnyohub"; import { useUnyohub } from "@/stateBox/useUnyohub";
import { useElesite } from "@/stateBox/useElesite"; import { useElesite } from "@/stateBox/useElesite";
import { useThemeColors } from "@/lib/theme"; import { useThemeColors } from "@/lib/theme";
import { formatWrappedTrainType, shouldWrapTrainType } from "@/lib/trainTypeWrap";
import { useResponsive } from "@/lib/responsive"; import { useResponsive } from "@/lib/responsive";
import { normalizeIconDisplayMode } from "@/lib/iconDisplayMode"; import { normalizeIconDisplayMode } from "@/lib/iconDisplayMode";
@@ -75,7 +76,7 @@ export const HeaderText: FC<Props> = ({
const [ const [
typeName, typeName,
trainName, trainName,
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -96,7 +97,7 @@ export const HeaderText: FC<Props> = ({
to_data, to_data,
directions, directions,
} = result; } = result;
const [typeString, fontAvailable, isOneMan] = getStringConfig( const [typeString, fontFamily, isOneMan] = getStringConfig(
type, type,
trainNum, trainNum,
); );
@@ -108,7 +109,7 @@ export const HeaderText: FC<Props> = ({
(train_num_distance !== "" && !isNaN(parseInt(train_num_distance)) (train_num_distance !== "" && !isNaN(parseInt(train_num_distance))
? ` ${parseInt(trainNum) - parseInt(train_num_distance)}` ? ` ${parseInt(trainNum) - parseInt(train_num_distance)}`
: ""), : ""),
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -121,7 +122,7 @@ export const HeaderText: FC<Props> = ({
return [ return [
typeString, typeString,
"", "",
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -134,7 +135,7 @@ export const HeaderText: FC<Props> = ({
return [ return [
typeString, typeString,
`${to_data}行き`, `${to_data}行き`,
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -149,7 +150,7 @@ export const HeaderText: FC<Props> = ({
migrateTrainName( migrateTrainName(
`${trainData[trainData.length - 1].split(",")[0]}行き`, `${trainData[trainData.length - 1].split(",")[0]}行き`,
), ),
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -211,7 +212,7 @@ export const HeaderText: FC<Props> = ({
hasUnyohubFormation || hasUnyohubFormation ||
hasElesiteFormation; hasElesiteFormation;
const [isWrapped, setIsWrapped] = useState(false); const wrapType = shouldWrapTrainType(typeName, trainName);
const openTrainInfoUrl = () => { const openTrainInfoUrl = () => {
if (!trainInfoUrl) return; if (!trainInfoUrl) return;
@@ -251,15 +252,23 @@ export const HeaderText: FC<Props> = ({
}} }}
> >
<Text <Text
numberOfLines={wrapType ? 2 : 1}
style={{ style={{
fontSize: fontScale(20), fontSize: fontScale(20),
color: fixed.textOnPrimary, color: fixed.textOnPrimary,
fontFamily: fontAvailable ? "JR-Nishi" : undefined, fontFamily,
fontWeight: !fontAvailable ? "bold" : undefined, fontWeight: fontFamily ? undefined : "bold",
marginRight: 5, marginRight: 5,
flexShrink: 0,
...(wrapType
? { width: fontScale(44), lineHeight: fontScale(22) }
: {}),
}} }}
> >
{isWrapped ? typeName.replace(/(.{2})/g, "$1\n").trim() : typeName} {wrapType ? formatWrappedTrainType(typeName) : typeName}
{fontFamily === "JR-WEST-PLUS" ? (
<Text style={{ fontFamily: "DiaPro", fontSize: 7 }}>{"\u00A0"}</Text>
) : null}
</Text> </Text>
{isOneMan && <OneManText />} {isOneMan && <OneManText />}
</View> </View>
@@ -291,9 +300,6 @@ export const HeaderText: FC<Props> = ({
: { fontSize: fontScale(17) }), : { fontSize: fontScale(17) }),
flexShrink: 1, flexShrink: 1,
}} }}
onTextLayout={(e) => {
if (e.nativeEvent.lines.length > 1) setIsWrapped(true);
}}
> >
{trainName} {trainName}
</Text> </Text>
@@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { lineListPair, stationIDPair } from '@/lib/getStationList'; import { lineListPair, stationIDPair } from '@/lib/getStationList';
import { useStationList } from '@/stateBox/useStationList'; import { useStationList } from '@/stateBox/useStationList';
import { OriginalStationList } from '@/lib/CommonTypes';
const computeThroughStations = ( const computeThroughStations = (
trainData: string[], trainData: string[],
stationList: any[][], stationList: any[][],
originalStationList: Record<string, any[]> originalStationList: OriginalStationList
): { trainDataWithThrough: string[]; haveThrough: boolean } => { ): { trainDataWithThrough: string[]; haveThrough: boolean } => {
if (!trainData.length) return { trainDataWithThrough: [], haveThrough: false }; if (!trainData.length) return { trainDataWithThrough: [], haveThrough: false };
@@ -1082,7 +1082,7 @@ const TrainInfoDetail: FC<{
uwasa, uwasa,
optional_text, optional_text,
} = data; } = data;
const { fontAvailable } = getTrainType({ const { fontFamily } = getTrainType({
type: data.type, type: data.type,
whiteMode: false, whiteMode: false,
}); });
@@ -1120,13 +1120,16 @@ const TrainInfoDetail: FC<{
style={[ style={[
styles.typeTagText, styles.typeTagText,
{ {
fontFamily: fontAvailable ? "JR-Nishi" : undefined, fontFamily,
fontWeight: !fontAvailable ? "bold" : undefined, fontWeight: fontFamily ? undefined : "bold",
paddingTop: fontAvailable ? 2 : 0, paddingTop: fontFamily ? 2 : 0,
}, },
]} ]}
> >
{typeName} {typeName}
{fontFamily === "JR-WEST-PLUS" ? (
<Text style={{ fontFamily: "DiaPro", fontSize: 7 }}>{"\u00A0"}</Text>
) : null}
</Text> </Text>
</View> </View>
)} )}
+14 -13
View File
@@ -28,6 +28,7 @@ import { normalizeIconDisplayMode } from "@/lib/iconDisplayMode";
import { InfogramText } from "./ActionSheetComponents/EachTrainInfoCore/HeaderTextParts/InfogramText"; import { InfogramText } from "./ActionSheetComponents/EachTrainInfoCore/HeaderTextParts/InfogramText";
import { resolveTrainIconEntries } from "@/lib/trainIconEntries"; import { resolveTrainIconEntries } from "@/lib/trainIconEntries";
import { TrainIconStack } from "./ActionSheetComponents/EachTrainInfoCore/TrainIconStack"; import { TrainIconStack } from "./ActionSheetComponents/EachTrainInfoCore/TrainIconStack";
import { formatWrappedTrainType, shouldWrapTrainType } from "@/lib/trainTypeWrap";
export const AllTrainDiagramView: FC = () => { export const AllTrainDiagramView: FC = () => {
const { colors, fixed } = useThemeColors(); const { colors, fixed } = useThemeColors();
@@ -107,9 +108,7 @@ export const AllTrainDiagramView: FC = () => {
if (directions != undefined) { if (directions != undefined) {
iconTrainDirection = directions ? true : false; iconTrainDirection = directions ? true : false;
} }
const [isWrapped, setIsWrapped] = useState(false); const [typeString, fontFamily, isOneMan] = getStringConfig(type, id);
const [typeString, fontAvailable, isOneMan] = getStringConfig(type, id);
const trainNameString = (() => { const trainNameString = (() => {
switch (true) { switch (true) {
@@ -134,6 +133,7 @@ export const AllTrainDiagramView: FC = () => {
return migrateTrainName(hoge + "行き"); return migrateTrainName(hoge + "行き");
} }
})(); })();
const wrapType = shouldWrapTrainType(typeString, trainNameString);
return ( return (
<TouchableOpacity <TouchableOpacity
style={{ style={{
@@ -164,17 +164,23 @@ export const AllTrainDiagramView: FC = () => {
> >
{typeString && ( {typeString && (
<Text <Text
numberOfLines={wrapType ? 2 : 1}
style={{ style={{
fontSize: 20, fontSize: 20,
color: fixed.textOnPrimary, color: fixed.textOnPrimary,
fontFamily: fontAvailable ? "JR-Nishi" : undefined, fontFamily,
fontWeight: !fontAvailable ? "bold" : undefined, fontWeight: fontFamily ? undefined : "bold",
marginRight: 5, marginRight: 5,
flexShrink: 0,
...(wrapType
? { width: 44, lineHeight: 22 }
: {}),
}} }}
> >
{isWrapped {wrapType ? formatWrappedTrainType(typeString) : typeString}
? typeString.replace(/(.{2})/g, "$1\n").trim() {fontFamily === "JR-WEST-PLUS" ? (
: typeString} <Text style={{ fontFamily: "DiaPro", fontSize: 7 }}>{"\u00A0"}</Text>
) : null}
</Text> </Text>
)} )}
{isOneMan && <OneManText />} {isOneMan && <OneManText />}
@@ -195,11 +201,6 @@ export const AllTrainDiagramView: FC = () => {
color: fixed.textOnPrimary, color: fixed.textOnPrimary,
flexShrink: 1, flexShrink: 1,
}} }}
onTextLayout={(e) => {
if (e.nativeEvent.lines.length > 1) {
setIsWrapped(true);
}
}}
> >
{trainNameString} {trainNameString}
</Text> </Text>
@@ -58,7 +58,7 @@ type props = {
export const FixedStation: FC<props> = ({ stationID }) => { export const FixedStation: FC<props> = ({ stationID }) => {
const { colors, fixed } = useThemeColors(); const { colors, fixed } = useThemeColors();
const { mapSwitch } = useTrainMenu(); const { mapSwitch, playbackCurrentTimeIso } = useTrainMenu();
const { const {
currentTrain, currentTrain,
fixedPosition, fixedPosition,
@@ -151,11 +151,11 @@ export const FixedStation: FC<props> = ({ stationID }) => {
if (!currentTrain) return () => {}; if (!currentTrain) return () => {};
const data = trainTimeAndNumber const data = trainTimeAndNumber
.filter((d) => currentTrain.map((m) => m.num).includes(d.train)) //現在の列車に絞る[ToDo] .filter((d) => currentTrain.map((m) => m.num).includes(d.train)) //現在の列車に絞る[ToDo]
.filter((d) => trainTimeFiltering({ d, currentTrain, station })) //時間フィルター .filter((d) => trainTimeFiltering({ d, currentTrain, station, stationList, now: playbackCurrentTimeIso })) //時間フィルター
.filter((d) => !d.isThrough) .filter((d) => !d.isThrough)
.filter((d) => d.lastStation != station[0].Station_JP); //最終列車表示設定 .filter((d) => d.lastStation != station[0].Station_JP); //最終列車表示設定
setSelectedTrain(data); setSelectedTrain(data);
}, [trainTimeAndNumber, currentTrain, tick /*liveActivity periodic refresh*/]); }, [trainTimeAndNumber, currentTrain, stationList, playbackCurrentTimeIso, tick /*liveActivity periodic refresh*/]);
useEffect(() => { useEffect(() => {
liveNotifyIdRef.current = liveNotifyId; liveNotifyIdRef.current = liveNotifyId;
+275 -116
View File
@@ -28,6 +28,7 @@ import { useTrainMenu } from "@/stateBox/useTrainMenu";
import { useThemeColors } from "@/lib/theme"; import { useThemeColors } from "@/lib/theme";
import { normalizeIconDisplayMode } from "@/lib/iconDisplayMode"; import { normalizeIconDisplayMode } from "@/lib/iconDisplayMode";
import { resolveTrainDataIcon } from "@/lib/trainDataIcon"; import { resolveTrainDataIcon } from "@/lib/trainDataIcon";
import { resolveTrainIconEntries } from "@/lib/trainIconEntries";
import { import {
startTrainFollowActivity, startTrainFollowActivity,
updateTrainFollowActivity, updateTrainFollowActivity,
@@ -39,6 +40,45 @@ type props = {
trainID: string; trainID: string;
}; };
type TrainPathEntry = {
raw: string;
station: string;
se: string;
time: string;
index: number;
isThrough: boolean;
hasTime: boolean;
};
const normalizeTrainPosLabel = (value: string) =>
value
.replace(
/(下り)|(上り)|\(下り\)|\(上り\)|(徳島線)|(高徳線)|(坂出方)|(児島方)/g,
""
)
.trim();
const isHiddenThroughPointEntry = (raw: string) => {
const [station = "", se = ""] = (raw || "").split(",");
return station.startsWith(".") && se.includes("通");
};
const calcDistanceMinute = (
time: string,
delayTime: number,
playbackCurrentTimeIso?: string | null
) => {
if (!time || time === "") return null;
const now = playbackCurrentTimeIso ? dayjs(playbackCurrentTimeIso) : dayjs();
const hour = parseInt(time.split(":")[0], 10);
const target = now
.hour(hour < 4 ? hour + 24 : hour)
.minute(parseInt(time.split(":")[1], 10));
let diff = target.diff(now, "minute") + delayTime;
if (now.hour() < 4 && hour < 4) diff -= 1440;
return diff;
};
export const FixedTrain: FC<props> = ({ trainID }) => { export const FixedTrain: FC<props> = ({ trainID }) => {
const { colors, fixed } = useThemeColors(); const { colors, fixed } = useThemeColors();
const { const {
@@ -50,8 +90,9 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
setFixedPositionSize, setFixedPositionSize,
} = useCurrentTrain(); } = useCurrentTrain();
const { mapSwitch, iconSetting } = useTrainMenu(); const { mapSwitch, iconSetting, playbackCurrentTimeIso } = useTrainMenu();
const { allCustomTrainData, allTrainDiagram } = useAllTrainDiagram(); const { allCustomTrainData, allTrainDiagram, getTodayOperationByTrainId } =
useAllTrainDiagram();
const iconDisplayMode = normalizeIconDisplayMode(iconSetting); const iconDisplayMode = normalizeIconDisplayMode(iconSetting);
const [liveNotifyId, setLiveNotifyId] = useState<string | null>(null); const [liveNotifyId, setLiveNotifyId] = useState<string | null>(null);
@@ -62,7 +103,23 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
const [customData, setCustomData] = useState<CustomTrainData>( const [customData, setCustomData] = useState<CustomTrainData>(
getCurrentTrainData(trainID, currentTrain, allCustomTrainData) getCurrentTrainData(trainID, currentTrain, allCustomTrainData)
); );
const customTrainIcon = resolveTrainDataIcon(customData, iconDisplayMode); const todayOperation = useMemo(
() => (getTodayOperationByTrainId(trainID) ?? []).filter((d) => d.state !== 100),
[getTodayOperationByTrainId, trainID]
);
const customTrainIcon = useMemo(() => {
const iconEntry = resolveTrainIconEntries({
trainNum: trainID,
customTrainData: customData,
todayOperation,
iconDisplayMode,
})[0];
return (
iconEntry?.vehicle_info_img ||
resolveTrainDataIcon(customData, iconDisplayMode)
);
}, [customData, iconDisplayMode, todayOperation, trainID]);
useEffect(() => { useEffect(() => {
setCustomData( setCustomData(
getCurrentTrainData(trainID, currentTrain, allCustomTrainData) getCurrentTrainData(trainID, currentTrain, allCustomTrainData)
@@ -89,8 +146,11 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
); );
const computedTrainDataWithThrough = useMemo(() => { const computedTrainDataWithThrough = useMemo(() => {
const trainData = allTrainDiagram[trainID]?.split("#"); const trainData =
if (!trainData) return []; allTrainDiagram[trainID]
?.split("#")
.filter((entry) => !isHiddenThroughPointEntry(entry)) ?? [];
if (trainData.length === 0) return [];
// 駅名ごとに駅情報を集約するマップを構築 // 駅名ごとに駅情報を集約するマップを構築
const stationByNameMap = new Map(); const stationByNameMap = new Map();
@@ -136,6 +196,8 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
originalStationList[ originalStationList[
lineListPair[stationIDPair[betweenStationLine]] lineListPair[stationIDPair[betweenStationLine]]
].forEach((d) => { ].forEach((d) => {
const isHiddenPassPoint = d.Station_JP?.startsWith(".");
if (isHiddenPassPoint) return;
if ( if (
d.StationNumber > baseStationNumberFirst && d.StationNumber > baseStationNumberFirst &&
d.StationNumber < baseStationNumberSecond d.StationNumber < baseStationNumberSecond
@@ -193,6 +255,22 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
setStopStationList(computedStopStationIDList); setStopStationList(computedStopStationIDList);
}, [computedStopStationIDList]); }, [computedStopStationIDList]);
const [currentPosition, setCurrentPosition] = useState<string[]>([]); const [currentPosition, setCurrentPosition] = useState<string[]>([]);
const parsedTrainPath = useMemo<TrainPathEntry[]>(
() =>
trainDataWidhThrough.map((raw, index) => {
const [station = "", se = "", time = ""] = (raw || "").split(",");
return {
raw,
station,
se,
time,
index,
isThrough: se.includes("通"),
hasTime: time !== "",
};
}),
[trainDataWidhThrough]
);
useEffect(() => { useEffect(() => {
let position = getPosition(train); let position = getPosition(train);
@@ -227,96 +305,127 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
} }
}, [train, stopStationIDList]); }, [train, stopStationIDList]);
const [nextStationData, setNextStationData] = useState<StationProps[]>([]); const [currentPointData, setCurrentPointData] = useState<StationProps[]>([]);
const [untilStationData, setUntilStationData] = useState<StationProps[]>([]); const [nextStopStationData, setNextStopStationData] = useState<StationProps[]>([]);
const [untilStationData, setUntilStationData] = useState<string[]>([]);
const [probably, setProbably] = useState(false); const [probably, setProbably] = useState(false);
const [isCurrentPointDisplay, setIsCurrentPointDisplay] = useState(false);
const [currentDisplayIndex, setCurrentDisplayIndex] = useState(0);
const [tick, setTick] = useState(0);
useEffect(() => {
if (playbackCurrentTimeIso) return;
const interval = setInterval(() => setTick((value) => value + 1), 15000);
return () => clearInterval(interval);
}, [playbackCurrentTimeIso]);
useEffect(() => { useEffect(() => {
//棒線駅判定を入れて、棒線駅なら時間を見て分数がマイナスならcontinue;
const points = findReversalPoints(currentPosition, stopStationIDList); const points = findReversalPoints(currentPosition, stopStationIDList);
if (!points) return; if (!points || points.length === 0) return;
if (points.length == 0) return;
let searchCountFirst = points.findIndex((d) => d == true);
let searchCountLast = points.findLastIndex((d) => d == true);
const delayTime = train?.delay == "入線" ? 0 : train?.delay; const pointIndexes = points.reduce(
let additionalSkipCount = 0; (acc: number[], isCurrent, index) => {
if (isCurrent) acc.push(index);
return acc;
},
[] as number[]
);
if (!pointIndexes.length) return;
// 2駅間走行中の場合: ダイヤ順で後ろのインデックスが進行方向の駅 const firstMatchedIndex = pointIndexes[0];
// Direction に関係なく、travel-order で大きい方が「向かう駅」 const isCurrentStationCluster = currentPosition.length <= 1;
let searchStart: number; const delayTime =
if (currentPosition.length === 2) { train?.delay == "入線" ? 0 : parseInt(String(train?.delay), 10) || 0;
const idx0 = stopStationIDList.findIndex(d => d.includes(currentPosition[0]));
const idx1 = stopStationIDList.findIndex(d => d.includes(currentPosition[1]));
const aheadIdx = Math.max(
idx0 >= 0 ? idx0 : -1,
idx1 >= 0 ? idx1 : -1
);
searchStart = aheadIdx >= 0 ? aheadIdx : searchCountLast;
} else {
searchStart = searchCountFirst;
}
for ( let anchorIndex = firstMatchedIndex;
let searchCount = searchStart; let usedTimeEstimation = false;
searchCount < trainDataWidhThrough.length; const shouldShowCurrentPoint = isCurrentStationCluster;
searchCount++ const hasIntermediateCurrentPoints = pointIndexes.length > 1;
) {
const nextPos = trainDataWidhThrough[searchCount];
if (nextPos) { if (!isCurrentStationCluster && hasIntermediateCurrentPoints) {
const [station, se, time] = nextPos.split(","); let upcomingTimedIndex = -1;
let lastPassedTimedIndex = -1;
// 通過駅はスキップ for (
if (se.includes("通")) { let searchCount = firstMatchedIndex;
searchCount < parsedTrainPath.length;
searchCount++
) {
const entry = parsedTrainPath[searchCount];
if (!entry?.raw || !entry.hasTime) continue;
const distanceMinute = calcDistanceMinute(
entry.time,
delayTime,
playbackCurrentTimeIso
);
if (distanceMinute == null) continue;
if (distanceMinute < 0) {
lastPassedTimedIndex = searchCount;
continue; continue;
} }
// 駅に停車中(1点一致)の場合は時刻判定不要 upcomingTimedIndex = searchCount;
if (searchCountFirst == searchCountLast) { break;
setNextStationData(getStationDataFromName(station));
break;
}
// 2駅間走行中: 時刻で既に通過済みか判定
let distanceMinute = 0;
if (time != "") {
const now = dayjs();
const hour = parseInt(time.split(":")[0]);
const distanceTime = now
.hour(hour < 4 ? hour + 24 : hour)
.minute(parseInt(time.split(":")[1]));
distanceMinute = distanceTime.diff(now, "minute") + delayTime;
if (now.hour() < 4) {
if (hour < 4) {
distanceMinute = distanceMinute - 1440;
}
}
}
if (distanceMinute >= 0) {
setNextStationData(getStationDataFromName(station));
break;
} else {
additionalSkipCount++;
continue;
}
} }
const baseIndex =
lastPassedTimedIndex >= 0 ? lastPassedTimedIndex + 1 : firstMatchedIndex;
if (upcomingTimedIndex >= 0) {
const hasUntimedGapBeforeUpcoming = parsedTrainPath
.slice(baseIndex, upcomingTimedIndex)
.some((entry) => entry?.raw && !entry.hasTime);
anchorIndex = hasUntimedGapBeforeUpcoming ? baseIndex : upcomingTimedIndex;
} else if (lastPassedTimedIndex >= 0) {
const lastPassedEntry = parsedTrainPath[lastPassedTimedIndex];
anchorIndex = lastPassedEntry?.isThrough
? Math.min(lastPassedTimedIndex + 1, parsedTrainPath.length - 1)
: lastPassedTimedIndex;
}
usedTimeEstimation = anchorIndex !== firstMatchedIndex;
} }
let trainList = [];
for ( const anchorEntry = parsedTrainPath[anchorIndex];
let searchCount = searchCountFirst - 1; const currentPointName = anchorEntry?.station || "";
searchCount < points.length; setCurrentPointData(
searchCount++ currentPointName ? getStationDataFromName(currentPointName) : []
) { );
trainList.push(trainDataWidhThrough[searchCount]);
} const nextStopSearchStart = shouldShowCurrentPoint
if (additionalSkipCount > 0) { ? anchorIndex + 1
trainList = trainList.slice(additionalSkipCount); : anchorIndex;
setProbably(true); const nextStopEntry = parsedTrainPath.find(
} else { (entry, index) =>
setProbably(false); index >= nextStopSearchStart && !!entry?.station && !entry.isThrough
} );
const nextStopName = nextStopEntry?.station || "";
setNextStopStationData(
nextStopName ? getStationDataFromName(nextStopName) : []
);
const visibleStart = Math.max(anchorIndex - 1, 0);
const trainList = parsedTrainPath
.slice(visibleStart)
.map((entry) => entry.raw)
.filter((entry) => !!entry);
setProbably(usedTimeEstimation);
setIsCurrentPointDisplay(shouldShowCurrentPoint);
setCurrentDisplayIndex(Math.max(anchorIndex - visibleStart, 0));
setUntilStationData(trainList); setUntilStationData(trainList);
}, [currentPosition, trainDataWidhThrough]); }, [
currentPosition,
parsedTrainPath,
playbackCurrentTimeIso,
stopStationIDList,
tick,
train?.delay,
getStationDataFromName,
]);
const [ToData, setToData] = useState(""); const [ToData, setToData] = useState("");
useEffect(() => { useEffect(() => {
if (customData.to_data && customData.to_data != "") { if (customData.to_data && customData.to_data != "") {
@@ -335,7 +444,9 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
setStation(data); setStation(data);
}, [ToData]); }, [ToData]);
const lineColor = const lineColor =
station.length > 0 customData.to_data_color && customData.to_data_color.length > 0
? customData.to_data_color[0]
: station.length > 0
? lineColorList[station[0]?.StationNumber?.slice(0, 1)] ? lineColorList[station[0]?.StationNumber?.slice(0, 1)]
: "black"; : "black";
//const lineColor = "red"; //const lineColor = "red";
@@ -355,7 +466,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
(last: number, d: string, i: number) => (d ? i : last), -1 (last: number, d: string, i: number) => (d ? i : last), -1
); );
const filteredTrainData = trainDataWidhThrough.filter((d, idx) => { const filteredTrainData = trainDataWidhThrough.filter((d, idx) => {
if (!d) return false; if (!d || isHiddenThroughPointEntry(d)) return false;
const [, se] = d.split(","); const [, se] = d.split(",");
if (!se) return true; if (!se) return true;
// 着を含み発を含まないエントリは終着駅のみ許可 // 着を含み発を含まないエントリは終着駅のみ許可
@@ -407,13 +518,22 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
}; };
}); });
const shouldShowCurrentLabel =
isCurrentPointDisplay || currentPointData[0]?.Station_JP === train?.Pos;
const currentStationLabel = shouldShowCurrentLabel
? currentPointData[0]?.Station_JP || train?.Pos || ""
: train?.Pos || "";
const bannerStationData = shouldShowCurrentLabel
? currentPointData
: nextStopStationData;
// 全駅リスト中の現在地インデックス // 全駅リスト中の現在地インデックス
const currentStationIndex = (() => { const currentStationIndex = (() => {
const pos = train?.Pos || ""; const pos = currentStationLabel;
if (!pos) return 0; if (!pos) return 0;
// Pos は "駅名" (駅にいる時) or "駅A~駅B" (走行中) の形式 // Pos は "駅名" (駅にいる時) or "駅A~駅B" (走行中) の形式
const posStations = pos.split("").map((s: string) => const posStations = pos.split("").map((s: string) =>
s.replace(/(下り)|(上り)|\(下り\)|\(上り\)/g, "").trim() normalizeTrainPosLabel(s)
); );
// 完全一致 // 完全一致
const firstIdx = allStations.findIndex((s) => s.name === posStations[0]); const firstIdx = allStations.findIndex((s) => s.name === posStations[0]);
@@ -427,7 +547,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
})(); })();
const nextStationIndex = (() => { const nextStationIndex = (() => {
const name = nextStationData[0]?.Station_JP; const name = nextStopStationData[0]?.Station_JP;
if (!name) return -1; if (!name) return -1;
const idx = stationStops.indexOf(name); const idx = stationStops.indexOf(name);
if (idx >= 0) return idx; if (idx >= 0) return idx;
@@ -452,11 +572,10 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
if (!liveNotifyId || !train) return; if (!liveNotifyId || !train) return;
const delayNum = train.delay === "入線" ? 0 : parseInt(String(train.delay)) || 0; const delayNum = train.delay === "入線" ? 0 : parseInt(String(train.delay)) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻"; const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus = const positionStatus = shouldShowCurrentLabel ? "ただいま" : "次は";
nextStationData[0]?.Station_JP === train.Pos ? "ただいま" : "次は";
updateTrainFollowActivity(liveNotifyId, { updateTrainFollowActivity(liveNotifyId, {
currentStation: train.Pos || "", currentStation: currentStationLabel,
nextStation: nextStationData[0]?.Station_JP || "", nextStation: nextStopStationData[0]?.Station_JP || "",
delayMinutes: delayNum, delayMinutes: delayNum,
scheduledArrival: "", scheduledArrival: "",
trainNumber: trainID, trainNumber: trainID,
@@ -472,7 +591,16 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
allStations, allStations,
currentStationIndex, currentStationIndex,
}).catch(() => {}); }).catch(() => {});
}, [train, nextStationData, liveNotifyId, stationStops, nextStationIndex, currentStationIndex]); }, [
train,
nextStopStationData,
liveNotifyId,
stationStops,
nextStationIndex,
currentStationIndex,
currentStationLabel,
shouldShowCurrentLabel,
]);
// バナー表示と同時にLive Activityを自動開始 // バナー表示と同時にLive Activityを自動開始
// iOSのみ一時的に無効化中(Androidは有効) // iOSのみ一時的に無効化中(Androidは有効)
@@ -489,15 +617,14 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
} }
const delayNum = train?.delay === "入線" ? 0 : parseInt(String(train?.delay)) || 0; const delayNum = train?.delay === "入線" ? 0 : parseInt(String(train?.delay)) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻"; const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus = const positionStatus = shouldShowCurrentLabel ? "ただいま" : "次は";
nextStationData[0]?.Station_JP === train?.Pos ? "ただいま" : "次は";
try { try {
const id = await startTrainFollowActivity({ const id = await startTrainFollowActivity({
trainNumber: trainID, trainNumber: trainID,
lineName: "", lineName: "",
destination: ToData, destination: ToData,
currentStation: train?.Pos || "", currentStation: currentStationLabel,
nextStation: nextStationData[0]?.Station_JP || "", nextStation: nextStopStationData[0]?.Station_JP || "",
delayMinutes: delayNum, delayMinutes: delayNum,
scheduledArrival: "", scheduledArrival: "",
trainType: customTrainType.shortName, trainType: customTrainType.shortName,
@@ -517,7 +644,22 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
} }
}; };
startActivity(); startActivity();
}, [train]); }, [
train,
nextStopStationData,
currentStationLabel,
shouldShowCurrentLabel,
ToData,
trainID,
customTrainType.shortName,
trainNameText,
customTrainType.color,
lineColor,
stationStops,
nextStationIndex,
allStations,
currentStationIndex,
]);
return ( return (
<View <View
@@ -576,19 +718,18 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
<Text <Text
style={{ style={{
fontSize: trainNameText.length > 4 ? 12 : 14, fontSize: trainNameText.length > 4 ? 12 : 14,
fontFamily: customTrainType.fontAvailable fontFamily: customTrainType.fontFamily,
? "JR-Nishi" fontWeight: customTrainType.fontFamily ? undefined : "bold",
: undefined, marginTop: customTrainType.fontFamily ? 3 : 0,
fontWeight: !customTrainType.fontAvailable
? "bold"
: undefined,
marginTop: customTrainType.fontAvailable ? 3 : 0,
color: fixed.textOnPrimary, color: fixed.textOnPrimary,
textAlignVertical: "center", textAlignVertical: "center",
textAlign: "left", textAlign: "left",
}} }}
> >
{customTrainType.shortName} {customTrainType.shortName}
{customTrainType.fontFamily === "JR-WEST-PLUS" ? (
<Text style={{ fontFamily: "DiaPro", fontSize: 7 }}>{"\u00A0"}</Text>
) : null}
</Text> </Text>
{customData.train_name && ( {customData.train_name && (
<Text <Text
@@ -693,9 +834,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
marginVertical: -1, marginVertical: -1,
}} }}
> >
{nextStationData[0]?.Station_JP == train?.Pos {shouldShowCurrentLabel ? "ただいま" : "次は"}
? "ただいま"
: "次は"}
</Text> </Text>
{probably && ( {probably && (
<Text <Text
@@ -713,7 +852,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
)} )}
</View> </View>
<StationNumberMaker <StationNumberMaker
currentStation={nextStationData} currentStation={bannerStationData}
singleSize={20} singleSize={20}
useEach={true} useEach={true}
/> />
@@ -725,7 +864,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
flex: 1, flex: 1,
}} }}
> >
{nextStationData[0]?.Station_JP || "不明"} {bannerStationData[0]?.Station_JP || "不明"}
</Text> </Text>
{fixedPositionSize !== 226 && ( {fixedPositionSize !== 226 && (
<View <View
@@ -749,6 +888,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
train={train} train={train}
lineColor={lineColor} lineColor={lineColor}
trainDataWithThrough={untilStationData} trainDataWithThrough={untilStationData}
currentDisplayIndex={currentDisplayIndex}
isSmall={fixedPositionSize !== 226} isSmall={fixedPositionSize !== 226}
/> />
</View> </View>
@@ -875,6 +1015,7 @@ const CurrentPositionBox = ({
train, train,
lineColor, lineColor,
trainDataWithThrough, trainDataWithThrough,
currentDisplayIndex,
isSmall, isSmall,
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
@@ -884,13 +1025,13 @@ const CurrentPositionBox = ({
const { isBetween, Pos: PosData } = trainPosition(train); const { isBetween, Pos: PosData } = trainPosition(train);
if (isBetween === true) { if (isBetween === true) {
const { from, to } = PosData; const { from, to } = PosData;
firstText = from; firstText = normalizeTrainPosLabel(from);
secondText = to; secondText = normalizeTrainPosLabel(to);
marginText = "→"; marginText = "→";
} else { } else {
const { Pos } = PosData; const { Pos } = PosData;
if (Pos !== "") { if (Pos !== "") {
firstText = Pos; firstText = normalizeTrainPosLabel(Pos);
} }
} }
const delayTime = train?.delay == "入線" ? 0 : parseInt(train?.delay); const delayTime = train?.delay == "入線" ? 0 : parseInt(train?.delay);
@@ -943,6 +1084,7 @@ const CurrentPositionBox = ({
(() => { (() => {
// 着→発ペアを同一駅で統合(EachStopListと同様) // 着→発ペアを同一駅で統合(EachStopListと同様)
const merged: { d: string; arrivalTime: string | null }[] = []; const merged: { d: string; arrivalTime: string | null }[] = [];
let mergedCurrentDisplayIndex = Math.max(currentDisplayIndex, 0);
for (let i = 0; i < trainDataWithThrough.length; i++) { for (let i = 0; i < trainDataWithThrough.length; i++) {
const d = trainDataWithThrough[i]; const d = trainDataWithThrough[i];
if (!d) continue; if (!d) continue;
@@ -963,11 +1105,17 @@ const CurrentPositionBox = ({
const [prevSt, prevSe, prevTime] = prev.split(","); const [prevSt, prevSe, prevTime] = prev.split(",");
if (prevSt === st && prevSe?.includes("着")) { if (prevSt === st && prevSe?.includes("着")) {
merged.push({ d, arrivalTime: prevTime }); merged.push({ d, arrivalTime: prevTime });
if (i === currentDisplayIndex || i - 1 === currentDisplayIndex) {
mergedCurrentDisplayIndex = merged.length - 1;
}
continue; continue;
} }
} }
} }
merged.push({ d, arrivalTime: null }); merged.push({ d, arrivalTime: null });
if (i === currentDisplayIndex) {
mergedCurrentDisplayIndex = merged.length - 1;
}
} }
return merged.map(({ d, arrivalTime }, index) => ( return merged.map(({ d, arrivalTime }, index) => (
<EachStopData <EachStopData
@@ -977,6 +1125,7 @@ const CurrentPositionBox = ({
delayTime={delayTime} delayTime={delayTime}
isSmall={isSmall} isSmall={isSmall}
secondText={secondText} secondText={secondText}
currentDisplayIndex={mergedCurrentDisplayIndex}
arrivalTime={arrivalTime} arrivalTime={arrivalTime}
/> />
)); ));
@@ -992,18 +1141,28 @@ type eachStopType = {
isSmall: boolean; isSmall: boolean;
index: number; index: number;
secondText: string; secondText: string;
currentDisplayIndex: number;
arrivalTime?: string | null; arrivalTime?: string | null;
}; };
const EachStopData: FC<eachStopType> = (props) => { const EachStopData: FC<eachStopType> = (props) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const { d, delayTime, isSmall, index, secondText, arrivalTime } = props; const { playbackCurrentTimeIso } = useTrainMenu();
const {
d,
delayTime,
isSmall,
index,
secondText,
currentDisplayIndex,
arrivalTime,
} = props;
if (!d) return null; if (!d) return null;
if (d == "") return null; if (d == "") return null;
const [station, se, time] = d.split(","); const [station, se, time] = d.split(",");
const calcMinute = (t: string) => { const calcMinute = (t: string) => {
if (!t || t === "") return null; if (!t || t === "") return null;
const now = dayjs(); const now = playbackCurrentTimeIso ? dayjs(playbackCurrentTimeIso) : dayjs();
const hour = parseInt(t.split(":")[0]); const hour = parseInt(t.split(":")[0]);
const dt = now const dt = now
.hour(hour < 4 ? hour + 24 : hour) .hour(hour < 4 ? hour + 24 : hour)
@@ -1078,7 +1237,7 @@ const EachStopData: FC<eachStopType> = (props) => {
style={{ style={{
fontSize: isSmall ? 8 : 14, fontSize: isSmall ? 8 : 14,
color: color:
index == 1 && secondText == "" index === currentDisplayIndex && secondText === ""
? "#ffe852ff" ? "#ffe852ff"
: se.includes("通") : se.includes("通")
? "#020202ff" ? "#020202ff"
@@ -1088,14 +1247,14 @@ const EachStopData: FC<eachStopType> = (props) => {
fontWeight: "bold", fontWeight: "bold",
}} }}
> >
{index == 1 && secondText == "" {index === currentDisplayIndex && secondText === ""
? "→" ? "→"
: se.includes("通") : se.includes("通")
? null ? null
: "●"} : "●"}
</Text> </Text>
</View> </View>
{index == 0 && secondText != "" && ( {index === 0 && secondText !== "" && (
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram"; import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { useStationList } from "@/stateBox/useStationList"; import { useStationList } from "@/stateBox/useStationList";
import { OriginalStationList } from "@/lib/CommonTypes";
interface StationIDPair { interface StationIDPair {
[key: string]: string; [key: string]: string;
@@ -10,12 +11,10 @@ interface LineListPair {
[key: string]: string; [key: string]: string;
} }
interface OriginalStationList { const isHiddenThroughPointEntry = (raw: string) => {
[key: string]: Array<{ const [station = "", se = ""] = (raw || "").split(",");
Station_JP: string; return station.startsWith(".") && se.includes("通");
StationNumber: string; };
}>;
}
/** /**
* *
@@ -33,8 +32,11 @@ export const useTrainDataWithThrough = (
const [trainDataWithThrough, setTrainDataWithThrough] = useState<string[]>([]); const [trainDataWithThrough, setTrainDataWithThrough] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
const trainData = allTrainDiagram[trainID]?.split("#"); const trainData =
if (!trainData) return; allTrainDiagram[trainID]
?.split("#")
.filter((entry) => !isHiddenThroughPointEntry(entry)) ?? [];
if (trainData.length === 0) return;
// 各停車駅の情報を取得 // 各停車駅の情報を取得
const stopStationList = trainData.map((i) => { const stopStationList = trainData.map((i) => {
@@ -76,6 +78,8 @@ export const useTrainDataWithThrough = (
if (!lineStations) return []; if (!lineStations) return [];
lineStations.forEach((d) => { lineStations.forEach((d) => {
const isHiddenPassPoint = d.Station_JP?.startsWith(".");
if (isHiddenPassPoint) return;
// 順方向の判定 // 順方向の判定
if ( if (
d.StationNumber > baseStationNumberFirst && d.StationNumber > baseStationNumberFirst &&
+15 -1
View File
@@ -55,7 +55,7 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo, onInitialLoadReady
data, data,
}); });
}; };
const { remountKey, processHandlers, pingHandlers, webViewRef } = useWebViewRemount({ const { remountKey, remount, processHandlers, pingHandlers, webViewRef } = useWebViewRemount({
pingEnabled: Platform.OS === "ios", pingEnabled: Platform.OS === "ios",
backgroundThresholdMs: null, backgroundThresholdMs: null,
isFocused, isFocused,
@@ -83,6 +83,20 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo, onInitialLoadReady
const loadEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const loadEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const focusedRef = useRef(isFocused); const focusedRef = useRef(isFocused);
const initialLoadReadyNotifiedRef = useRef(false); const initialLoadReadyNotifiedRef = useRef(false);
const previousMockApiFeatureEnabledRef = useRef(mockApiFeatureEnabled);
useEffect(() => {
const previousEnabled = previousMockApiFeatureEnabledRef.current;
if (previousEnabled === mockApiFeatureEnabled) return;
previousMockApiFeatureEnabledRef.current = mockApiFeatureEnabled;
addWebViewBreadcrumb("mock mode changed; remounting webview", {
previousEnabled,
enabled: mockApiFeatureEnabled,
focused: focusedRef.current,
});
remount();
}, [mockApiFeatureEnabled, remount]);
useEffect(() => { useEffect(() => {
focusedRef.current = isFocused; focusedRef.current = isFocused;
+11 -8
View File
@@ -59,7 +59,7 @@ export const ListViewItem: FC<{
const [ const [
typeString, typeString,
trainName, trainName,
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -79,7 +79,7 @@ export const ListViewItem: FC<{
to_data_color, to_data_color,
train_number_override, train_number_override,
} = customTrainDataDetector(d.trainNumber, allCustomTrainData); } = customTrainDataDetector(d.trainNumber, allCustomTrainData);
const [typeString, fontAvailable, isOneMan] = getStringConfig( const [typeString, fontFamily, isOneMan] = getStringConfig(
type, type,
d.trainNumber d.trainNumber
); );
@@ -111,7 +111,7 @@ export const ListViewItem: FC<{
return [ return [
typeString, typeString,
displayName, displayName,
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -128,7 +128,7 @@ export const ListViewItem: FC<{
return [ return [
typeString, typeString,
"", "",
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -145,7 +145,7 @@ export const ListViewItem: FC<{
migrateTrainName( migrateTrainName(
trainDataArray[trainDataArray.length - 1].split(",")[0] + "行き" trainDataArray[trainDataArray.length - 1].split(",")[0] + "行き"
), ),
fontAvailable, fontFamily,
isOneMan, isOneMan,
infogram, infogram,
priority, priority,
@@ -314,15 +314,18 @@ export const ListViewItem: FC<{
<Text <Text
style={{ style={{
fontSize: 15, fontSize: 15,
fontFamily: fontAvailable ? "JR-Nishi" : undefined, fontFamily,
fontWeight: !fontAvailable ? "bold" : undefined, fontWeight: fontFamily ? undefined : "bold",
paddingTop: fontAvailable ? 2 : 0, paddingTop: fontFamily ? 2 : 0,
paddingLeft: 10, paddingLeft: 10,
color: isCancelled ? "gray" : color, color: isCancelled ? "gray" : color,
textDecorationLine: isCancelled ? "line-through" : "none", textDecorationLine: isCancelled ? "line-through" : "none",
}} }}
> >
{typeString} {typeString}
{fontFamily === "JR-WEST-PLUS" ? (
<Text style={{ fontFamily: "DiaPro", fontSize: 7 }}>{"\u00A0"}</Text>
) : null}
</Text> </Text>
{showVehicle {showVehicle
? (() => { ? (() => {
+7 -223
View File
@@ -1,225 +1,9 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; import type { StyleProp, ViewStyle } from "react-native";
import { View, Text, TouchableOpacity, Linking } from "react-native";
//import MapView from "react-native-maps"; type TrainMenuProps = {
import { useCurrentTrain } from "../stateBox/useCurrentTrain"; style?: StyleProp<ViewStyle>;
import { useNavigation } from "@react-navigation/native"; };
import lineColorList from "../assets/originData/lineColorList";
import { lineListPair, stationIDPair } from "../lib/getStationList"; export default function TrainMenu(_props: TrainMenuProps) {
import { SheetManager } from "react-native-actions-sheet";
import { useTrainMenu } from "../stateBox/useTrainMenu";
//import { MapPin } from "./TrainMenu/MapPin";
import { UsefulBox } from "./TrainMenu/UsefulBox";
import { MapsButton } from "./TrainMenu/MapsButton";
import { useThemeColors } from "@/lib/theme";
export default function TrainMenu({ style }) {
const { fixed } = useThemeColors();
return null; return null;
const { webview } = useCurrentTrain();
const mapRef = useRef();
const { navigate, goBack } = useNavigation();
const [stationPin, setStationPin] = useState([]);
const {
selectedLine,
setSelectedLine,
mapsStationData: stationData,
} = useTrainMenu();
useEffect(() => {
const stationPinData = [];
Object.keys(stationData).forEach((d, indexBase) => {
stationData[d].forEach((D, index) => {
if (!D.StationMap) return null;
if (selectedLine && selectedLine != d) return;
const latlng = D.StationMap.replace(
"https://www.google.co.jp/maps/place/",
""
).split(",");
if (latlng.length == 0) return null;
stationPinData.push({ D, d, latlng, indexBase: 0, index });
});
});
setStationPin(stationPinData);
}, [stationData, selectedLine]);
useLayoutEffect(() => {
mapRef.current.fitToCoordinates(
stationPin.map(({ latlng }) => ({
latitude: parseFloat(latlng[0]),
longitude: parseFloat(latlng[1]),
})),
{ edgePadding: { top: 80, bottom: 120, left: 50, right: 50 } } // Add margin values here
);
}, [stationPin]);
return (
<View style={{ height: "100%", backgroundColor: fixed.primary, ...style }}>
<MapView
style={{ flex: 1, width: "100%", height: "100%" }}
showsUserLocation={true}
loadingEnabled={true}
showsMyLocationButton={false}
moveOnMarkerPress={false}
showsCompass={false}
ref={mapRef}
//provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 33.774519,
longitude: 133.533306,
latitudeDelta: 1.8, //小さくなるほどズーム
longitudeDelta: 1.8,
}}
>
{stationPin.map(({ D, d, latlng, indexBase, index }) => (
<MapPin
index={index}
indexBase={indexBase}
latlng={latlng}
D={D}
d={d}
navigate={navigate}
webview={webview}
key={D.StationNumber + d}
/>
))}
</MapView>
<View style={{ position: "relative" }}>
<View
style={{
flexDirection: "row",
position: "absolute",
width: "100vw",
bottom: 0,
flex: 1,
}}
>
<TouchableOpacity
style={{
backgroundColor: selectedLine
? lineColorList[stationIDPair[selectedLine]]
: fixed.primary,
padding: 10,
zIndex: 1,
alignItems: "center",
position: "absolute",
bottom: 0,
width: "100%",
paddingBottom: 50,
}}
onPress={() => SheetManager.show("TrainMenuLineSelector")}
>
<Text
style={{
color: "white",
fontWeight: "bold",
fontSize: 10,
textAlign: "center",
}}
>
    
</Text>
<Text
style={{
color: "white",
fontWeight: "bold",
fontSize: 20,
textAlign: "center",
}}
>
{selectedLine
? lineListPair[stationIDPair[selectedLine]]
: "JR四国 対象全駅"}
</Text>
</TouchableOpacity>
<Text style={{ position: "absolute", bottom: 40 }}>
</Text>
{Object.keys(stationData).map((d) => (
<TouchableOpacity
key={stationIDPair[d]}
style={{
flex: 1,
backgroundColor: lineColorList[stationIDPair[d]],
padding: 5,
margin: 2,
borderRadius: 10,
borderColor: "white",
borderWidth: 1,
borderStyle: "solid",
alignItems: "center",
opacity: selectedLine == d ? 1 : !selectedLine ? 1 : 0.5,
zIndex: 10,
}}
onPress={() => {
const s = selectedLine == d ? undefined : d;
if(!s) return;
setSelectedLine(s);
Object.keys(stationData).forEach((data, indexBase) => {
stationData[data].forEach((D, index) => {
if (!D.StationMap) return null;
if (s && s != data) return;
const latlng = D.StationMap.replace(
"https://www.google.co.jp/maps/place/",
""
).split(",");
if (latlng.length == 0) return null;
if (index == 0 && stationPin.length > 0) {
webview.current
?.injectJavaScript(`MoveDisplayStation('${data}_${D.MyStation}_${D.Station_JP}');
document.getElementById("disp").insertAdjacentHTML("afterbegin", "<div />");`);
}
});
});
}}
>
<Text
style={{ color: "white", fontWeight: "bold", fontSize: 20 }}
>
{stationIDPair[d]}
</Text>
</TouchableOpacity>
))}
</View>
</View>
{navigate && (
<View style={{ flexDirection: "row" }}>
<UsefulBox
backgroundColor={"#F89038"}
icon="train-car"
flex={1}
onPressButton={() =>
navigate("howto", {
info: "https://train.jr-shikoku.co.jp/usage.htm",
})
}
>
使
</UsefulBox>
<UsefulBox
backgroundColor={"#EA4752"}
icon="star"
flex={1}
onPressButton={() => navigate("favoriteList")}
>
</UsefulBox>
<UsefulBox
backgroundColor={"#91C31F"}
icon="clipboard-list-outline"
flex={1}
onPressButton={() =>
Linking.openURL(
"https://nexcloud.haruk.in/apps/forms/ZRHjWFF7znr5Xjr2"
)
}
>
</UsefulBox>
</View>
)}
<MapsButton
onPress={() => {
goBack();
}}
top={0}
mapSwitch={"flex"}
/>
</View>
);
} }
+4 -2
View File
@@ -28,6 +28,7 @@ import { getCurrentTrainData } from "@/lib/getCurrentTrainData";
import type { NavigateFunction } from "@/types"; import type { NavigateFunction } from "@/types";
import { PlatformNumber } from "./LED_inside_Component/PlatformNumber"; import { PlatformNumber } from "./LED_inside_Component/PlatformNumber";
import { useThemeColors } from "@/lib/theme"; import { useThemeColors } from "@/lib/theme";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
type Props = { type Props = {
d: eachTrainDiagramType; d: eachTrainDiagramType;
@@ -48,6 +49,7 @@ export const EachData: FC<Props> = (props) => {
} = props; } = props;
const { currentTrain } = useCurrentTrain(); const { currentTrain } = useCurrentTrain();
const { stationList } = useStationList(); const { stationList } = useStationList();
const { playbackCurrentTimeIso } = useTrainMenu();
const { allCustomTrainData } = useAllTrainDiagram(); const { allCustomTrainData } = useAllTrainDiagram();
const { fixed } = useThemeColors(); const { fixed } = useThemeColors();
const openTrainInfo = (d: { const openTrainInfo = (d: {
@@ -134,7 +136,7 @@ export const EachData: FC<Props> = (props) => {
const [h, m] = d.time.split(":"); const [h, m] = d.time.split(":");
const IntH = parseInt(h); const IntH = parseInt(h);
const IntM = parseInt(m); const IntM = parseInt(m);
const currentTime = dayjs(); const currentTime = playbackCurrentTimeIso ? dayjs(playbackCurrentTimeIso) : dayjs();
const trainTime = currentTime const trainTime = currentTime
.set("hour", IntH < 4 ? IntH + 24 : IntH) .set("hour", IntH < 4 ? IntH + 24 : IntH)
.set("minute", IntM); .set("minute", IntM);
@@ -145,7 +147,7 @@ export const EachData: FC<Props> = (props) => {
setIsDepartureNow(false); setIsDepartureNow(false);
setIsShow(true); setIsShow(true);
}; };
}, [d.time, currentTrainData]); }, [d.time, currentTrainData, playbackCurrentTimeIso]);
useInterval(() => { useInterval(() => {
if (isDepartureNow) { if (isDepartureNow) {
setIsShow(!isShow); setIsShow(!isShow);
+6 -2
View File
@@ -14,6 +14,8 @@ import { eachTrainDiagramType, StationProps } from "@/lib/CommonTypes";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useThemeColors } from "@/lib/theme"; import { useThemeColors } from "@/lib/theme";
import { stackAwareNavigate } from "@/lib/rootNavigation"; import { stackAwareNavigate } from "@/lib/rootNavigation";
import { useStationList } from "@/stateBox/useStationList";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
const readBooleanSetting = async (key: string) => { const readBooleanSetting = async (key: string) => {
try { try {
@@ -60,6 +62,8 @@ export const LED_vision: FC<props> = (props) => {
const { navigate } = useNavigation(); const { navigate } = useNavigation();
const { currentTrain } = useCurrentTrain(); const { currentTrain } = useCurrentTrain();
const { stationList } = useStationList();
const { playbackCurrentTimeIso } = useTrainMenu();
const [stationDiagram, setStationDiagram] = useState<{ const [stationDiagram, setStationDiagram] = useState<{
[key: string]: string; [key: string]: string;
}>({}); //当該駅の全時刻表 }>({}); //当該駅の全時刻表
@@ -121,10 +125,10 @@ export const LED_vision: FC<props> = (props) => {
if (!currentTrain) return () => {}; if (!currentTrain) return () => {};
const data = trainTimeAndNumber const data = trainTimeAndNumber
.filter((d) => currentTrain.map((m) => m.num).includes(d.train)) //現在の列車に絞る[ToDo] .filter((d) => currentTrain.map((m) => m.num).includes(d.train)) //現在の列車に絞る[ToDo]
.filter((d) => trainTimeFiltering({ d, currentTrain, station })) //時間フィルター .filter((d) => trainTimeFiltering({ d, currentTrain, station, stationList, now: playbackCurrentTimeIso })) //時間フィルター
.filter((d) => !!finalSwitch || d.lastStation != station[0].Station_JP); //最終列車表示設定 .filter((d) => !!finalSwitch || d.lastStation != station[0].Station_JP); //最終列車表示設定
setSelectedTrain(data); setSelectedTrain(data);
}, [trainTimeAndNumber, currentTrain, finalSwitch]); }, [trainTimeAndNumber, currentTrain, finalSwitch, stationList, playbackCurrentTimeIso]);
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const adjustedWidth = width * 0.98; const adjustedWidth = width * 0.98;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
# X投稿向け運行情報画像セット:実装指示書
## 依頼
`ndView.tsx` に、X(旧Twitter)へ複数画像で投稿することを前提とした運行情報画像セット生成機能を実装してください。
既存の「この項目を切り出す」「この小見出しを切り出す」「表示中の運行情報をすべて切り出す」「項目ごとに複数画像で共有」は残し、新たに「X投稿向け画像を作成」を追加します。
実装まで行い、型チェックと生成処理の確認をしてください。調査・提案だけで終了しないでください。
## ユーザー体験
新しいボタンを押すと、次の順番で最大4枚のPNGを生成し、既存の複数ファイル共有処理へ渡します。
1. 1枚目:運行状況の表紙(路線図+短い要約)
2. 2枚目以降:表示中の運行情報の詳細
Xで1枚目と2枚目が横並びに表示されたときに自然につながって見える構成にします。ただし、各画像は単体表示されても意味が通じる必要があります。画像の境界をまたいで文章や必須情報を配置しないでください。
## 出力仕様
- 各画像は `1080 x 1440 px`3:4)の固定サイズ
- PNG
- 画像数は1〜4枚。運行情報が存在する通常ケースでは表紙+詳細で2〜4枚
- ファイル名は投稿順を明確にする
- `operation-info-x-01-map-<timestamp>.png`
- `operation-info-x-02-detail-<timestamp>.png`
- 以下同様
- ネイティブ側へ送る `batchIndex` はゼロ始まりの表示順と一致させる
- 既存の `Share.open({ urls })` に渡る配列順を維持する
- 画像生成途中で1枚でも失敗した場合、不完全なセットを共有せず既存形式のエラーを返す
## 1枚目:表紙
必須要素:
- `JR四国 列車運行情報` のタイトル
- 現在表示中の状態を示す短い要約
- 四国の路線図
- 更新日時(取得できる最新のもの)
- 詳細ページがある場合は `詳細は次の画像へ →`
- 非公式アプリによる生成画像であり、最新情報はJR四国公式を確認する旨
- ページ番号(例:`1/4`
路線図は既存の `buildMapImage()``img/map/*` を再利用します。現在ページ上で有効になっている路線レイヤーを反映してください。既存挙動を壊さない範囲で、影響路線を目立たせ、影響のない部分を弱められるなら実装してください。状態と路線の対応を正確に判定できない場合は、誤った色分けを推測せず、現在表示されているレイヤー表現を使用してください。
表紙は路線図を主役にし、要約文が長い場合も地図を極端に小さくしないでください。要約は路線名・状態を中心に短く整形し、詳細本文を表紙へ詰め込みません。
## 2枚目以降:詳細ページ
必須要素:
- 路線・項目タイトル
- サブタイトル/状態
- 更新日時
- 小見出しと本文
- ページ番号
- 非公式画像の注意書き
既存の `getOperationItems()``parseDetailBlocks()``splitCaptureBlocksIntoSections()` のデータ構造を再利用します。
詳細は「1項目=必ず1画像」ではなく、固定高のページへ順に組版してください。
- 1件が短ければ、同じページに次の項目も配置してよい
- 1件が長ければ、ブロックまたは小見出し単位で次ページへ送る
- 小見出しだけをページ末尾に残さない
- 本文を文字単位で不自然に分断するより、小見出し単位の改ページを優先する
- 4枚を超える場合は、詳細3ページへ読みやすく収める
- それでも収まらない場合は、文字を極端に縮小したり内容を黙って欠落させたりしない
4枚で収まらないケースについては、実装上安全な扱いを選び、コードコメントと完了報告で明示してください。推奨は、最小可読サイズを守ったうえで4枚では収まらないことをユーザーへ知らせ、既存の項目別共有を案内することです。
## レイアウト規則
- 背景は白を基本とし、既存のJR四国風配色(`#0099CB` など)を継承
- 左右80px程度をX側のクロップに備えた安全領域とする
- 必須情報は端へ寄せない
- 全ページでヘッダー、ページ番号、フッターの位置を統一
- 本文の可読性を優先し、既存画像と同程度の文字サイズを維持
- 1枚目右側の矢印などで2枚目への視線誘導を作ってよいが、その装飾が切れても意味が失われないようにする
- 角丸や画像間の隙間はX側が付けるため、画像自体には外周の角丸を焼き込まない
## 既存コードの入口
主な対象は `ndView.tsx` です。
- `buildOperationPageScript()`WebViewへ注入する生成処理
- `buildMapImage()`:路線図Canvas合成
- `appendCaptureItemLayout()` / `drawCaptureLayout()`:既存組版・描画
- `getOperationItems()`:表示中の運行情報抽出
- `installPageCaptureButton()`:共有ボタン追加
- `renderAllItemsToSeparateImages()`:既存バッチ送信の参考
- `saveOperationCaptureBatchItem()`:ネイティブ側で順番どおりにファイルを集約
- `shareOperationCaptureFiles()`:単一/複数ファイル共有
必要ならX投稿向けのレイアウト計算・描画関数を独立させてください。既存の可変幅・可変高画像生成ロジックへ多数の条件分岐を足すより、データ抽出と基本描画だけ共有する構成を優先します。
## 実装上の制約
- 既存の切り出し/共有機能を壊さない
- 横画面サイネージモードでは、従来どおりキャプチャボタンを表示しない
- iOS・Android双方で動く既存のWebViewメッセージ方式を維持
- 新しい依存パッケージは原則追加しない
- WebView内で利用できない新しいJavaScript構文を安易に導入しない
- 生成中の二重タップで複数バッチが競合しないよう考慮する
- ユーザーの既存変更があるファイルを勝手に巻き戻さない
- `scripts/generate-operation-info-userscript.mjs``ndView.tsx` の注入コードを元に生成物を作るため、必要なら生成物も更新する。ただし生成物を直接手編集しない
## UI文言
新規ボタン:
`X投稿向け画像を作成`
生成・共有に失敗した場合は、既存の「切り出し失敗」系ダイアログとトーンをそろえます。4枚へ収まらない場合は、理由と代替手段が分かる日本語を表示してください。
## 完了条件
- 新規ボタンから表紙+詳細画像が正しい順番で共有される
- 全画像が厳密に1080×1440
- 1件、2件、3件、4件以上、長文1件のレイアウトを確認している
- 長文がCanvas外へはみ出さない
- 空のタイトル、サブタイトル、更新日時でも壊れない
- 地図画像のロード失敗時も白紙画像を共有せず、明確に失敗扱いまたは安全なフォールバックになる
- 既存4種類の切り出し操作が維持される
- TypeScript型チェックが通る
- `git diff --check` が通る
## 検証
最低限、次を実行してください。
```bash
npx tsc --noEmit
git diff --check
```
注入スクリプトを変更した場合は、リポジトリ既存の生成手順も確認します。
```bash
node scripts/generate-operation-info-userscript.mjs
```
可能なら、Canvas描画を開発用に呼び出せる小さなテスト入口または純粋なページ分割関数を用意し、上記の件数・長文パターンを検証してください。本番UIへデバッグ操作を残さないでください。
## 完了報告に含めるもの
- 変更したファイル
- 表紙・詳細ページの分割ルール
- 4枚で収まらない場合の扱い
- 実行した検証と結果
- 端末で確認が必要な残件
+3
View File
@@ -89,6 +89,9 @@ export type CustomTrainData = {
lng: number; lng: number;
isSpot?: boolean; isSpot?: boolean;
}; };
export type OriginalStationList = Record<string, StationProps[]>;
export type OperationLogs = { export type OperationLogs = {
id: number; id: number;
operation_id?: string; operation_id?: string;
+2 -39
View File
@@ -2,6 +2,8 @@
// Source: https://github.com/micolous/metrodroid (GPL-3.0) // Source: https://github.com/micolous/metrodroid (GPL-3.0)
// Keys: stationId = ((areaCode)<<16) | ((lineCode)<<8) | stationCode // Keys: stationId = ((areaCode)<<16) | ((lineCode)<<8) | stationCode
// areaCode = data[15] >> 6 (from FeliCa history block byte 15) // areaCode = data[15] >> 6 (from FeliCa history block byte 15)
// The source contains historical duplicate IDs; this map intentionally keeps the last
// record for each ID to preserve the original object last-record-wins lookup.
interface FelicaStationEntry { s: string; l: string; c: string } interface FelicaStationEntry { s: string; l: string; c: string }
@@ -61,7 +63,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x000147: { s: '清水', l: '東海道本', c: '東海旅客鉄道' }, 0x000147: { s: '清水', l: '東海道本', c: '東海旅客鉄道' },
0x000149: { s: '草薙', l: '東海道本', c: '東海旅客鉄道' }, 0x000149: { s: '草薙', l: '東海道本', c: '東海旅客鉄道' },
0x00014b: { s: '東静岡', l: '東海道本', c: '東海旅客鉄道' }, 0x00014b: { s: '東静岡', l: '東海道本', c: '東海旅客鉄道' },
0x00014b: { s: '東静岡', l: '東海道本', c: '東海旅客鉄道' },
0x00014c: { s: '静岡', l: '東海道本', c: '東海旅客鉄道' }, 0x00014c: { s: '静岡', l: '東海道本', c: '東海旅客鉄道' },
0x00014e: { s: '安倍川', l: '東海道本', c: '東海旅客鉄道' }, 0x00014e: { s: '安倍川', l: '東海道本', c: '東海旅客鉄道' },
0x00014f: { s: '用宗', l: '東海道本', c: '東海旅客鉄道' }, 0x00014f: { s: '用宗', l: '東海道本', c: '東海旅客鉄道' },
@@ -267,7 +268,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x000271: { s: '太子堂', l: '東北本', c: '東日本旅客鉄道' }, 0x000271: { s: '太子堂', l: '東北本', c: '東日本旅客鉄道' },
0x000272: { s: '長町', l: '東北本', c: '東日本旅客鉄道' }, 0x000272: { s: '長町', l: '東北本', c: '東日本旅客鉄道' },
0x000273: { s: '仙台', l: '東北本', c: '東日本旅客鉄道' }, 0x000273: { s: '仙台', l: '東北本', c: '東日本旅客鉄道' },
0x000273: { s: '仙台', l: '東北本', c: '東日本旅客鉄道' },
0x000275: { s: '東仙台', l: '東北本', c: '東日本旅客鉄道' }, 0x000275: { s: '東仙台', l: '東北本', c: '東日本旅客鉄道' },
0x000276: { s: '岩切', l: '東北本', c: '東日本旅客鉄道' }, 0x000276: { s: '岩切', l: '東北本', c: '東日本旅客鉄道' },
0x000277: { s: '陸前山王', l: '東北本', c: '東日本旅客鉄道' }, 0x000277: { s: '陸前山王', l: '東北本', c: '東日本旅客鉄道' },
@@ -665,9 +665,7 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00062c: { s: '大野城', l: '鹿児島本', c: '九州旅客鉄道' }, 0x00062c: { s: '大野城', l: '鹿児島本', c: '九州旅客鉄道' },
0x00062d: { s: '水城', l: '鹿児島本', c: '九州旅客鉄道' }, 0x00062d: { s: '水城', l: '鹿児島本', c: '九州旅客鉄道' },
0x00062e: { s: '都府楼南', l: '鹿児島本', c: '九州旅客鉄道' }, 0x00062e: { s: '都府楼南', l: '鹿児島本', c: '九州旅客鉄道' },
0x00062f: { s: '二日市', l: '鹿児島本', c: '九州旅客鉄道' },
0x00062f: { s: '都府楼南', l: '鹿児島本', c: '九州旅客鉄道' }, 0x00062f: { s: '都府楼南', l: '鹿児島本', c: '九州旅客鉄道' },
0x000630: { s: '二日市', l: '鹿児島本', c: '九州旅客鉄道' },
0x000630: { s: '天拝山', l: '鹿児島本', c: '九州旅客鉄道' }, 0x000630: { s: '天拝山', l: '鹿児島本', c: '九州旅客鉄道' },
0x000631: { s: '天拝山', l: '鹿児島本', c: '九州旅客鉄道' }, 0x000631: { s: '天拝山', l: '鹿児島本', c: '九州旅客鉄道' },
0x000632: { s: '原田', l: '鹿児島本', c: '九州旅客鉄道' }, 0x000632: { s: '原田', l: '鹿児島本', c: '九州旅客鉄道' },
@@ -725,7 +723,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00071a: { s: '三毛門', l: '日豊本', c: '九州旅客鉄道' }, 0x00071a: { s: '三毛門', l: '日豊本', c: '九州旅客鉄道' },
0x00071b: { s: '吉富', l: '日豊本', c: '九州旅客鉄道' }, 0x00071b: { s: '吉富', l: '日豊本', c: '九州旅客鉄道' },
0x00071c: { s: '中津', l: '日豊本', c: '九州旅客鉄道' }, 0x00071c: { s: '中津', l: '日豊本', c: '九州旅客鉄道' },
0x000728: { s: '中津', l: '日豊本', c: '九州旅客鉄道' },
0x000728: { s: '宇佐', l: '日豊本', c: '九州旅客鉄道' }, 0x000728: { s: '宇佐', l: '日豊本', c: '九州旅客鉄道' },
0x00072a: { s: '西屋敷', l: '日豊本', c: '九州旅客鉄道' }, 0x00072a: { s: '西屋敷', l: '日豊本', c: '九州旅客鉄道' },
0x00073b: { s: '別府', l: '日豊本', c: '九州旅客鉄道' }, 0x00073b: { s: '別府', l: '日豊本', c: '九州旅客鉄道' },
@@ -1113,9 +1110,7 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00104c: { s: '高野川', l: '予讃', c: '四国旅客鉄道' }, 0x00104c: { s: '高野川', l: '予讃', c: '四国旅客鉄道' },
0x001058: { s: '五郎', l: '予讃', c: '四国旅客鉄道' }, 0x001058: { s: '五郎', l: '予讃', c: '四国旅客鉄道' },
0x001059: { s: '伊予大洲', l: '予讃', c: '四国旅客鉄道' }, 0x001059: { s: '伊予大洲', l: '予讃', c: '四国旅客鉄道' },
0x001081: { s: '御免', l: 'ごめんなはり', c: '土佐くろしお鉄道' },
0x001081: { s: '後免', l: '阿佐', c: '土佐くろしお鉄道' }, 0x001081: { s: '後免', l: '阿佐', c: '土佐くろしお鉄道' },
0x001083: { s: '後免町', l: '阿佐', c: '土佐くろしお鉄道' },
0x001083: { s: '御免町', l: 'ごめんなはり', c: '土佐くろしお鉄道' }, 0x001083: { s: '御免町', l: 'ごめんなはり', c: '土佐くろしお鉄道' },
0x001102: { s: '金蔵寺', l: '土讃', c: '四国旅客鉄道' }, 0x001102: { s: '金蔵寺', l: '土讃', c: '四国旅客鉄道' },
0x001103: { s: '善通寺', l: '土讃', c: '四国旅客鉄道' }, 0x001103: { s: '善通寺', l: '土讃', c: '四国旅客鉄道' },
@@ -1123,7 +1118,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00110c: { s: '佃', l: '土讃', c: '四国旅客鉄道' }, 0x00110c: { s: '佃', l: '土讃', c: '四国旅客鉄道' },
0x00110d: { s: '阿波池田', l: '土讃', c: '四国旅客鉄道' }, 0x00110d: { s: '阿波池田', l: '土讃', c: '四国旅客鉄道' },
0x001113: { s: '大歩危', l: '土讃', c: '四国旅客鉄道' }, 0x001113: { s: '大歩危', l: '土讃', c: '四国旅客鉄道' },
0x001124: { s: '御免', l: '土讃', c: '四国旅客鉄道' },
0x001124: { s: '後免', l: '土讃', c: '四国旅客鉄道' }, 0x001124: { s: '後免', l: '土讃', c: '四国旅客鉄道' },
0x00112a: { s: '高知', l: '土讃', c: '四国旅客鉄道' }, 0x00112a: { s: '高知', l: '土讃', c: '四国旅客鉄道' },
0x00112b: { s: '入明', l: '土讃', c: '四国旅客鉄道' }, 0x00112b: { s: '入明', l: '土讃', c: '四国旅客鉄道' },
@@ -1649,7 +1643,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x002984: { s: '岩手和井内', l: '岩泉', c: '東日本旅客鉄道' }, 0x002984: { s: '岩手和井内', l: '岩泉', c: '東日本旅客鉄道' },
0x002986: { s: '押角', l: '岩泉', c: '東日本旅客鉄道' }, 0x002986: { s: '押角', l: '岩泉', c: '東日本旅客鉄道' },
0x002a0c: { s: '新川崎', l: '横須賀', c: '東日本旅客鉄道' }, 0x002a0c: { s: '新川崎', l: '横須賀', c: '東日本旅客鉄道' },
0x002a0d: { s: '武蔵小杉', l: '横須賀', c: '東日本旅客鉄道' },
0x002a0d: { s: '新川崎', l: '横須賀', c: '東日本旅客鉄道' }, 0x002a0d: { s: '新川崎', l: '横須賀', c: '東日本旅客鉄道' },
0x002a0e: { s: '西大井', l: '横須賀', c: '東日本旅客鉄道' }, 0x002a0e: { s: '西大井', l: '横須賀', c: '東日本旅客鉄道' },
0x002a15: { s: '新日本橋', l: '総武本', c: '東日本旅客鉄道' }, 0x002a15: { s: '新日本橋', l: '総武本', c: '東日本旅客鉄道' },
@@ -1671,7 +1664,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x002ada: { s: '登別', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002ada: { s: '登別', l: '室蘭本', c: '北海道旅客鉄道' },
0x002ae0: { s: '白老', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002ae0: { s: '白老', l: '室蘭本', c: '北海道旅客鉄道' },
0x002ae3: { s: '錦岡', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002ae3: { s: '錦岡', l: '室蘭本', c: '北海道旅客鉄道' },
0x002ae3: { s: '錦岡', l: '室蘭本', c: '北海道旅客鉄道' },
0x002ae5: { s: '糸井', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002ae5: { s: '糸井', l: '室蘭本', c: '北海道旅客鉄道' },
0x002ae9: { s: '苫小牧', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002ae9: { s: '苫小牧', l: '室蘭本', c: '北海道旅客鉄道' },
0x002aeb: { s: '沼ノ端', l: '室蘭本', c: '北海道旅客鉄道' }, 0x002aeb: { s: '沼ノ端', l: '室蘭本', c: '北海道旅客鉄道' },
@@ -2281,7 +2273,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00524a: { s: '三角', l: '三角', c: '九州旅客鉄道' }, 0x00524a: { s: '三角', l: '三角', c: '九州旅客鉄道' },
0x005318: { s: '太田部', l: '小海', c: '東日本旅客鉄道' }, 0x005318: { s: '太田部', l: '小海', c: '東日本旅客鉄道' },
0x005319: { s: '中込', l: '小海', c: '東日本旅客鉄道' }, 0x005319: { s: '中込', l: '小海', c: '東日本旅客鉄道' },
0x00531a: { s: '滑津', l: '小海', c: '東日本旅客鉄道' },
0x00531a: { s: '中込', l: '小海', c: '東日本旅客鉄道' }, 0x00531a: { s: '中込', l: '小海', c: '東日本旅客鉄道' },
0x00531b: { s: '北中込', l: '小海', c: '東日本旅客鉄道' }, 0x00531b: { s: '北中込', l: '小海', c: '東日本旅客鉄道' },
0x00531c: { s: '岩村田', l: '小海', c: '東日本旅客鉄道' }, 0x00531c: { s: '岩村田', l: '小海', c: '東日本旅客鉄道' },
@@ -2291,7 +2282,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x005352: { s: '人吉', l: '肥薩', c: '九州旅客鉄道' }, 0x005352: { s: '人吉', l: '肥薩', c: '九州旅客鉄道' },
0x00540c: { s: '戸狩野沢温泉', l: '飯山', c: '東日本旅客鉄道' }, 0x00540c: { s: '戸狩野沢温泉', l: '飯山', c: '東日本旅客鉄道' },
0x005415: { s: '森宮野原', l: '飯山', c: '東日本旅客鉄道' }, 0x005415: { s: '森宮野原', l: '飯山', c: '東日本旅客鉄道' },
0x005415: { s: '森宮野原', l: '飯山', c: '東日本旅客鉄道' },
0x00541f: { s: '十日町', l: '飯山', c: '東日本旅客鉄道' }, 0x00541f: { s: '十日町', l: '飯山', c: '東日本旅客鉄道' },
0x005420: { s: '魚沼中条', l: '飯山', c: '東日本旅客鉄道' }, 0x005420: { s: '魚沼中条', l: '飯山', c: '東日本旅客鉄道' },
0x005515: { s: '吉田', l: '越後', c: '東日本旅客鉄道' }, 0x005515: { s: '吉田', l: '越後', c: '東日本旅客鉄道' },
@@ -2715,7 +2705,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x007403: { s: '播磨高岡', l: '姫新', c: '西日本旅客鉄道' }, 0x007403: { s: '播磨高岡', l: '姫新', c: '西日本旅客鉄道' },
0x007404: { s: '余部', l: '姫新', c: '西日本旅客鉄道' }, 0x007404: { s: '余部', l: '姫新', c: '西日本旅客鉄道' },
0x007407: { s: '本竜野', l: '姫新', c: '西日本旅客鉄道' }, 0x007407: { s: '本竜野', l: '姫新', c: '西日本旅客鉄道' },
0x007408: { s: '東觜崎', l: '姫新線', c: '西日本旅客鉄道' },
0x007408: { s: '東觜崎', l: '姫新', c: '西日本旅客鉄道' }, 0x007408: { s: '東觜崎', l: '姫新', c: '西日本旅客鉄道' },
0x007409: { s: '播磨新宮', l: '姫新', c: '西日本旅客鉄道' }, 0x007409: { s: '播磨新宮', l: '姫新', c: '西日本旅客鉄道' },
0x007410: { s: '佐用', l: '姫新', c: '西日本旅客鉄道' }, 0x007410: { s: '佐用', l: '姫新', c: '西日本旅客鉄道' },
@@ -3453,7 +3442,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00c708: { s: '京王多摩センター', l: '相模原', c: '京王電鉄' }, 0x00c708: { s: '京王多摩センター', l: '相模原', c: '京王電鉄' },
0x00c709: { s: '京王堀之内', l: '相模原', c: '京王電鉄' }, 0x00c709: { s: '京王堀之内', l: '相模原', c: '京王電鉄' },
0x00c70a: { s: '南大沢', l: '相模原', c: '京王電鉄' }, 0x00c70a: { s: '南大沢', l: '相模原', c: '京王電鉄' },
0x00c70a: { s: '南大沢', l: '相模原', c: '京王電鉄' },
0x00c70b: { s: '多摩境', l: '相模原', c: '京王電鉄' }, 0x00c70b: { s: '多摩境', l: '相模原', c: '京王電鉄' },
0x00c70c: { s: '橋本', l: '相模原', c: '京王電鉄' }, 0x00c70c: { s: '橋本', l: '相模原', c: '京王電鉄' },
0x00c801: { s: '北野', l: '高尾', c: '京王電鉄' }, 0x00c801: { s: '北野', l: '高尾', c: '京王電鉄' },
@@ -3596,7 +3584,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00d403: { s: '新高島', l: 'みなとみらい', c: '横浜高速鉄道' }, 0x00d403: { s: '新高島', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d405: { s: 'みなとみらい', l: 'みなとみらい', c: '横浜高速鉄道' }, 0x00d405: { s: 'みなとみらい', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d407: { s: '馬車道', l: 'みなとみらい', c: '横浜高速鉄道' }, 0x00d407: { s: '馬車道', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d409: { s: '日本大通', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d409: { s: '日本大通り', l: 'みなとみらい', c: '横浜高速鉄道' }, 0x00d409: { s: '日本大通り', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d40b: { s: '元町・中華街', l: 'みなとみらい', c: '横浜高速鉄道' }, 0x00d40b: { s: '元町・中華街', l: 'みなとみらい', c: '横浜高速鉄道' },
0x00d501: { s: '泉岳寺', l: '本', c: '京浜急行電鉄' }, 0x00d501: { s: '泉岳寺', l: '本', c: '京浜急行電鉄' },
@@ -3654,14 +3641,8 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00d603: { s: '大鳥居', l: '空港', c: '京浜急行電鉄' }, 0x00d603: { s: '大鳥居', l: '空港', c: '京浜急行電鉄' },
0x00d604: { s: '穴守稲荷', l: '空港', c: '京浜急行電鉄' }, 0x00d604: { s: '穴守稲荷', l: '空港', c: '京浜急行電鉄' },
0x00d605: { s: '天空橋', l: '空港', c: '京浜急行電鉄' }, 0x00d605: { s: '天空橋', l: '空港', c: '京浜急行電鉄' },
0x00d606: { s: '国際ターミナル駅(仮称)', l: '空港', c: '京浜急行電鉄' },
0x00d606: { s: '羽田空港国際線ターミナル', l: '空港', c: '京浜急行電鉄' }, 0x00d606: { s: '羽田空港国際線ターミナル', l: '空港', c: '京浜急行電鉄' },
0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行電鉄' }, 0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行電鉄' },
0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行電鉄' },
0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行電鉄' },
0x00d607: { s: '羽田空港国内線ターミナル', l: '空港', c: '京浜急行電鉄' },
0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行' },
0x00d607: { s: '羽田空港', l: '空港', c: '京浜急行電鉄' },
0x00d701: { s: '京急川崎', l: '大師', c: '京浜急行電鉄' }, 0x00d701: { s: '京急川崎', l: '大師', c: '京浜急行電鉄' },
0x00d702: { s: '港町', l: '大師', c: '京浜急行電鉄' }, 0x00d702: { s: '港町', l: '大師', c: '京浜急行電鉄' },
0x00d703: { s: '鈴木町', l: '大師', c: '京浜急行電鉄' }, 0x00d703: { s: '鈴木町', l: '大師', c: '京浜急行電鉄' },
@@ -4111,7 +4092,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x00fa07: { s: '整備場', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa07: { s: '整備場', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa08: { s: '羽田', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa08: { s: '羽田', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa09: { s: '天空橋', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa09: { s: '天空橋', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa0a: { s: '国際線ターミナルビル', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa0a: { s: '羽田空港国際線ビル', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa0a: { s: '羽田空港国際線ビル', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa0b: { s: '新整備場', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa0b: { s: '新整備場', l: '東京モノレール羽田', c: '東京モノレール' },
0x00fa0c: { s: '羽田空港第1ビル', l: '東京モノレール羽田', c: '東京モノレール' }, 0x00fa0c: { s: '羽田空港第1ビル', l: '東京モノレール羽田', c: '東京モノレール' },
@@ -4823,7 +4803,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02933e: { s: '元町', l: '本', c: '阪神電気鉄道' }, 0x02933e: { s: '元町', l: '本', c: '阪神電気鉄道' },
0x029401: { s: '大阪難波', l: '阪神なんば', c: '阪神電気鉄道' }, 0x029401: { s: '大阪難波', l: '阪神なんば', c: '阪神電気鉄道' },
0x029403: { s: '桜川', l: '阪神なんば', c: '阪神電気鉄道' }, 0x029403: { s: '桜川', l: '阪神なんば', c: '阪神電気鉄道' },
0x029404: { s: '桜川', l: '阪神なんば', c: '阪神電気鉄道' },
0x029404: { s: 'ドーム前', l: '阪神なんば', c: '阪神電気鉄道' }, 0x029404: { s: 'ドーム前', l: '阪神なんば', c: '阪神電気鉄道' },
0x029405: { s: '九条', l: '阪神なんば', c: '阪神電気鉄道' }, 0x029405: { s: '九条', l: '阪神なんば', c: '阪神電気鉄道' },
0x029407: { s: '西九条', l: '西大阪', c: '阪神電気鉄道' }, 0x029407: { s: '西九条', l: '西大阪', c: '阪神電気鉄道' },
@@ -5122,12 +5101,9 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02c54e: { s: '鳥羽街道', l: '京阪本', c: '京阪電気鉄道' }, 0x02c54e: { s: '鳥羽街道', l: '京阪本', c: '京阪電気鉄道' },
0x02c550: { s: '東福寺', l: '京阪本', c: '京阪電気鉄道' }, 0x02c550: { s: '東福寺', l: '京阪本', c: '京阪電気鉄道' },
0x02c552: { s: '七条', l: '京阪本', c: '京阪電気鉄道' }, 0x02c552: { s: '七条', l: '京阪本', c: '京阪電気鉄道' },
0x02c554: { s: '五条', l: '京阪本', c: '京阪電気鉄道' },
0x02c554: { s: '清水五条', l: '京阪本', c: '京阪電気鉄道' }, 0x02c554: { s: '清水五条', l: '京阪本', c: '京阪電気鉄道' },
0x02c556: { s: '祇園四条', l: '京阪本', c: '京阪電気鉄道' },
0x02c556: { s: '四条', l: '京阪本', c: '京阪電気鉄道' }, 0x02c556: { s: '四条', l: '京阪本', c: '京阪電気鉄道' },
0x02c558: { s: '三条', l: '京阪本', c: '京阪電気鉄道' }, 0x02c558: { s: '三条', l: '京阪本', c: '京阪電気鉄道' },
0x02c55a: { s: '丸太町', l: '鴨東', c: '京阪電気鉄道' },
0x02c55a: { s: '神宮丸太町', l: '鴨東', c: '京阪電気鉄道' }, 0x02c55a: { s: '神宮丸太町', l: '鴨東', c: '京阪電気鉄道' },
0x02c55c: { s: '出町柳', l: '鴨東', c: '京阪電気鉄道' }, 0x02c55c: { s: '出町柳', l: '鴨東', c: '京阪電気鉄道' },
0x02c5c2: { s: '八幡市', l: '京阪鋼索', c: '京阪電気鉄道' }, 0x02c5c2: { s: '八幡市', l: '京阪鋼索', c: '京阪電気鉄道' },
@@ -5336,12 +5312,8 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02e603: { s: '近鉄新庄', l: '御所', c: '近畿日本鉄道' }, 0x02e603: { s: '近鉄新庄', l: '御所', c: '近畿日本鉄道' },
0x02e604: { s: '忍海', l: '御所', c: '近畿日本鉄道' }, 0x02e604: { s: '忍海', l: '御所', c: '近畿日本鉄道' },
0x02e605: { s: '近鉄御所', l: '御所', c: '近畿日本鉄道' }, 0x02e605: { s: '近鉄御所', l: '御所', c: '近畿日本鉄道' },
0x02e701: { s: '近鉄難波', l: '難波', c: '近畿日本鉄道' },
0x02e701: { s: '大阪難波', l: '難波', c: '近畿日本鉄道' },
0x02e701: { s: '大阪難波', l: '難波', c: '近畿日本鉄道' }, 0x02e701: { s: '大阪難波', l: '難波', c: '近畿日本鉄道' },
0x02e702: { s: '近鉄日本橋', l: '難波', c: '近畿日本鉄道' }, 0x02e702: { s: '近鉄日本橋', l: '難波', c: '近畿日本鉄道' },
0x02e703: { s: '上本町', l: '大阪', c: '近畿日本鉄道' },
0x02e703: { s: '大阪上本町', l: '大阪', c: '近畿日本鉄道' },
0x02e703: { s: '大阪上本町', l: '大阪', c: '近畿日本鉄道' }, 0x02e703: { s: '大阪上本町', l: '大阪', c: '近畿日本鉄道' },
0x02e704: { s: '鶴橋', l: '大阪', c: '近畿日本鉄道' }, 0x02e704: { s: '鶴橋', l: '大阪', c: '近畿日本鉄道' },
0x02e706: { s: '今里', l: '大阪', c: '近畿日本鉄道' }, 0x02e706: { s: '今里', l: '大阪', c: '近畿日本鉄道' },
@@ -5495,7 +5467,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02ef16: { s: '桑名', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef16: { s: '桑名', l: '名古屋', c: '近畿日本鉄道' },
0x02ef17: { s: '益生', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef17: { s: '益生', l: '名古屋', c: '近畿日本鉄道' },
0x02ef19: { s: '伊勢朝日', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef19: { s: '伊勢朝日', l: '名古屋', c: '近畿日本鉄道' },
0x02ef1b: { s: '富洲原', l: '名古屋', c: '近畿日本鉄道' },
0x02ef1b: { s: '川越富洲原', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef1b: { s: '川越富洲原', l: '名古屋', c: '近畿日本鉄道' },
0x02ef1d: { s: '近鉄富田', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef1d: { s: '近鉄富田', l: '名古屋', c: '近畿日本鉄道' },
0x02ef1f: { s: '霞ヶ浦', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef1f: { s: '霞ヶ浦', l: '名古屋', c: '近畿日本鉄道' },
@@ -5512,7 +5483,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02ef2c: { s: '伊勢若松', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef2c: { s: '伊勢若松', l: '名古屋', c: '近畿日本鉄道' },
0x02ef2e: { s: '千代崎', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef2e: { s: '千代崎', l: '名古屋', c: '近畿日本鉄道' },
0x02ef30: { s: '白子', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef30: { s: '白子', l: '名古屋', c: '近畿日本鉄道' },
0x02ef30: { s: '白子', l: '名古屋', c: '近畿日本鉄道' },
0x02ef31: { s: '鼓ヶ浦', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef31: { s: '鼓ヶ浦', l: '名古屋', c: '近畿日本鉄道' },
0x02ef33: { s: '磯山', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef33: { s: '磯山', l: '名古屋', c: '近畿日本鉄道' },
0x02ef35: { s: '千里', l: '名古屋', c: '近畿日本鉄道' }, 0x02ef35: { s: '千里', l: '名古屋', c: '近畿日本鉄道' },
@@ -5573,7 +5543,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02f303: { s: '馬道 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f303: { s: '馬道 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f304: { s: '西別所 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f304: { s: '西別所 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f305: { s: '蓮花寺 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f305: { s: '蓮花寺 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f305: { s: '蓮花寺 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f306: { s: '在良 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f306: { s: '在良 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f307: { s: '坂井橋 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f307: { s: '坂井橋 廃止', l: '北勢', c: '近畿日本鉄道' },
0x02f30b: { s: '六把野 廃止', l: '北勢', c: '近畿日本鉄道' }, 0x02f30b: { s: '六把野 廃止', l: '北勢', c: '近畿日本鉄道' },
@@ -5629,15 +5598,12 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x02ffb7: { s: '貿易センター', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffb7: { s: '貿易センター', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffb9: { s: 'ポートターミナル', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffb9: { s: 'ポートターミナル', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffbb: { s: '中公園', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffbb: { s: '中公園', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffbd: { s: '市民病院前', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffbd: { s: 'みなとじま', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffbd: { s: 'みなとじま', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffbf: { s: '市民広場', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffbf: { s: '市民広場', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc1: { s: '南公園', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffc1: { s: '南公園', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc3: { s: '中埠頭', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffc3: { s: '中埠頭', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc5: { s: '北埠頭', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffc5: { s: '北埠頭', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc7: { s: '先端医療センター前', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc7: { s: '医療センター', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffc7: { s: '医療センター', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc9: { s: '京コンピュータ前', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffc9: { s: 'ポートアイランド南', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffc9: { s: 'ポートアイランド南', l: 'ポートアイランド', c: '神戸新交通' },
0x02ffcb: { s: '神戸空港', l: 'ポートアイランド', c: '神戸新交通' }, 0x02ffcb: { s: '神戸空港', l: 'ポートアイランド', c: '神戸新交通' },
0x032841: { s: '川西', l: '錦川清流', c: '錦川鉄道' }, 0x032841: { s: '川西', l: '錦川清流', c: '錦川鉄道' },
@@ -5722,7 +5688,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x03be19: { s: '城北', l: 'アストラムライン', c: '広島高速交通' }, 0x03be19: { s: '城北', l: 'アストラムライン', c: '広島高速交通' },
0x03be1d: { s: '白島', l: 'アストラムライン', c: '広島高速交通' }, 0x03be1d: { s: '白島', l: 'アストラムライン', c: '広島高速交通' },
0x03be1f: { s: '牛田', l: 'アストラムライン', c: '広島高速交通' }, 0x03be1f: { s: '牛田', l: 'アストラムライン', c: '広島高速交通' },
0x03be21: { s: '不動院前', l: 'アストラムライン', c: '広島' },
0x03be21: { s: '不動院前', l: 'アストラムライン', c: '広島高速交通' }, 0x03be21: { s: '不動院前', l: 'アストラムライン', c: '広島高速交通' },
0x03be23: { s: '祇園新橋北', l: 'アストラムライン', c: '広島高速交通' }, 0x03be23: { s: '祇園新橋北', l: 'アストラムライン', c: '広島高速交通' },
0x03be25: { s: '西原', l: 'アストラムライン', c: '広島高速交通' }, 0x03be25: { s: '西原', l: 'アストラムライン', c: '広島高速交通' },
@@ -5832,7 +5797,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x03d64d: { s: '西鉄千早', l: '貝塚', c: '西日本鉄道' }, 0x03d64d: { s: '西鉄千早', l: '貝塚', c: '西日本鉄道' },
0x03d64f: { s: '名島', l: '貝塚', c: '西日本鉄道' }, 0x03d64f: { s: '名島', l: '貝塚', c: '西日本鉄道' },
0x03d651: { s: '貝塚', l: '貝塚', c: '西日本鉄道' }, 0x03d651: { s: '貝塚', l: '貝塚', c: '西日本鉄道' },
0x03d765: { s: '西鉄福岡(天神)', l: '天神大牟田線', c: '西日本鉄道' },
0x03d765: { s: '西鉄福岡(天神)', l: '天神大牟田', c: '西日本鉄道' }, 0x03d765: { s: '西鉄福岡(天神)', l: '天神大牟田', c: '西日本鉄道' },
0x03d767: { s: '薬院', l: '天神大牟田', c: '西日本鉄道' }, 0x03d767: { s: '薬院', l: '天神大牟田', c: '西日本鉄道' },
0x03d769: { s: '西鉄平尾', l: '天神大牟田', c: '西日本鉄道' }, 0x03d769: { s: '西鉄平尾', l: '天神大牟田', c: '西日本鉄道' },
@@ -5846,7 +5810,6 @@ export const FELICA_STATION_MAP: Record<number, FelicaStationEntry> = {
0x03d777: { s: '下大利', l: '天神大牟田', c: '西日本鉄道' }, 0x03d777: { s: '下大利', l: '天神大牟田', c: '西日本鉄道' },
0x03d779: { s: '都府楼前', l: '天神大牟田', c: '西日本鉄道' }, 0x03d779: { s: '都府楼前', l: '天神大牟田', c: '西日本鉄道' },
0x03d77b: { s: '西鉄二日市', l: '天神大牟田', c: '西日本鉄道' }, 0x03d77b: { s: '西鉄二日市', l: '天神大牟田', c: '西日本鉄道' },
0x03d77c: { s: '二日市南', l: '天神大牟田', c: '西日本鉄道' },
0x03d77c: { s: '紫', l: '天神大牟田', c: '西日本鉄道' }, 0x03d77c: { s: '紫', l: '天神大牟田', c: '西日本鉄道' },
0x03d77d: { s: '朝倉街道', l: '天神大牟田', c: '西日本鉄道' }, 0x03d77d: { s: '朝倉街道', l: '天神大牟田', c: '西日本鉄道' },
0x03d77f: { s: '桜台', l: '天神大牟田', c: '西日本鉄道' }, 0x03d77f: { s: '桜台', l: '天神大牟田', c: '西日本鉄道' },
+14 -49
View File
@@ -1,53 +1,18 @@
import { trainTypeID } from "@/lib/CommonTypes"; import { trainTypeID } from "@/lib/CommonTypes";
import {
getTrainType,
type TrainTypeFontFamily,
} from "@/lib/getTrainType";
type types = (
type: trainTypeID,
id: string,
) => [string, TrainTypeFontFamily | undefined, boolean];
type types = (types: trainTypeID, id: string) => [string, boolean, boolean];
export const getStringConfig: types = (type, id) => { export const getStringConfig: types = (type, id) => {
switch (type) { const { shortName, fontFamily, isOneMan } = getTrainType({ type, id });
case "Normal": const typeString =
return ["普通", true, false]; type === "Other" && shortName === "その他" ? "" : shortName;
case "OneMan":
return ["普通", true, true]; return [typeString, fontFamily, isOneMan];
case "Rapid":
return ["快速", true, false];
case "OneManRapid":
return ["快速", true, true];
case "LTDEXP":
return ["特急", true, false];
case "NightLTDEXP":
return ["特急", true, false];
case "SPCL":
return ["臨時", true, false];
case "SPCL_Normal":
return ["臨時", true, false];
case "SPCL_Rapid":
return ["臨時快速", true, false];
case "SPCL_EXP":
return ["臨時特急", true, false];
case "Party":
return ["団体", true, false];
case "Freight":
return ["貨物", false, false];
case "Forwarding":
return ["回送", false, false];
case "Trial":
return ["試運転", false, false];
case "Construction":
return ["工事", false, false];
case "FreightForwarding":
return ["単機回送", false, false];
case "Other":
switch (true) {
case !!id.includes("T"):
return ["単機回送", false, false];
case !!id.includes("R"):
case !!id.includes("E"):
case !!id.includes("L"):
case !!id.includes("A"):
case !!id.includes("B"):
return ["回送", false, false];
case !!id.includes("H"):
return ["試運転", false, false];
}
return ["", false, false];
}
}; };
+21 -20
View File
@@ -29,6 +29,7 @@ type trainTypeString =
| "試運転" | "試運転"
| "工事" | "工事"
| "その他"; | "その他";
export type TrainTypeFontFamily = "JR-Nishi" | "JR-WEST-PLUS";
type trainTypeDataString = "rapid" | "express" | "normal" | "notService"; type trainTypeDataString = "rapid" | "express" | "normal" | "notService";
type getTrainType = (e: { type getTrainType = (e: {
type: trainTypeID; type: trainTypeID;
@@ -38,7 +39,7 @@ type getTrainType = (e: {
color: colorString; color: colorString;
name: trainTypeString; name: trainTypeString;
shortName: string; shortName: string;
fontAvailable: boolean; fontFamily: TrainTypeFontFamily | undefined;
isOneMan: boolean; isOneMan: boolean;
data: trainTypeDataString; data: trainTypeDataString;
}; };
@@ -49,7 +50,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#333333ff" : "white", color: whiteMode ? "#333333ff" : "white",
name: "普通列車", name: "普通列車",
shortName: "普通", shortName: "普通",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
@@ -58,7 +59,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#333333ff" : "white", color: whiteMode ? "#333333ff" : "white",
name: "普通列車(ワンマン)", name: "普通列車(ワンマン)",
shortName: "普通", shortName: "普通",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: true, isOneMan: true,
data: "normal", data: "normal",
}; };
@@ -67,7 +68,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#00a0bdff" : "aqua", color: whiteMode ? "#00a0bdff" : "aqua",
name: "快速", name: "快速",
shortName: "快速", shortName: "快速",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "rapid", data: "rapid",
}; };
@@ -76,7 +77,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#00a0bdff" : "aqua", color: whiteMode ? "#00a0bdff" : "aqua",
name: "快速", name: "快速",
shortName: "快速", shortName: "快速",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: true, isOneMan: true,
data: "rapid", data: "rapid",
}; };
@@ -85,7 +86,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#ee424dff", color: "#ee424dff",
name: "特急", name: "特急",
shortName: "特急", shortName: "特急",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "express", data: "express",
}; };
@@ -94,7 +95,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#e000b0ff" : "pink", color: whiteMode ? "#e000b0ff" : "pink",
name: "寝台特急", name: "寝台特急",
shortName: "特急", shortName: "特急",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "express", data: "express",
}; };
@@ -104,7 +105,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#297bff", color: "#297bff",
name: "臨時", name: "臨時",
shortName: "臨時", shortName: "臨時",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
@@ -113,7 +114,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#297bff", color: "#297bff",
name: "臨時快速", name: "臨時快速",
shortName: "臨時快速", shortName: "臨時快速",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
@@ -122,7 +123,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#297bff", color: "#297bff",
name: "臨時特急", name: "臨時特急",
shortName: "臨時特急", shortName: "臨時特急",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
@@ -131,7 +132,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#ff7300ff", color: "#ff7300ff",
name: "団体", name: "団体",
shortName: "団体", shortName: "団体",
fontAvailable: true, fontFamily: "JR-Nishi",
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
@@ -140,7 +141,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#007488ff", color: "#007488ff",
name: "貨物", name: "貨物",
shortName: "貨物", shortName: "貨物",
fontAvailable: false, fontFamily: undefined,
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -149,7 +150,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "回送", name: "回送",
shortName: "回送", shortName: "回送",
fontAvailable: false, fontFamily: "JR-WEST-PLUS",
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -158,7 +159,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "試運転", name: "試運転",
shortName: "試運転", shortName: "試運転",
fontAvailable: false, fontFamily: "JR-WEST-PLUS",
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -167,7 +168,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "工事", name: "工事",
shortName: "工事", shortName: "工事",
fontAvailable: false, fontFamily: undefined,
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -176,7 +177,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "単機回送", name: "単機回送",
shortName: "単機回送", shortName: "単機回送",
fontAvailable: false, fontFamily: undefined,
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -189,7 +190,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "単機回送", name: "単機回送",
shortName: "単機回送", shortName: "単機回送",
fontAvailable: false, fontFamily: undefined,
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -202,7 +203,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "回送", name: "回送",
shortName: "回送", shortName: "回送",
fontAvailable: false, fontFamily: "JR-WEST-PLUS",
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -211,7 +212,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: "#5f5f5fff", color: "#5f5f5fff",
name: "試運転", name: "試運転",
shortName: "試運転", shortName: "試運転",
fontAvailable: false, fontFamily: "JR-WEST-PLUS",
isOneMan: false, isOneMan: false,
data: "notService", data: "notService",
}; };
@@ -221,7 +222,7 @@ export const getTrainType: getTrainType = ({ type, id, whiteMode }) => {
color: whiteMode ? "#333333ff" : "white", color: whiteMode ? "#333333ff" : "white",
name: "その他", name: "その他",
shortName: "その他", shortName: "その他",
fontAvailable: false, fontFamily: undefined,
isOneMan: false, isOneMan: false,
data: "normal", data: "normal",
}; };
+13 -6
View File
@@ -1,4 +1,5 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { checkDuplicateTrainData } from "@/lib/checkDuplicateTrainData";
import { trainDataType, trainPosition } from "@/lib/trainPositionTextArray"; import { trainDataType, trainPosition } from "@/lib/trainPositionTextArray";
import { eachTrainDiagramType, StationProps } from "@/lib/CommonTypes"; import { eachTrainDiagramType, StationProps } from "@/lib/CommonTypes";
import betweenData from "@/assets/originData/between"; import betweenData from "@/assets/originData/between";
@@ -6,12 +7,15 @@ type trainDataProps = {
d: eachTrainDiagramType; d: eachTrainDiagramType;
currentTrain: trainDataType[]; currentTrain: trainDataType[];
station: StationProps[]; station: StationProps[];
stationList: StationProps[][];
now?: string | null;
}; };
export const trainTimeFiltering: (x: trainDataProps) => boolean = (props) => { export const trainTimeFiltering: (x: trainDataProps) => boolean = (props) => {
const { d, currentTrain, station } = props; const { d, currentTrain, station, stationList, now } = props;
const baseTime = 2; // 何時間以内の列車を表示するか const baseTime = 2; // 何時間以内の列車を表示するか
if (currentTrain.filter((t) => t.num == d.train).length == 0) { const currentTrainMatches = currentTrain.filter((t) => t.num == d.train);
const date = dayjs(); if (currentTrainMatches.length == 0) {
const date = now ? dayjs(now) : dayjs();
const trainTime = date const trainTime = date
.hour(parseInt(d.time.split(":")[0])) .hour(parseInt(d.time.split(":")[0]))
.minute(parseInt(d.time.split(":")[1])); .minute(parseInt(d.time.split(":")[1]));
@@ -23,7 +27,10 @@ export const trainTimeFiltering: (x: trainDataProps) => boolean = (props) => {
} }
return false; return false;
} else { } else {
const Pos = trainPosition(currentTrain.filter((t) => t.num == d.train)[0]); const currentTrainData =
checkDuplicateTrainData(currentTrainMatches, stationList) ??
currentTrainMatches[0];
const Pos = trainPosition(currentTrainData);
let nextPos = ""; let nextPos = "";
let PrePos = ""; let PrePos = "";
// //
@@ -65,9 +72,9 @@ export const trainTimeFiltering: (x: trainDataProps) => boolean = (props) => {
break; break;
} }
const [h, m] = d.time.split(":"); const [h, m] = d.time.split(":");
const delayData = currentTrain.filter((t) => t.num == d.train)[0].delay; const delayData = currentTrainData.delay;
let delay = delayData === "入線" ? 0 : delayData; let delay = delayData === "入線" ? 0 : delayData;
const date = dayjs(); const date = now ? dayjs(now) : dayjs();
const IntH = parseInt(h); const IntH = parseInt(h);
const IntM = parseInt(m); const IntM = parseInt(m);
const currentHour = date.hour(); const currentHour = date.hour();
+23
View File
@@ -0,0 +1,23 @@
const WRAPPABLE_TYPE_LENGTH = 4;
const MIN_LONG_TRAIN_NAME_LENGTH = 8;
const countCharacters = (value: string) => Array.from(value).length;
const normalizeTrainName = (trainName: string) => trainName.replace(/\s+/g, "");
export const shouldWrapTrainType = (
typeName: string,
trainName: string,
): boolean =>
countCharacters(typeName) === WRAPPABLE_TYPE_LENGTH &&
countCharacters(normalizeTrainName(trainName)) >= MIN_LONG_TRAIN_NAME_LENGTH;
export const formatWrappedTrainType = (typeName: string): string => {
const chars = Array.from(typeName);
if (chars.length !== WRAPPABLE_TYPE_LENGTH) {
return typeName;
}
return `${chars.slice(0, 2).join("")}\n${chars.slice(2).join("")}`;
};
+47 -19
View File
@@ -192,6 +192,7 @@ export const injectJavascriptData = ({
// データ変数の宣言 // データ変数の宣言
let stationList = {}; let stationList = {};
let trainDataList = []; let trainDataList = [];
let trainDataListReady = false;
let operationList = []; let operationList = [];
let trainDiagramData2 = {}; let trainDiagramData2 = {};
let probremsData = []; let probremsData = [];
@@ -259,11 +260,13 @@ export const injectJavascriptData = ({
const DatalistUpdate = () => { const DatalistUpdate = () => {
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()) fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json())
.then(data => { .then(data => {
if (hasChanged('trainDataList', data.data)) { const nextTrainDataList = Array.isArray(data?.data) ? data.data : null;
trainDataList = data.data; if (!nextTrainDataList) return;
_wcache.set('trainDataList', trainDataList); trainDataListReady = true;
setReload(); if (!hasChanged('trainDataList', nextTrainDataList)) return;
} trainDataList = nextTrainDataList;
_wcache.set('trainDataList', trainDataList);
setReload();
}) })
.catch(() => {}) .catch(() => {})
.finally(() => { setTimeout(DatalistUpdate, ${INTERVALS.DELAY_UPDATE}); }); .finally(() => { setTimeout(DatalistUpdate, ${INTERVALS.DELAY_UPDATE}); });
@@ -362,8 +365,12 @@ export const injectJavascriptData = ({
Object.keys(_hashes).forEach(k => { _hashes[k] = null; }); Object.keys(_hashes).forEach(k => { _hashes[k] = null; });
Promise.allSettled([ Promise.allSettled([
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => { fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => {
trainDataList = data.data ?? trainDataList; if (Array.isArray(data?.data)) {
_hashes['trainDataList'] = JSON.stringify(trainDataList); trainDataList = data.data;
trainDataListReady = true;
_hashes['trainDataList'] = JSON.stringify(trainDataList);
_wcache.set('trainDataList', trainDataList);
}
}).catch(() => {}), }).catch(() => {}),
fetch("${API_ENDPOINTS.OPERATION_LOGS}").then(r => r.json()).then(data => { fetch("${API_ENDPOINTS.OPERATION_LOGS}").then(r => r.json()).then(data => {
const source = Array.isArray(data?.data) ? data.data : []; const source = Array.isArray(data?.data) ? data.data : [];
@@ -406,6 +413,7 @@ export const injectJavascriptData = ({
if (_c0.operationList) { operationList = _c0.operationList; _hashes['operationList'] = JSON.stringify(_c0.operationList); anyHit = true; } if (_c0.operationList) { operationList = _c0.operationList; _hashes['operationList'] = JSON.stringify(_c0.operationList); anyHit = true; }
if (_c0.probremsData) { probremsData = _c0.probremsData; _hashes['probremsData'] = JSON.stringify(_c0.probremsData); anyHit = true; } if (_c0.probremsData) { probremsData = _c0.probremsData; _hashes['probremsData'] = JSON.stringify(_c0.probremsData); anyHit = true; }
if (_c0.trainDataList) { trainDataList = _c0.trainDataList; _hashes['trainDataList'] = JSON.stringify(_c0.trainDataList); anyHit = true; } if (_c0.trainDataList) { trainDataList = _c0.trainDataList; _hashes['trainDataList'] = JSON.stringify(_c0.trainDataList); anyHit = true; }
if (_c0.trainDataList) trainDataListReady = true;
if (_c0.trainDiagramData2) { trainDiagramData2 = _c0.trainDiagramData2; _hashes['trainDiagramData2'] = JSON.stringify(_c0.trainDiagramData2); anyHit = true; } if (_c0.trainDiagramData2) { trainDiagramData2 = _c0.trainDiagramData2; _hashes['trainDiagramData2'] = JSON.stringify(_c0.trainDiagramData2); anyHit = true; }
if (_c0.unyohubData) { unyohubData = _c0.unyohubData; anyHit = true; } if (_c0.unyohubData) { unyohubData = _c0.unyohubData; anyHit = true; }
if (_c0.elesiteData) { elesiteData = _c0.elesiteData; anyHit = true; } if (_c0.elesiteData) { elesiteData = _c0.elesiteData; anyHit = true; }
@@ -443,7 +451,9 @@ export const injectJavascriptData = ({
// TRAIN_DATA_API: 0.84s/1MB, DIAGRAM_TODAY: 0.27s/522KB // TRAIN_DATA_API: 0.84s/1MB, DIAGRAM_TODAY: 0.27s/522KB
Promise.allSettled([ Promise.allSettled([
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => { fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => {
trainDataList = data.data ?? []; if (!Array.isArray(data?.data)) return;
trainDataList = data.data;
trainDataListReady = true;
_hashes['trainDataList'] = JSON.stringify(trainDataList); _hashes['trainDataList'] = JSON.stringify(trainDataList);
_wcache.set('trainDataList', trainDataList); _wcache.set('trainDataList', trainDataList);
}).catch(() => {}), }).catch(() => {}),
@@ -696,6 +706,21 @@ export const injectJavascriptData = ({
let isSeason = false; let isSeason = false;
let TrainNumberOverride; let TrainNumberOverride;
let optionalText = ""; let optionalText = "";
const registeredTrainData = trainDataList.find(e => String(e.train_id) === String());
// 列車マスタに未登録でも在線カード自体は残す。
// 推測した種別・行先等は表示せず、列番と車両アイコンだけの最小表示にする。
if(trainDataListReady && !registeredTrainData){
.innerText = "";
${uiSetting === "tokyo" ? `
.insertAdjacentHTML('beforebegin', "<div data-jrs-train-data='unavailable' style='width:100%;display:flex;flex:1;flex-direction:" + (isLeft ? "column-reverse" : "column") + ";font-weight:bold;-webkit-text-size-adjust:100%;text-size-adjust:100%;'><p style='font-size:6px;padding:0;color:black;text-align:center;'>" + + "</p><div style='flex:1;'></div><p style='font-size:8px;font-weight:bold;padding:0;text-align:center;color:" + (hasProblem ? "red" : "black") + ";" + (hasProblem ? "animation:_jrs_blink 3s linear infinite;" : "") + "'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p></div>");
` : `
var _missingTextColor = _isDark ? 'white' : 'black';
.insertAdjacentHTML('beforebegin', "<p data-jrs-train-data='unavailable' style='font-size:10px;padding:0;color:" + _missingTextColor + ";'>" + + "</p>");
`}
.remove();
return;
}
try{ try{
const diagram = trainDiagramData2[] || trainTimeInfo[]; const diagram = trainDiagramData2[] || trainTimeInfo[];
if(diagram){ if(diagram){
@@ -944,6 +969,10 @@ export const injectJavascriptData = ({
} }
const optionalTextColor = optionalText.includes("最終") ? "red" : "black"; const optionalTextColor = optionalText.includes("最終") ? "red" : "black";
const hasViaData = !!viaData && viaData.trim() !== "";
const hasOptionalText = !!optionalText && optionalText.trim() !== "";
const hasTrainName = !!trainName && trainName.trim() !== "";
const hasToData = !!ToData && ToData.trim() !== "";
const trainTypeTextStyle = "font-size:10px;font-weight:bold;font-style:italic;padding:0;margin:0;color:#ffffff;text-align:center;line-height:1;"; const trainTypeTextStyle = "font-size:10px;font-weight:bold;font-style:italic;padding:0;margin:0;color:#ffffff;text-align:center;line-height:1;";
const trainTypeFrameStyle = "position:relative;height:10px;width:100%;overflow:hidden;display:flex;align-items:center;justify-content:center;"; const trainTypeFrameStyle = "position:relative;height:10px;width:100%;overflow:hidden;display:flex;align-items:center;justify-content:center;";
const trainTypeMainStyle = trainTypeTextStyle + "display:flex;align-items:center;justify-content:center;width:100%;height:100%;"; const trainTypeMainStyle = trainTypeTextStyle + "display:flex;align-items:center;justify-content:center;width:100%;height:100%;";
@@ -997,12 +1026,17 @@ export const injectJavascriptData = ({
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + offsetStyle + "background-color:#44bb44;border-radius:50%;border:1px solid #228822;width:19px;height:19px;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='https://storage.haruk.in/elesite_logo.jpg' style='width:19px;height:19px;'/></div>"; badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + offsetStyle + "background-color:#44bb44;border-radius:50%;border:1px solid #228822;width:19px;height:19px;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='https://storage.haruk.in/elesite_logo.jpg' style='width:19px;height:19px;'/></div>";
} }
.insertAdjacentHTML('beforebegin', "<div style='width:100%;display:flex;flex:1;flex-direction:"+(isLeft ? "column-reverse" : "column") + ";font-weight:bold;-webkit-text-size-adjust:100%;text-size-adjust:100%;'>" + badgeHtml + "<p style='font-size:6px;padding:0;color:black;text-align:center;'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p><div style='flex:1;'></div><p style='font-size:6px;font-weight:bold;padding:0;color:black;text-align:center;border-style:solid;border-width: "+(!!yosan2Color ? "2px" : "0px")+";border-color:" + yosan2Color + "'>" + viaData + "</p><p style='font-size:8px;font-weight:bold;padding:0;color: " + optionalTextColor + ";text-align:center;'>" + optionalText + "</p><p style='font-size:8px;font-weight:bold;padding:0;color:black;text-align:center;'>" + trainName + "</p><div style='width:100%;height:13px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:" + gradient + ";overflow:hidden;'><p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:white;line-height:1;text-align:center;text-shadow:1px 1px 0px #00000030, -1px -1px 0px #00000030,-1px 1px 0px #00000030, 1px -1px 0px #00000030,1px 0px 0px #00000030, -1px 0px 0px #00000030,0px 1px 0px #00000030, 0px -1px 0px #00000030;'>" + (ToData ? ToData + "行" : ToData) + "</p></div><div style='width:100%;height:13px;box-sizing:border-box;background:" + trainTypeColor + ";border-radius:"+(isLeft ? "4px 4px 0 0" : "0 0 4px 4px")+";display:flex;align-items:center;justify-content:center;padding:" + trainTypeContainerPadding + ";margin:0;overflow:hidden;'>" + trainTypeHtml + "</div><p style='font-size:8px;font-weight:bold;padding:0;text-align:center;color: "+(hasProblem ? "red": "black")+"; "+(hasProblem ? "animation:_jrs_blink 3s linear infinite;" : "")+"'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p></div>"); const viaDataHtml = hasViaData ? "<p style='font-size:6px;font-weight:bold;padding:0;margin:0;color:black;text-align:center;border-style:solid;border-width: " + (!!yosan2Color ? "2px" : "0px") + ";border-color:" + yosan2Color + "'>" + viaData + "</p>" : "";
const optionalTextHtml = hasOptionalText ? "<p style='font-size:8px;font-weight:bold;padding:0;margin:0;color: " + optionalTextColor + ";text-align:center;'>" + optionalText + "</p>" : "";
const trainNameHtml = hasTrainName ? "<p style='font-size:8px;font-weight:bold;padding:0;margin:0;color:black;text-align:center;'>" + trainName + "</p>" : "";
const toDataHtml = hasToData ? "<div style='width:100%;min-height:13px;height:auto;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:" + gradient + ";padding:1px 0;overflow:visible;'><p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:white;line-height:1.1;text-align:center;text-shadow:1px 1px 0px #00000030, -1px -1px 0px #00000030,-1px 1px 0px #00000030, 1px -1px 0px #00000030,1px 0px 0px #00000030, -1px 0px 0px #00000030,0px 1px 0px #00000030, 0px -1px 0px #00000030;'>" + ToData + "行</p></div>" : "";
const problemHtml = hasProblem ? "<p style='font-size:8px;font-weight:bold;padding:0;margin:0;text-align:center;color:red;animation:_jrs_blink 3s linear infinite;'>‼️停止中‼️</p>" : "";
.insertAdjacentHTML('beforebegin', "<div style='width:100%;display:flex;flex:1;flex-direction:"+(isLeft ? "column-reverse" : "column") + ";font-weight:bold;-webkit-text-size-adjust:100%;text-size-adjust:100%;'>" + badgeHtml + "<p style='font-size:6px;padding:0;margin:0;color:black;text-align:center;'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p><div style='flex:1;'></div>" + viaDataHtml + optionalTextHtml + trainNameHtml + toDataHtml + "<div style='width:100%;height:13px;box-sizing:border-box;background:" + trainTypeColor + ";border-radius:"+(isLeft ? "4px 4px 0 0" : "0 0 4px 4px")+";display:flex;align-items:center;justify-content:center;padding:" + trainTypeContainerPadding + ";margin:0;overflow:hidden;'>" + trainTypeHtml + "</div>" + problemHtml + "</div>");
`: ` `: `
var _defTextColor = _isDark ? 'white' : 'black'; var _defTextColor = _isDark ? 'white' : 'black';
.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color:" + _defTextColor + ";'>" + returnText1 + "</p>"); if(returnText1){ .insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:" + _defTextColor + ";'>" + returnText1 + "</p>"); }
.insertAdjacentHTML('beforebegin', "<div style='display:inline-flex;flex-direction:row;'><p style='font-size:10px;font-weight: bold;padding:0;color:" + _defTextColor + ";'>" + (ToData ? ToData + "行 " : ToData) + "</p><p style='font-size:10px;padding:0;color:" + _defTextColor + ";'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p></div>"); .insertAdjacentHTML('beforebegin', "<div style='display:inline-flex;flex-direction:row;'><p style='font-size:10px;font-weight: bold;padding:0;margin:0;color:" + _defTextColor + ";'>" + (ToData ? ToData + "行 " : "") + "</p><p style='font-size:10px;padding:0;margin:0;color:" + _defTextColor + ";'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p></div>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: "+(hasProblem ? "red": _defTextColor)+"; "+(hasProblem ? "animation:_jrs_blink 3s linear infinite;" : "")+"'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p>"); if(hasProblem){ .insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:red;animation:_jrs_blink 3s linear infinite;'>‼️停止中‼️</p>"); }
`} `}
} }
`; `;
@@ -1022,13 +1056,7 @@ const setNewTrainItem = (element,hasProblem,type)=>{
element.style.backgroundColor = _bgc; element.style.backgroundColor = _bgc;
}else{ }else{
element.style.backgroundColor = '#ffffffff'; element.style.backgroundColor = '#ffffffff';
if(element.getAttribute('offclick').includes("express")){ element.style.borderColor = _isDark ? '#30363d' : 'black';
element.style.borderColor = '#ff0000ff';
}else if(element.getAttribute('offclick').includes("rapid")){
element.style.borderColor = '#008cffff';
}else{
element.style.borderColor = _isDark ? '#30363d' : 'black';
}
} }
element.style.borderWidth = _isDark ? '1px' : '2px'; element.style.borderWidth = _isDark ? '1px' : '2px';
element.style.borderStyle = 'solid'; element.style.borderStyle = 'solid';
+1 -1
View File
@@ -517,7 +517,7 @@ export const Menu: FC<props> = (props) => {
/> />
)} )}
{originalStationList.length != 0 && isHeavyContentReady && ( {Object.keys(originalStationList).length !== 0 && isHeavyContentReady && (
<> <>
<CarouselBox <CarouselBox
{...{ {...{
+605 -31
View File
@@ -247,6 +247,9 @@ const buildOperationPageScript = (layout: {
'.jrs-capture-page-link:active{opacity:.9 !important;transform:translateY(1px) !important;}', '.jrs-capture-page-link:active{opacity:.9 !important;transform:translateY(1px) !important;}',
'.jrs-capture-page-link.is-secondary{background:#ffffff !important;color:#0076a8 !important;border:1px solid #0099CB !important;box-shadow:none !important;}', '.jrs-capture-page-link.is-secondary{background:#ffffff !important;color:#0076a8 !important;border:1px solid #0099CB !important;box-shadow:none !important;}',
'.jrs-capture-page-link.is-secondary:visited{color:#0076a8 !important;}', '.jrs-capture-page-link.is-secondary:visited{color:#0076a8 !important;}',
'.jrs-capture-page-link.is-x{background:linear-gradient(135deg,#111827 0%,#0f76a8 100%) !important;color:#fff !important;border:none !important;box-shadow:0 6px 14px rgba(15,118,168,.24) !important;}',
'.jrs-capture-page-link.is-x:visited{color:#fff !important;}',
'.jrs-capture-page-link.is-disabled,.jrs-capture-link.is-disabled,.jrs-subcapture-link.is-disabled{pointer-events:none !important;opacity:.52 !important;transform:none !important;}',
'.jrs-capture-wrap{display:block !important;text-align:right !important;margin:12px 0 8px !important;}', '.jrs-capture-wrap{display:block !important;text-align:right !important;margin:12px 0 8px !important;}',
'.jrs-capture-link{display:inline-block !important;background:#0a84ff !important;color:#fff !important;padding:10px 14px !important;border-radius:999px !important;font-size:12px !important;font-weight:700 !important;line-height:1.2 !important;text-decoration:none !important;box-shadow:0 4px 12px rgba(10,132,255,.25) !important;position:relative !important;z-index:9999 !important;}', '.jrs-capture-link{display:inline-block !important;background:#0a84ff !important;color:#fff !important;padding:10px 14px !important;border-radius:999px !important;font-size:12px !important;font-weight:700 !important;line-height:1.2 !important;text-decoration:none !important;box-shadow:0 4px 12px rgba(10,132,255,.25) !important;position:relative !important;z-index:9999 !important;}',
'.jrs-capture-link:visited{color:#fff !important;}', '.jrs-capture-link:visited{color:#fff !important;}',
@@ -394,8 +397,38 @@ const buildOperationPageScript = (layout: {
} }
function postMessage(payload) { function postMessage(payload) {
if (!window.ReactNativeWebView) return;
payload.type = 'operationInfoCapture'; payload.type = 'operationInfoCapture';
postNativeMessage(payload); window.ReactNativeWebView.postMessage(JSON.stringify(payload));
}
function setCaptureButtonsBusy(isBusy) {
qa('.jrs-capture-page-link, .jrs-capture-link, .jrs-subcapture-link').forEach(function(element) {
if (!element || !element.classList) return;
element.classList[isBusy ? 'add' : 'remove']('is-disabled');
element.setAttribute('aria-disabled', isBusy ? 'true' : 'false');
});
}
function runCaptureAction(action) {
if (window.__jrCaptureBusy) {
return;
}
window.__jrCaptureBusy = true;
setCaptureButtonsBusy(true);
Promise.resolve()
.then(action)
.catch(function() {
postMessage({ error: true });
})
.then(function() {
window.setTimeout(function() {
window.__jrCaptureBusy = false;
setCaptureButtonsBusy(false);
}, 120);
});
} }
function parseDetailBlocks(html) { function parseDetailBlocks(html) {
@@ -660,6 +693,518 @@ const buildOperationPageScript = (layout: {
}); });
} }
var X_CAPTURE_PAGE_WIDTH = 1080;
var X_CAPTURE_PAGE_HEIGHT = 1440;
var X_CAPTURE_SAFE_X = 80;
var X_CAPTURE_HEADER_TOP = 76;
var X_CAPTURE_CONTENT_TOP = 208;
var X_CAPTURE_CONTENT_BOTTOM = 1218;
var X_CAPTURE_FOOTER_Y = 1262;
var X_CAPTURE_ITEM_GAP = 26;
var X_CAPTURE_UNIT_GAP = 18;
function getXCaptureNotice() {
return 'この画像は非公式アプリで生成したものです。最新情報・詳細はJR四国公式の運行情報をご確認ください。';
}
function getXPageNumberText(pageIndex, totalPages) {
return String(pageIndex + 1) + '/' + String(totalPages);
}
function parseUpdatedAtValue(value) {
var text = strip(value);
if (!text) return null;
var normalized = text
.replace(/更新日時[:]?/g, '')
.replace(/年/g, '/')
.replace(/月/g, '/')
.replace(/日/g, '')
.replace(/時/g, ':')
.replace(/分/g, '')
.replace(/\./g, '/')
.replace(/-/g, '/')
.trim();
var parsed = Date.parse(normalized);
return isNaN(parsed) ? null : parsed;
}
function getLatestUpdatedAt(items) {
var bestText = '';
var bestValue = null;
(items || []).forEach(function(item) {
var updatedAt = strip(item && item.updatedAt);
if (!updatedAt) return;
var parsed = parseUpdatedAtValue(updatedAt);
if (parsed == null) {
if (!bestText) bestText = updatedAt;
return;
}
if (bestValue == null || parsed > bestValue) {
bestValue = parsed;
bestText = updatedAt;
}
});
return bestText;
}
function buildXCoverSummary(items) {
var entries = (items || []).map(function(item) {
var title = strip(item && item.title) || '運行情報';
var subTitle = strip(item && item.subTitle);
return subTitle ? title + '' + subTitle : title;
}).filter(function(text) {
return !!text;
});
if (!entries.length) {
return '現在表示中の運行情報はありません。';
}
if (entries.length === 1) {
return entries[0];
}
if (entries.length === 2) {
return entries[0] + ' / ' + entries[1];
}
return entries.slice(0, 3).join(' / ') + (entries.length > 3 ? ' ほか' : '');
}
function measureXDetailHeader(ctx, item, contentWidth, continued) {
var textWidth = contentWidth - 54;
ctx.font = "800 38px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var titleLines = wrapText(ctx, strip(item.title) || '運行情報', textWidth);
ctx.font = "700 26px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var subTitleLines = strip(item.subTitle) ? wrapText(ctx, item.subTitle, textWidth) : [];
ctx.font = "500 22px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var updatedLines = strip(item.updatedAt) ? wrapText(ctx, item.updatedAt, textWidth) : [];
var height = 30 + titleLines.length * 44 + (subTitleLines.length ? 10 + subTitleLines.length * 32 : 0) + (updatedLines.length ? 12 + updatedLines.length * 28 : 0) + 24;
return {
titleLines: titleLines,
subTitleLines: subTitleLines,
updatedLines: updatedLines,
continued: !!continued,
height: Math.max(118, height)
};
}
function createXDetailUnits(ctx, item, bodyWidth) {
var units = [];
var pendingHeading = '';
var blocks = item && item.blocks ? item.blocks : [];
blocks.forEach(function(block) {
if (block.type === 'badge') {
pendingHeading = strip(block.text);
return;
}
ctx.font = "400 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var bodyLines = wrapText(ctx, strip(block.text), bodyWidth);
if (!bodyLines.length && !pendingHeading) return;
ctx.font = "700 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var headingLines = pendingHeading ? wrapText(ctx, pendingHeading, bodyWidth - 24) : [];
units.push({
headingLines: headingLines,
bodyLines: bodyLines,
headingText: pendingHeading,
lineHeight: 39
});
pendingHeading = '';
});
if (!units.length) {
ctx.font = "400 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
units.push({
headingLines: [],
bodyLines: wrapText(ctx, '詳細情報はJR四国公式の運行情報をご確認ください。', bodyWidth),
headingText: '',
lineHeight: 39
});
}
return units;
}
function getXUnitHeight(unit) {
var headingHeight = unit.headingLines && unit.headingLines.length ? Math.max(56, unit.headingLines.length * 35 + 20) : 0;
var bodyHeight = Math.max(unit.bodyLines.length, 1) * unit.lineHeight;
return 22 + headingHeight + (headingHeight ? 14 : 0) + bodyHeight + 18;
}
function buildXUnitChunk(unit, availableHeight) {
var fullHeight = getXUnitHeight(unit);
if (fullHeight <= availableHeight) {
return {
chunk: {
headingLines: unit.headingLines.slice(),
bodyLines: unit.bodyLines.slice(),
lineHeight: unit.lineHeight,
height: fullHeight
},
consumed: true,
rest: null,
height: fullHeight
};
}
var headingHeight = unit.headingLines && unit.headingLines.length ? Math.max(56, unit.headingLines.length * 35 + 20) : 0;
var availableForBody = availableHeight - 22 - headingHeight - (headingHeight ? 14 : 0) - 18;
var lineCapacity = Math.floor(availableForBody / unit.lineHeight);
var minimumLines = headingHeight ? 2 : 3;
if (lineCapacity < minimumLines) {
return null;
}
var firstLines = unit.bodyLines.slice(0, lineCapacity);
if (!firstLines.length) {
return null;
}
var restLines = unit.bodyLines.slice(lineCapacity);
var chunk = {
headingLines: unit.headingLines.slice(),
bodyLines: firstLines,
lineHeight: unit.lineHeight
};
chunk.height = getXUnitHeight(chunk);
return {
chunk: chunk,
consumed: restLines.length === 0,
rest: restLines.length ? {
headingLines: [],
bodyLines: restLines,
lineHeight: unit.lineHeight
} : null,
height: chunk.height
};
}
function createEmptyXDetailPage() {
return { items: [], usedHeight: 0 };
}
function paginateXDetailPages(ctx, items, contentWidth) {
var bodyWidth = contentWidth - 32;
var maxHeight = X_CAPTURE_CONTENT_BOTTOM - X_CAPTURE_CONTENT_TOP;
var pages = [createEmptyXDetailPage()];
for (var itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
var item = items[itemIndex];
var units = createXDetailUnits(ctx, item, bodyWidth);
var unitIndex = 0;
var continued = false;
while (unitIndex < units.length) {
var page = pages[pages.length - 1];
var gapBeforeItem = page.items.length ? X_CAPTURE_ITEM_GAP : 0;
var header = measureXDetailHeader(ctx, item, contentWidth, continued);
var availableForStart = maxHeight - page.usedHeight - gapBeforeItem - header.height;
var preview = buildXUnitChunk(units[unitIndex], availableForStart);
if (!preview) {
if (page.items.length) {
pages.push(createEmptyXDetailPage());
continue;
}
return null;
}
var pageItem = {
header: header,
units: []
};
if (gapBeforeItem) {
page.usedHeight += gapBeforeItem;
}
page.items.push(pageItem);
page.usedHeight += header.height;
while (unitIndex < units.length) {
var availableHeight = maxHeight - page.usedHeight;
var chunkInfo = buildXUnitChunk(units[unitIndex], availableHeight);
if (!chunkInfo) {
break;
}
pageItem.units.push(chunkInfo.chunk);
page.usedHeight += chunkInfo.height;
if (chunkInfo.consumed) {
unitIndex += 1;
} else if (chunkInfo.rest) {
units[unitIndex] = chunkInfo.rest;
}
if (unitIndex < units.length) {
page.usedHeight += X_CAPTURE_UNIT_GAP;
}
}
if (unitIndex < units.length) {
pages.push(createEmptyXDetailPage());
continued = true;
}
}
}
return pages.filter(function(page) {
return page.items.length > 0;
});
}
function drawXPageChrome(ctx, pageIndex, totalPages, subHeading) {
var titleX = X_CAPTURE_SAFE_X;
ctx.fillStyle = '#0099CB';
ctx.fillRect(titleX, X_CAPTURE_HEADER_TOP + 4, 12, 76);
ctx.fillStyle = '#0f1720';
ctx.font = "800 56px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText('JR四国 列車運行情報', titleX + 28, X_CAPTURE_HEADER_TOP + 57);
ctx.fillStyle = '#4b5563';
ctx.font = "700 24px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText(subHeading, titleX + 30, X_CAPTURE_HEADER_TOP + 96);
ctx.fillStyle = '#0f1720';
ctx.font = "700 24px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var pageText = getXPageNumberText(pageIndex, totalPages);
var pageTextWidth = ctx.measureText(pageText).width;
ctx.fillText(pageText, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X - pageTextWidth, X_CAPTURE_HEADER_TOP + 52);
}
function drawXFooter(ctx, pageIndex, totalPages) {
var notice = getXCaptureNotice();
ctx.strokeStyle = '#d6dde5';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(X_CAPTURE_SAFE_X, X_CAPTURE_FOOTER_Y - 18);
ctx.lineTo(X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X, X_CAPTURE_FOOTER_Y - 18);
ctx.stroke();
ctx.fillStyle = '#5b6470';
ctx.font = "400 20px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
wrapText(ctx, notice, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2 - 90).slice(0, 3).forEach(function(line, index) {
ctx.fillText(line, X_CAPTURE_SAFE_X, X_CAPTURE_FOOTER_Y + index * 26);
});
ctx.fillStyle = '#111827';
ctx.font = "800 22px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var pageText = getXPageNumberText(pageIndex, totalPages);
var pageWidth = ctx.measureText(pageText).width;
ctx.fillText(pageText, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X - pageWidth, X_CAPTURE_FOOTER_Y + 52);
}
function drawXDetailHeaderBlock(ctx, header, x, y, width) {
ctx.fillStyle = '#0099CB';
ctx.fillRect(x, y, width, header.height);
ctx.fillStyle = '#ffffff';
ctx.font = "800 38px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var textY = y + 44;
header.titleLines.forEach(function(line) {
ctx.fillText(line, x + 22, textY);
textY += 44;
});
if (header.subTitleLines.length) {
ctx.font = "700 26px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
textY += 2;
header.subTitleLines.forEach(function(line) {
ctx.fillText(line, x + 22, textY);
textY += 32;
});
}
if (header.updatedLines.length) {
ctx.font = "500 22px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
textY += 4;
header.updatedLines.forEach(function(line) {
ctx.fillText(line, x + 22, textY);
textY += 28;
});
}
if (header.continued) {
ctx.fillStyle = 'rgba(255,255,255,.22)';
ctx.fillRect(x + width - 152, y + 18, 124, 34);
ctx.fillStyle = '#ffffff';
ctx.font = "700 20px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText('続き', x + width - 109, y + 41);
}
}
function drawXUnitBlock(ctx, unit, x, y, width) {
ctx.strokeStyle = '#d7dee7';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, width, unit.height);
var textY = y + 34;
if (unit.headingLines.length) {
var headingHeight = Math.max(56, unit.headingLines.length * 35 + 20);
ctx.fillStyle = '#f3f8fb';
ctx.fillRect(x, y, width, headingHeight + 20);
ctx.fillStyle = '#0099CB';
ctx.fillRect(x + 14, y + 12, 8, headingHeight - 4);
ctx.fillStyle = '#0f1720';
ctx.font = "700 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
unit.headingLines.forEach(function(line) {
ctx.fillText(line, x + 34, textY);
textY += 35;
});
textY = y + headingHeight + 34;
}
ctx.fillStyle = '#111827';
ctx.font = "400 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
unit.bodyLines.forEach(function(line) {
ctx.fillText(line, x + 18, textY);
textY += unit.lineHeight;
});
}
async function buildXCoverPage(items, totalPages, pageIndex) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
if (!ctx) return null;
canvas.width = X_CAPTURE_PAGE_WIDTH;
canvas.height = X_CAPTURE_PAGE_HEIGHT;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawXPageChrome(ctx, pageIndex, totalPages, 'X投稿向け画像');
var summary = buildXCoverSummary(items);
ctx.font = "700 38px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
var summaryLines = wrapText(ctx, summary, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2 - 44).slice(0, 4);
var summaryHeight = Math.max(138, 32 + summaryLines.length * 46);
ctx.fillStyle = '#0f76a8';
ctx.fillRect(X_CAPTURE_SAFE_X, 186, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2, summaryHeight);
ctx.fillStyle = '#ffffff';
summaryLines.forEach(function(line, index) {
ctx.fillText(line, X_CAPTURE_SAFE_X + 24, 236 + index * 46);
});
var mapCanvas;
try {
mapCanvas = await buildMapImage(X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2 - 30);
} catch (e) {
return null;
}
if (!mapCanvas) return null;
var mapCardY = 186 + summaryHeight + 34;
var mapX = X_CAPTURE_SAFE_X + 15;
var mapY = mapCardY + 16;
ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#c7dcea';
ctx.lineWidth = 2;
ctx.strokeRect(X_CAPTURE_SAFE_X, mapCardY, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2, mapCanvas.height + 32);
ctx.drawImage(mapCanvas, mapX, mapY, mapCanvas.width, mapCanvas.height);
var latestUpdatedAt = getLatestUpdatedAt(items);
var infoY = mapCardY + mapCanvas.height + 72;
ctx.fillStyle = '#0f1720';
ctx.font = "700 28px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText('現在表示中の路線図と運行情報', X_CAPTURE_SAFE_X, infoY);
if (latestUpdatedAt) {
ctx.fillStyle = '#4b5563';
ctx.font = "500 24px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText(latestUpdatedAt, X_CAPTURE_SAFE_X, infoY + 40);
}
if (totalPages > 1) {
ctx.fillStyle = '#0099CB';
ctx.font = "800 30px -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', sans-serif";
ctx.fillText('詳細は次の画像へ →', X_CAPTURE_SAFE_X, infoY + 94);
}
drawXFooter(ctx, pageIndex, totalPages);
return canvas;
}
function buildXDetailPage(page, pageIndex, totalPages) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
if (!ctx) return null;
canvas.width = X_CAPTURE_PAGE_WIDTH;
canvas.height = X_CAPTURE_PAGE_HEIGHT;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawXPageChrome(ctx, pageIndex, totalPages, '運行情報詳細');
var y = X_CAPTURE_CONTENT_TOP;
var width = X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2;
page.items.forEach(function(pageItem, itemIndex) {
if (itemIndex > 0) {
y += X_CAPTURE_ITEM_GAP;
}
drawXDetailHeaderBlock(ctx, pageItem.header, X_CAPTURE_SAFE_X, y, width);
y += pageItem.header.height;
pageItem.units.forEach(function(unit, unitIndex) {
drawXUnitBlock(ctx, unit, X_CAPTURE_SAFE_X, y, width);
y += unit.height;
if (unitIndex < pageItem.units.length - 1) {
y += X_CAPTURE_UNIT_GAP;
}
});
});
drawXFooter(ctx, pageIndex, totalPages);
return canvas;
}
async function renderXPostImageSet(items, fileNameBase) {
try {
if (!items || !items.length) {
postMessage({ error: true, reason: 'x-empty' });
return;
}
var measureCanvas = document.createElement('canvas');
var measureCtx = measureCanvas.getContext('2d');
if (!measureCtx) {
postMessage({ error: true });
return;
}
var detailPages = paginateXDetailPages(measureCtx, items, X_CAPTURE_PAGE_WIDTH - X_CAPTURE_SAFE_X * 2);
if (!detailPages || detailPages.length > 3) {
postMessage({ error: true, reason: 'x-overflow' });
return;
}
var totalPages = 1 + detailPages.length;
var timestamp = strip(fileNameBase) || String(Date.now());
var cover = await buildXCoverPage(items, totalPages, 0);
if (!cover) {
postMessage({ error: true, reason: 'x-map' });
return;
}
var pages = [cover];
detailPages.forEach(function(page, index) {
var detailCanvas = buildXDetailPage(page, index + 1, totalPages);
if (detailCanvas) {
pages.push(detailCanvas);
}
});
if (pages.length !== totalPages) {
postMessage({ error: true });
return;
}
var batchId = 'operation-info-x-batch-' + Date.now() + '-' + Math.floor(Math.random() * 100000);
for (var index = 0; index < pages.length; index += 1) {
postMessage({
batchId: batchId,
batchIndex: index,
batchTotal: pages.length,
dataUrl: pages[index].toDataURL('image/png'),
fileName: index === 0
? 'operation-info-x-01-map-' + timestamp + '.png'
: 'operation-info-x-0' + String(index + 1) + '-detail-' + timestamp + '.png'
});
}
} catch (e) {
postMessage({ error: true });
}
}
function appendCaptureSectionLayout(layout, ctx, section, startY, options) { function appendCaptureSectionLayout(layout, ctx, section, startY, options) {
var boxX = options && options.x != null ? options.x : 0; var boxX = options && options.x != null ? options.x : 0;
var boxY = startY; var boxY = startY;
@@ -1105,20 +1650,22 @@ const buildOperationPageScript = (layout: {
link.onclick = function(event) { link.onclick = function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
var updatedAtNode = q('.upd_time', dd); runCaptureAction(function() {
var subTitleNode = q('.delay_subttl', heading); var updatedAtNode = q('.upd_time', dd);
var sectionHtml = getSubsectionHtml(midasi); var subTitleNode = q('.delay_subttl', heading);
if (!sectionHtml) { var sectionHtml = getSubsectionHtml(midasi);
postMessage({ error: true }); if (!sectionHtml) {
return; postMessage({ error: true });
} return;
renderItemToImage( }
getTitleText(heading), return renderItemToImage(
subTitleNode ? subTitleNode.textContent : '', getTitleText(heading),
updatedAtNode ? updatedAtNode.textContent : '', subTitleNode ? subTitleNode.textContent : '',
sectionHtml, updatedAtNode ? updatedAtNode.textContent : '',
'operation-info-' + infoId + '-section-' + index + '-' + Date.now() sectionHtml,
); 'operation-info-' + infoId + '-section-' + index + '-' + Date.now()
);
});
}; };
wrap.appendChild(link); wrap.appendChild(link);
if (midasi.nextSibling) { if (midasi.nextSibling) {
@@ -1157,7 +1704,9 @@ const buildOperationPageScript = (layout: {
allLink.onclick = function(event) { allLink.onclick = function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
renderAllItemsToImage(getOperationItems(), getStatusText(q('.delay_status')), 'operation-info-all-' + Date.now()); runCaptureAction(function() {
return renderAllItemsToImage(getOperationItems(), getStatusText(q('.delay_status')), 'operation-info-all-' + Date.now());
});
}; };
wrap.appendChild(allLink); wrap.appendChild(allLink);
} }
@@ -1172,13 +1721,31 @@ const buildOperationPageScript = (layout: {
batchLink.onclick = function(event) { batchLink.onclick = function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
renderAllItemsToSeparateImages(getOperationItems(), 'operation-info-share-' + Date.now()); runCaptureAction(function() {
return renderAllItemsToSeparateImages(getOperationItems(), 'operation-info-share-' + Date.now());
});
}; };
wrap.appendChild(batchLink); wrap.appendChild(batchLink);
} }
} else if (batchLink && batchLink.parentNode) { } else if (batchLink && batchLink.parentNode) {
batchLink.parentNode.removeChild(batchLink); batchLink.parentNode.removeChild(batchLink);
} }
var xLink = q('.jrs-capture-page-link.is-x', wrap);
if (!xLink) {
xLink = document.createElement('a');
xLink.href = '#';
xLink.className = 'jrs-capture-page-link is-x';
xLink.textContent = 'X投稿向け画像を作成';
xLink.onclick = function(event) {
event.preventDefault();
event.stopPropagation();
runCaptureAction(function() {
return renderXPostImageSet(getOperationItems(), String(Date.now()));
});
};
wrap.appendChild(xLink);
}
} }
function removeCaptureButtons() { function removeCaptureButtons() {
@@ -1216,19 +1783,21 @@ const buildOperationPageScript = (layout: {
link.onclick = function(event) { link.onclick = function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
var updatedAtNode = q('.upd_time', dd); runCaptureAction(function() {
var subTitleNode = q('.delay_subttl', heading); var updatedAtNode = q('.upd_time', dd);
if (!detailNode.innerHTML) { var subTitleNode = q('.delay_subttl', heading);
postMessage({ error: true }); if (!detailNode.innerHTML) {
return; postMessage({ error: true });
} return;
renderItemToImage( }
getTitleText(heading), return renderItemToImage(
subTitleNode ? subTitleNode.textContent : '', getTitleText(heading),
updatedAtNode ? updatedAtNode.textContent : '', subTitleNode ? subTitleNode.textContent : '',
detailNode.innerHTML, updatedAtNode ? updatedAtNode.textContent : '',
'operation-info-' + infoId + '-' + Date.now() detailNode.innerHTML,
); 'operation-info-' + infoId + '-' + Date.now()
);
});
}; };
wrap.appendChild(link); wrap.appendChild(link);
dd.insertBefore(wrap, dd.firstChild); dd.insertBefore(wrap, dd.firstChild);
@@ -2633,7 +3202,12 @@ export default function tndView() {
}, },
}); });
if (payload.error) { if (payload.error) {
Alert.alert("切り出し失敗", "項目画像の生成に失敗しました。"); const message = payload.reason === "x-overflow"
? "X投稿向け画像は4枚以内に読みやすく収まりませんでした。『項目ごとに複数画像で共有』をご利用ください。"
: payload.reason === "x-map"
? "路線図の読み込みに失敗したため、X投稿向け画像を生成できませんでした。"
: "項目画像の生成に失敗しました。";
Alert.alert("切り出し失敗", message);
return; return;
} }
if (payload.batchId) { if (payload.batchId) {
+3 -1
View File
@@ -6,6 +6,7 @@
"android": "expo run:android", "android": "expo run:android",
"ios": "expo run:ios", "ios": "expo run:ios",
"eject": "expo eject", "eject": "expo eject",
"postinstall": "patch-package",
"pushWeb": "npx expo export -p web && netlify deploy --dir dist --prod", "pushWeb": "npx expo export -p web && netlify deploy --dir dist --prod",
"checkDiagram": "bash ./check.sh" "checkDiagram": "bash ./check.sh"
}, },
@@ -106,7 +107,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "~19.2.14", "@types/react": "~19.2.14",
"babel-preset-expo": "~55.0.12", "babel-preset-expo": "~55.0.12",
"baseline-browser-mapping": "^2.10.9" "baseline-browser-mapping": "^2.10.9",
"patch-package": "^8.0.1"
}, },
"private": true, "private": true,
"name": "jrshikoku", "name": "jrshikoku",
@@ -0,0 +1,18 @@
diff --git a/node_modules/@react-navigation/bottom-tabs/src/views/BottomTabView.tsx b/node_modules/@react-navigation/bottom-tabs/src/views/BottomTabView.tsx
index ec0491e..c546b88 100644
--- a/node_modules/@react-navigation/bottom-tabs/src/views/BottomTabView.tsx
+++ b/node_modules/@react-navigation/bottom-tabs/src/views/BottomTabView.tsx
@@ -318,7 +318,12 @@ export function BottomTabView(props: Props) {
return (
<MaybeScreen
key={route.key}
- style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
+ style={[
+ StyleSheet.absoluteFill,
+ Platform.OS === 'android'
+ ? { opacity: isFocused ? 1 : 0 }
+ : { zIndex: isFocused ? 0 : -1 },
+ ]}
active={activityState}
enabled={detachInactiveScreens}
freezeOnBlur={freezeOnBlur}
+1 -1
View File
@@ -130,7 +130,7 @@ export const CurrentTrainProvider: FC<props> = ({ children }) => {
let nearest: StationProps | null = null; let nearest: StationProps | null = null;
let nearestDist = Infinity; let nearestDist = Infinity;
Object.keys(originalStationList).forEach((lineName) => { Object.keys(originalStationList).forEach((lineName) => {
(originalStationList as any)[lineName]?.forEach((station: StationProps) => { originalStationList[lineName]?.forEach((station) => {
const d = _calcDistance({ lat: station.lat, lng: station.lng }, userPos); const d = _calcDistance({ lat: station.lat, lng: station.lng }, userPos);
if (d < nearestDist) { if (d < nearestDist) {
nearestDist = d; nearestDist = d;
+10 -6
View File
@@ -10,11 +10,13 @@ import {
getStationList, getStationList,
lineList_LineWebID, lineList_LineWebID,
} from "@/lib/getStationList"; } from "@/lib/getStationList";
import { StationProps } from "@/lib/CommonTypes"; import { OriginalStationList, StationProps } from "@/lib/CommonTypes";
type initialStateType = { type initialStateType = {
originalStationList: StationProps[][]; originalStationList: OriginalStationList;
setOriginalStationList: React.Dispatch<React.SetStateAction<StationProps[][]>>; setOriginalStationList: React.Dispatch<
React.SetStateAction<OriginalStationList>
>;
getStationDataFromName: (id: string) => StationProps[]; getStationDataFromName: (id: string) => StationProps[];
getStationDataFromId: (id: string) => StationProps[]; getStationDataFromId: (id: string) => StationProps[];
getStationDataFromNameBase: (name: string) => StationProps[]; getStationDataFromNameBase: (name: string) => StationProps[];
@@ -27,7 +29,7 @@ type initialStateType = {
}; };
const initialState = { const initialState = {
originalStationList: [[]], originalStationList: {},
setOriginalStationList: () => {}, setOriginalStationList: () => {},
getStationDataFromName: () => [], getStationDataFromName: () => [],
getStationDataFromId: () => [], getStationDataFromId: () => [],
@@ -51,7 +53,8 @@ export const useStationList = () => {
}; };
export const StationListProvider: FC<Props> = ({ children }) => { export const StationListProvider: FC<Props> = ({ children }) => {
const [originalStationList, setOriginalStationList] = useState<StationProps[][]>([]); const [originalStationList, setOriginalStationList] =
useState<OriginalStationList>({});
useEffect(() => { useEffect(() => {
getStationList().then(setOriginalStationList); getStationList().then(setOriginalStationList);
}, []); }, []);
@@ -105,9 +108,10 @@ export const StationListProvider: FC<Props> = ({ children }) => {
const [stationList, setStationList] = useState<StationProps[][]>([[]]); const [stationList, setStationList] = useState<StationProps[][]>([[]]);
useEffect(() => { useEffect(() => {
if (originalStationList.length === 0) return; if (Object.keys(originalStationList).length === 0) return;
const stationList = lineList.map((d) => const stationList = lineList.map((d) =>
originalStationList[d].map((a) => ({ originalStationList[d].map((a) => ({
...a,
StationNumber: a.StationNumber, StationNumber: a.StationNumber,
StationName: a.Station_JP, StationName: a.Station_JP,
})) }))
+9 -6
View File
@@ -6,16 +6,18 @@ import React, {
FC, FC,
} from "react"; } from "react";
import { lineList, getStationList } from "../lib/getStationList"; import { lineList, getStationList } from "../lib/getStationList";
import { StationProps } from "@/lib/CommonTypes"; import { OriginalStationList, StationProps } from "@/lib/CommonTypes";
type initialStateType = { type initialStateType = {
originalStationList: StationProps[][]; originalStationList: OriginalStationList;
setOriginalStationList: React.Dispatch<React.SetStateAction<StationProps[][]>>; setOriginalStationList: React.Dispatch<
React.SetStateAction<OriginalStationList>
>;
getStationData: (id: string) => StationProps[]; getStationData: (id: string) => StationProps[];
stationList: { StationNumber: string; StationName: string }[][]; stationList: { StationNumber: string; StationName: string }[][];
}; };
const initialState = { const initialState = {
originalStationList: [[]], originalStationList: {},
setOriginalStationList: () => {}, setOriginalStationList: () => {},
getStationData: () => [], getStationData: () => [],
stationList: [], stationList: [],
@@ -30,7 +32,8 @@ export const useTopMenu = () => {
}; };
export const TopMenuProvider: FC<Props> = ({ children }) => { export const TopMenuProvider: FC<Props> = ({ children }) => {
const [originalStationList, setOriginalStationList] = useState<StationProps[][]>([]); const [originalStationList, setOriginalStationList] =
useState<OriginalStationList>({});
useEffect(() => { useEffect(() => {
getStationList().then(setOriginalStationList); getStationList().then(setOriginalStationList);
}, []); }, []);
@@ -47,7 +50,7 @@ export const TopMenuProvider: FC<Props> = ({ children }) => {
}; };
const [stationList, setStationList] = useState<{ StationNumber: string; StationName: string }[][]>([[]]); const [stationList, setStationList] = useState<{ StationNumber: string; StationName: string }[][]>([[]]);
useEffect(()=>{ useEffect(()=>{
if(originalStationList.length === 0) return; if (Object.keys(originalStationList).length === 0) return;
const stationList = const stationList =
originalStationList && originalStationList &&
lineList.map((d) => lineList.map((d) =>
+10
View File
@@ -107,6 +107,8 @@ const initialState = {
activeRecording: null as TrainRecording | null, activeRecording: null as TrainRecording | null,
/** 現在再生中のスナップショットインデックス */ /** 現在再生中のスナップショットインデックス */
playbackIndex: 0, playbackIndex: 0,
/** 再生中スナップショットに対応する基準時刻(通常時は null) */
playbackCurrentTimeIso: null as string | null,
/** 再生一時停止中か */ /** 再生一時停止中か */
playbackPaused: false, playbackPaused: false,
startRecording: () => {}, startRecording: () => {},
@@ -229,6 +231,13 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
const [recordingList, setRecordingList] = useState<RecordingMeta[]>([]); const [recordingList, setRecordingList] = useState<RecordingMeta[]>([]);
const [activeRecording, setActiveRecording] = useState<TrainRecording | null>(null); const [activeRecording, setActiveRecording] = useState<TrainRecording | null>(null);
const [playbackIndex, setPlaybackIndex] = useState(0); const [playbackIndex, setPlaybackIndex] = useState(0);
const playbackCurrentTimeIso =
recorderState === 'playing' && activeRecording && activeRecording.snapshots.length > 0
? new Date(
new Date(activeRecording.recordedAt).getTime() +
(activeRecording.snapshots[playbackIndex]?.t ?? 0)
).toISOString()
: null;
const [playbackPaused, setPlaybackPaused] = useState(false); const [playbackPaused, setPlaybackPaused] = useState(false);
const recordingStartTimeRef = useRef<number>(0); const recordingStartTimeRef = useRef<number>(0);
const recordingSnapshotsRef = useRef<Array<{ t: number; trains: TrainEntry[] }>>([]); const recordingSnapshotsRef = useRef<Array<{ t: number; trains: TrainEntry[] }>>([]);
@@ -563,6 +572,7 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
recordingList, recordingList,
activeRecording, activeRecording,
playbackIndex, playbackIndex,
playbackCurrentTimeIso,
playbackPaused, playbackPaused,
startRecording, startRecording,
stopRecording, stopRecording,
+2 -1
View File
@@ -6,7 +6,8 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"],
"expo-live-activity": ["./modules/expo-live-activity/src/index.ts"]
} }
}, },
"extends": "expo/tsconfig.base" "extends": "expo/tsconfig.base"
+225 -8
View File
@@ -2084,6 +2084,11 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3"
integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==
"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
abort-controller@^3.0.0: abort-controller@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -2539,6 +2544,32 @@ cacheable-request@^7.0.2:
normalize-url "^6.0.1" normalize-url "^6.0.1"
responselike "^2.0.0" responselike "^2.0.0"
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bind@^1.0.8:
version "1.0.9"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7"
integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
get-intrinsic "^1.3.0"
set-function-length "^1.2.2"
call-bound@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
camelcase@^5.3.1: camelcase@^5.3.1:
version "5.3.1" version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
@@ -2614,7 +2645,7 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
ci-info@^3.2.0, ci-info@^3.3.0: ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0:
version "3.9.0" version "3.9.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
@@ -2788,7 +2819,7 @@ cross-fetch@^3.1.5:
dependencies: dependencies:
node-fetch "^2.7.0" node-fetch "^2.7.0"
cross-spawn@^7.0.6: cross-spawn@^7.0.3, cross-spawn@^7.0.6:
version "7.0.6" version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -2895,6 +2926,15 @@ defer-to-connect@^2.0.0:
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
gopd "^1.0.1"
define-lazy-prop@^2.0.0: define-lazy-prop@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
@@ -2959,6 +2999,15 @@ domutils@^3.0.1:
domelementtype "^2.3.0" domelementtype "^2.3.0"
domhandler "^5.0.3" domhandler "^5.0.3"
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
eastasianwidth@^0.2.0: eastasianwidth@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -3020,11 +3069,23 @@ error-stack-parser@^2.0.6:
dependencies: dependencies:
stackframe "^1.3.4" stackframe "^1.3.4"
es-define-property@^1.0.0, es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0: es-errors@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b"
integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==
dependencies:
es-errors "^1.3.0"
escalade@^3.1.1, escalade@^3.2.0: escalade@^3.1.1, escalade@^3.2.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@@ -3463,6 +3524,13 @@ find-up@^7.0.0:
path-exists "^5.0.0" path-exists "^5.0.0"
unicorn-magic "^0.1.0" unicorn-magic "^0.1.0"
find-yarn-workspace-root@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
dependencies:
micromatch "^4.0.2"
flow-enums-runtime@^0.0.6: flow-enums-runtime@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787"
@@ -3486,6 +3554,15 @@ fresh@~0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
fs-extra@^10.0.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs.realpath@^1.0.0: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -3511,11 +3588,35 @@ get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.2.4, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-package-type@^0.1.0: get-package-type@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-stream@^5.1.0: get-stream@^5.1.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
@@ -3561,6 +3662,11 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
gopd@^1.0.1, gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
got@^11.5.1: got@^11.5.1:
version "11.8.6" version "11.8.6"
resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a"
@@ -3578,7 +3684,7 @@ got@^11.5.1:
p-cancelable "^2.0.0" p-cancelable "^2.0.0"
responselike "^2.0.0" responselike "^2.0.0"
graceful-fs@^4.2.4, graceful-fs@^4.2.9: graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.11" version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -3600,7 +3706,19 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
hasown@^2.0.3: has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
dependencies:
es-define-property "^1.0.0"
has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
hasown@^2.0.2, hasown@^2.0.3:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
@@ -3841,6 +3959,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
dependencies: dependencies:
is-docker "^2.0.0" is-docker "^2.0.0"
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isexe@^2.0.0: isexe@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -4010,11 +4133,36 @@ json-buffer@3.0.1:
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-stable-stringify@^1.0.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz#8903cfac42ea1a0f97f35d63a4ce0518f0cc6a70"
integrity sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==
dependencies:
call-bind "^1.0.8"
call-bound "^1.0.4"
isarray "^2.0.5"
jsonify "^0.0.1"
object-keys "^1.1.1"
json5@^2.2.3: json5@^2.2.3:
version "2.2.3" version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6"
integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
keyv@^4.0.0: keyv@^4.0.0:
version "4.5.4" version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -4022,6 +4170,13 @@ keyv@^4.0.0:
dependencies: dependencies:
json-buffer "3.0.1" json-buffer "3.0.1"
klaw-sync@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
dependencies:
graceful-fs "^4.1.11"
kleur@^3.0.3: kleur@^3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@@ -4201,6 +4356,11 @@ marky@^1.2.2:
resolved "https://registry.yarnpkg.com/marky/-/marky-1.3.0.tgz#422b63b0baf65022f02eda61a238eccdbbc14997" resolved "https://registry.yarnpkg.com/marky/-/marky-1.3.0.tgz#422b63b0baf65022f02eda61a238eccdbbc14997"
integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ== integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mdn-data@2.0.14: mdn-data@2.0.14:
version "2.0.14" version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -4420,7 +4580,7 @@ metro@0.83.7, metro@^0.83.6:
ws "^7.5.10" ws "^7.5.10"
yargs "^17.6.2" yargs "^17.6.2"
micromatch@^4.0.4: micromatch@^4.0.2, micromatch@^4.0.4:
version "4.0.8" version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -4498,6 +4658,11 @@ minimist@1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw== integrity sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==
minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3: "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3:
version "7.1.3" version "7.1.3"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
@@ -4622,6 +4787,11 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
on-finished@~2.3.0: on-finished@~2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -4655,7 +4825,7 @@ onetime@^2.0.0:
dependencies: dependencies:
mimic-fn "^1.0.0" mimic-fn "^1.0.0"
open@^7.0.3: open@^7.0.3, open@^7.4.2:
version "7.4.2" version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
@@ -4769,6 +4939,26 @@ parseurl@~1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
patch-package@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.1.tgz#79d02f953f711e06d1f8949c8a13e5d3d7ba1a60"
integrity sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
chalk "^4.1.2"
ci-info "^3.7.0"
cross-spawn "^7.0.3"
find-yarn-workspace-root "^2.0.0"
fs-extra "^10.0.0"
json-stable-stringify "^1.0.2"
klaw-sync "^6.0.0"
minimist "^1.2.6"
open "^7.4.2"
semver "^7.5.3"
slash "^2.0.0"
tmp "^0.2.4"
yaml "^2.2.2"
path-exists@^4.0.0: path-exists@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -5364,7 +5554,7 @@ semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.1.3, semver@^7.3.5, semver@^7.5.4, semver@^7.6.0: semver@^7.1.3, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0:
version "7.8.5" version "7.8.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69"
integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
@@ -5403,6 +5593,18 @@ serve-static@^1.16.2:
parseurl "~1.3.3" parseurl "~1.3.3"
send "~0.19.1" send "~0.19.1"
set-function-length@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
define-data-property "^1.1.4"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
setimmediate@^1.0.5: setimmediate@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@@ -5466,6 +5668,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slash@^3.0.0: slash@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -5730,6 +5937,11 @@ tmp@^0.0.33:
dependencies: dependencies:
os-tmpdir "~1.0.2" os-tmpdir "~1.0.2"
tmp@^0.2.4:
version "0.2.7"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059"
integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==
tmpl@1.0.5: tmpl@1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -5820,6 +6032,11 @@ unicorn-magic@^0.1.0:
resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4"
integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unpipe@~1.0.0: unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -6030,7 +6247,7 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3"
integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==
yaml@^2.6.1: yaml@^2.2.2, yaml@^2.6.1:
version "2.9.0" version "2.9.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4"
integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==