SAR Search Pattern Update
This commit is contained in:
@@ -50,7 +50,7 @@ Returns a JSON array of all indexed simulations, including their ID, name, creat
|
|||||||
|
|
||||||
### `POST /api/simulations/create`
|
### `POST /api/simulations/create`
|
||||||
Creates a new simulation entry.
|
Creates a new simulation entry.
|
||||||
- **Request Body:** JSON object containing the combined configurations (e.g., `search`, `uav`, `ugv`). The configuration *must* contain at least one search pattern definition (either `spiral`, `lawnmower`, or `levy`) inside the JSON.
|
- **Request Body:** JSON object containing the combined configurations (e.g., `search`, `uav`, `ugv`). The configuration *must* contain at least one search pattern definition (either `spiral`, `lawnmower`, `levy`, or `sar`) inside the JSON.
|
||||||
- **Behavior:** Checks if an identical config exists. If so, it returns the existing simulation ID/Name for overwriting. If not, it allocates a new `simulation_X` incrementally, inserts it into the database, builds the file directory in `../results`, and returns the new ID/Name. Returns `400 Bad Request` if payload is not valid JSON or lacks a known search pattern.
|
- **Behavior:** Checks if an identical config exists. If so, it returns the existing simulation ID/Name for overwriting. If not, it allocates a new `simulation_X` incrementally, inserts it into the database, builds the file directory in `../results`, and returns the new ID/Name. Returns `400 Bad Request` if payload is not valid JSON or lacks a known search pattern.
|
||||||
- **Response:** JSON object `{"id": <int>, "name": <string>}`
|
- **Response:** JSON object `{"id": <int>, "name": <string>}`
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,15 @@ func (rt *Router) CreateSimulation(w http.ResponseWriter, r *http.Request) {
|
|||||||
if _, ok := searchMap["levy"]; ok {
|
if _, ok := searchMap["levy"]; ok {
|
||||||
hasPattern = true
|
hasPattern = true
|
||||||
}
|
}
|
||||||
|
if _, ok := searchMap["sar"]; ok {
|
||||||
|
hasPattern = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasPattern {
|
if !hasPattern {
|
||||||
http.Error(w, "Simulation configuration must include a search pattern (spiral, lawnmower, or levy)", http.StatusBadRequest)
|
http.Error(w, "Simulation configuration must include a search pattern (spiral, lawnmower, levy, or sar)", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<option value="spiral">Spiral</option>
|
<option value="spiral">Spiral</option>
|
||||||
<option value="lawnmower">Lawnmower</option>
|
<option value="lawnmower">Lawnmower</option>
|
||||||
<option value="levy">Levy</option>
|
<option value="levy">Levy</option>
|
||||||
|
<option value="sar">SAR</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -1,57 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
parseConfig,
|
parseConfig,
|
||||||
formatDate,
|
formatDate,
|
||||||
type SimulationState,
|
type SimulationState,
|
||||||
} from "./simulationState.svelte";
|
} from "./simulationState.svelte";
|
||||||
import { formatTime } from "$lib/ts/utils";
|
import { formatTime } from "$lib/ts/utils";
|
||||||
|
|
||||||
let { state }: { state: SimulationState } = $props();
|
let { state }: { state: SimulationState } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="table-content">
|
<main class="table-content">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Sim Name</th>
|
<th>Sim Name</th>
|
||||||
<th>Search Pattern</th>
|
<th>Search Pattern</th>
|
||||||
<th>Search Time</th>
|
<th>Search Time</th>
|
||||||
<th>Total Time</th>
|
<th>Total Time</th>
|
||||||
<th>Date Run</th>
|
<th>Date Run</th>
|
||||||
<th>Link</th>
|
<th>Link</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each state.paginatedSimulations as sim}
|
{#each state.paginatedSimulations as sim}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{sim.id}</td>
|
<td>{sim.id}</td>
|
||||||
<td>{sim.name}</td>
|
<td>{sim.name}</td>
|
||||||
<td style="text-transform: capitalize;"
|
<td style="text-transform: capitalize;"
|
||||||
>{parseConfig(sim.config).pattern || "Unknown"}</td
|
>{parseConfig(sim.config).pattern === "sar"
|
||||||
>
|
? "SAR"
|
||||||
<td>{formatTime(sim.search_time || NaN)}</td>
|
: parseConfig(sim.config).pattern || "Unknown"}</td
|
||||||
<td>{formatTime(sim.total_time || NaN)}</td>
|
>
|
||||||
<td>{formatDate(sim.created_at)}</td>
|
<td>{formatTime(sim.search_time || NaN)}</td>
|
||||||
<td><a href={`/simulation/${sim.id}`}>View Details</a></td>
|
<td>{formatTime(sim.total_time || NaN)}</td>
|
||||||
</tr>
|
<td>{formatDate(sim.created_at)}</td>
|
||||||
{/each}
|
<td><a href={`/simulation/${sim.id}`}>View Details</a></td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<button
|
<button
|
||||||
onclick={() => state.goToPage(state.currentPage - 1)}
|
onclick={() => state.goToPage(state.currentPage - 1)}
|
||||||
disabled={state.currentPage === 1}
|
disabled={state.currentPage === 1}
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</button>
|
</button>
|
||||||
<span>Page {state.currentPage} of {state.totalPages}</span>
|
<span>Page {state.currentPage} of {state.totalPages}</span>
|
||||||
<button
|
<button
|
||||||
onclick={() => state.goToPage(state.currentPage + 1)}
|
onclick={() => state.goToPage(state.currentPage + 1)}
|
||||||
disabled={state.currentPage === state.totalPages}
|
disabled={state.currentPage === state.totalPages}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function parseConfig(config: string) {
|
|||||||
if (searchObj.spiral) pattern = "spiral";
|
if (searchObj.spiral) pattern = "spiral";
|
||||||
else if (searchObj.lawnmower) pattern = "lawnmower";
|
else if (searchObj.lawnmower) pattern = "lawnmower";
|
||||||
else if (searchObj.levy) pattern = "levy";
|
else if (searchObj.levy) pattern = "levy";
|
||||||
|
else if (searchObj.sar) pattern = "sar";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
altitude: searchObj.altitude != null ? parseFloat(searchObj.altitude) : null,
|
altitude: searchObj.altitude != null ? parseFloat(searchObj.altitude) : null,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,364 +1,340 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { formatDate } from "$lib/simulationState.svelte";
|
import { formatDate } from "$lib/simulationState.svelte";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type SimulationDetails,
|
type SimulationDetails,
|
||||||
flattenJSON,
|
flattenJSON,
|
||||||
isImage,
|
isImage,
|
||||||
getResourceUrl,
|
getResourceUrl,
|
||||||
formatTime,
|
formatTime,
|
||||||
} from "$lib/ts/utils";
|
} from "$lib/ts/utils";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let id = $derived(data.id);
|
let id = $derived(data.id);
|
||||||
|
|
||||||
let simulation = $state<SimulationDetails | null>(null);
|
let simulation = $state<SimulationDetails | null>(null);
|
||||||
|
|
||||||
let parsedConfig: { key: string; value: string }[] | null = $derived.by(
|
let parsedConfig: { key: string; value: string }[] | null = $derived.by(
|
||||||
() => {
|
() => {
|
||||||
if (!simulation?.config) return null;
|
if (!simulation?.config) return null;
|
||||||
try {
|
try {
|
||||||
let cleanedConfig = simulation.config.replace(
|
let cleanedConfig = simulation.config.replace(
|
||||||
/"([^"]+)\.yaml"\s*:/g,
|
/"([^"]+)\.yaml"\s*:/g,
|
||||||
'"$1":',
|
'"$1":',
|
||||||
);
|
);
|
||||||
return flattenJSON(JSON.parse(cleanedConfig));
|
return flattenJSON(JSON.parse(cleanedConfig));
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let searchConfig = $derived(
|
let searchConfig = $derived(
|
||||||
parsedConfig
|
parsedConfig
|
||||||
?.filter((c) => c.key.startsWith("search."))
|
?.filter((c) => c.key.startsWith("search."))
|
||||||
.map((c) => ({ ...c, key: c.key.replace("search.", "") })),
|
.map((c) => ({ ...c, key: c.key.replace("search.", "") })),
|
||||||
);
|
);
|
||||||
let uavConfig = $derived(
|
let uavConfig = $derived(
|
||||||
parsedConfig
|
parsedConfig
|
||||||
?.filter((c) => c.key.startsWith("uav."))
|
?.filter((c) => c.key.startsWith("uav."))
|
||||||
.map((c) => ({ ...c, key: c.key.replace("uav.", "") })),
|
.map((c) => ({ ...c, key: c.key.replace("uav.", "") })),
|
||||||
);
|
);
|
||||||
let ugvConfig = $derived(
|
let ugvConfig = $derived(
|
||||||
parsedConfig
|
parsedConfig
|
||||||
?.filter((c) => c.key.startsWith("ugv."))
|
?.filter((c) => c.key.startsWith("ugv."))
|
||||||
.map((c) => ({ ...c, key: c.key.replace("ugv.", "") })),
|
.map((c) => ({ ...c, key: c.key.replace("ugv.", "") })),
|
||||||
);
|
);
|
||||||
let otherConfig = $derived(
|
let otherConfig = $derived(
|
||||||
parsedConfig?.filter(
|
parsedConfig?.filter(
|
||||||
(c) =>
|
(c) =>
|
||||||
!c.key.startsWith("search.") &&
|
!c.key.startsWith("search.") &&
|
||||||
!c.key.startsWith("uav.") &&
|
!c.key.startsWith("uav.") &&
|
||||||
!c.key.startsWith("ugv."),
|
!c.key.startsWith("ugv."),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
function groupConfig(data: { key: string; value: string }[] | undefined) {
|
function groupConfig(data: { key: string; value: string }[] | undefined) {
|
||||||
if (!data) return {};
|
if (!data) return {};
|
||||||
const groups: Record<string, { key: string; value: string }[]> = {
|
const groups: Record<string, { key: string; value: string }[]> = {
|
||||||
_general: [],
|
_general: [],
|
||||||
};
|
};
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
const parts = item.key.split(".");
|
const parts = item.key.split(".");
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
const groupName = parts[0];
|
const groupName = parts[0];
|
||||||
const subKey = parts.slice(1).join(".");
|
const subKey = parts.slice(1).join(".");
|
||||||
if (!groups[groupName]) groups[groupName] = [];
|
if (!groups[groupName]) groups[groupName] = [];
|
||||||
groups[groupName].push({ key: subKey, value: item.value });
|
groups[groupName].push({ key: subKey, value: item.value });
|
||||||
} else {
|
} else {
|
||||||
groups["_general"].push(item);
|
groups["_general"].push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
let imagesList = $derived<string[]>(
|
let imagesList = $derived<string[]>(
|
||||||
simulation && simulation.resources
|
simulation && simulation.resources
|
||||||
? simulation.resources.filter(isImage)
|
? simulation.resources.filter(isImage)
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/simulations/${id}`);
|
const res = await fetch(`/api/simulations/${id}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
simulation = await res.json();
|
simulation = await res.json();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.print();
|
window.print();
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if simulation}
|
{#if simulation}
|
||||||
<div class="print-container">
|
<div class="print-container">
|
||||||
<h1>Simulation Report: {simulation.name}</h1>
|
<h1>Simulation Report: {simulation.name}</h1>
|
||||||
<p><strong>ID:</strong> {simulation.id}</p>
|
<p><strong>ID:</strong> {simulation.id}</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Search Pattern:</strong>
|
<strong>Search Pattern:</strong>
|
||||||
<span style="text-transform: capitalize;">
|
<span style="text-transform: capitalize;">
|
||||||
{parsedConfig?.find((c) => c.key === "search.spiral.max_legs")
|
{parsedConfig?.find((c) => c.key === "search.spiral.max_legs")
|
||||||
? "Spiral"
|
? "Spiral"
|
||||||
: parsedConfig?.find(
|
: parsedConfig?.find((c) => c.key === "search.lawnmower.width")
|
||||||
(c) => c.key === "search.lawnmower.width",
|
? "Lawnmower"
|
||||||
)
|
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
||||||
? "Lawnmower"
|
? "Levy"
|
||||||
: parsedConfig?.find(
|
: parsedConfig?.find((c) => c.key.startsWith("search.sar"))
|
||||||
(c) => c.key === "search.levy.max_steps",
|
? "SAR"
|
||||||
)
|
: "Unknown"}
|
||||||
? "Levy"
|
</span>
|
||||||
: "Unknown"}
|
</p>
|
||||||
</span>
|
<p><strong>Date Code:</strong> {formatDate(simulation.created_at)}</p>
|
||||||
</p>
|
|
||||||
<p><strong>Date Code:</strong> {formatDate(simulation.created_at)}</p>
|
|
||||||
|
|
||||||
{#if simulation.search_time !== null && simulation.total_time !== null}
|
{#if simulation.search_time !== null && simulation.total_time !== null}
|
||||||
<p>
|
<p>
|
||||||
<strong>Search Time:</strong>
|
<strong>Search Time:</strong>
|
||||||
{formatTime(simulation.search_time)} |
|
{formatTime(simulation.search_time)} |
|
||||||
<strong>Total Time:</strong>
|
<strong>Total Time:</strong>
|
||||||
{formatTime(simulation.total_time)}
|
{formatTime(simulation.total_time)}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if simulation.cpu_info || simulation.gpu_info || simulation.ram_info}
|
{#if simulation.cpu_info || simulation.gpu_info || simulation.ram_info}
|
||||||
<div
|
<div
|
||||||
class="hardware-box"
|
class="hardware-box"
|
||||||
style="margin-top: 20px; border: 1px solid #ccc; padding: 15px; background-color: #f9f9f9; break-inside: avoid;"
|
style="margin-top: 20px; border: 1px solid #ccc; padding: 15px; background-color: #f9f9f9; break-inside: avoid;"
|
||||||
>
|
>
|
||||||
<h3>Hardware Spec</h3>
|
<h3>Hardware Spec</h3>
|
||||||
<p><strong>CPU:</strong> {simulation.cpu_info || "N/A"}</p>
|
<p><strong>CPU:</strong> {simulation.cpu_info || "N/A"}</p>
|
||||||
<p><strong>GPU:</strong> {simulation.gpu_info || "N/A"}</p>
|
<p><strong>GPU:</strong> {simulation.gpu_info || "N/A"}</p>
|
||||||
<p><strong>RAM:</strong> {simulation.ram_info || "N/A"}</p>
|
<p><strong>RAM:</strong> {simulation.ram_info || "N/A"}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if simulation.config}
|
{#if simulation.config}
|
||||||
<div class="config-box">
|
<div class="config-box">
|
||||||
<strong>Configuration Options:</strong>
|
<strong>Configuration Options:</strong>
|
||||||
|
|
||||||
{#snippet ConfigTable(
|
{#snippet ConfigTable(
|
||||||
title: string,
|
title: string,
|
||||||
data: { key: string; value: string }[] | undefined,
|
data: { key: string; value: string }[] | undefined,
|
||||||
)}
|
)}
|
||||||
{#if data && data.length > 0}
|
{#if data && data.length > 0}
|
||||||
{@const groups = groupConfig(data)}
|
{@const groups = groupConfig(data)}
|
||||||
<div class="config-category">
|
<div class="config-category">
|
||||||
<h2>{title}</h2>
|
<h2>{title}</h2>
|
||||||
<div class="tables-container">
|
<div class="tables-container">
|
||||||
{#if groups["_general"].length > 0}
|
{#if groups["_general"].length > 0}
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<h3 class="table-label">General</h3>
|
<h3 class="table-label">General</h3>
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
><th>Parameter</th><th
|
</thead>
|
||||||
>Value</th
|
<tbody>
|
||||||
></tr
|
{#each groups["_general"] as item}
|
||||||
>
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
</thead>
|
{/each}
|
||||||
<tbody>
|
</tbody>
|
||||||
{#each groups["_general"] as item}
|
</table>
|
||||||
<tr
|
</div>
|
||||||
><td>{item.key}</td><td
|
{/if}
|
||||||
>{item.value}</td
|
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
||||||
></tr
|
<div class="table-wrapper">
|
||||||
>
|
<h3 class="table-label" style="text-transform: capitalize;">
|
||||||
{/each}
|
{groupName}
|
||||||
</tbody>
|
</h3>
|
||||||
</table>
|
<table class="config-table">
|
||||||
</div>
|
<thead>
|
||||||
{/if}
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
</thead>
|
||||||
<div class="table-wrapper">
|
<tbody>
|
||||||
<h3
|
{#each items as item}
|
||||||
class="table-label"
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
style="text-transform: capitalize;"
|
{/each}
|
||||||
>
|
</tbody>
|
||||||
{groupName}
|
</table>
|
||||||
</h3>
|
</div>
|
||||||
<table class="config-table">
|
{/each}
|
||||||
<thead>
|
</div>
|
||||||
<tr
|
</div>
|
||||||
><th>Parameter</th><th
|
{/if}
|
||||||
>Value</th
|
{/snippet}
|
||||||
></tr
|
|
||||||
>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each items as item}
|
|
||||||
<tr
|
|
||||||
><td>{item.key}</td><td
|
|
||||||
>{item.value}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<div class="super-container">
|
<div class="super-container">
|
||||||
{@render ConfigTable("Search", searchConfig)}
|
{@render ConfigTable("Search", searchConfig)}
|
||||||
{@render ConfigTable("UGV", ugvConfig)}
|
{@render ConfigTable("UGV", ugvConfig)}
|
||||||
{@render ConfigTable("UAV", uavConfig)}
|
{@render ConfigTable("UAV", uavConfig)}
|
||||||
{@render ConfigTable("Other", otherConfig)}
|
{@render ConfigTable("Other", otherConfig)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h2>Visuals</h2>
|
<h2>Visuals</h2>
|
||||||
{#if imagesList.length > 0}
|
{#if imagesList.length > 0}
|
||||||
<div class="image-grid">
|
<div class="image-grid">
|
||||||
{#each imagesList as image}
|
{#each imagesList as image}
|
||||||
<div class="image-wrapper">
|
<div class="image-wrapper">
|
||||||
<img
|
<img src={getResourceUrl(simulation.name, image)} alt={image} />
|
||||||
src={getResourceUrl(simulation.name, image)}
|
<p class="caption">{image}</p>
|
||||||
alt={image}
|
</div>
|
||||||
/>
|
{/each}
|
||||||
<p class="caption">{image}</p>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
{/each}
|
<p>No images found in this simulation.</p>
|
||||||
</div>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<p>No images found in this simulation.</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<p>Loading printable report...</p>
|
<p>Loading printable report...</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(body) {
|
:global(body) {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
color: #000;
|
color: #000;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-container {
|
.print-container {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
border-bottom: 2px solid #000;
|
border-bottom: 2px solid #000;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-box {
|
.config-box {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.super-container {
|
.super-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-category {
|
.config-category {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-category h2 {
|
.config-category h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tables-container {
|
.tables-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table {
|
.config-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table td {
|
.config-table td {
|
||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table tr:nth-child(even) {
|
.config-table tr:nth-child(even) {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-grid {
|
.image-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-wrapper {
|
.image-wrapper {
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caption {
|
.caption {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
@page {
|
||||||
margin: 1cm;
|
margin: 1cm;
|
||||||
}
|
}
|
||||||
.print-container {
|
.print-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user