Update signage view configuration
This commit is contained in:
@@ -47,6 +47,12 @@
|
||||
},
|
||||
"production7.0": {
|
||||
"channel": "familymart"
|
||||
},
|
||||
"beta7.1": {
|
||||
"channel": "gmarket"
|
||||
},
|
||||
"production7.1": {
|
||||
"channel": "geekbuying"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
|
||||
+175
-31
@@ -13,6 +13,7 @@ import {
|
||||
} from "react-native";
|
||||
import * as FileSystem from "expo-file-system/legacy";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import Share from "react-native-share";
|
||||
import {
|
||||
activateKeepAwakeAsync,
|
||||
deactivateKeepAwake,
|
||||
@@ -30,6 +31,17 @@ import lineColorList from "./assets/originData/lineColorList";
|
||||
|
||||
const KEEP_AWAKE_TAG = "operation-info-landscape";
|
||||
|
||||
type OperationCaptureImagePayload = {
|
||||
dataUrl?: string;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
type OperationCaptureBatchState = {
|
||||
total: number;
|
||||
files: Array<string | undefined>;
|
||||
shared: boolean;
|
||||
};
|
||||
|
||||
const isActivityUnavailableError = (error: unknown) =>
|
||||
String(error).includes("The current activity is no longer available");
|
||||
|
||||
@@ -212,10 +224,12 @@ const buildOperationPageScript = (layout: {
|
||||
}
|
||||
var compat = getSignageCompatibilityProfile();
|
||||
var rules = [
|
||||
'.jrs-capture-page-wrap{display:block !important;text-align:right !important;margin:12px 0 10px !important;}',
|
||||
'.jrs-capture-page-wrap{display:flex !important;flex-wrap:wrap !important;justify-content:flex-end !important;gap:8px !important;margin:12px 0 10px !important;}',
|
||||
'.jrs-capture-page-link{display:inline-block !important;background:#0076a8 !important;color:#fff !important;padding:11px 16px !important;border-radius:999px !important;font-size:12px !important;font-weight:800 !important;line-height:1.2 !important;text-decoration:none !important;box-shadow:0 4px 12px rgba(0,118,168,.22) !important;position:relative !important;z-index:9999 !important;}',
|
||||
'.jrs-capture-page-link:visited{color:#fff !important;}',
|
||||
'.jrs-capture-page-link:active{opacity:.9 !important;transform:translateY(1px) !important;}',
|
||||
'.jrs-capture-page-link.is-secondary{background:#ffffff !important;color:#0076a8 !important;border:1px solid #0099CB !important;box-shadow:none !important;}',
|
||||
'.jrs-capture-page-link.is-secondary:visited{color:#0076a8 !important;}',
|
||||
'.jrs-capture-wrap{display:block !important;text-align:right !important;margin:12px 0 8px !important;}',
|
||||
'.jrs-capture-link{display:inline-block !important;background:#0a84ff !important;color:#fff !important;padding:10px 14px !important;border-radius:999px !important;font-size:12px !important;font-weight:700 !important;line-height:1.2 !important;text-decoration:none !important;box-shadow:0 4px 12px rgba(10,132,255,.25) !important;position:relative !important;z-index:9999 !important;}',
|
||||
'.jrs-capture-link:visited{color:#fff !important;}',
|
||||
@@ -864,6 +878,40 @@ const buildOperationPageScript = (layout: {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderAllItemsToSeparateImages(items, fileNameBase) {
|
||||
try {
|
||||
if (!items || !items.length) {
|
||||
postMessage({ error: true });
|
||||
return;
|
||||
}
|
||||
|
||||
var batchId = 'operation-info-batch-' + Date.now() + '-' + Math.floor(Math.random() * 100000);
|
||||
for (var index = 0; index < items.length; index += 1) {
|
||||
var item = items[index];
|
||||
var capture = await buildItemCaptureImageData(
|
||||
item.title,
|
||||
item.subTitle,
|
||||
item.updatedAt,
|
||||
item.blocks || [],
|
||||
fileNameBase + '-' + (item.infoId || (index + 1))
|
||||
);
|
||||
if (!capture) {
|
||||
postMessage({ error: true });
|
||||
return;
|
||||
}
|
||||
postMessage({
|
||||
batchId: batchId,
|
||||
batchIndex: index,
|
||||
batchTotal: items.length,
|
||||
dataUrl: capture.dataUrl,
|
||||
fileName: capture.fileName
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
postMessage({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function renderAllItemsToImage(items, statusText, fileNameBase) {
|
||||
try {
|
||||
if (!items || !items.length) {
|
||||
@@ -1065,29 +1113,50 @@ const buildOperationPageScript = (layout: {
|
||||
if (!infoRoot) return;
|
||||
|
||||
var items = getOperationItems();
|
||||
var existingWrap = q('.jrs-capture-page-wrap');
|
||||
var wrap = q('.jrs-capture-page-wrap');
|
||||
if (!items.length) {
|
||||
if (existingWrap && existingWrap.parentNode) existingWrap.parentNode.removeChild(existingWrap);
|
||||
if (wrap && wrap.parentNode) wrap.parentNode.removeChild(wrap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingWrap) return;
|
||||
if (!wrap) {
|
||||
wrap = document.createElement('div');
|
||||
wrap.className = 'jrs-capture-page-wrap';
|
||||
if (infoRoot.parentNode) {
|
||||
infoRoot.parentNode.insertBefore(wrap, infoRoot);
|
||||
}
|
||||
}
|
||||
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'jrs-capture-page-wrap';
|
||||
var link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.className = 'jrs-capture-page-link';
|
||||
link.textContent = '表示中の運行情報をすべて切り出す';
|
||||
link.onclick = function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
renderAllItemsToImage(getOperationItems(), getStatusText(q('.delay_status')), 'operation-info-all-' + Date.now());
|
||||
};
|
||||
wrap.appendChild(link);
|
||||
var allLink = q('.jrs-capture-page-link.is-all', wrap);
|
||||
if (!allLink) {
|
||||
allLink = document.createElement('a');
|
||||
allLink.href = '#';
|
||||
allLink.className = 'jrs-capture-page-link is-all';
|
||||
allLink.textContent = '表示中の運行情報をすべて切り出す';
|
||||
allLink.onclick = function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
renderAllItemsToImage(getOperationItems(), getStatusText(q('.delay_status')), 'operation-info-all-' + Date.now());
|
||||
};
|
||||
wrap.appendChild(allLink);
|
||||
}
|
||||
|
||||
if (infoRoot.parentNode) {
|
||||
infoRoot.parentNode.insertBefore(wrap, infoRoot);
|
||||
var batchLink = q('.jrs-capture-page-link.is-batch', wrap);
|
||||
if (items.length > 1) {
|
||||
if (!batchLink) {
|
||||
batchLink = document.createElement('a');
|
||||
batchLink.href = '#';
|
||||
batchLink.className = 'jrs-capture-page-link is-secondary is-batch';
|
||||
batchLink.textContent = '項目ごとに複数画像で共有';
|
||||
batchLink.onclick = function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
renderAllItemsToSeparateImages(getOperationItems(), 'operation-info-share-' + Date.now());
|
||||
};
|
||||
wrap.appendChild(batchLink);
|
||||
}
|
||||
} else if (batchLink && batchLink.parentNode) {
|
||||
batchLink.parentNode.removeChild(batchLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1790,6 +1859,7 @@ export default function tndView() {
|
||||
const webViewKey = [remountKey, layoutKey].join("-");
|
||||
const reloadButtonKey = [layoutKey, Math.round(reloadTop), Math.round(reloadRight)].join("-");
|
||||
const bgAtRef = useRef<number | null>(null);
|
||||
const operationCaptureBatchesRef = useRef<Record<string, OperationCaptureBatchState>>({});
|
||||
const [operationLandscapeEnabled, setOperationLandscapeEnabled] = React.useState(false);
|
||||
const [operationCaptureEnabled, setOperationCaptureEnabled] = React.useState(false);
|
||||
const operationLineBadges = useMemo(() => {
|
||||
@@ -1991,35 +2061,105 @@ export default function tndView() {
|
||||
return clearReloadFadeTimer;
|
||||
}, [clearReloadFadeTimer, isLandscape, reloadOpacity, scheduleReloadButtonFade]);
|
||||
|
||||
const saveOperationCapture = useCallback(async (dataUrl?: string, fileName?: string) => {
|
||||
const writeOperationCaptureFile = useCallback(async (dataUrl?: string, fileName?: string) => {
|
||||
if (!dataUrl?.startsWith("data:image/png;base64,")) {
|
||||
Alert.alert("切り出し失敗", "画像データの生成に失敗しました。");
|
||||
throw new Error("invalid-image-data");
|
||||
}
|
||||
if (!FileSystem.cacheDirectory) {
|
||||
throw new Error("cache-directory-unavailable");
|
||||
}
|
||||
|
||||
const base64 = dataUrl.replace("data:image/png;base64,", "");
|
||||
const uniquePrefix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const safeFileName = (fileName ?? `operation-info-${uniquePrefix}.png`).replace(/[^\w.-]+/g, "_");
|
||||
const fileUri = `${FileSystem.cacheDirectory}${uniquePrefix}-${safeFileName}`;
|
||||
|
||||
await FileSystem.writeAsStringAsync(fileUri, base64, {
|
||||
encoding: FileSystem.EncodingType.Base64,
|
||||
});
|
||||
return fileUri;
|
||||
}, []);
|
||||
|
||||
const shareOperationCaptureFiles = useCallback(async (fileUris: string[]) => {
|
||||
if (!fileUris.length) {
|
||||
Alert.alert("切り出し失敗", "共有する画像が見つかりませんでした。");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (fileUris.length === 1) {
|
||||
const sharingAvailable = await Sharing.isAvailableAsync();
|
||||
if (!sharingAvailable) {
|
||||
Alert.alert("共有不可", "この端末では画像共有を利用できません。");
|
||||
return;
|
||||
}
|
||||
|
||||
const base64 = dataUrl.replace("data:image/png;base64,", "");
|
||||
const safeFileName = (fileName ?? `operation-info-${Date.now()}.png`).replace(/[^\w.-]+/g, "_");
|
||||
const fileUri = `${FileSystem.cacheDirectory}${safeFileName}`;
|
||||
|
||||
await FileSystem.writeAsStringAsync(fileUri, base64, {
|
||||
encoding: FileSystem.EncodingType.Base64,
|
||||
});
|
||||
await Sharing.shareAsync(fileUri, {
|
||||
await Sharing.shareAsync(fileUris[0], {
|
||||
mimeType: "image/png",
|
||||
dialogTitle: "運行情報を共有",
|
||||
});
|
||||
} catch {
|
||||
Alert.alert("切り出し失敗", "画像の保存または共有に失敗しました。");
|
||||
return;
|
||||
}
|
||||
|
||||
await Share.open({
|
||||
urls: fileUris,
|
||||
type: "image/png",
|
||||
title: "運行情報を共有",
|
||||
failOnCancel: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const saveOperationCapture = useCallback(
|
||||
async (dataUrl?: string, fileName?: string) => {
|
||||
try {
|
||||
const fileUri = await writeOperationCaptureFile(dataUrl, fileName);
|
||||
await shareOperationCaptureFiles([fileUri]);
|
||||
} catch {
|
||||
Alert.alert("切り出し失敗", "画像の保存または共有に失敗しました。");
|
||||
}
|
||||
},
|
||||
[shareOperationCaptureFiles, writeOperationCaptureFile]
|
||||
);
|
||||
|
||||
const saveOperationCaptureBatchItem = useCallback(
|
||||
async (payload: OperationCaptureImagePayload & { batchId?: string; batchIndex?: number; batchTotal?: number }) => {
|
||||
const batchId = typeof payload.batchId === "string" ? payload.batchId : "";
|
||||
const batchTotal = Number(payload.batchTotal ?? 0);
|
||||
const batchIndex = Number(payload.batchIndex ?? 0);
|
||||
|
||||
if (!batchId || batchTotal < 1 || batchIndex < 0 || batchIndex >= batchTotal) {
|
||||
await saveOperationCapture(payload.dataUrl, payload.fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileUri = await writeOperationCaptureFile(payload.dataUrl, payload.fileName);
|
||||
const batches = operationCaptureBatchesRef.current;
|
||||
const entry = batches[batchId] ?? {
|
||||
total: batchTotal,
|
||||
files: new Array(batchTotal),
|
||||
shared: false,
|
||||
};
|
||||
|
||||
entry.total = batchTotal;
|
||||
if (!entry.files[batchIndex]) {
|
||||
entry.files[batchIndex] = fileUri;
|
||||
}
|
||||
batches[batchId] = entry;
|
||||
|
||||
const readyFiles = entry.files.filter((uri): uri is string => !!uri);
|
||||
if (!entry.shared && readyFiles.length === entry.total) {
|
||||
entry.shared = true;
|
||||
delete batches[batchId];
|
||||
await shareOperationCaptureFiles(readyFiles);
|
||||
}
|
||||
} catch {
|
||||
delete operationCaptureBatchesRef.current[batchId];
|
||||
Alert.alert("切り出し失敗", "画像の保存または共有に失敗しました。");
|
||||
}
|
||||
},
|
||||
[saveOperationCapture, shareOperationCaptureFiles, writeOperationCaptureFile]
|
||||
);
|
||||
|
||||
const goToTrainMenu = () => {
|
||||
if (Platform.OS === "web") {
|
||||
Linking.openURL("https://www.jr-shikoku.co.jp/info/");
|
||||
@@ -2091,6 +2231,10 @@ export default function tndView() {
|
||||
Alert.alert("切り出し失敗", "項目画像の生成に失敗しました。");
|
||||
return;
|
||||
}
|
||||
if (payload.batchId) {
|
||||
await saveOperationCaptureBatchItem(payload);
|
||||
return;
|
||||
}
|
||||
await saveOperationCapture(payload.dataUrl, payload.fileName);
|
||||
} catch {
|
||||
// __ping 以外の文字列メッセージは無視
|
||||
|
||||
Reference in New Issue
Block a user