Files
KSP_project/app/templates/mission_timeline_preview.html
T
2026-05-08 15:16:04 +08:00

240 lines
13 KiB
HTML

{% extends "base.html" %}
{% from "_datetime_picker_field.html" import datetime_picker_field %}
{% block content %}
{% include "_mission_ops_tabs.html" %}
{% include "_mission_preview_header.html" %}
<section class="panel mission-control-panel top-gap">
<form method="get" action="{{ url_for('web.mission_timeline_preview') }}" class="mission-toolbar-grid">
<input type="hidden" name="range_mode" value="{{ timeline_range_mode }}" id="timeline-range-mode">
<div class="mission-toolbar-block">
<span class="field-label">时间范围</span>
<div class="mission-toolbar-inline">
{{ datetime_picker_field('开始', 'start', timeline_range_start, field_id='timeline-start') }}
{{ datetime_picker_field('结束', 'end', timeline_range_end, field_id='timeline-end') }}
<label>
<span class="field-label">尺度</span>
<select name="scale">
{% for option in timeline_scale_options %}
<option value="{{ option.value }}" {% if option.value == timeline_scale_mode %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</label>
</div>
</div>
<div class="mission-toolbar-block">
<span class="field-label">时间线筛选</span>
<div class="mission-filter-grid">
<label>
<span class="field-label">资产类型</span>
<select name="asset_type">
<option value="">全部类型</option>
{% for option in asset_type_options %}
<option value="{{ option }}" {% if option == asset_type_filter %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
</label>
<label>
<span class="field-label">地点</span>
<select name="location">
<option value="">全部地点</option>
{% for option in location_options %}
<option value="{{ option }}" {% if option == location_filter %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
</label>
<label>
<span class="field-label">记录范围</span>
<select name="record_scope">
{% for option in record_scope_options %}
<option value="{{ option.value }}" {% if option.value == record_scope %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</label>
<button class="primary-button" type="submit">应用</button>
<a class="secondary-button" href="{{ reset_url }}">重置</a>
</div>
</div>
<div class="mission-toolbar-block mission-toolbar-span-two">
<span class="field-label">显示资产</span>
{% if timeline_asset_options %}
<div class="mission-asset-picker" data-asset-picker>
<div class="mission-asset-picker-toolbar">
<label class="mission-asset-picker-search">
<span class="field-label">按名称搜索</span>
<input type="search" placeholder="搜索资产名称" data-asset-picker-search>
</label>
<div class="mission-asset-picker-actions">
<button class="secondary-button" type="button" data-asset-picker-select-all>全选可见</button>
<button class="secondary-button" type="button" data-asset-picker-clear>清空选择</button>
<span class="small-muted" data-asset-picker-summary></span>
</div>
</div>
<div class="mission-asset-picker-grid">
{% for option in timeline_asset_options %}
<label class="mission-asset-picker-item" data-asset-picker-item data-asset-picker-name="{{ option.label|lower }}">
<input type="checkbox" name="asset_ids" value="{{ option.value }}" data-asset-picker-checkbox {% if option.selected %}checked{% endif %}>
<span>
<strong>{{ option.label }}</strong>
<small>{{ option.asset_type }}</small>
</span>
</label>
{% endfor %}
</div>
</div>
<p class="field-hint">未勾选任何 Asset 时,默认显示当前筛选命中的全部资产。</p>
{% else %}
<p class="small-muted">当前筛选条件下没有可选资产。</p>
{% endif %}
</div>
</form>
</section>
<section class="panel mission-timeline-panel top-gap">
<div class="mission-timeline-toolbar">
<div>
<p class="eyebrow">Timeline</p>
<h2>多资产时间线</h2>
</div>
<div class="mission-legend">
<span class="legend-item"><span class="legend-swatch warning"></span>转移段</span>
<span class="legend-item"><span class="legend-swatch primary"></span>持续运行</span>
<span class="legend-item"><span class="legend-swatch neutral"></span>停泊 / 地表 / 待命</span>
<span class="legend-item"><span class="legend-swatch current"></span>当前活动状态</span>
</div>
</div>
<div class="mission-filter-pill-row">
<span class="mission-filter-pill"><strong>范围:</strong> {{ timeline_range_display }}</span>
<span class="mission-filter-pill"><strong>尺度:</strong> {{ timeline_scale_mode|title }}</span>
<span class="mission-filter-pill"><strong>资产:</strong> {% if selected_asset_count %}已选 {{ selected_asset_count }} 个{% else %}显示 {{ timeline_rows|length }} 条{% endif %}</span>
<span class="mission-filter-pill"><strong>模式:</strong> {{ record_scope_options|selectattr('value', 'equalto', record_scope)|map(attribute='label')|first }}</span>
</div>
{% if timeline_rows %}
<div class="mission-timeline-scroll">
<div class="mission-timeline-frame" style="min-width: {{ 320 + (timeline_scale|length * timeline_column_width) }}px;">
<div class="mission-timeline-scale">
<div class="mission-timeline-corner"></div>
<div class="mission-scale-labels" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px;">
{% for label in timeline_scale %}
<span>{{ label }}</span>
{% endfor %}
</div>
</div>
<div class="mission-timeline-rows">
{% for row in timeline_rows %}
<div class="mission-timeline-row">
<a class="mission-timeline-label mission-timeline-label-link" href="{{ url_for('web.asset_detail', asset_id=row.asset_id, sim_time=simulation_time_input) }}">
<h3>{{ row.name }}</h3>
<p class="small-muted mission-timeline-meta">{{ row.asset_type }}</p>
<p class="small-muted mission-timeline-status">Current Status: {{ row.current_state }}</p>
</a>
<div class="mission-timeline-track-stack">
<div class="mission-timeline-lane mission-timeline-state-lane {% if timeline_scale_mode == 'year' %}mission-timeline-lane-year{% endif %}" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px; --timeline-track-count: {{ row.state_track_count }};">
{% for segment in row.segments %}
<a class="mission-segment mission-timeline-link {% if segment.compact %}mission-segment-compact {% endif %}{% for tone in segment.tone.split() %}mission-tone-{{ tone }} {% endfor %}" style="left: {{ segment.left_pct }}; width: {{ segment.track_width_pct }}; --timeline-track-index: {{ segment.track }};" title="{{ segment.label }}" data-full-label="{{ segment.label }}" href="{{ segment.url }}">
<span class="mission-segment-body" style="width: {{ segment.body_width_pct }};"></span>
<span class="mission-segment-text">{{ segment.label }}</span>
</a>
{% endfor %}
</div>
<div class="mission-timeline-lane mission-timeline-event-lane {% if timeline_scale_mode == 'year' %}mission-timeline-lane-year{% endif %}" style="--timeline-columns: {{ timeline_scale|length }}; --timeline-column-width: {{ timeline_column_width }}px; --timeline-track-count: {{ row.event_track_count }};">
{% for event in row.events %}
<a class="mission-event-node mission-timeline-link {% if event.state_node %}mission-event-node-state{% endif %}{% if event.label_side == 'left' %} mission-event-node-label-left{% endif %}" style="left: {{ event.left_pct }}; --timeline-track-index: {{ event.track }}; --event-label-width: {{ event.label_width_px }}px;{% if event.connector_height_px %} --state-node-connector-height: {{ event.connector_height_px }};{% endif %}" title="{{ event.label }}" data-full-label="{{ event.full_label }}" href="{{ event.url }}">
{% if event.state_node %}
<span class="mission-state-node-connector" aria-hidden="true"></span>
{% endif %}
<span class="mission-event-node-label">{{ event.label }}</span>
</a>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% else %}
<section class="empty-state compact-empty">
<h2>当前时间范围内没有时间线条目</h2>
</section>
{% endif %}
</section>
<script>
(() => {
const picker = document.querySelector('[data-asset-picker]');
if (!picker) {
return;
}
const searchInput = picker.querySelector('[data-asset-picker-search]');
const summary = picker.querySelector('[data-asset-picker-summary]');
const items = Array.from(picker.querySelectorAll('[data-asset-picker-item]'));
const rangeModeField = document.getElementById('timeline-range-mode');
const rangeInputs = [document.getElementById('timeline-start'), document.getElementById('timeline-end')].filter(Boolean);
const checkboxFor = (item) => item.querySelector('[data-asset-picker-checkbox]');
const updateSummary = () => {
const visibleCount = items.filter((item) => !item.hidden).length;
const selectedCount = items.filter((item) => checkboxFor(item)?.checked).length;
summary.textContent = `已选 ${selectedCount} 个 · 可见 ${visibleCount} 个`;
};
const setRangeModeManual = () => {
if (rangeModeField) {
rangeModeField.value = 'manual';
}
};
const applySearch = () => {
const term = (searchInput?.value || '').trim().toLowerCase();
for (const item of items) {
const assetName = item.dataset.assetPickerName || '';
item.hidden = Boolean(term) && !assetName.includes(term);
}
updateSummary();
};
picker.querySelector('[data-asset-picker-select-all]')?.addEventListener('click', () => {
for (const item of items) {
const checkbox = checkboxFor(item);
if (!item.hidden && checkbox) {
checkbox.checked = true;
}
}
updateSummary();
});
picker.querySelector('[data-asset-picker-clear]')?.addEventListener('click', () => {
for (const item of items) {
const checkbox = checkboxFor(item);
if (checkbox) {
checkbox.checked = false;
}
}
updateSummary();
});
searchInput?.addEventListener('input', applySearch);
for (const item of items) {
checkboxFor(item)?.addEventListener('change', updateSummary);
}
for (const input of rangeInputs) {
input.addEventListener('input', setRangeModeManual);
input.addEventListener('change', setRangeModeManual);
}
applySearch();
})();
</script>
{% endblock %}