/* panels.jsx — 各コンポーネントのドロワー(v4) */
const EP = window.UWEngine;
const cl = (o) => JSON.parse(JSON.stringify(o));
/* カード右のボタン:選択中は「選択中」、未選択は ラベル(+ 追加/選択) */
const actA = (on, fn, lbl, off) => ({ label: on ? off ? T("外す") : T("選択中") : lbl || T("+ 追加"), ghost: on, onClick: on && !off ? () => {} : fn });
const actS = (on, fn) => actA(on, fn, T("選択"));
const upd = (st, set, path, v) => {const s = cl(st);let o = s;const k = path.split('.');k.slice(0, -1).forEach((x) => o = o[x]);o[k[k.length - 1]] = v;set(s);};
/* 選択パネルを開いたときの最初のスクロール位置。
* パネルは項目を切り替えても同じ .dw-b を使い回すので、前の項目のスクロール位置が残る。開くたびに位置を決め直す。
* ref あり … その要素を .dw-b の中央に / ref なし(または要素なし)… 一番上 */
function useInitialScroll(ref) {
React.useLayoutEffect(() => {
const el = ref && ref.current,box = el ? el.closest('.dw-b') : document.querySelector('.dw-b');
if (!box) return;
if (!el) {box.scrollTop = 0;return;}
const b = box.getBoundingClientRect(),r = el.getBoundingClientRect();
box.scrollTop = box.scrollTop + (r.top - b.top) - (box.clientHeight - r.height) / 2; // 範囲外は自動で端に止まる
}, []);
}
/* ---------------- カメラ ---------------- */
function PanelCamera({ st, set, go, notify }) {
const tg = st.system === 'tg',cams = EP.camerasFor(st.system),cur = tg ? st.tgCamera : st.mlCamera;
const brands = [...new Set(cams.map((c) => c.brand))];
const pick = (id) => {
if (tg) {const s = { ...st, tgCamera: id };set(s);go('camera', null, s);return;}
const r = EP.onMirrorlessCameraChange(st, id);
if (r.notice === 'lensUnsupported') notify(T("選択中のレンズは ") + EP.CAM[id].name_ja + T(" に対応していません。レンズを選び直してください"));else
if (r.notice === 'port') notify(T("レンズはそのまま使えます。ポート構成を選んでください"));
go('camera', r.next, r.st);
};
return <>
{T("カメラを選ぶと、対応するハウジングが自動で決まります。")}
{brands.map((b) => {b}
{cams.filter((c) => c.brand === b).map((c) => {
const hs = EP.housingsForCamera(c.camera_id);
return
pick(c.camera_id)} act={actS(cur === c.camera_id, () => pick(c.camera_id))} desc={T("ハウジング:") + hs.map((h) => h.name_ja).join(' / ')} noWeight />;
})} )}
>;
}
/* ---------------- ミラーレス:レンズ ---------------- */
function PanelLens({ st, set, res, go }) {
const h = res.housing;if (!h) return {T("先にカメラを選択してください。")}
;
const lensIds = EP.lensesForHousing(h),lens = lensIds.includes(st.ml.lens) ? st.ml.lens : '';
const ls = lens ? EP.setsFor(h, lens) : [],cur = ls.find((s) => s.set_id === st.ml.setId) || null;
const [q, setQ] = React.useState(''),[edit, setEdit] = React.useState(!lens);
const shown = lensIds.filter((id) => !q || EP.lensName(id).toLowerCase().includes(q.toLowerCase()));
const pickLens = (id) => {uwTrack(id, 'select', 'mirrorless');const s = cl(st);s.ml.lens = id;const x = EP.setsFor(h, id);s.ml.setId = x.length === 1 ? x[0].set_id : '';s.ml.gears = 'auto';set(s);setEdit(false);setQ('');};
const gears = cur ? EP.gearsOf(cur) : [],selG = cur ? st.ml.gears === 'auto' ? EP.autoGears(cur, lens) : st.ml.gears : [];
const togG = (g) => {const s = cl(st);const now = selG.includes(g) ? selG.filter((x) => x !== g) : [...selG, g];s.ml.gears = now;set(s);};
const pickPort = (s) => {if (cur && s.set_id === cur.set_id) return;const x = cl(st);x.ml.setId = s.set_id;x.ml.gears = 'auto';set(x);};
return <>
{T("① レンズ")} {h.name_ja} {T("対応")} {lensIds.length} {T("本")}
{lens && !edit ?
setEdit(true)} acts={} /> :
<> setQ(e.target.value)} />
{shown.map((id) => )}
>}
{lens && <>
{T("② ポート構成")} {T("システムビルダーのセット定義")}
{!cur && ls.length > 1 && {T("ポート構成を選んでください")}
}
{ls.map((s) =>
pickPort(s)} act={actS(!!(cur && s.set_id === cur.set_id), () => pickPort(s))}
desc={{EP.portParts(s).map((p, i) => - {EP.info(p).name}
)}{EP.gearsOf(s).map((g) => - {EP.gearLabel(g)}
)}
} />
)}
{cur && gears.length > 0 && <>
{T("③ ギア")} {EP.isZoomLens(lens) ? T("ズームギアは必須") : T("必要なものだけ選択")}
{EP.isZoomLens(lens) ? T("ズームレンズの場合、ズームギアは水中でレンズのズーム機能を利用するために必須です。") : T("単焦点レンズはAFで使う方が多く、通常ギアは不要です(機種によってはギアを付けるとAFが効きません)。")}
{gears.map((g) => togG(g)} act={actA(selG.includes(g), () => togG(g), T("選択"), true)} />)}
>}
{T("システムビルダーで構成を確認")}{T("ポート・リング・ギアの詳細はシステムビルダーを参照")}
{T("→")}
>}
>;
}
/* ---------------- ミラーレス:コンバーター ---------------- */
function PanelConverter({ st, set, res, go }) {
const convs = EP.convertersFor(res.housing);
const label = (c) => c.category_id === 'converter_electric' ? T("電気接続(現在は選択できません)") : /UWT/.test(c.name_ja) ? T("HSS/TTL対応") : /MDX/.test(c.name_ja) ? T("TTL対応") : null;
// 最初に開いたとき、おすすめ(最初の「おすすめ」カード)を画面の中央に表示する
const recId = (convs.find((c) => c.category_id !== 'converter_electric' && /UWT/.test(c.name_ja)) || {}).product_id;
const recRef = React.useRef(null);
useInitialScroll(recId ? recRef : null);
return <>
{T("水中ストロボを利用するために必要な製品です。")}
{convs.map((c) => {const el = c.category_id === 'converter_electric';
return {const s = cl(st);s.ml.converter = c.product_id;go('conv', null, s);})}
onClick={() => {const s = cl(st);s.ml.converter = c.product_id;go('conv', null, s);}} />
;})}
{const s = cl(st);s.ml.converter = '';go('conv', null, s);}} />
>;
}
/* ---------------- TG:RIG ---------------- */
function PanelRig({ st, set, go, notify }) {
const t = st.tg,u = (k, v) => upd(st, set, 'tg.' + k, v);
const on = t.rig !== 'none',single = t.rig === 'single',frame = t.rig === 'frame';
/* グリップは「フレーム」とは別の選択項目。tg.grips はシングルグリップセット付属の1本を含む総本数(状態の持ち方は従来どおり)。
* RSスタンダードグリップ(独立)… シングルグリップセットは付属1本に追加で最大1本(合計2本)/メインフレームは最大2本
* RSマルチアングルグリップ(独立)… メインフレーム選択時のみ。スタンダードグリップとは併用不可(片方を選ぶともう片方を自動で解除) */
const bundled = single ? 1 : 0,stdMax = single ? 1 : 2;
const stdQty = on && !t.multi ? Math.max(0, t.grips - bundled) : 0;
const ballCap = (grips, multi) => multi ? 2 : Math.max(0, 2 - grips);
const maxBall = ballCap(t.grips, t.multi);
const setRig = (r) => {
if (r === t.rig) return; // 選択中のカードを押し直してもグリップ・ボールベースの選択は変えない
const s = cl(st),prev = t.rig,had = prev === 'frame' ? (t.multi ? 2 : t.grips) : prev === 'single' ? t.grips - 1 : 0;
s.tg.rig = r;
if (r === 'none') {s.tg.grips = 0;s.tg.multi = false;s.tg.ballBase = 0;s.tg.acc = [];s.tg.top = 'none';}
if (r === 'single') { // 追加分は1本までに切り詰め、マルチアングルは解除
s.tg.multi = false;s.tg.grips = 1 + Math.min(1, prev === 'frame' && !t.multi ? t.grips : prev === 'single' ? t.grips - 1 : 0);
s.tg.ballBase = Math.min(s.tg.ballBase, ballCap(s.tg.grips, false));
if (prev === 'frame' && (t.multi || had > 1)) notify && notify(T("RSシングルグリップセットに合わせてグリップを調整しました"));
}
if (r === 'frame') {s.tg.multi = false;s.tg.grips = 0;}
set(s);
};
const setStd = (n) => {
const s = cl(st),wasMulti = s.tg.multi;
s.tg.multi = false;s.tg.grips = bundled + n;s.tg.ballBase = Math.min(s.tg.ballBase, ballCap(s.tg.grips, false));
set(s);
if (wasMulti && n > 0) notify && notify(T("RSスタンダードグリップを選んだため、RSマルチアングルグリップを解除しました"));
};
const setMulti = (v) => {
const s = cl(st),hadStd = !t.multi && t.grips - bundled > 0;
s.tg.multi = v;if (v) s.tg.grips = 0;
set(s);
if (v && hadStd) notify && notify(T("RSマルチアングルグリップを選んだため、RSスタンダードグリップを解除しました"));
};
const acc = EP.DB.uw_components.filter((c) => c.category === 'grip_accessory');
return <>
{T("フレーム")}
setRig('none')} />
setRig('single')} act={actA(t.rig === 'single', () => setRig('single'))} desc={T("メインフレーム+グリップ1本")} />
setRig('frame')} act={actA(t.rig === 'frame', () => setRig('frame'))} />
{T("グリップ")}
0} onClick={() => setStd(stdQty > 0 ? 0 : 1)} act={actA(stdQty > 0, () => setStd(stdQty > 0 ? 0 : 1), null, true)}
desc={!on ? T("フレームを選ぶと追加できます") : single ? T("シングルグリップセット付属の1本に追加できます(最大1本)") : T("最大 ") + stdMax + T(" 本")}>
{stdQty > 0 && }
setMulti(!t.multi)} act={actA(t.multi && frame, () => setMulti(!t.multi), null, true)}
desc={!frame ? T("RSメインフレームを選ぶと使えます") : T("スタンダードグリップとは併用できません")} />
{T("ボールベース")}
0} onClick={() => u('ballBase', t.ballBase > 0 ? 0 : 1)} act={actA(t.ballBase > 0, () => u('ballBase', t.ballBase > 0 ? 0 : 1), null, true)}
desc={!on ? T("フレームを選ぶと追加できます") : maxBall === 0 ? T("グリップ2本の場合は追加できません") : T("最大 ") + maxBall + T(" 個")}>
{t.ballBase > 0 && u('ballBase', v)} />}
{T("アクセサリー")}
{acc.map((c) => {const has = t.acc.includes(c.component_id),tg = () => u('acc', has ? t.acc.filter((x) => x !== c.component_id) : [...t.acc, c.component_id]);
return ;})}
>;
}
/* ---------------- TG:受け側(リングライト・レンズで共通) ---------------- */
function Receiver({ st, set }) {
const t = st.tg,hasBY = (t.lenses || []).includes(EP.BY_INCLUDED);
return <>
{T("受け側(PT-059前面)")} {T("リングライトとレンズで共通・1つだけ")}
upd(st, set, 'tg.receiver', v)}
options={EP.RECEIVERS.map((r) => ({ v: r.id, l: r.label + (r.rec ? T("(おすすめ)") : ''), x: hasBY && r.id === 'screw' }))} />
{T("バヨネットアダプターは自動で追加します。")}
>;
}
/* 前面(リングライト+レンズ)は合計2つまで:正面に1つ、上部の RSバヨネットレンズホルダーに1つ。
* 2つ目を選んだら上部をホルダーにする(RIGがないときは2つ目を選べない) */
const frontCount = (t, ring, lenses) => (ring ? 1 : 0) + lenses.length;
/* RSバヨネットレンズホルダーは自動では追加しない。2つ目を選んだときは、足りないものを警告(engine.js の build)で知らせる。
* 1つ以下に戻り、RIGなしなら残っているホルダーを外す */
const withHolder = (s) => {const n = frontCount(s.tg, s.tg.ringLight, s.tg.lenses || []);
if (n < 2 && s.tg.top === 'holder' && s.tg.rig === 'none') s.tg.top = 'none';return false;};
function FrontNote({ st }) {
const t = st.tg,lay = EP.tgFrontLayout(t),n = frontCount(t, t.ringLight, t.lenses || []);
const nm = (id) => id ? EP.info(id).name : T("-");
return
{T("正面:")}{nm(lay.front)}
{T("上部(RSバヨネットレンズホルダー):")}{nm(lay.top)}
{n >= 2 ? T("リングライト・レンズは合計2つまでです。リングライトは正面、レンズは上部に付きます。") : T("1つ目は正面、2つ目は上部の RSバヨネットレンズホルダーに付きます(リングライトは常に正面)。")}
;
}
function PanelRing({ st, set, go, notify }) {
const t = st.tg,lenses = t.lenses || [];
const pick = (id) => {
const s = cl(st);s.tg.ringLight = id;
withHolder(s);
if (id && frontCount(s.tg, id, lenses) >= 2) notify && notify(T("リングライトとレンズを併用するには、RSバヨネットレンズホルダーが必要です(警告を確認してください)"));
set(s);
};
// レンズ2つ選択中は追加できない。レンズと併用するときは M52ねじ込みのリングライトは選べない(受け側をバヨネットに)
const ringFull = !t.ringLight && lenses.length >= EP.MAX_FRONTS,ringScrew = lenses.length >= 1 && t.receiver === 'screw',ringDis = ringFull || ringScrew;
return <>
{T("リングライト")}
{(t.ringLight || lenses.length > 0) && }
{ringFull && {T("レンズを2つ選択中のため、リングライトは追加できません(合計2つまで)")}
}
{!ringFull && ringScrew && {T("レンズと併用する場合、M52ねじ込みのリングライトは選べません。受け側をバヨネットにしてください")}
}
{!ringDis && lenses.length === 1 && !t.ringLight && {T("追加すると、リングライトを正面、レンズを上部の RSバヨネットレンズホルダーに付けます(RIG SYSTEM と RSバヨネットレンズホルダーを別途追加してください)。")}
}
pick('')} />
{EP.RING_LIGHTS.map((id) => pick(id)} act={actA(t.ringLight === id, () => pick(t.ringLight === id ? '' : id), null, true)}
desc={t.receiver && t.receiver !== 'screw' ? T("+ ") + EP.info(EP.ADAPTER[id]).name : t.receiver === 'screw' ? T("M52ねじ込み。ストロボ併用時は MG01+光ファイバーケーブルが必要です") : null} />)}
>;
}
function PanelTgLens({ st, set, go, notify }) {
const t = st.tg,lenses = t.lenses || [],n = frontCount(t, t.ringLight, lenses),full = n >= EP.MAX_FRONTS,needRig = false;
const tog = (id) => {
const s = cl(st),on = lenses.includes(id);
if (!on && (full || needRig)) return;
s.tg.lenses = on ? lenses.filter((x) => x !== id) : [...lenses, id];
if (id === EP.BY_INCLUDED && !on && s.tg.receiver !== 'RS_BAYBASE') s.tg.receiver = EP.BY_BASE;
withHolder(s);
if (!on && frontCount(s.tg, s.tg.ringLight, s.tg.lenses) >= 2) notify && notify(T("2つ目のリングライト・レンズには、上部の RSバヨネットレンズホルダーが必要です(警告を確認してください)"));
set(s);
};
return <>
{T("レンズ")} {t.ringLight ? T("リングライトと合わせて2つまで") : T("2つまで(正面+上部)")}
{n > 0 && }
{full && {T("リングライト・レンズは合計2つまでです。入れ替える場合は、選択中のものを外してください")}
}
{!full && needRig && {T("2つ目を付けるには、RIG(RSメインフレーム)と上部の RSバヨネットレンズホルダーが必要です")}
}
{EP.FRONT_LENSES.map((id) => tog(id)} act={actA(lenses.includes(id), () => tog(id), null, true)}
desc={id === EP.BY_INCLUDED ? EP.EN ? 'With SEA&SEA Bayonet Mount Base M52' : T("FIXマウントベースM52とのセット") : t.receiver && t.receiver !== 'screw' ? T("+ ") + EP.info(EP.ADAPTER[id]).name : T("M52ねじ込み")} />)}
>;
}
/* ---------------- TG:シューベース ---------------- */
function PanelTop({ st, set, go }) {
const t = st.tg,frame = t.rig !== 'none',free = !frame || t.top === 'shoemount';
const conv = EP.DB.uw_components.filter((c) => c.category === 'shoe_ball_base');
// 上にストロボ・ライトを付けられる状態になったら「ストロボ」へ戻る(RSボールベース、またはシューベースの部品を選んだとき)
const setTop = (v) => {const s = cl(st);s.tg.top = v;if (v !== 'shoemount' && frame || v === 'holder') s.tg.shoeConv = 'none';if (v === 'ballbase' && go) go('top', 'strobe', s);else set(s);};
const setConv = (id) => {const s = cl(st);s.tg.shoeConv = id;if (id !== 'none' && go) go('top', 'strobe', s);else set(s);};
const T1 = (id, v, desc) => setTop(v)} act={actA(t.top === v, () => setTop(v))} desc={desc} />;
const two = frontCount(t, t.ringLight, t.lenses || []) >= 2; // リングライト・レンズを2つ使う → 上部は RSバヨネットレンズホルダーが必要
return <>
{two && {T("リングライト・レンズを2つ使う場合、上部は RSバヨネットレンズホルダー を選びます。シューベース(RSシューマウント+アクセサリーシューボールベースII/PTSA-02、PTSA-02単体)やRSボールベースとは、どちらか一方しか使えません。")}
}
{(frame || t.top === 'holder') && <>
{T("① 上部に付けるもの")}{T("どれか1つ")}
setTop('none')} />
{frame && T1('RS_SHOEMOUNT', 'shoemount', T("シューベースを復活"))}
{T1('RS_BAYHOLDER', 'holder', T("2つ目のレンズを保持"))}
{frame && T1('RS_BALLBASE', 'ballbase', T("上部にボールベース(クランプで取付)"))}
>}
{t.top === 'ballbase' ? {T("上部はRSボールベースのみです。ストロボ・ライトの「+ 上に追加」で取り付けてください。")}
: <>
{frame ? T("② ") : ''}{T("シューベースに付けるもの")}
{!free && {T("RSシューマウントを選ぶと選択できます")}
}
setConv('none')} />
{conv.map((c) => setConv(c.component_id)}
act={actA(t.shoeConv === c.component_id, () => setConv(c.component_id))} desc={c.provides_ys_base ? T("YSベース") : T("ボールベース")} />)}
>}
>;
}
/* ---------------- ストロボ/ライト ---------------- */
function LightPanel({ st, set, kind, go, topHint }) {
const s0 = EP.normalize(st),M = s0.mounts;
const fams = EP.lightingFamilies(st.system, kind);
const isK = (pid) => pid && EP.isStrobe(pid) === (kind === 'strobe');
const used = EP.MOUNTS.filter((k) => isK(M[k].pid));
const strobes = EP.strobeCount(s0),topOk = st.system === 'mirrorless' || st.tg.shoeConv !== 'none' || st.tg.rig !== 'none' && st.tg.top === 'ballbase';
const total = EP.MOUNTS.filter((k) => M[k].pid).length,full = total >= EP.MAX_STROBES; // ストロボ・ライトは合計3つまで
const recF = EP.recommendedStrobe(st); // おすすめのストロボ(ミラーレス・ワイドレンズ選択時=YS-D130R、TGでそれ以外=CUBee)
const addFam = (f, pos) => {uwTrack(EP.defaultMember(f), pos === 'T' ? 'add_top' : 'add', st.system);set(EP.addLighting(st, EP.defaultMember(f), pos));};
const setPid = (k, pid) => {const s = EP.normalize(st);s.mounts[k].pid = pid;if (s.sym && k !== 'T') s.mounts[k === 'R' ? 'L' : 'R'].pid = pid;set(EP.normalize(s));};
return <>
{topHint && {st.system === 'tg' ? T("上に付けるには「シューベース」でアクセサリーシューボールベースII・PTSA-02、またはRSボールベース(RIG使用時)を選んでください") : T("シューベースに付ける場合は「+ 上に追加」を押してください(アクセサリーシューボールベースIIを自動で追加)")}
}
{T("ストロボ・ライトは合計3つまで。右・左・上を併用できます。")}
{used.length > 0 && {T("選択中")}
}
{used.map((k) => {if (st.sym && k === 'L') return null;const m = M[k],f = EP.FAM[EP.FAM_OF[m.pid]],mem = EP.member(m.pid);
return
{st.sym && k !== 'T' ? T("左右") : EP.MOUNT_NAME[k]}
{EP.famName(m.pid)}{m.pid}
{f && f.axes.map((ax, i) => {const opts = EP.axisOptions(f.id, i);if (opts.length < 2) return null;
return
{EP.AXIS_LABEL[ax] || ax}
setPid(k, EP.withVariant(m.pid, i, v))} options={opts.map((o) => ({ v: o, l: T(o) }))} />
;})}
{k === 'T' && st.system === 'tg' && st.tg.shoeConv === 'OM_PTSA02' &&
{T("PTSA-02 にYSベースで取付")}
}
;})}
{kind === 'light' && st.system === 'tg' && {T("前面に付けるリングライト(RL1500)は「リングライト」で選択します。")}
}
{fams.length > 0 && {T("製品を追加")}
}
{fams.slice().sort((a, b) => (kind === 'strobe' && b.id === recF) - (kind === 'strobe' && a.id === recF)).map(({ id: f, note }) => {const pid = EP.defaultMember(f);
return {EP.FAM[f].name}} desc={note} onClick={() => !full && addFam(f)}
acts={<>
{topOk && }>} />;})}
{full && {T("ストロボ・ライトは3つ選択済みです")}
}
>;
}
const PanelStrobe = (p) => ;
const PanelLight = (p) => ;
/* ---------------- ストロボ接続(光ファイバー) ---------------- */
function PanelFiber({ st, set, res, openChart }) {
const r = res.rule;
return <>
{!res.strobes ? {T("ストロボを選ぶと自動で追加されます(ライトのみの場合は不要)。")}
: <>
{r ? r.note : T("適用ルールなし")}
{T("YS-D3 DUO は電気接続にも対応していますが、現在は光接続のみ選択できます。数量は内訳から調整できます。")}
{r && r.mg01 && }
{r && r.cable_id && }
{r && r.extra_id && }
>}
{(!r || !!r.per_strobe) && <>
{T("長さ")}
upd(st, set, 'fiberSize', v)} options={EP.FIBER.map((f) => ({ v: f.size, l: f.size }))} />
f.size === st.fiberSize) || {}).pid} on={!!res.strobes} desc={res.strobes ? '× ' + res.strobes + T("(灯数分)") : null} />
>}
>;
}
/* ---------------- アーム ---------------- */
function PanelArms({ st, set, openChart, go }) {
const s0 = EP.normalize(st),[k, setK] = React.useState('R'),[size, setSize] = React.useState('M');
const key = st.sym && k === 'L' ? 'R' : k,arms = s0.mounts[key].arms;
const idOf = (z) => EP.ARMS.find((id) => EP.armSize(id) === z);
const setArms = (a) => {const s = EP.normalize(st);s.mounts[key].arms = a;set(EP.normalize(s));};
const where = st.sym ? [['R', T("左右")], ['T', T("上")]] : [['R', T("右")], ['L', T("左")], ['T', T("上")]];
return <>
{T("取付位置")}
({ v: x, l: l + (s0.mounts[x].pid ? T(":") + EP.famName(s0.mounts[x].pid).replace('YS-D12S ', '') : '') }))} />
{T("アーム(ハウジング側から順)")}{T("1か所に最大")} {EP.MAX_ARMS} {T("本")}{st.sym && key === 'R' ? T("・左右それぞれに付きます") : ''}
{!arms.length && {T("アームなし(ストロボ・ライトをベースに直付け)")}
}
{arms.map((a, i) => setArms(arms.filter((_, j) => j !== i))}>
{const x = arms.slice();x[i] = idOf(v);setArms(x);}} options={EP.ARMS.map((id) => ({ v: EP.armSize(id), l: EP.armSize(id) }))} />
)}
{T("サイズ")}
({ v: EP.armSize(id), l: EP.armSize(id) }))} />
arms.length < EP.MAX_ARMS && setArms([...arms, idOf(size)])}
acts={} />
{T("アーム1本につきクランプが1個増えます(自動)。")}
>;
}
/* ---------------- クランプ ---------------- */
function PanelClamp({ st, set, res }) {
const it = res.items.find((i) => i.key === 'auto:clamp'),adj = st.adj['auto:clamp'] || 0;
return <>
{T("クランプはボールとボールをつなぐ部品です。取付位置ごとに「アームの本数+1」個が必要です →")} {T("自動")} {res.clampAuto} {T("個")}
{!it && {T("ストロボ・ライトまたはアームを選ぶと自動で追加されます。")}
}
{it && 0} desc={T("自動 ") + res.clampAuto + (adj ? T("(調整 ") + (adj > 0 ? '+' : '') + adj + T(")") : '')}>
set(EP.setQty(st, it, v))} />
{adj !== 0 && }
}
>;
}
/* ---------------- フロート ---------------- */
function PanelFloats({ st, set, res, openChart, go }) {
const setQ = (id, v) => {const s = cl(st);if (v) s.floats[id] = v;else delete s.floats[id];set(s);};
const rec = React.useMemo(() => EP.recommendFloats(st), [JSON.stringify({ ...st, floats: {}, view: 0, off: 0, mode: 0 })]);
const sea = st.water === 'sea',uwNow = Math.round(sea ? res.totals.totalUnderwaterWeightSea : res.totals.totalUnderwaterWeightFresh),hasF = EP.FLOATS.some((id) => st.floats[id] > 0);
const msg = uwNow < 0 ? hasF ? T("浮いてしまいます。必要に応じてフロートを調整してください。") : T("浮いてしまいます。組み合わせを変更することで総重量を変化させられます。") : null;
useInitialScroll(null); // 開いたときは一番上(快適な範囲の表示)から
return <>
{T("快適な範囲 +")}{EP.REC_RANGE.min}{T("〜+")}{EP.REC_RANGE.max}g{sea ? T("海水") : T("淡水")}{T("でほんの少し沈む重さ。手を離しても機材がゆっくり沈み、構図に集中できます。")}
{!rec.arms ? <>{T("フロートはアームに付けます。先にアームを選んでください。")}
> : <>
{T("フロートなしの水中重量:")}{fmt(Math.round(rec.base), true)}g{rec.incomplete ? T("(重量未登録 ") + rec.incomplete + T(" 品目・参考)") : ''}
{msg && {msg}
}
{rec.list.length ? rec.list.map((r, i) =>
set({ ...st, floats: r.floats })}>
{i + 1}{Object.keys(r.floats).map((f) => EP.info(f).name + ' ×' + r.floats[f]).join(T("\u3000"))}
{fmt(Math.round(r.total), true)}g
) :
msg ? null : {rec.base < EP.REC_RANGE.min ? T("フロートなしで +") + EP.REC_RANGE.min + T("g を下回っています(") + fmt(Math.round(rec.base), true) + T("g)") : T("今のアームでは範囲内の組み合わせがありません")}
}
>}
{T("個別に選ぶ")}
{EP.FLOATS.map((id) => !st.floats[id] && setQ(id, st.sym ? 2 : 1)}
act={{ label: T("+ 追加"), onClick: () => setQ(id, Math.min(12, (st.floats[id] || 0) + (st.sym ? 2 : 1))) }}>
setQ(id, v)} />
)}
>;
}
/* ---------------- 未登録の製品を追加 ---------------- */
function PanelAdd({ st, set, onHelp }) {
const upC = (i, p) => {const s = cl(st);Object.assign(s.custom[i], p);set(s);};
const del = (i) => {const s = cl(st);s.custom.splice(i, 1);set(s);};
return <>
{T("登録されていない製品を、計算に加えます。水中に露出するかどうかで計算方法が変わります。")}
{st.custom.map((c, i) => {const exp = c.exposed !== false;
return ;})}
- {T("※メーカーによっては水中重量を浮力と表現している場合があります。")}
- {T("※メーカーによっては浮力の符号が反転している場合があります。")}
>;
}
Object.assign(window, { PanelCamera, PanelLens, PanelConverter, PanelRig, PanelRing, PanelTgLens, PanelTop, PanelStrobe, PanelLight, PanelFiber, PanelArms, PanelClamp, PanelFloats, PanelAdd });