Compare commits
121 Commits
feature/un
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d454cf9d4 | ||
|
|
699e3039b9 | ||
|
|
5af3164f21 | ||
|
|
cd585b8206 | ||
|
|
927c73567a | ||
|
|
09170c6636 | ||
|
|
8705f69725 | ||
|
|
9562e04ffe | ||
|
|
2ffb47b60c | ||
|
|
1208a78831 | ||
|
|
493ef92bd6 | ||
|
|
a51ffe4a82 | ||
|
|
0ea25f1e97 | ||
|
|
16bd0fe192 | ||
|
|
d75a495b19 | ||
|
|
a346a1477c | ||
|
|
c93ca94a61 | ||
|
|
4480c161d1 | ||
|
|
deb9b40949 | ||
|
|
fced009607 | ||
|
|
0890c1b9ff | ||
|
|
4b3816940b | ||
|
|
9ea23d6eba | ||
|
|
31ea303b88 | ||
|
|
7716cb516c | ||
|
|
70976c0554 | ||
|
|
5616c7ed96 | ||
|
|
13580a57d4 | ||
|
|
c9481fb0c2 | ||
|
|
5cda45740c | ||
|
|
84d1305796 | ||
|
|
336e3510fa | ||
|
|
25780e1664 | ||
|
|
e6fab84393 | ||
|
|
390acdeab7 | ||
|
|
ff7a5624e6 | ||
|
|
9b79f224d3 | ||
|
|
2f76e3776f | ||
|
|
026da47d82 | ||
|
|
29be052bf6 | ||
|
|
2fb7d97d33 | ||
|
|
76ebf8055b | ||
|
|
7b572cd657 | ||
|
|
61fa0f8484 | ||
|
|
935aaf2610 | ||
|
|
8ec53d6e06 | ||
|
|
e8b1a21a3b | ||
|
|
e6b89842a3 | ||
|
|
1fc5220405 | ||
|
|
5489406578 | ||
|
|
2b217e98c7 | ||
|
|
2c2e61a2fa | ||
|
|
c222b303df | ||
|
|
8f8c095ecd | ||
|
|
e31e84ff34 | ||
|
|
2c2b355a3e | ||
|
|
5461087ca0 | ||
|
|
57c7285b6e | ||
|
|
5c134c95cc | ||
|
|
8781653fe8 | ||
|
|
67ccc37c17 | ||
|
|
92caab03f5 | ||
|
|
dd3a57b3ae | ||
|
|
8df32b9c1d | ||
|
|
4b901d5015 | ||
|
|
fc5c62685a | ||
|
|
fbc98b2ff7 | ||
|
|
3502043176 | ||
|
|
2d0ad8d59e | ||
|
|
b5172df7a9 | ||
|
|
740d414d2d | ||
|
|
8db3e6c218 | ||
|
|
fb35b01d2e | ||
|
|
5fa6b1f73e | ||
|
|
c16f7401ea | ||
|
|
f260c5d2dd | ||
|
|
80e9f1a869 | ||
|
|
26096ba244 | ||
|
|
435a910ef9 | ||
|
|
68b9236d65 | ||
|
|
0148c12e08 | ||
|
|
34dc62aee6 | ||
|
|
54ae681f00 | ||
|
|
db40351fec | ||
|
|
2dd8e62f85 | ||
|
|
0937fbb619 | ||
|
|
32edab3289 | ||
|
|
fa96e68770 | ||
|
|
1681be4437 | ||
|
|
7f96c44e88 | ||
|
|
7d485c466c | ||
|
|
7869cbee6d | ||
|
|
c2ad681891 | ||
|
|
163f9ead18 | ||
|
|
611fc9953f | ||
|
|
fd95e99874 | ||
|
|
f5d0f993db | ||
|
|
b7e763d265 | ||
|
|
0afa6f402b | ||
|
|
eacce53775 | ||
|
|
47b1eb325e | ||
|
|
a145e2c24f | ||
|
|
862742cd3e | ||
|
|
d5f2d367dd | ||
|
|
b1f72f18b5 | ||
|
|
d4d86fb7eb | ||
|
|
056a059a68 | ||
|
|
12676d59cf | ||
|
|
bca2b300d3 | ||
|
|
406808ee58 | ||
|
|
a8aadb7f08 | ||
|
|
b810405573 | ||
|
|
6f62418426 | ||
|
|
88621dd1c8 | ||
|
|
12d9c1f49d | ||
|
|
30c2b8a2f9 | ||
|
|
8bc7069c4e | ||
|
|
737cd25539 | ||
|
|
2c50e5af67 | ||
|
|
ea677b4da5 | ||
|
|
4525443e39 |
@@ -1,5 +1,5 @@
|
||||
import React, { CSSProperties } from "react";
|
||||
import { Alert, BackHandler, View, ViewProps } from "react-native";
|
||||
import { BackHandler, View, ViewProps } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import { BigButton } from "./components/atom/BigButton";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
@@ -33,15 +33,7 @@ export default ({ route }) => {
|
||||
source={{ uri }}
|
||||
allowsBackForwardNavigationGestures
|
||||
ref={webViewRef}
|
||||
onNavigationStateChange={(navState) => {
|
||||
setCanGoBack(navState.canGoBack);
|
||||
if (navState.url === "https://unyohub.2pd.jp/integration/succeeded.php") {
|
||||
goBack();
|
||||
Alert.alert("鉄道運用HUBへの投稿完了", "運用HUBからのこのアプリへのデータ反映には暫く時間がかかりますので、しばらくお待ちください。", [
|
||||
{ text: "完了" },
|
||||
]);
|
||||
}
|
||||
}}
|
||||
onNavigationStateChange={(navState) => setCanGoBack(navState.canGoBack)}
|
||||
onMessage={(event) => {
|
||||
const { data } = event.nativeEvent;
|
||||
const { type } = JSON.parse(data);
|
||||
|
||||
@@ -70,22 +70,22 @@ export const EachStopList: FC<props> = ({
|
||||
string
|
||||
]; // 阿波池田,発,6:21,1
|
||||
let beforeSameStationData = null;
|
||||
// 発(通常発・休発・休発編)の場合、前の着(通常着・休着・休着編)と統合する
|
||||
if (se.includes("発")) {
|
||||
// 運休系でない通常の発のみ、前の着を統合する
|
||||
// 休編(非推奨)は発着が不明なため、次の発と統合する
|
||||
if ((se.includes("発") && !se.includes("休")) || se === "休編") {
|
||||
if (index > 0) {
|
||||
const beforeData = array[index - 1].split(",") as [string, seTypes, string];
|
||||
// 前が着(通常着でも休着でも)の場合は統合
|
||||
if (beforeData[0] == station && beforeData[1].includes("着")) {
|
||||
if (beforeData[0] == station) {
|
||||
beforeSameStationData = beforeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
let afterSameStationData = null;
|
||||
// 着(通常着・休着・休着編)の場合、次の発(通常発・休発・休発編)と統合される(非表示)
|
||||
if (se.includes("着")) {
|
||||
// 運休系でない通常の着のみ、次の発と統合する
|
||||
// 運休着(休着、休着編)は独立して表示する必要がある
|
||||
if (se.includes("着") && !se.includes("休")) {
|
||||
const afterData = array[index + 1]?.split(",") as [string, seTypes, string];
|
||||
// 次が発(通常発でも休発でも)なら、この着を非表示にして次の発で両方表示
|
||||
if (afterData && afterData[0] == station && afterData[1].includes("発")) {
|
||||
if (afterData && afterData[0] == station) {
|
||||
afterSameStationData = afterData;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { getStringConfig } from "@/lib/getStringConfig";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { getPDFViewURL } from "@/lib/getPdfViewURL";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
import { useUnyohub } from "@/stateBox/useUnyohub";
|
||||
|
||||
type Props = {
|
||||
data: { trainNum: string; limited: string };
|
||||
@@ -58,14 +57,6 @@ export const HeaderText: FC<Props> = ({
|
||||
const { allCustomTrainData, getTodayOperationByTrainId } =
|
||||
useAllTrainDiagram();
|
||||
const { expoPushToken } = useNotification();
|
||||
const { getUnyohubByTrainNumber, getUnyohubEntriesByTrainNumber, useUnyohub: unyohubEnabled } = useUnyohub();
|
||||
|
||||
// 追加ソースのON/OFFをここで管理(将来ソースが増えたらここに足す)
|
||||
const additionalSources = {
|
||||
unyohub: unyohubEnabled,
|
||||
// exampleSource: exampleEnabled,
|
||||
};
|
||||
const hasAdditionalSources = Object.values(additionalSources).some(Boolean);
|
||||
|
||||
// 列車名、種別、フォントの取得
|
||||
const [
|
||||
@@ -77,7 +68,6 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
trainInfoUrl,
|
||||
directions,
|
||||
] = useMemo(() => {
|
||||
const {
|
||||
type,
|
||||
@@ -88,7 +78,6 @@ export const HeaderText: FC<Props> = ({
|
||||
uwasa,
|
||||
train_info_url,
|
||||
to_data,
|
||||
directions,
|
||||
} = customTrainDataDetector(trainNum, allCustomTrainData);
|
||||
const [typeString, fontAvailable, isOneMan] = getStringConfig(
|
||||
type,
|
||||
@@ -110,7 +99,6 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
];
|
||||
case trainData[trainData.length - 1] === undefined:
|
||||
return [
|
||||
@@ -122,7 +110,6 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
];
|
||||
case to_data && to_data !== "":
|
||||
// 行先がある場合は、行先を取得
|
||||
@@ -135,7 +122,6 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
];
|
||||
default:
|
||||
// 行先がある場合は、行先を取得
|
||||
@@ -150,7 +136,6 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
];
|
||||
}
|
||||
}, [trainData]);
|
||||
@@ -158,19 +143,6 @@ export const HeaderText: FC<Props> = ({
|
||||
const todayOperation = getTodayOperationByTrainId(trainNum).filter(
|
||||
(d) => d.state !== 100,
|
||||
);
|
||||
|
||||
let iconTrainDirection =
|
||||
parseInt(trainNum.replace(/[^\d]/g, "")) % 2 == 0 ? true : false;
|
||||
if (directions != undefined) {
|
||||
iconTrainDirection = directions ? true : false;
|
||||
}
|
||||
|
||||
const unyohubFormation = getUnyohubByTrainNumber(trainNum);
|
||||
const unyohubEntries = getUnyohubEntriesByTrainNumber(trainNum);
|
||||
|
||||
const hasExtraInfo =
|
||||
priority > 200 || todayOperation?.length > 0 || !!unyohubFormation;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
|
||||
@@ -183,7 +155,6 @@ export const HeaderText: FC<Props> = ({
|
||||
navigate={navigate}
|
||||
from={from}
|
||||
todayOperation={todayOperation}
|
||||
direction={iconTrainDirection}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
@@ -258,27 +229,15 @@ export const HeaderText: FC<Props> = ({
|
||||
</TouchableOpacity>
|
||||
<MaterialCommunityIcons
|
||||
name="database"
|
||||
color={hasExtraInfo ? "yellow" : "white"}
|
||||
color={
|
||||
priority > 200 || todayOperation?.length > 0 ? "yellow" : "white"
|
||||
}
|
||||
size={30}
|
||||
style={{ margin: 5 }}
|
||||
onPress={() => {
|
||||
if (hasAdditionalSources) {
|
||||
(SheetManager.show as any)("TrainDataSources", {
|
||||
payload: {
|
||||
trainNum,
|
||||
unyohubEntries,
|
||||
todayOperation,
|
||||
navigate,
|
||||
expoPushToken,
|
||||
priority,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 追加ソースが全てオフ → 元の挙動(直接 DB ページを開く)
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
}
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
import React, { FC } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Image,
|
||||
} from "react-native";
|
||||
import { MaterialCommunityIcons, Ionicons } from "@expo/vector-icons";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
import type { OperationLogs } from "@/lib/CommonTypes";
|
||||
import type { UnyohubData } from "@/types/unyohub";
|
||||
|
||||
type Props = {
|
||||
trainNum: string;
|
||||
unyohubEntries: UnyohubData[];
|
||||
todayOperation: OperationLogs[];
|
||||
navigate: NavigateFunction;
|
||||
expoPushToken: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
/** 情報ソース別カードを並べて表示するパネル */
|
||||
export const TrainSourcesPanel: FC<Props> = ({
|
||||
trainNum,
|
||||
unyohubEntries,
|
||||
todayOperation,
|
||||
navigate,
|
||||
expoPushToken,
|
||||
onClose,
|
||||
}) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* ヘッダー */}
|
||||
<View style={styles.panelHeader}>
|
||||
<Text style={styles.panelHeaderText}>運用情報ソース</Text>
|
||||
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
||||
<Ionicons name="close" size={20} color="#0099CC" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scroll}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* ──────────────────────────────────────── */}
|
||||
{/* JR四国データベース(常に表示) */}
|
||||
{/* ──────────────────────────────────────── */}
|
||||
<SourceCard
|
||||
iconName="database"
|
||||
iconColor="#0099CC"
|
||||
tag="公式"
|
||||
tagColor="#0099CC"
|
||||
title="JR四国データベース"
|
||||
description="コミュニティが管理する列車データ。編成・運用の詳細を確認・編集できます。"
|
||||
available
|
||||
onPress={() => {
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ──────────────────────────────────────── */}
|
||||
{/* 列車運用hub */}
|
||||
{/* ──────────────────────────────────────── */}
|
||||
{unyohubEntries.length > 0 ? (
|
||||
<>
|
||||
<SectionLabel label="列車運用hub" icon="train" />
|
||||
{unyohubEntries.map((entry, i) => (
|
||||
<UnyohubCard key={`unyo-${i}`} entry={entry} trainNum={trainNum} navigate={navigate} onClose={onClose} />
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<SourceCard
|
||||
iconName="train"
|
||||
iconColor="#aaa"
|
||||
tag="外部"
|
||||
tagColor="#aaa"
|
||||
title="列車運用hub"
|
||||
description="この列車の運用データはありません。"
|
||||
available={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ──────────────────────────────────────── */}
|
||||
{/* 今日の運用情報 */}
|
||||
{/* ──────────────────────────────────────── */}
|
||||
{todayOperation.length > 0 ? (
|
||||
<>
|
||||
<SectionLabel label="今日の運用情報" icon="history" />
|
||||
{todayOperation.map((op, i) => (
|
||||
<OperationCard
|
||||
key={`op-${i}`}
|
||||
op={op}
|
||||
index={i}
|
||||
navigate={navigate}
|
||||
onClose={onClose}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<SourceCard
|
||||
iconName="history"
|
||||
iconColor="#aaa"
|
||||
tag="記録"
|
||||
tagColor="#aaa"
|
||||
title="今日の運用情報"
|
||||
description="本日の運用記録はありません。"
|
||||
available={false}
|
||||
/>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* サブコンポーネント */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/** セクションラベル */
|
||||
const SectionLabel: FC<{ label: string; icon: string }> = ({ label, icon }) => (
|
||||
<View style={styles.sectionLabel}>
|
||||
<MaterialCommunityIcons name={icon as any} size={14} color="#888" />
|
||||
<Text style={styles.sectionLabelText}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
/** 汎用ソースカード */
|
||||
const SourceCard: FC<{
|
||||
iconName: string;
|
||||
iconColor: string;
|
||||
tag: string;
|
||||
tagColor: string;
|
||||
title: string;
|
||||
description: string;
|
||||
available: boolean;
|
||||
onPress?: () => void;
|
||||
}> = ({ iconName, iconColor, tag, tagColor, title, description, available, onPress }) => (
|
||||
<TouchableOpacity
|
||||
style={[styles.card, !available && styles.cardDisabled]}
|
||||
onPress={available ? onPress : undefined}
|
||||
activeOpacity={available ? 0.7 : 1}
|
||||
>
|
||||
<View style={[styles.cardIconWrap, { backgroundColor: iconColor + "22" }]}>
|
||||
<MaterialCommunityIcons name={iconName as any} size={22} color={iconColor} />
|
||||
</View>
|
||||
<View style={styles.cardBody}>
|
||||
<View style={styles.cardTitleRow}>
|
||||
<Text style={[styles.cardTitle, !available && styles.cardTitleDisabled]}>{title}</Text>
|
||||
<View style={[styles.tag, { backgroundColor: tagColor + "33" }]}>
|
||||
<Text style={[styles.tagText, { color: tagColor }]}>{tag}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>{description}</Text>
|
||||
</View>
|
||||
{available && (
|
||||
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
/** 列車運用hubカード(1編成分) */
|
||||
const UnyohubCard: FC<{
|
||||
entry: UnyohubData;
|
||||
trainNum: string;
|
||||
navigate: NavigateFunction;
|
||||
onClose: () => void;
|
||||
}> = ({ entry, trainNum, navigate, onClose }) => {
|
||||
const matchedTrain = entry.trains?.find((t) => t.train_number === trainNum);
|
||||
const positionText = matchedTrain
|
||||
? `位置: ${matchedTrain.position_forward}〜${matchedTrain.position_rear}両目`
|
||||
: null;
|
||||
const carInfo = `${entry.car_count}両 (${entry.min_car_count}〜${entry.max_car_count}両)`;
|
||||
const timeInfo =
|
||||
entry.starting_time && entry.ending_time
|
||||
? `${entry.starting_time} → ${entry.ending_time}`
|
||||
: null;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.card}
|
||||
activeOpacity={0.7}
|
||||
onPress={() => {
|
||||
// 編成番号+列番でページを開く(将来的にunyohub公式ページへの対応も想定)
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?formation=${encodeURIComponent(entry.formations)}&source=unyohub`;
|
||||
navigate("generalWebView", { uri, useExitButton: true });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{/* 編成色インジケーター */}
|
||||
<View
|
||||
style={[
|
||||
styles.cardIconWrap,
|
||||
{ backgroundColor: (entry.main_color || "#FF9800") + "33" },
|
||||
]}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name="train-car"
|
||||
size={22}
|
||||
color={entry.main_color || "#FF9800"}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.cardBody}>
|
||||
<View style={styles.cardTitleRow}>
|
||||
<Text style={styles.cardTitle}>{entry.formations}</Text>
|
||||
<View style={[styles.tag, { backgroundColor: "#FF980033" }]}>
|
||||
<Text style={[styles.tagText, { color: "#FF9800" }]}>運用hub</Text>
|
||||
</View>
|
||||
{entry.from_beginner && (
|
||||
<View style={[styles.tag, { backgroundColor: "#9C27B033" }]}>
|
||||
<Text style={[styles.tagText, { color: "#9C27B0" }]}>初心者</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 詳細行 */}
|
||||
<View style={styles.unyoDetailRow}>
|
||||
{positionText && (
|
||||
<DetailChip icon="seat" text={positionText} />
|
||||
)}
|
||||
<DetailChip icon="train-car" text={carInfo} />
|
||||
{timeInfo && <DetailChip icon="clock-outline" text={timeInfo} />}
|
||||
{entry.starting_location && (
|
||||
<DetailChip icon="map-marker" text={entry.starting_location} />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{entry.comment && (
|
||||
<Text style={styles.unyoComment}>{entry.comment}</Text>
|
||||
)}
|
||||
|
||||
<View style={styles.postsRow}>
|
||||
<MaterialCommunityIcons name="account-group" size={12} color="#888" />
|
||||
<Text style={styles.postsText}>{entry.posts_count}件の投稿</Text>
|
||||
</View>
|
||||
</View>
|
||||
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
/** 今日の運用情報カード */
|
||||
const OperationCard: FC<{
|
||||
op: OperationLogs;
|
||||
index: number;
|
||||
navigate: NavigateFunction;
|
||||
onClose: () => void;
|
||||
}> = ({ op, index, navigate, onClose }) => {
|
||||
const hasUrl = !!op.vehicle_info_url;
|
||||
const trainIds = [...(op.train_ids || []), ...(op.related_train_ids || [])];
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.card, !hasUrl && styles.cardNoArrow]}
|
||||
activeOpacity={hasUrl ? 0.7 : 1}
|
||||
onPress={
|
||||
hasUrl
|
||||
? () => {
|
||||
navigate("howto", { info: op.vehicle_info_url, goTo: "menu" });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
onClose();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{/* 車両サムネイル or アイコン */}
|
||||
{op.vehicle_img ? (
|
||||
<Image
|
||||
source={{ uri: op.vehicle_img }}
|
||||
style={styles.vehicleThumb}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.cardIconWrap, { backgroundColor: "#4CAF5022" }]}>
|
||||
<MaterialCommunityIcons name="train" size={22} color="#4CAF50" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.cardBody}>
|
||||
<View style={styles.cardTitleRow}>
|
||||
<Text style={styles.cardTitle}>運用記録 #{index + 1}</Text>
|
||||
<View style={[styles.tag, { backgroundColor: "#4CAF5033" }]}>
|
||||
<Text style={[styles.tagText, { color: "#4CAF50" }]}>記録</Text>
|
||||
</View>
|
||||
</View>
|
||||
{trainIds.length > 0 && (
|
||||
<Text style={styles.cardDesc} numberOfLines={1}>
|
||||
列番: {trainIds.slice(0, 4).join(" / ")}
|
||||
{trainIds.length > 4 ? " ..." : ""}
|
||||
</Text>
|
||||
)}
|
||||
{op.date && (
|
||||
<Text style={styles.cardDesc}>日付: {op.date}</Text>
|
||||
)}
|
||||
</View>
|
||||
{hasUrl && (
|
||||
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
/** 小さい詳細チップ */
|
||||
const DetailChip: FC<{ icon: string; text: string }> = ({ icon, text }) => (
|
||||
<View style={styles.chip}>
|
||||
<MaterialCommunityIcons name={icon as any} size={11} color="#666" />
|
||||
<Text style={styles.chipText}>{text}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* スタイル */
|
||||
/* ------------------------------------------------------------------ */
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "#fff",
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "#e0e0e0",
|
||||
},
|
||||
panelHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
},
|
||||
panelHeaderText: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
},
|
||||
closeButton: {
|
||||
padding: 4,
|
||||
},
|
||||
scroll: {
|
||||
maxHeight: 360,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
paddingBottom: 16,
|
||||
},
|
||||
sectionLabel: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
marginBottom: 4,
|
||||
gap: 4,
|
||||
},
|
||||
sectionLabelText: {
|
||||
fontSize: 12,
|
||||
color: "#888",
|
||||
fontWeight: "600",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
card: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#f9f9f9",
|
||||
borderRadius: 10,
|
||||
marginBottom: 8,
|
||||
padding: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: "#eeeeee",
|
||||
gap: 10,
|
||||
},
|
||||
cardDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
cardNoArrow: {
|
||||
// no change needed
|
||||
},
|
||||
cardIconWrap: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
},
|
||||
vehicleThumb: {
|
||||
width: 50,
|
||||
height: 40,
|
||||
borderRadius: 6,
|
||||
flexShrink: 0,
|
||||
backgroundColor: "#eee",
|
||||
},
|
||||
cardBody: {
|
||||
flex: 1,
|
||||
gap: 3,
|
||||
},
|
||||
cardTitleRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
color: "#222",
|
||||
},
|
||||
cardTitleDisabled: {
|
||||
color: "#aaa",
|
||||
},
|
||||
cardDesc: {
|
||||
fontSize: 12,
|
||||
color: "#666",
|
||||
},
|
||||
tag: {
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 1,
|
||||
},
|
||||
tagText: {
|
||||
fontSize: 10,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
unyoDetailRow: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: 4,
|
||||
marginTop: 2,
|
||||
},
|
||||
unyoComment: {
|
||||
fontSize: 11,
|
||||
color: "#888",
|
||||
fontStyle: "italic",
|
||||
marginTop: 2,
|
||||
},
|
||||
postsRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
marginTop: 2,
|
||||
},
|
||||
postsText: {
|
||||
fontSize: 11,
|
||||
color: "#888",
|
||||
},
|
||||
chip: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#f0f0f0",
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 2,
|
||||
gap: 3,
|
||||
},
|
||||
chipText: {
|
||||
fontSize: 11,
|
||||
color: "#555",
|
||||
},
|
||||
});
|
||||
@@ -17,18 +17,17 @@ type Props = {
|
||||
navigate: NavigateFunction;
|
||||
from: string;
|
||||
todayOperation: OperationLogs[];
|
||||
direction?: boolean;
|
||||
};
|
||||
type apt = {
|
||||
name: GlyphNames;
|
||||
color: string;
|
||||
};
|
||||
export const TrainIconStatus: FC<Props> = (props) => {
|
||||
const { data, navigate, from, todayOperation, direction } = props;
|
||||
const { data, navigate, from, todayOperation } = props;
|
||||
const [anpanmanStatus, setAnpanmanStatus] = useState<apt>();
|
||||
const { allCustomTrainData } = useAllTrainDiagram();
|
||||
const [trainIconData, setTrainIcon] = useState<
|
||||
{ vehicle_info_img: string;vehicle_info_right_img: string; vehicle_info_url: string }[]
|
||||
{ vehicle_info_img: string; vehicle_info_url: string }[]
|
||||
>([]);
|
||||
useEffect(() => {
|
||||
if (!data.trainNum) return;
|
||||
@@ -80,12 +79,11 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
})
|
||||
.map((op) => ({
|
||||
vehicle_info_img: op.vehicle_img || vehicle_info_img,
|
||||
vehicle_info_right_img: op.vehicle_img_right || vehicle_info_img,
|
||||
vehicle_info_url: op.vehicle_info_url,
|
||||
})) || [];
|
||||
setTrainIcon(returnData);
|
||||
} else if (vehicle_info_img) {
|
||||
setTrainIcon([{ vehicle_info_img, vehicle_info_right_img: vehicle_info_img, vehicle_info_url }]);
|
||||
setTrainIcon([{ vehicle_info_img, vehicle_info_url }]);
|
||||
}
|
||||
|
||||
switch (data.trainNum) {
|
||||
@@ -115,6 +113,30 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "2074D":
|
||||
case "2076D":
|
||||
case "2080D":
|
||||
case "2082D":
|
||||
case "2071D":
|
||||
case "2073D":
|
||||
case "2079D":
|
||||
case "2081D":
|
||||
fetch(
|
||||
`https://n8n.haruk.in/webhook/dosan-anpanman-first?trainNum=${
|
||||
data.trainNum
|
||||
}&month=${dayjs().format("M")}&day=${dayjs().format("D")}`
|
||||
)
|
||||
.then((d) => d.json())
|
||||
.then((d) => {
|
||||
if (d.trainStatus == "〇") {
|
||||
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
|
||||
} else if (d.trainStatus == "▲") {
|
||||
setAnpanmanStatus({ name: "warning-outline", color: "yellow" });
|
||||
} else if (d.trainStatus == "×") {
|
||||
//setAnpanmanStatus({ name: "close-circle-outline", color: "red" });
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}, [data.trainNum, allCustomTrainData, todayOperation]);
|
||||
const [move, setMove] = useState(true);
|
||||
@@ -129,7 +151,7 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
return (
|
||||
<>
|
||||
{trainIconData.map(
|
||||
({ vehicle_info_img: trainIcon, vehicle_info_right_img: trainIconRight, vehicle_info_url: address }, index) => (
|
||||
({ vehicle_info_img: trainIcon, vehicle_info_url: address }, index) => (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
navigate("howto", {
|
||||
@@ -142,7 +164,7 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
>
|
||||
{move ? (
|
||||
<Image
|
||||
source={{ uri: direction ? trainIcon : trainIconRight || trainIcon }}
|
||||
source={{ uri: trainIcon }}
|
||||
style={{
|
||||
height: index > 0 ? 15 : 30,
|
||||
width: index > 0 ? 12 : 24,
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
import React, { FC } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import ActionSheet, { SheetManager } from "react-native-actions-sheet";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
import type { OperationLogs } from "@/lib/CommonTypes";
|
||||
import type { UnyohubData } from "@/types/unyohub";
|
||||
|
||||
export type TrainDataSourcesPayload = {
|
||||
trainNum: string;
|
||||
unyohubEntries: UnyohubData[];
|
||||
todayOperation: OperationLogs[];
|
||||
navigate: NavigateFunction;
|
||||
expoPushToken: string;
|
||||
/** customTrainDataDetector から取得した priority 値 */
|
||||
priority: number;
|
||||
};
|
||||
|
||||
export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
|
||||
payload,
|
||||
}) => {
|
||||
if (!payload) return null;
|
||||
|
||||
const { trainNum, unyohubEntries, todayOperation, navigate, expoPushToken, priority } =
|
||||
payload;
|
||||
|
||||
const close = () => SheetManager.hide("TrainDataSources");
|
||||
|
||||
const openWebView = (uri: string, useExitButton: boolean) => {
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
SheetManager.hide("TrainDataSources");
|
||||
navigate("generalWebView", { uri, useExitButton });
|
||||
};
|
||||
|
||||
/* ── 各ソースの状態 ─────────────────────────────── */
|
||||
const opCount = todayOperation.length;
|
||||
const unyoCount = unyohubEntries.length;
|
||||
const hasTrainInfo = priority > 200;
|
||||
|
||||
// 運用情報: 全エントリの unit_ids をフラットに収集・重複除去
|
||||
const allUnitIds = [
|
||||
...new Set(todayOperation.flatMap((op) => op.unit_ids ?? [])),
|
||||
];
|
||||
const unitIdsSub =
|
||||
allUnitIds.length > 0
|
||||
? allUnitIds.slice(0, 6).join("・") +
|
||||
(allUnitIds.length > 6 ? ` 他${allUnitIds.length - 6}件` : "")
|
||||
: opCount > 0
|
||||
? todayOperation
|
||||
.flatMap((op) => op.train_ids ?? [])
|
||||
.slice(0, 4)
|
||||
.join("・") || "運用記録あり"
|
||||
: "本日の運用記録なし";
|
||||
|
||||
// 列車運用hub: 編成名リスト
|
||||
const formationNames =
|
||||
unyohubEntries
|
||||
.slice(0, 4)
|
||||
.map((e) => e.formations)
|
||||
.join("・") + (unyoCount > 4 ? ` 他${unyoCount - 4}件` : "");
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
gestureEnabled
|
||||
CustomHeaderComponent={<></>}
|
||||
isModal={Platform.OS === "ios"}
|
||||
>
|
||||
{/* ヘッダー */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.handleBar} />
|
||||
<View style={styles.headerRow}>
|
||||
<Text style={styles.headerTitle}>運用情報ソース</Text>
|
||||
<Text style={styles.headerSub}>{trainNum}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scroll}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
scrollEnabled={false}
|
||||
>
|
||||
{/* ─── jr-shikoku-data-system (列車情報 + 運用情報) ─── */}
|
||||
<CombinedCard
|
||||
rows={[
|
||||
{
|
||||
icon: "database-search",
|
||||
title: "列車情報",
|
||||
sub: hasTrainInfo
|
||||
? "臨時情報あり / 編成データ・独自データを確認・編集"
|
||||
: "編成データ・コミュニティ独自データを確認・編集",
|
||||
badge: hasTrainInfo ? "!" : null,
|
||||
},
|
||||
{
|
||||
icon: "calendar-clock",
|
||||
title: "運用情報",
|
||||
sub: unitIdsSub,
|
||||
badge: opCount > 0 ? opCount : null,
|
||||
},
|
||||
]}
|
||||
color="#0099CC"
|
||||
label="jr-shikoku-data-system"
|
||||
onPress={() =>
|
||||
openWebView(
|
||||
`https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`,
|
||||
false
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* ─── 列車運用hub ─────────────────────────── */}
|
||||
<SourceCard
|
||||
icon="train-car"
|
||||
color="#FF9800"
|
||||
title="列車運用hub"
|
||||
label="外部コミュニティデータ"
|
||||
sub={unyoCount > 0 ? formationNames : "運用データなし / 新規投稿もここから"}
|
||||
badge={unyoCount > 0 ? unyoCount : null}
|
||||
badgeColor="#FF9800"
|
||||
onPress={() =>
|
||||
openWebView(
|
||||
`https://jr-shikoku-data-system.pages.dev/unyohub-connection-train-data/${trainNum}`,
|
||||
true
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.footer} />
|
||||
</ActionSheet>
|
||||
);
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* CombinedCard: 複数行を 1 枠にまとめるカード */
|
||||
/* ------------------------------------------------------------------ */
|
||||
type CombinedRow = {
|
||||
icon: string;
|
||||
title: string;
|
||||
sub: string;
|
||||
badge: number | string | null;
|
||||
};
|
||||
|
||||
const CombinedCard: FC<{
|
||||
rows: CombinedRow[];
|
||||
color: string;
|
||||
label: string;
|
||||
onPress: () => void;
|
||||
}> = ({ rows, color, label, onPress }) => (
|
||||
<TouchableOpacity style={[styles.card, styles.combinedCard]} activeOpacity={0.7} onPress={onPress}>
|
||||
{/* 共通ヘッダー */}
|
||||
<View style={styles.combinedHeader}>
|
||||
<View style={[styles.colorBar, { backgroundColor: color }]} />
|
||||
<Text style={[styles.labelText, styles.combinedLabel]}>{label}</Text>
|
||||
</View>
|
||||
{/* 各行 */}
|
||||
{rows.map((row, i) => (
|
||||
<React.Fragment key={row.title}>
|
||||
{i > 0 && <View style={[styles.divider, { marginLeft: 14 }]} />}
|
||||
<View style={styles.combinedRow}>
|
||||
{/* 左カラーバー分のスペーサー */}
|
||||
<View style={{ width: 4 }} />
|
||||
<View style={[styles.iconWrap, { backgroundColor: color + "18" }]}>
|
||||
<MaterialCommunityIcons name={row.icon as any} size={22} color={color} />
|
||||
</View>
|
||||
<View style={styles.textWrap}>
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={styles.cardTitle}>{row.title}</Text>
|
||||
{row.badge !== null && (
|
||||
<View style={[styles.badge, { backgroundColor: color }]}>
|
||||
<Text style={styles.badgeText}>{row.badge}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.subText} numberOfLines={1}>{row.sub}</Text>
|
||||
</View>
|
||||
{i === rows.length - 1 && (
|
||||
<MaterialCommunityIcons name="chevron-right" size={20} color="#ccc" style={{ marginRight: 10 }} />
|
||||
)}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* SourceCard */
|
||||
/* ------------------------------------------------------------------ */
|
||||
type SourceCardProps = {
|
||||
icon: string;
|
||||
color: string;
|
||||
title: string;
|
||||
label: string;
|
||||
sub: string;
|
||||
badge: number | string | null;
|
||||
badgeColor: string;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const SourceCard: FC<SourceCardProps> = ({
|
||||
icon,
|
||||
color,
|
||||
title,
|
||||
label,
|
||||
sub,
|
||||
badge,
|
||||
badgeColor,
|
||||
onPress,
|
||||
}) => (
|
||||
<TouchableOpacity
|
||||
style={styles.card}
|
||||
activeOpacity={0.7}
|
||||
onPress={onPress}
|
||||
>
|
||||
{/* 左カラーバー */}
|
||||
<View style={[styles.colorBar, { backgroundColor: color }]} />
|
||||
|
||||
{/* アイコン */}
|
||||
<View style={[styles.iconWrap, { backgroundColor: color + "18" }]}>
|
||||
<MaterialCommunityIcons
|
||||
name={icon as any}
|
||||
size={24}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* テキスト */}
|
||||
<View style={styles.textWrap}>
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={styles.cardTitle}>{title}</Text>
|
||||
<Text style={styles.labelText}>{label}</Text>
|
||||
</View>
|
||||
<Text style={styles.subText} numberOfLines={1}>{sub}</Text>
|
||||
</View>
|
||||
|
||||
{/* バッジ + 矢印 */}
|
||||
<View style={styles.rightArea}>
|
||||
{badge !== null ? (
|
||||
<View style={[styles.badge, { backgroundColor: badgeColor }]}>
|
||||
<Text style={styles.badgeText}>{badge}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<MaterialCommunityIcons name="chevron-right" size={20} color="#ccc" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* スタイル */
|
||||
/* ------------------------------------------------------------------ */
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
paddingTop: 8,
|
||||
paddingBottom: 12,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
handleBar: {
|
||||
width: 40,
|
||||
height: 5,
|
||||
borderRadius: 3,
|
||||
backgroundColor: "#ddd",
|
||||
alignSelf: "center",
|
||||
marginBottom: 12,
|
||||
},
|
||||
headerRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "baseline",
|
||||
gap: 8,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 17,
|
||||
fontWeight: "bold",
|
||||
color: "#111",
|
||||
},
|
||||
headerSub: {
|
||||
fontSize: 14,
|
||||
color: "#888",
|
||||
},
|
||||
scroll: {},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 14,
|
||||
gap: 10,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
card: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: "#ebebeb",
|
||||
overflow: "hidden",
|
||||
minHeight: 70,
|
||||
},
|
||||
combinedCard: {
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
},
|
||||
combinedHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingTop: 8,
|
||||
paddingBottom: 2,
|
||||
gap: 8,
|
||||
},
|
||||
combinedLabel: {
|
||||
color: "#888",
|
||||
marginLeft: 4,
|
||||
},
|
||||
combinedRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
minHeight: 58,
|
||||
},
|
||||
divider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: "#ebebeb",
|
||||
},
|
||||
colorBar: {
|
||||
width: 4,
|
||||
alignSelf: "stretch",
|
||||
},
|
||||
iconWrap: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginHorizontal: 10,
|
||||
flexShrink: 0,
|
||||
},
|
||||
textWrap: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
gap: 3,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
color: "#111",
|
||||
},
|
||||
cardTitleDisabled: {
|
||||
color: "#aaa",
|
||||
},
|
||||
labelText: {
|
||||
fontSize: 10,
|
||||
color: "#aaa",
|
||||
fontWeight: "500",
|
||||
},
|
||||
subText: {
|
||||
fontSize: 12,
|
||||
color: "#555",
|
||||
},
|
||||
subTextDisabled: {
|
||||
color: "#bbb",
|
||||
},
|
||||
rightArea: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingRight: 10,
|
||||
gap: 4,
|
||||
},
|
||||
badge: {
|
||||
minWidth: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
badgeText: {
|
||||
color: "#fff",
|
||||
fontSize: 11,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
footer: {
|
||||
height: 20,
|
||||
},
|
||||
});
|
||||
@@ -6,7 +6,6 @@ import { TrainMenuLineSelector } from "./TrainMenuLineSelector";
|
||||
import { TrainIconUpdate } from "./TrainIconUpdate";
|
||||
import { SpecialTrainInfo } from "./SpecialTrainInfo";
|
||||
import { Social } from "./SocialMenu";
|
||||
import { TrainDataSources } from "./TrainDataSources";
|
||||
|
||||
registerSheet("EachTrainInfo", EachTrainInfo);
|
||||
registerSheet("JRSTraInfo", JRSTraInfo);
|
||||
@@ -15,6 +14,5 @@ registerSheet("TrainMenuLineSelector", TrainMenuLineSelector);
|
||||
registerSheet("TrainIconUpdate", TrainIconUpdate);
|
||||
registerSheet("SpecialTrainInfo", SpecialTrainInfo);
|
||||
registerSheet("Social", Social);
|
||||
registerSheet("TrainDataSources", TrainDataSources);
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { View, Text, ScrollView, StyleSheet } from "react-native";
|
||||
import { Switch } from "react-native-elements";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
||||
import { AS } from "../../storageControl";
|
||||
import { STORAGE_KEYS } from "@/constants";
|
||||
import { useTrainMenu } from "@/stateBox/useTrainMenu";
|
||||
|
||||
export const DataSourceSettings = () => {
|
||||
const navigation = useNavigation();
|
||||
const { updatePermission, dataSourcePermission } = useTrainMenu();
|
||||
const canAccess = updatePermission || Object.values(dataSourcePermission).some(Boolean);
|
||||
const [useUnyohub, setUseUnyohub] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
AS.getItem(STORAGE_KEYS.USE_UNYOHUB).then((value) => {
|
||||
setUseUnyohub(value === true || value === "true");
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleToggleUnyohub = (value: boolean) => {
|
||||
setUseUnyohub(value);
|
||||
AS.setItem(STORAGE_KEYS.USE_UNYOHUB, value.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<SheetHeaderItem
|
||||
title="情報ソース設定"
|
||||
LeftItem={{
|
||||
title: "戻る",
|
||||
onPress: () => navigation.goBack(),
|
||||
}}
|
||||
/>
|
||||
{!canAccess ? (
|
||||
<View style={styles.noPermissionContainer}>
|
||||
<Text style={styles.noPermissionText}>この設定にアクセスする権限がありません。</Text>
|
||||
<Text style={styles.noPermissionSubText}>列車運用hubまたはアプリ管理者の権限が必要です。</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView style={styles.content}>
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>外部データソース</Text>
|
||||
|
||||
<View style={styles.settingItem}>
|
||||
<View style={styles.settingTextContainer}>
|
||||
<Text style={styles.settingTitle}>列車運用hub</Text>
|
||||
<Text style={styles.settingDescription}>
|
||||
列車の運用番号(車両編成番号)を表示します。
|
||||
{"\n"}
|
||||
データがある列車では地図上に黄色いマークが表示され、列車情報画面のデータベースアイコンも黄色になります。
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={useUnyohub}
|
||||
onValueChange={handleToggleUnyohub}
|
||||
color="#0099CC"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoSection}>
|
||||
<Text style={styles.infoText}>
|
||||
情報ソース設定は、外部のコミュニティデータソースとの連携を管理します。
|
||||
{"\n\n"}
|
||||
データの正確性は保証されません。参考情報としてご利用ください。
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
noPermissionContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f8f8fc",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 30,
|
||||
gap: 10,
|
||||
},
|
||||
noPermissionText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
textAlign: "center",
|
||||
},
|
||||
noPermissionSubText: {
|
||||
fontSize: 13,
|
||||
color: "#666",
|
||||
textAlign: "center",
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#0099CC",
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f8f8fc",
|
||||
},
|
||||
section: {
|
||||
backgroundColor: "white",
|
||||
marginTop: 20,
|
||||
marginHorizontal: 10,
|
||||
borderRadius: 10,
|
||||
padding: 15,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
marginBottom: 15,
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 10,
|
||||
},
|
||||
settingTextContainer: {
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
},
|
||||
settingTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
marginBottom: 5,
|
||||
},
|
||||
settingDescription: {
|
||||
fontSize: 13,
|
||||
color: "#666",
|
||||
lineHeight: 18,
|
||||
},
|
||||
infoSection: {
|
||||
backgroundColor: "#fff3cd",
|
||||
marginTop: 20,
|
||||
marginHorizontal: 10,
|
||||
borderRadius: 10,
|
||||
padding: 15,
|
||||
marginBottom: 30,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 13,
|
||||
color: "#856404",
|
||||
lineHeight: 18,
|
||||
},
|
||||
});
|
||||
@@ -16,7 +16,6 @@ import TouchableScale from "react-native-touchable-scale";
|
||||
import { SwitchArea } from "../atom/SwitchArea";
|
||||
import { useNotification } from "../../stateBox/useNotifications";
|
||||
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
||||
import { useTrainMenu } from "../../stateBox/useTrainMenu";
|
||||
|
||||
const versionCode = "6.2.1"; // Update this version code as needed
|
||||
|
||||
@@ -28,11 +27,7 @@ export const SettingTopPage = ({
|
||||
}) => {
|
||||
const { width } = useWindowDimensions();
|
||||
const { expoPushToken } = useNotification();
|
||||
const { updatePermission, dataSourcePermission } = useTrainMenu();
|
||||
const navigation = useNavigation();
|
||||
// admin またはいずれかのソース権限を持つ場合のみ表示
|
||||
const canAccessDataSourceSettings =
|
||||
updatePermission || Object.values(dataSourcePermission).some(Boolean);
|
||||
return (
|
||||
<View style={{ height: "100%", backgroundColor: "#0099CC" }}>
|
||||
<SheetHeaderItem title="アプリの設定画面" LeftItem={{
|
||||
@@ -113,14 +108,6 @@ export const SettingTopPage = ({
|
||||
navigation.navigate("setting", { screen: "LayoutSettings" })
|
||||
}
|
||||
/>
|
||||
{canAccessDataSourceSettings && (
|
||||
<SettingList
|
||||
string="情報ソース設定"
|
||||
onPress={() =>
|
||||
navigation.navigate("setting", { screen: "DataSourceSettings" })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{Platform.OS === "android" ? (
|
||||
<SettingList
|
||||
string="ウィジェット設定"
|
||||
|
||||
@@ -24,7 +24,6 @@ import { FavoriteSettings } from "./FavoriteSettings";
|
||||
import { WidgetSettings } from "./WidgetSettings";
|
||||
import { NotificationSettings } from "./NotificationSettings";
|
||||
import { LauncherIconSettings } from "./LauncherIconSettings";
|
||||
import { DataSourceSettings } from "./DataSourceSettings";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
export default function Setting(props) {
|
||||
@@ -168,17 +167,6 @@ export default function Setting(props) {
|
||||
}}
|
||||
component={FavoriteSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="DataSourceSettings"
|
||||
options={{
|
||||
gestureEnabled: true,
|
||||
...TransitionPresets.SlideFromRightIOS,
|
||||
cardOverlayEnabled: true,
|
||||
headerTransparent: true,
|
||||
headerShown: false,
|
||||
}}
|
||||
component={DataSourceSettings}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ export const ExGridSimpleViewItem: FC<{
|
||||
type: trainData?.type,
|
||||
whiteMode: true,
|
||||
});
|
||||
// 行き先(駅名)の取得
|
||||
const [destinationName] = useMemo(() => {
|
||||
// 列車名、種別、フォントの取得
|
||||
const [trainName] = useMemo(() => {
|
||||
// to_dataが設定されていればそれを優先
|
||||
if (trainData?.to_data) {
|
||||
return [trainData.to_data];
|
||||
@@ -75,9 +75,6 @@ export const ExGridSimpleViewItem: FC<{
|
||||
return [migrateTrainName(trainName)];
|
||||
}
|
||||
}, [d.array, trainData]);
|
||||
|
||||
// 列車名の取得(上部表示用)
|
||||
const trainName = trainData?.train_name || "";
|
||||
const timeArray = d.time.split(":").map((s) => parseInt(s));
|
||||
const formattedTime = dayjs()
|
||||
.set("hour", timeArray[0])
|
||||
@@ -149,7 +146,7 @@ export const ExGridSimpleViewItem: FC<{
|
||||
// to_dataがある場合は、to_dataから駅名を抽出して色を判定
|
||||
const stationNameForColor = trainData?.to_data
|
||||
? trainData.to_data.replace(/行き$/, "") // 「行き」を除去
|
||||
: destinationName;
|
||||
: trainName;
|
||||
|
||||
const Stations = stationList
|
||||
.map((a) => a.filter((d) => d.StationName == stationNameForColor))
|
||||
@@ -164,7 +161,7 @@ export const ExGridSimpleViewItem: FC<{
|
||||
);
|
||||
setStationColor(stationLineColor || ["gray"]);
|
||||
}
|
||||
}, [stationList, destinationName, trainData]);
|
||||
}, [stationList, trainName, trainData]);
|
||||
// if(typeString == "回送"){
|
||||
// return<></>;
|
||||
// }
|
||||
@@ -240,7 +237,7 @@ export const ExGridSimpleViewItem: FC<{
|
||||
textDecorationLine: isCancelled ? "line-through" : "none",
|
||||
}}
|
||||
>
|
||||
{destinationName}
|
||||
{trainName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1 }} />
|
||||
|
||||
@@ -13,7 +13,6 @@ import { TrainName } from "@/components/発車時刻表/LED_inside_Component/Tra
|
||||
import { TrainPosition } from "@/components/発車時刻表/LED_inside_Component/TrainPosition";
|
||||
import { StationPosPushDialog } from "@/components/発車時刻表/LED_inside_Component/TrainPositionDataPush";
|
||||
import { StationPosDeleteDialog } from "@/components/発車時刻表/LED_inside_Component/TrainPositionDataDelete";
|
||||
import { ScrollingDescription } from "@/components/発車時刻表/LED_inside_Component/ScrollingDescription";
|
||||
import { useStationList } from "@/stateBox/useStationList";
|
||||
import useInterval from "@/lib/useInterval";
|
||||
import dayjs from "dayjs";
|
||||
@@ -198,19 +197,17 @@ export const EachData: FC<Props> = (props) => {
|
||||
trainID={d.train}
|
||||
type={train.type}
|
||||
isThrew={d.isThrough}
|
||||
se={d.se}
|
||||
/>
|
||||
<LastStation
|
||||
lastStation={d.lastStation}
|
||||
ToData={train.to_data}
|
||||
Station_JP={station.Station_JP}
|
||||
se={d.se}
|
||||
/>
|
||||
<PlatformNumber platform={d.platformNum} se={d.se} />
|
||||
<PlatformNumber platform={d.platformNum} />
|
||||
{timeDisplay ? (
|
||||
<DependTime time={d.time} isDelay={isDelay} se={d.se} />
|
||||
<DependTime time={d.time} isDelay={isDelay} />
|
||||
) : (
|
||||
<StatusAndDelay trainDelayStatus={trainDelayStatus} se={d.se} />
|
||||
<StatusAndDelay trainDelayStatus={trainDelayStatus} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
{!!isDepartureNow && (
|
||||
@@ -246,20 +243,7 @@ export const EachData: FC<Props> = (props) => {
|
||||
/>
|
||||
)}
|
||||
{trainDescriptionSwitch && !!train.train_info && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
alignContent: "center",
|
||||
alignItems: "center",
|
||||
width: "94%",
|
||||
marginVertical: 5,
|
||||
marginHorizontal: "3%",
|
||||
backgroundColor: "#000",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
key={d.train + "-description"}
|
||||
>
|
||||
<ScrollingDescription description={train.train_info} />
|
||||
</TouchableOpacity>
|
||||
<Description info={train.train_info} key={d.train + "-description"} />
|
||||
)}
|
||||
{trainDescriptionSwitch && !!train.uwasa && (
|
||||
<Description info={train.uwasa} key={d.train + "-uwasa"} />
|
||||
|
||||
@@ -7,17 +7,13 @@ const descriptionStyle: TextStyle = {
|
||||
type Props = {
|
||||
time: string;
|
||||
isDelay?: boolean;
|
||||
se?: string;
|
||||
};
|
||||
export const DependTime: FC<Props> = ({ time, isDelay, se }) => {
|
||||
const isCanceled = se?.includes("休");
|
||||
return (
|
||||
<View style={{ flex: 4 }}>
|
||||
<Text
|
||||
style={{ ...descriptionStyle, color: isCanceled ? "#999" : isDelay ? "#ffd16fff" : "white", textDecorationLine: isCanceled ? "line-through" : "none" }}
|
||||
>
|
||||
{time}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export const DependTime: FC<Props> = ({ time, isDelay }) => (
|
||||
<View style={{ flex: 4 }}>
|
||||
<Text
|
||||
style={{ ...descriptionStyle, color: isDelay ? "#ffd16fff" : "white" }}
|
||||
>
|
||||
{time}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -5,33 +5,17 @@ type Props = {
|
||||
lastStation: string;
|
||||
ToData: string;
|
||||
Station_JP: string;
|
||||
se?: string;
|
||||
};
|
||||
export const LastStation: FC<Props> = ({ lastStation, ToData, Station_JP, se }) => {
|
||||
export const LastStation: FC<Props> = ({ lastStation, ToData, Station_JP }) => {
|
||||
const isEdit = !ToData ? false : ToData !== lastStation;
|
||||
const string = isEdit ? ToData : lastStation;
|
||||
const isCanceled = se?.includes("休");
|
||||
|
||||
return (
|
||||
<View style={{ flex: 4, flexDirection: "row" }}>
|
||||
{isCanceled && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: parseInt("12%"),
|
||||
color: "#ff6b6b",
|
||||
fontWeight: "bold",
|
||||
marginRight: 4,
|
||||
}}
|
||||
>
|
||||
運休
|
||||
</Text>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: lastStation?.length > 4 ? parseInt("12%") : parseInt("16%"),
|
||||
color: isCanceled ? "#999" : isEdit ? "#ffd16fff" : "white",
|
||||
color: isEdit ? "#ffd16fff" : "white",
|
||||
fontWeight: "bold",
|
||||
textDecorationLine: isCanceled ? "line-through" : "none",
|
||||
}}
|
||||
>
|
||||
{string === Station_JP ? "当駅止" : string}
|
||||
|
||||
@@ -6,13 +6,11 @@ const descriptionStyle: TextStyle = {
|
||||
};
|
||||
type Props = {
|
||||
platform: string;
|
||||
se?: string;
|
||||
};
|
||||
export const PlatformNumber: FC<Props> = ({ platform, se }) => {
|
||||
const isCanceled = se?.includes("休");
|
||||
export const PlatformNumber: FC<Props> = ({ platform }) => {
|
||||
return (
|
||||
<View style={{ flex: 2 }}>
|
||||
<Text style={{ ...descriptionStyle, color: isCanceled ? "#999" : "white", paddingLeft: 1, textDecorationLine: isCanceled ? "line-through" : "none" }}>
|
||||
<Text style={{ ...descriptionStyle, color: "white", paddingLeft: 1 }}>
|
||||
{platform}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { FC, useEffect, useRef, useState } from "react";
|
||||
import { Animated, Text, View, LayoutChangeEvent } from "react-native";
|
||||
|
||||
type Props = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export const ScrollingDescription: FC<Props> = ({ description }) => {
|
||||
const scrollX = useRef(new Animated.Value(0)).current;
|
||||
const [textWidth, setTextWidth] = useState(0);
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
|
||||
// 改行を削除して1行にする
|
||||
const singleLineDescription = description?.replace(/\n/g, " ") || "";
|
||||
|
||||
useEffect(() => {
|
||||
if (!singleLineDescription || textWidth === 0 || containerWidth === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// テキストが画面幅より短い場合はスクロールしない
|
||||
if (textWidth <= containerWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 初期位置を設定(画面の右端から開始)
|
||||
scrollX.setValue(containerWidth);
|
||||
|
||||
const distance = textWidth + containerWidth;
|
||||
const duration = distance * 10; // スクロール速度
|
||||
|
||||
const animation = Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.delay(500), // 最初に0.5秒待つ
|
||||
Animated.timing(scrollX, {
|
||||
toValue: -textWidth - 20,
|
||||
duration: duration,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.delay(500),
|
||||
// 瞬時に右端に戻る
|
||||
Animated.timing(scrollX, {
|
||||
toValue: containerWidth,
|
||||
duration: 0,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
animation.start();
|
||||
|
||||
return () => {
|
||||
animation.stop();
|
||||
scrollX.setValue(containerWidth);
|
||||
};
|
||||
}, [singleLineDescription, textWidth, containerWidth]);
|
||||
|
||||
const handleTextLayout = (event: LayoutChangeEvent) => {
|
||||
const { width } = event.nativeEvent.layout;
|
||||
if (width > 0) {
|
||||
setTextWidth(width);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContainerLayout = (event: LayoutChangeEvent) => {
|
||||
const { width } = event.nativeEvent.layout;
|
||||
if (width > 0) {
|
||||
setContainerWidth(width);
|
||||
}
|
||||
};
|
||||
|
||||
if (!singleLineDescription) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 20,
|
||||
overflow: "hidden",
|
||||
backgroundColor: "#000",
|
||||
}}
|
||||
onLayout={handleContainerLayout}
|
||||
>
|
||||
{/* 測定用の透明なテキスト(画面外、幅制限なし) */}
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -1000,
|
||||
left: 0,
|
||||
width: 9999, // 十分な幅を確保してテキストが折り返されないようにする
|
||||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
onLayout={handleTextLayout}
|
||||
>
|
||||
{singleLineDescription}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 実際に表示されるスクロールテキスト(幅制限なし) */}
|
||||
<Animated.View
|
||||
style={{
|
||||
position: "absolute",
|
||||
transform: [{ translateX: scrollX }],
|
||||
width: 9999, // テキストが折り返されないように十分な幅を確保
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
color: "#d3a203",
|
||||
}}
|
||||
>
|
||||
{singleLineDescription}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -6,13 +6,11 @@ const descriptionStyle: TextStyle = {
|
||||
};
|
||||
type Props = {
|
||||
trainDelayStatus: string;
|
||||
se?: string;
|
||||
};
|
||||
export const StatusAndDelay: FC<Props> = ({ trainDelayStatus, se }) => {
|
||||
const isCanceled = se?.includes("休");
|
||||
export const StatusAndDelay: FC<Props> = ({ trainDelayStatus }) => {
|
||||
return (
|
||||
<View style={{ flex: 4 }}>
|
||||
<Text style={{ ...descriptionStyle, color: isCanceled ? "#999" : "#ffd16fff", paddingLeft: 1, textDecorationLine: isCanceled ? "line-through" : "none" }}>
|
||||
<Text style={{ ...descriptionStyle, color: "#ffd16fff", paddingLeft: 1 }}>
|
||||
{trainDelayStatus}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -9,10 +9,9 @@ type Props = {
|
||||
trainID: string;
|
||||
type: trainTypeID;
|
||||
isThrew: boolean;
|
||||
se?: string;
|
||||
};
|
||||
export const TrainName: FC<Props> = (props) => {
|
||||
const { trainName, trainNumDistance, trainIDSwitch, trainID, type, isThrew, se } =
|
||||
const { trainName, trainNumDistance, trainIDSwitch, trainID, type, isThrew } =
|
||||
props;
|
||||
const { name, color } = getTrainType({ type });
|
||||
const TrainNumber =
|
||||
@@ -24,15 +23,13 @@ export const TrainName: FC<Props> = (props) => {
|
||||
parseInt(trainNumDistance)
|
||||
}号`
|
||||
: "";
|
||||
const isCanceled = se?.includes("休");
|
||||
return (
|
||||
<View style={{ flex: 9, flexDirection: "row", alignItems: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: trainName.length > 6 ? parseInt("11%") : parseInt("15%"),
|
||||
color: isCanceled ? "#999" : color,
|
||||
color: color,
|
||||
fontWeight: "bold",
|
||||
textDecorationLine: isCanceled ? "line-through" : "none",
|
||||
}}
|
||||
>
|
||||
{trainIDSwitch
|
||||
|
||||
@@ -31,9 +31,6 @@ export const API_ENDPOINTS = {
|
||||
|
||||
/** 位置情報問題データ */
|
||||
POSITION_PROBLEMS: 'https://n8n.haruk.in/webhook/jrshikoku-position-problems',
|
||||
|
||||
/** 列車運用hub運用データ */
|
||||
UNYOHUB_DATA: 'https://jr-shikoku-api-data-storage.haruk.in/thirdparty/unyohub-unyo.json',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,13 +80,6 @@ export const STORAGE_KEYS = {
|
||||
|
||||
/** 奇妙な列車通知 */
|
||||
STRANGE_TRAIN: 'strangeTrain',
|
||||
|
||||
// 情報ソース設定系
|
||||
/** 列車運用hub使用設定 */
|
||||
USE_UNYOHUB: 'useUnyohub',
|
||||
|
||||
/** 列車運用hubデータ */
|
||||
UNYOHUB_DATA: 'unyohubData',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,7 +70,6 @@ export type CustomTrainData = {
|
||||
lastStation: string;
|
||||
isThrough: boolean;
|
||||
platformNum: string | null;
|
||||
se?: string;
|
||||
};
|
||||
|
||||
export type StationProps = {
|
||||
@@ -94,7 +93,6 @@ export type OperationLogs = {
|
||||
train_ids?: string[];
|
||||
unit_ids?: string[];
|
||||
vehicle_img: string;
|
||||
vehicle_img_right: string;
|
||||
vehicle_info_url: string;
|
||||
related_train_ids?: string[];
|
||||
state: number | null;
|
||||
|
||||
@@ -74,7 +74,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
|
||||
isThrough: false,
|
||||
train: trainNum,
|
||||
platformNum: null,
|
||||
se: undefined,
|
||||
};
|
||||
stationDiagram[trainNum].split("#").forEach((data) => {
|
||||
const [stationName, type, time, platformNum] = data.split(",");
|
||||
@@ -84,7 +83,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
|
||||
}
|
||||
if (stationName === station.Station_JP) {
|
||||
trainData.platformNum = platformNum;
|
||||
trainData.se = type;
|
||||
if (type.match("発")) {
|
||||
trainData.time = time;
|
||||
} else if (type.match("通")) {
|
||||
@@ -101,7 +99,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
|
||||
lastStation: trainData.lastStation,
|
||||
isThrough: trainData.isThrough,
|
||||
platformNum: trainData.platformNum,
|
||||
se: trainData.se,
|
||||
};
|
||||
})
|
||||
.filter((d) => d.time);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AppState, AppStateStatus, Platform } from "react-native";
|
||||
|
||||
type Control = {
|
||||
start: () => void;
|
||||
@@ -13,15 +12,10 @@ type Fn = () => void;
|
||||
export const useInterval = (fn: Fn, interval: number, autostart = true) => {
|
||||
const onUpdateRef = useRef<Fn>();
|
||||
const [state, setState] = useState("RUNNING");
|
||||
// ユーザー操作によるSTOP(AppStateによる一時停止と区別する)
|
||||
const userStoppedRef = useRef(!autostart);
|
||||
|
||||
const start = () => {
|
||||
userStoppedRef.current = false;
|
||||
setState("RUNNING");
|
||||
};
|
||||
const stop = () => {
|
||||
userStoppedRef.current = true;
|
||||
setState("STOPPED");
|
||||
};
|
||||
useEffect(() => {
|
||||
@@ -29,48 +23,22 @@ export const useInterval = (fn: Fn, interval: number, autostart = true) => {
|
||||
}, [fn]);
|
||||
useEffect(() => {
|
||||
if (autostart) {
|
||||
userStoppedRef.current = false;
|
||||
setState("RUNNING");
|
||||
} else {
|
||||
userStoppedRef.current = true;
|
||||
}else{
|
||||
setState("STOPPED");
|
||||
}
|
||||
}, [autostart]);
|
||||
|
||||
// バックグラウンド移行時に停止、フォアグラウンド復帰時に即時実行して再開
|
||||
useEffect(() => {
|
||||
if (Platform.OS === "web") return;
|
||||
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||
if (nextAppState === "active") {
|
||||
if (!userStoppedRef.current) {
|
||||
// 復帰直後に即時フェッチして最新データを取得
|
||||
onUpdateRef.current?.();
|
||||
setState("RUNNING");
|
||||
}
|
||||
} else if (nextAppState === "background" || nextAppState === "inactive") {
|
||||
if (!userStoppedRef.current) {
|
||||
// バックグラウンド中はインターバルを停止してムダなfetchエラーを防ぐ
|
||||
setState("STOPPED");
|
||||
}
|
||||
}
|
||||
};
|
||||
const subscription = AppState.addEventListener("change", handleAppStateChange);
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let timerId: ReturnType<typeof setInterval> | undefined;
|
||||
let timerId;
|
||||
if (state === "RUNNING") {
|
||||
timerId = setInterval(() => {
|
||||
onUpdateRef.current?.();
|
||||
}, interval);
|
||||
} else {
|
||||
if (timerId) clearInterval(timerId);
|
||||
timerId && clearInterval(timerId);
|
||||
}
|
||||
return () => {
|
||||
if (timerId) clearInterval(timerId);
|
||||
timerId && clearInterval(timerId);
|
||||
};
|
||||
}, [interval, state]);
|
||||
return [state, { start, stop }];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* 駅情報データ (みどりの窓口・ICカード対応状況)
|
||||
* Feature フィールドは WebView 内で JSON.parse() して使用する。
|
||||
*/
|
||||
export interface StationDataItem {
|
||||
StationName: string;
|
||||
StationNumber: string;
|
||||
/** JSON文字列: { Midori: { style: "normal"|"plus"|"none" }, IC: boolean } */
|
||||
Feature: string;
|
||||
}
|
||||
|
||||
export const STATION_DATA: StationDataItem[] = [
|
||||
// ── 予讃線 ─────────────────────────────
|
||||
{ StationName: "高松", StationNumber: "Y00", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "香西", StationNumber: "Y01", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "鬼無", StationNumber: "Y02", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "端岡", StationNumber: "Y03", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "国分", StationNumber: "Y04", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "讃岐府中", StationNumber: "Y05", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "鴨川", StationNumber: "Y06", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "八十場", StationNumber: "Y07", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "坂出", StationNumber: "Y08", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "宇多津", StationNumber: "Y09", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "丸亀", StationNumber: "Y10", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "讃岐塩屋", StationNumber: "Y11", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "多度津", StationNumber: "Y12", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "詫間", StationNumber: "Y14", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
|
||||
{ StationName: "観音寺", StationNumber: "Y19", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "川之江", StationNumber: "Y22", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "伊予三島", StationNumber: "Y23", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "新居浜", StationNumber: "Y29", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "伊予西条", StationNumber: "Y31", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "壬生川", StationNumber: "Y36", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "今治", StationNumber: "Y40", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "伊予北条", StationNumber: "Y48", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "松山", StationNumber: "Y55", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
// ── 内子線・海線 ─────────────────────────
|
||||
{ StationName: "内子", StationNumber: "U10", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "伊予大洲", StationNumber: "U14", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "伊予大洲", StationNumber: "S18", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "八幡浜", StationNumber: "U18", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "宇和島", StationNumber: "U28", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
// ── 土讃線 ─────────────────────────────
|
||||
{ StationName: "多度津", StationNumber: "D12", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "善通寺", StationNumber: "D14", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
|
||||
{ StationName: "琴平", StationNumber: "D15", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
|
||||
{ StationName: "阿波池田", StationNumber: "D22", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "土佐山田", StationNumber: "D37", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "後免", StationNumber: "D40", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "高知", StationNumber: "D45", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
// ── 予土線 ─────────────────────────────
|
||||
{ StationName: "高知", StationNumber: "K00", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "朝倉", StationNumber: "K05", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "須崎", StationNumber: "K19", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "窪川", StationNumber: "K26", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
// ── 高徳線 ─────────────────────────────
|
||||
{ StationName: "高松", StationNumber: "T28", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
|
||||
{ StationName: "栗林公園北口", StationNumber: "T26", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "栗林", StationNumber: "T25", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
|
||||
{ StationName: "屋島", StationNumber: "T24", Feature: '{"Midori":{"style":"none"},"IC":true}' },
|
||||
{ StationName: "志度", StationNumber: "T19", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "三本松", StationNumber: "T12", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "徳島", StationNumber: "T00", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
// ── 鳴門線 ─────────────────────────────
|
||||
{ StationName: "鳴門", StationNumber: "N10", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
// ── 牟岐線 ─────────────────────────────
|
||||
{ StationName: "阿南", StationNumber: "", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
{ StationName: "牟岐", StationNumber: "", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
|
||||
// ── 徳島線 ─────────────────────────────
|
||||
{ StationName: "鴨島", StationNumber: "B09", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
{ StationName: "穴吹", StationNumber: "B16", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
|
||||
];
|
||||
@@ -1,455 +0,0 @@
|
||||
/**
|
||||
* 列番号 → 列車画像URL のマッピングデータ
|
||||
*
|
||||
* "__anpanman__" は動的URLのセンチネル値。
|
||||
* WebView inject 側で アンパンマン列車判定 URL に変換される。
|
||||
* (https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=<列番>)
|
||||
*/
|
||||
|
||||
const AP = "__anpanman__";
|
||||
|
||||
/** 完全一致マッピング (列番 → URL | "__anpanman__") */
|
||||
export const TRAIN_ICON_MAP: Record<string, string> = {
|
||||
// ── しおかぜ ───────────────────────────────────────
|
||||
// 8000 ノーマル
|
||||
"2M": "https://storage.haruk.in/s8000nr.png",
|
||||
"4M": "https://storage.haruk.in/s8000nr.png",
|
||||
"6M": "https://storage.haruk.in/s8000nr.png",
|
||||
"14M": "https://storage.haruk.in/s8000nr.png",
|
||||
"16M": "https://storage.haruk.in/s8000nr.png",
|
||||
"18M": "https://storage.haruk.in/s8000nr.png",
|
||||
"26M": "https://storage.haruk.in/s8000nr.png",
|
||||
"28M": "https://storage.haruk.in/s8000nr.png",
|
||||
"30M": "https://storage.haruk.in/s8000nr.png",
|
||||
"1M": "https://storage.haruk.in/s8000nr.png",
|
||||
"3M": "https://storage.haruk.in/s8000nr.png",
|
||||
"5M": "https://storage.haruk.in/s8000nr.png",
|
||||
"13M": "https://storage.haruk.in/s8000nr.png",
|
||||
"15M": "https://storage.haruk.in/s8000nr.png",
|
||||
"17M": "https://storage.haruk.in/s8000nr.png",
|
||||
"25M": "https://storage.haruk.in/s8000nr.png",
|
||||
"27M": "https://storage.haruk.in/s8000nr.png",
|
||||
"29M": "https://storage.haruk.in/s8000nr.png",
|
||||
// 8000 アンパン
|
||||
"10M": AP,
|
||||
"22M": AP,
|
||||
"9M": AP,
|
||||
"21M": AP,
|
||||
// 8600
|
||||
"8M": "https://storage.haruk.in/s8600.png",
|
||||
"12M": "https://storage.haruk.in/s8600.png",
|
||||
"20M": "https://storage.haruk.in/s8600.png",
|
||||
"24M": "https://storage.haruk.in/s8600.png",
|
||||
"7M": "https://storage.haruk.in/s8600.png",
|
||||
"11M": "https://storage.haruk.in/s8600.png",
|
||||
"19M": "https://storage.haruk.in/s8600.png",
|
||||
"23M": "https://storage.haruk.in/s8600.png",
|
||||
|
||||
// ── いしづち ───────────────────────────────────────
|
||||
// 8000 ノーマル
|
||||
"1004M": "https://storage.haruk.in/s8000no.png",
|
||||
"1006M": "https://storage.haruk.in/s8000no.png",
|
||||
"1014M": "https://storage.haruk.in/s8000no.png",
|
||||
"1016M": "https://storage.haruk.in/s8000no.png",
|
||||
"1018M": "https://storage.haruk.in/s8000no.png",
|
||||
"1026M": "https://storage.haruk.in/s8000no.png",
|
||||
"1028M": "https://storage.haruk.in/s8000no.png",
|
||||
"1030M": "https://storage.haruk.in/s8000no.png",
|
||||
"1001M": "https://storage.haruk.in/s8000no.png",
|
||||
"1003M": "https://storage.haruk.in/s8000no.png",
|
||||
"1005M": "https://storage.haruk.in/s8000no.png",
|
||||
"1013M": "https://storage.haruk.in/s8000no.png",
|
||||
"1015M": "https://storage.haruk.in/s8000no.png",
|
||||
"1017M": "https://storage.haruk.in/s8000no.png",
|
||||
"1025M": "https://storage.haruk.in/s8000no.png",
|
||||
"1027M": "https://storage.haruk.in/s8000no.png",
|
||||
"1029M": "https://storage.haruk.in/s8000no.png",
|
||||
// 8000 アンパン
|
||||
"1010M": AP,
|
||||
"1022M": AP,
|
||||
"1009M": AP,
|
||||
"1021M": AP,
|
||||
// 8600
|
||||
"1008M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1012M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1020M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1024M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1007M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1011M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1019M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1023M": "https://storage.haruk.in/s8600_isz.png",
|
||||
// MEXP
|
||||
"1092M": "https://storage.haruk.in/s8000nr.png",
|
||||
"1091M": "https://storage.haruk.in/s8600_isz.png",
|
||||
// 三桁いしづち アンパン
|
||||
"1041M": AP,
|
||||
"1044M": AP,
|
||||
// 三桁いしづち 8600
|
||||
"1043M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1042M": "https://storage.haruk.in/s8600_isz.png",
|
||||
"1046M": "https://storage.haruk.in/s8600_isz.png",
|
||||
|
||||
// ── 南風 ───────────────────────────────────────────
|
||||
// 2700 ノーマル
|
||||
"34D": "https://storage.haruk.in/s2700.png",
|
||||
"38D": "https://storage.haruk.in/s2700.png",
|
||||
"40D": "https://storage.haruk.in/s2700.png",
|
||||
"42D": "https://storage.haruk.in/s2700.png",
|
||||
"46D": "https://storage.haruk.in/s2700.png",
|
||||
"50D": "https://storage.haruk.in/s2700.png",
|
||||
"52D": "https://storage.haruk.in/s2700.png",
|
||||
"54D": "https://storage.haruk.in/s2700.png",
|
||||
"58D": "https://storage.haruk.in/s2700.png",
|
||||
"31D": "https://storage.haruk.in/s2700.png",
|
||||
"35D": "https://storage.haruk.in/s2700.png",
|
||||
"39D": "https://storage.haruk.in/s2700.png",
|
||||
"41D": "https://storage.haruk.in/s2700.png",
|
||||
"43D": "https://storage.haruk.in/s2700.png",
|
||||
"47D": "https://storage.haruk.in/s2700.png",
|
||||
"51D": "https://storage.haruk.in/s2700.png",
|
||||
"53D": "https://storage.haruk.in/s2700.png",
|
||||
"55D": "https://storage.haruk.in/s2700.png",
|
||||
// 2700 アンパン
|
||||
"32D": AP,
|
||||
"36D": AP,
|
||||
"44D": AP,
|
||||
"48D": AP,
|
||||
"56D": AP,
|
||||
"33D": AP,
|
||||
"37D": AP,
|
||||
"45D": AP,
|
||||
"49D": AP,
|
||||
"57D": AP,
|
||||
|
||||
// ── うずしお ───────────────────────────────────────
|
||||
// 2700
|
||||
"3004D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3006D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3010D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3014D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3016D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3022D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3028D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3003D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3007D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3013D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3019D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3025D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3031D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3008D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3020D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3026D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3001D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3005D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3011D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3017D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3023D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
"3029D": "https://storage.haruk.in/s2700_uzu.png",
|
||||
// 2600
|
||||
"3002D": AP,
|
||||
"3012D": AP,
|
||||
"3018D": AP,
|
||||
"3024D": AP,
|
||||
"3030D": AP,
|
||||
"3009D": AP,
|
||||
"3015D": AP,
|
||||
"3021D": AP,
|
||||
"3027D": AP,
|
||||
"3033D": AP,
|
||||
|
||||
// ── マリンライナー ─────────────────────────────────
|
||||
"3104M": "https://storage.haruk.in/s5001.png",
|
||||
"3106M": "https://storage.haruk.in/s5001.png",
|
||||
"3108M": "https://storage.haruk.in/s5001.png",
|
||||
"3110M": "https://storage.haruk.in/s5001.png",
|
||||
"3112M": "https://storage.haruk.in/s5001.png",
|
||||
"3114M": "https://storage.haruk.in/s5001.png",
|
||||
"3116M": "https://storage.haruk.in/s5001.png",
|
||||
"3118M": "https://storage.haruk.in/s5001.png",
|
||||
"3120M": "https://storage.haruk.in/s5001.png",
|
||||
"3122M": "https://storage.haruk.in/s5001.png",
|
||||
"3124M": "https://storage.haruk.in/s5001.png",
|
||||
"3126M": "https://storage.haruk.in/s5001.png",
|
||||
"3128M": "https://storage.haruk.in/s5001.png",
|
||||
"3130M": "https://storage.haruk.in/s5001.png",
|
||||
"3132M": "https://storage.haruk.in/s5001.png",
|
||||
"3134M": "https://storage.haruk.in/s5001.png",
|
||||
"3136M": "https://storage.haruk.in/s5001.png",
|
||||
"3138M": "https://storage.haruk.in/s5001.png",
|
||||
"3140M": "https://storage.haruk.in/s5001.png",
|
||||
"3142M": "https://storage.haruk.in/s5001.png",
|
||||
"3144M": "https://storage.haruk.in/s5001.png",
|
||||
"3146M": "https://storage.haruk.in/s5001.png",
|
||||
"3148M": "https://storage.haruk.in/s5001.png",
|
||||
"3150M": "https://storage.haruk.in/s5001.png",
|
||||
"3152M": "https://storage.haruk.in/s5001.png",
|
||||
"3154M": "https://storage.haruk.in/s5001.png",
|
||||
"3156M": "https://storage.haruk.in/s5001.png",
|
||||
"3158M": "https://storage.haruk.in/s5001.png",
|
||||
"3160M": "https://storage.haruk.in/s5001.png",
|
||||
"3162M": "https://storage.haruk.in/s5001.png",
|
||||
"3164M": "https://storage.haruk.in/s5001.png",
|
||||
"3166M": "https://storage.haruk.in/s5001.png",
|
||||
"3168M": "https://storage.haruk.in/s5001.png",
|
||||
"3170M": "https://storage.haruk.in/s5001.png",
|
||||
"3105M": "https://storage.haruk.in/s5001.png",
|
||||
"3107M": "https://storage.haruk.in/s5001.png",
|
||||
"3109M": "https://storage.haruk.in/s5001.png",
|
||||
"3111M": "https://storage.haruk.in/s5001.png",
|
||||
"3113M": "https://storage.haruk.in/s5001.png",
|
||||
"3115M": "https://storage.haruk.in/s5001.png",
|
||||
"3117M": "https://storage.haruk.in/s5001.png",
|
||||
"3119M": "https://storage.haruk.in/s5001.png",
|
||||
"3121M": "https://storage.haruk.in/s5001.png",
|
||||
"3123M": "https://storage.haruk.in/s5001.png",
|
||||
"3125M": "https://storage.haruk.in/s5001.png",
|
||||
"3127M": "https://storage.haruk.in/s5001.png",
|
||||
"3129M": "https://storage.haruk.in/s5001.png",
|
||||
"3131M": "https://storage.haruk.in/s5001.png",
|
||||
"3133M": "https://storage.haruk.in/s5001.png",
|
||||
"3135M": "https://storage.haruk.in/s5001.png",
|
||||
"3137M": "https://storage.haruk.in/s5001.png",
|
||||
"3139M": "https://storage.haruk.in/s5001.png",
|
||||
"3141M": "https://storage.haruk.in/s5001.png",
|
||||
"3143M": "https://storage.haruk.in/s5001.png",
|
||||
"3145M": "https://storage.haruk.in/s5001.png",
|
||||
"3147M": "https://storage.haruk.in/s5001.png",
|
||||
"3149M": "https://storage.haruk.in/s5001.png",
|
||||
"3151M": "https://storage.haruk.in/s5001.png",
|
||||
"3153M": "https://storage.haruk.in/s5001.png",
|
||||
"3155M": "https://storage.haruk.in/s5001.png",
|
||||
"3157M": "https://storage.haruk.in/s5001.png",
|
||||
"3159M": "https://storage.haruk.in/s5001.png",
|
||||
"3161M": "https://storage.haruk.in/s5001.png",
|
||||
"3163M": "https://storage.haruk.in/s5001.png",
|
||||
"3165M": "https://storage.haruk.in/s5001.png",
|
||||
"3167M": "https://storage.haruk.in/s5001.png",
|
||||
"3169M": "https://storage.haruk.in/s5001.png",
|
||||
"3175M": "https://storage.haruk.in/s5001.png",
|
||||
// マリンライナー(快速)
|
||||
"3102M": "https://storage.haruk.in/s5001k.png",
|
||||
"3101M": "https://storage.haruk.in/s5001k.png",
|
||||
"3103M": "https://storage.haruk.in/s5001k.png",
|
||||
"3171M": "https://storage.haruk.in/s5001k.png",
|
||||
"3173M": "https://storage.haruk.in/s5001k.png",
|
||||
|
||||
// ── サンライズ瀬戸 ─────────────────────────────────
|
||||
"5032M": "https://storage.haruk.in/w285.png",
|
||||
"5031M": "https://storage.haruk.in/w285.png",
|
||||
"8041M": "https://storage.haruk.in/w285.png",
|
||||
"8031M": "https://storage.haruk.in/w285.png",
|
||||
|
||||
// ── 宇和海 ─────────────────────────────────────────
|
||||
// 2000 ノーマル
|
||||
"1052D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1054D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1056D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1060D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1062D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1064D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1068D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1070D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1072D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1076D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1078D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1080D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1082D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1051D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1055D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1057D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1061D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1063D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1065D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1069D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1071D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1073D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1075D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1077D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1079D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
"1081D": "https://storage.haruk.in/s2000_uwa.png",
|
||||
// 2000 アンパン
|
||||
"1058D": AP,
|
||||
"1066D": AP,
|
||||
"1074D": AP,
|
||||
"1053D": AP,
|
||||
"1059D": AP,
|
||||
"1067D": AP,
|
||||
|
||||
// ── しまんと ───────────────────────────────────────
|
||||
"2002D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2004D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2001D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2003D": "https://storage.haruk.in/s2000_smn.png",
|
||||
|
||||
// ── あしずり ───────────────────────────────────────
|
||||
// 2000
|
||||
"2074D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2076D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2080D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2082D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2071D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2073D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2079D": "https://storage.haruk.in/s2000_smn.png",
|
||||
"2081D": "https://storage.haruk.in/s2000_smn.png",
|
||||
// 2700
|
||||
"2072D": "https://storage.haruk.in/s2700_asi.png",
|
||||
"2078D": "https://storage.haruk.in/s2700_asi.png",
|
||||
"2084D": "https://storage.haruk.in/s2700_asi.png",
|
||||
"2075D": "https://storage.haruk.in/s2700_asi.png",
|
||||
"2077D": "https://storage.haruk.in/s2700_asi.png",
|
||||
"2083D": "https://storage.haruk.in/s2700_asi.png",
|
||||
|
||||
// ── 剣山 ───────────────────────────────────────────
|
||||
"4002D": "https://storage.haruk.in/s185tu.png",
|
||||
"4004D": "https://storage.haruk.in/s185tu.png",
|
||||
"4006D": "https://storage.haruk.in/s185tu.png",
|
||||
"4001D": "https://storage.haruk.in/s185tu.png",
|
||||
"4003D": "https://storage.haruk.in/s185tu.png",
|
||||
"4005D": "https://storage.haruk.in/s185tu.png",
|
||||
"4007D": "https://storage.haruk.in/s185tu.png",
|
||||
|
||||
// ── よしのがわトロッコ ─────────────────────────────
|
||||
"8452D": "https://storage.haruk.in/s185to_ai.png",
|
||||
"8451D": "https://storage.haruk.in/s185to_ai.png",
|
||||
|
||||
// ── 岡山高松/琴平アントロ ──────────────────────────
|
||||
"8176D": "https://storage.haruk.in/s32to4.png",
|
||||
"8179D": "https://storage.haruk.in/s32to4.png",
|
||||
"8277D": "https://storage.haruk.in/s32to4.png",
|
||||
"8278D": "https://storage.haruk.in/s32to4.png",
|
||||
|
||||
// ── 千年ものがたり ─────────────────────────────────
|
||||
"8021D": "https://storage.haruk.in/s185mm1.png",
|
||||
"8022D": "https://storage.haruk.in/s185mm1.png",
|
||||
|
||||
// ── 夜明けものがたり ───────────────────────────────
|
||||
"8082D": "https://storage.haruk.in/s185ym1.png",
|
||||
"8083D": "https://storage.haruk.in/s185ym1.png",
|
||||
"8073D": "https://storage.haruk.in/s185ym1.png",
|
||||
"8074D": "https://storage.haruk.in/s185ym1.png",
|
||||
|
||||
// ── ラ・マルどこまでも ─────────────────────────────
|
||||
"9253M": "https://storage.haruk.in/w213w.png",
|
||||
"9256M": "https://storage.haruk.in/w213w.png",
|
||||
|
||||
// ── 貨物 ───────────────────────────────────────────
|
||||
"74": "https://storage.haruk.in/ef210a.png",
|
||||
"75": "https://storage.haruk.in/ef210a.png",
|
||||
"70": "https://storage.haruk.in/ef210a.png",
|
||||
"71": "https://storage.haruk.in/ef210a.png",
|
||||
"73": "https://storage.haruk.in/ef210a.png",
|
||||
"76": "https://storage.haruk.in/ef210a.png",
|
||||
"3070": "https://storage.haruk.in/ef210a.png",
|
||||
"3071": "https://storage.haruk.in/ef210a.png",
|
||||
"3072": "https://storage.haruk.in/ef210a.png",
|
||||
"3073": "https://storage.haruk.in/ef210a.png",
|
||||
"3076": "https://storage.haruk.in/ef210a.png",
|
||||
"3077": "https://storage.haruk.in/ef210a.png",
|
||||
"3078": "https://storage.haruk.in/ef210a.png",
|
||||
"3079": "https://storage.haruk.in/ef210a.png",
|
||||
"8070": "https://storage.haruk.in/ef210a.png",
|
||||
"8071": "https://storage.haruk.in/ef210a.png",
|
||||
"8072": "https://storage.haruk.in/ef210a.png",
|
||||
"8077": "https://storage.haruk.in/ef210a.png",
|
||||
|
||||
// ── 伊予灘ものがたり ───────────────────────────────
|
||||
"8091D": "https://storage.haruk.in/s185iyor.png",
|
||||
"8093D": "https://storage.haruk.in/s185iyor.png",
|
||||
"8092D": "https://storage.haruk.in/s185iyoy.png",
|
||||
"8094D": "https://storage.haruk.in/s185iyoy.png",
|
||||
|
||||
// ── 高徳線・徳島線・牟岐線・鳴門線 キハ40・47 ────
|
||||
"4303D": "https://storage.haruk.in/s40.png",
|
||||
"371D": "https://storage.haruk.in/s40.png",
|
||||
"316D": "https://storage.haruk.in/s40.png",
|
||||
"362D": "https://storage.haruk.in/s40.png",
|
||||
"4376D": "https://storage.haruk.in/s40.png",
|
||||
"951D": "https://storage.haruk.in/s40.png",
|
||||
"953D": "https://storage.haruk.in/s40.png",
|
||||
"955D": "https://storage.haruk.in/s40.png",
|
||||
"973D": "https://storage.haruk.in/s40.png",
|
||||
"975D": "https://storage.haruk.in/s40.png",
|
||||
"977D": "https://storage.haruk.in/s40.png",
|
||||
"979D": "https://storage.haruk.in/s40.png",
|
||||
"981D": "https://storage.haruk.in/s40.png",
|
||||
"950D": "https://storage.haruk.in/s40.png",
|
||||
"968D": "https://storage.haruk.in/s40.png",
|
||||
"970D": "https://storage.haruk.in/s40.png",
|
||||
"972D": "https://storage.haruk.in/s40.png",
|
||||
"974D": "https://storage.haruk.in/s40.png",
|
||||
"976D": "https://storage.haruk.in/s40.png",
|
||||
"980D": "https://storage.haruk.in/s40.png",
|
||||
"982D": "https://storage.haruk.in/s40.png",
|
||||
|
||||
// ── 1000形 ─────────────────────────────────────────
|
||||
"4311D": "https://storage.haruk.in/s1000.png",
|
||||
"363D": "https://storage.haruk.in/s1000.png",
|
||||
"356D": "https://storage.haruk.in/s1000.png",
|
||||
"4374D": "https://storage.haruk.in/s1000.png",
|
||||
"433D": "https://storage.haruk.in/s1000.png",
|
||||
"4447D": "https://storage.haruk.in/s1000.png",
|
||||
"451D": "https://storage.haruk.in/s1000.png",
|
||||
"450D": "https://storage.haruk.in/s1000.png",
|
||||
"4458D": "https://storage.haruk.in/s1000.png",
|
||||
"474D": "https://storage.haruk.in/s1000.png",
|
||||
|
||||
// ── 1200形 ─────────────────────────────────────────
|
||||
"4301D": "https://storage.haruk.in/s1200n.png",
|
||||
"4327D": "https://storage.haruk.in/s1200n.png",
|
||||
"4329D": "https://storage.haruk.in/s1200n.png",
|
||||
"4343D": "https://storage.haruk.in/s1200n.png",
|
||||
"353D": "https://storage.haruk.in/s1200n.png",
|
||||
"355D": "https://storage.haruk.in/s1200n.png",
|
||||
"367D": "https://storage.haruk.in/s1200n.png",
|
||||
"310D": "https://storage.haruk.in/s1200n.png",
|
||||
"4326D": "https://storage.haruk.in/s1200n.png",
|
||||
"4334D": "https://storage.haruk.in/s1200n.png",
|
||||
"4342D": "https://storage.haruk.in/s1200n.png",
|
||||
"358D": "https://storage.haruk.in/s1200n.png",
|
||||
"364D": "https://storage.haruk.in/s1200n.png",
|
||||
"4453D": "https://storage.haruk.in/s1200n.png",
|
||||
"4455D": "https://storage.haruk.in/s1200n.png",
|
||||
"4457D": "https://storage.haruk.in/s1200n.png",
|
||||
"463D": "https://storage.haruk.in/s1200n.png",
|
||||
"475D": "https://storage.haruk.in/s1200n.png",
|
||||
"477D": "https://storage.haruk.in/s1200n.png",
|
||||
"485D": "https://storage.haruk.in/s1200n.png",
|
||||
"4430D": "https://storage.haruk.in/s1200n.png",
|
||||
"434D": "https://storage.haruk.in/s1200n.png",
|
||||
"438D": "https://storage.haruk.in/s1200n.png",
|
||||
"4460D": "https://storage.haruk.in/s1200n.png",
|
||||
"4464D": "https://storage.haruk.in/s1200n.png",
|
||||
"4466D": "https://storage.haruk.in/s1200n.png",
|
||||
"478D": "https://storage.haruk.in/s1200n.png",
|
||||
"484D": "https://storage.haruk.in/s1200n.png",
|
||||
"957D": "https://storage.haruk.in/s1200n.png",
|
||||
"4959D": "https://storage.haruk.in/s1200n.png",
|
||||
"4963D": "https://storage.haruk.in/s1200n.png",
|
||||
"4967D": "https://storage.haruk.in/s1200n.png",
|
||||
"4971D": "https://storage.haruk.in/s1200n.png",
|
||||
"952D": "https://storage.haruk.in/s1200n.png",
|
||||
"4954D": "https://storage.haruk.in/s1200n.png",
|
||||
"4958D": "https://storage.haruk.in/s1200n.png",
|
||||
"4962D": "https://storage.haruk.in/s1200n.png",
|
||||
"4966D": "https://storage.haruk.in/s1200n.png",
|
||||
|
||||
// ── 半定期臨時 ─────────────────────────────────────
|
||||
"9174M": "https://storage.haruk.in/s5001.png",
|
||||
"9395D": "https://storage.haruk.in/s1500.png",
|
||||
};
|
||||
|
||||
/** 正規表現パターンマッチング (完全一致で未ヒットの場合に評価) */
|
||||
export const TRAIN_ICON_REGEX: Array<{ pattern: string; url: string }> = [
|
||||
// 高徳線 普通
|
||||
{ pattern: "^(4|5)3\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
|
||||
{ pattern: "^3\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
|
||||
// 徳島線 普通
|
||||
{ pattern: "^(4|5)4\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
|
||||
{ pattern: "^4\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
|
||||
// 鳴門線 普通
|
||||
{
|
||||
pattern: "^(4|5)9(5|6|7|8)\\dD$",
|
||||
url: "https://storage.haruk.in/s1500.png",
|
||||
},
|
||||
{ pattern: "^9(5|6|7|8)\\dD$", url: "https://storage.haruk.in/s1500.png" },
|
||||
];
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 列車種別ごとの表示設定
|
||||
*
|
||||
* - typeColor : nameReplace で使う種別ラベルの文字色
|
||||
* - borderColor: setNewTrainItem で使う枠線色
|
||||
* - bgColor : setNewTrainItem で使う背景色
|
||||
* - label : 種別ラベル文字列
|
||||
* - isWanman : ワンマン列車かどうか
|
||||
*/
|
||||
export interface TrainTypeConfig {
|
||||
label: string;
|
||||
typeColor: string;
|
||||
borderColor: string;
|
||||
bgColor: string;
|
||||
isWanman: boolean;
|
||||
}
|
||||
|
||||
export const TRAIN_TYPE_CONFIG: Record<string, TrainTypeConfig> = {
|
||||
Normal: { label: "普通", typeColor: "black", borderColor: "black", bgColor: "#ffffffcc", isWanman: false },
|
||||
OneMan: { label: "普通", typeColor: "black", borderColor: "black", bgColor: "#ffffffcc", isWanman: true },
|
||||
Rapid: { label: "快速", typeColor: "rgba(0, 140, 255, 1)", borderColor: "rgba(0, 140, 255, 1)", bgColor: "#ffffffcc", isWanman: false },
|
||||
OneManRapid: { label: "快速", typeColor: "rgba(0, 140, 255, 1)", borderColor: "rgba(0, 140, 255, 1)", bgColor: "#ffffffcc", isWanman: true },
|
||||
LTDEXP: { label: "特急", typeColor: "red", borderColor: "red", bgColor: "#ffffffcc", isWanman: false },
|
||||
NightLTDEXP: { label: "寝台特急", typeColor: "#d300b0ff", borderColor: "#d300b0ff", bgColor: "#ffffffcc", isWanman: false },
|
||||
SPCL: { label: "臨時", typeColor: "#008d07ff", borderColor: "#008d07ff", bgColor: "#ffffffcc", isWanman: false },
|
||||
SPCL_Normal: { label: "臨時", typeColor: "#008d07ff", borderColor: "#008d07ff", bgColor: "#ffffffcc", isWanman: false },
|
||||
SPCL_Rapid: { label: "臨時快速", typeColor: "rgba(0, 81, 255, 1)", borderColor: "#0051ffff", bgColor: "#ffffffcc", isWanman: false },
|
||||
SPCL_EXP: { label: "臨時特急", typeColor: "#a52e2eff", borderColor: "#a52e2eff", bgColor: "#ffffffcc", isWanman: false },
|
||||
Party: { label: "団体臨時", typeColor: "#ff7300ff", borderColor: "#ff7300ff", bgColor: "#ffd0a9ff", isWanman: false },
|
||||
Freight: { label: "貨物", typeColor: "#00869ecc", borderColor: "#00869ecc", bgColor: "#c7c7c7cc", isWanman: false },
|
||||
Forwarding: { label: "回送", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
|
||||
Trial: { label: "試運転", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
|
||||
Construction: { label: "工事", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
|
||||
FreightForwarding: { label: "単機回送", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
|
||||
};
|
||||
@@ -39,8 +39,6 @@ const initialState = {
|
||||
setTrainMenu: (e) => {},
|
||||
updatePermission: false,
|
||||
setUpdatePermission: (e) => {},
|
||||
/** 各情報ソースの利用権限 */
|
||||
dataSourcePermission: { unyohub: false } as { unyohub: boolean },
|
||||
injectJavascript: "",
|
||||
};
|
||||
|
||||
@@ -64,23 +62,20 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
||||
const [stationMenu, setStationMenu] = useState<boolType>(undefined);
|
||||
const [LoadError, setLoadError] = useState(false);
|
||||
|
||||
//更新権限所有確認・情報ソース別利用権限(将来ロールが増えたらここに足す)
|
||||
//更新権限所有確認
|
||||
const [updatePermission, setUpdatePermission] = useState(false);
|
||||
const [dataSourcePermission, setDataSourcePermission] = useState<{ unyohub: boolean }>({ unyohub: false });
|
||||
useEffect(() => {
|
||||
if (!expoPushToken) return;
|
||||
fetch(
|
||||
"https://n8n.haruk.in/webhook/data-edit-permission?token=" + expoPushToken
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const role: string = res.data ?? "";
|
||||
setUpdatePermission(role === "administrator");
|
||||
setDataSourcePermission({
|
||||
unyohub: role === "administrator" || role === "unyoHubEditor",
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
if (res.data == true) {
|
||||
setUpdatePermission(true);
|
||||
} else {
|
||||
setUpdatePermission(false);
|
||||
}
|
||||
});
|
||||
}, [expoPushToken]);
|
||||
|
||||
//列車情報表示関連
|
||||
@@ -95,19 +90,15 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
||||
|
||||
//GUIデザインベース
|
||||
const [uiSetting, setUiSetting] = useState("tokyo");
|
||||
|
||||
//列車運用hub
|
||||
const [useUnyohubSetting, setUseUnyohubSetting] = useState("false");
|
||||
|
||||
//地図表示テキスト
|
||||
const injectJavascript = injectJavascriptData({
|
||||
const injectJavascript = injectJavascriptData(
|
||||
mapSwitch,
|
||||
iconSetting,
|
||||
stationMenu,
|
||||
trainMenu,
|
||||
uiSetting,
|
||||
useUnyohub: useUnyohubSetting,
|
||||
});
|
||||
uiSetting
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//列車アイコンスイッチ
|
||||
@@ -120,8 +111,6 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
||||
ASCore({ k: STORAGE_KEYS.TRAIN_SWITCH, s: setTrainMenu, d: "true", u: true });
|
||||
//GUIデザインベーススイッチ
|
||||
ASCore({ k: STORAGE_KEYS.UI_SETTING, s: setUiSetting, d: "tokyo", u: true });
|
||||
//列車運用hubスイッチ
|
||||
ASCore({ k: STORAGE_KEYS.USE_UNYOHUB, s: setUseUnyohubSetting, d: "false", u: true });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -147,7 +136,6 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
||||
setTrainMenu,
|
||||
updatePermission,
|
||||
setUpdatePermission,
|
||||
dataSourcePermission,
|
||||
injectJavascript,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { AS } from "../storageControl";
|
||||
import { STORAGE_KEYS } from "@/constants";
|
||||
import { API_ENDPOINTS } from "@/constants";
|
||||
import type { UnyohubResponse, UnyohubData } from "@/types/unyohub";
|
||||
|
||||
type UnyohubHook = {
|
||||
/** 列車運用hub使用設定 */
|
||||
useUnyohub: boolean;
|
||||
/** 列車運用hubデータ */
|
||||
unyohubData: UnyohubResponse;
|
||||
/** 指定した列番の運用情報を文字列で取得(後方互換) */
|
||||
getUnyohubByTrainNumber: (trainNumber: string) => string | null;
|
||||
/** 指定した列番に紐づく UnyohubData の配列を取得 */
|
||||
getUnyohubEntriesByTrainNumber: (trainNumber: string) => UnyohubData[];
|
||||
/** 列車運用hub使用設定を更新 */
|
||||
setUseUnyohub: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const useUnyohub = (): UnyohubHook => {
|
||||
const [useUnyohub, setUseUnyohubState] = useState(false);
|
||||
const [unyohubData, setUnyohubData] = useState<UnyohubResponse>([]);
|
||||
|
||||
// 初期読み込み
|
||||
useEffect(() => {
|
||||
AS.getItem(STORAGE_KEYS.USE_UNYOHUB).then((value) => {
|
||||
setUseUnyohubState(value === true || value === "true");
|
||||
});
|
||||
|
||||
AS.getItem(STORAGE_KEYS.UNYOHUB_DATA).then((value) => {
|
||||
if (value) {
|
||||
try {
|
||||
setUnyohubData(JSON.parse(value as string));
|
||||
} catch (e) {
|
||||
console.error("Failed to parse unyohub data", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
// データ更新処理
|
||||
useEffect(() => {
|
||||
if (!useUnyohub) return;
|
||||
|
||||
const fetchUnyohubData = async () => {
|
||||
try {
|
||||
// キャッシュバスティング用にタイムスタンプを追加
|
||||
const cacheBuster = '?_=' + Date.now();
|
||||
const response = await fetch(API_ENDPOINTS.UNYOHUB_DATA + cacheBuster);
|
||||
const data = await response.json();
|
||||
setUnyohubData(data);
|
||||
await AS.setItem(STORAGE_KEYS.UNYOHUB_DATA, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch unyohub data", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUnyohubData();
|
||||
|
||||
// 10分ごとにデータを更新
|
||||
const interval = setInterval(fetchUnyohubData, 10 * 60 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [useUnyohub]);
|
||||
|
||||
// 列番から運用番号を取得(複数ある場合はposition_forward順)
|
||||
const getUnyohubByTrainNumber = (trainNumber: string): string | null => {
|
||||
if (!useUnyohub || unyohubData.length === 0) return null;
|
||||
|
||||
const foundUnyos: Array<{ formations: string; position_forward: number; position_rear: number }> = [];
|
||||
|
||||
for (const unyo of unyohubData) {
|
||||
if (!unyo.trains) continue;
|
||||
const found = unyo.trains.find(train => train.train_number === trainNumber);
|
||||
if (found) {
|
||||
foundUnyos.push({
|
||||
formations: unyo.formations,
|
||||
position_forward: found.position_forward,
|
||||
position_rear: found.position_rear,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (foundUnyos.length === 0) return null;
|
||||
|
||||
// position_forward順にソート
|
||||
foundUnyos.sort((a, b) => a.position_forward - b.position_forward);
|
||||
|
||||
// 「運用番号(編成位置)」の形式で結合
|
||||
return foundUnyos
|
||||
.map(u => `${u.formations}(${u.position_forward}-${u.position_rear})`)
|
||||
.join(', ');
|
||||
};
|
||||
|
||||
// 列番に紐づく UnyohubData エントリをすべて取得
|
||||
const getUnyohubEntriesByTrainNumber = (trainNumber: string): UnyohubData[] => {
|
||||
if (!useUnyohub || unyohubData.length === 0) return [];
|
||||
return unyohubData.filter(
|
||||
(unyo) => unyo.trains?.some((t) => t.train_number === trainNumber)
|
||||
);
|
||||
};
|
||||
|
||||
// 設定を更新
|
||||
const setUseUnyohub = (value: boolean) => {
|
||||
setUseUnyohubState(value);
|
||||
AS.setItem(STORAGE_KEYS.USE_UNYOHUB, value.toString());
|
||||
};
|
||||
|
||||
return {
|
||||
useUnyohub,
|
||||
unyohubData,
|
||||
getUnyohubByTrainNumber,
|
||||
getUnyohubEntriesByTrainNumber,
|
||||
setUseUnyohub,
|
||||
};
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 列車運用hub APIのデータ型定義
|
||||
*/
|
||||
|
||||
export type UnyohubTrain = {
|
||||
train_number: string;
|
||||
line_id: string;
|
||||
first_departure_time: string;
|
||||
final_arrival_time: string;
|
||||
starting_station: string;
|
||||
terminal_station: string;
|
||||
position_forward: number;
|
||||
position_rear: number;
|
||||
direction: string;
|
||||
};
|
||||
|
||||
export type UnyohubData = {
|
||||
formations: string;
|
||||
posts_count: number;
|
||||
from_beginner: boolean;
|
||||
trains: UnyohubTrain[];
|
||||
starting_location: string;
|
||||
starting_track: string;
|
||||
starting_time: string;
|
||||
terminal_location: string;
|
||||
terminal_track: string;
|
||||
ending_time: string;
|
||||
car_count: number;
|
||||
min_car_count: number;
|
||||
max_car_count: number;
|
||||
main_color: string;
|
||||
comment: string | null;
|
||||
};
|
||||
|
||||
export type UnyohubResponse = UnyohubData[];
|
||||
@@ -18,13 +18,13 @@ const SE_MAPPING: Record<string, SeStringResult> = {
|
||||
"頃編": ["頃", "community"],
|
||||
// 運休系
|
||||
"休編": ["運休", "community"], // 後方互換性のため残す
|
||||
"休発": ["出発", "normal"],
|
||||
"休着": ["到着", "normal"],
|
||||
"休発編": ["出発", "community"],
|
||||
"休着編": ["到着", "community"],
|
||||
"通休編": ["通過", "community"],
|
||||
"通発休編": ["出発", "community"],
|
||||
"通着休編": ["到着", "community"],
|
||||
"休発": ["出発(運休)", "normal"],
|
||||
"休着": ["到着(運休)", "normal"],
|
||||
"休発編": ["出発(運休)", "community"],
|
||||
"休着編": ["到着(運休)", "community"],
|
||||
"通休編": ["通過(運休)", "community"],
|
||||
"通発休編": ["出発(運休)", "community"],
|
||||
"通着休編": ["到着(運休)", "community"],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user