Merge remote-tracking branch 'origin/velocity' into components
This commit is contained in:
commit
0e4d5a8d47
5 changed files with 365 additions and 57 deletions
215
src/routes/TimeLine.svelte
Normal file
215
src/routes/TimeLine.svelte
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
<!-- Timeline.svelte -->
|
||||
<script>
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
|
||||
export let initialHour = 0;
|
||||
export let totalHours = 72; // 3 суток
|
||||
export let updateInterval = 3; // шаг в 3 часа
|
||||
export let playSpeed = 2000; // скорость анимации (мс)
|
||||
|
||||
let currentHour = initialHour;
|
||||
let isPlaying = false;
|
||||
let timer;
|
||||
|
||||
// События для коммуникации с родительским компонентом
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// Автоматическое проигрывание
|
||||
function togglePlay() {
|
||||
isPlaying = !isPlaying;
|
||||
|
||||
if (isPlaying) {
|
||||
timer = setInterval(() => {
|
||||
currentHour = (currentHour + updateInterval) % totalHours;
|
||||
dispatchTimeUpdate();
|
||||
}, playSpeed);
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchTimeUpdate() {
|
||||
dispatch('timeupdate', {
|
||||
hour: currentHour,
|
||||
timestamp: calculateTimestamp(currentHour)
|
||||
});
|
||||
}
|
||||
|
||||
function calculateTimestamp(hour) {
|
||||
const now = new Date();
|
||||
now.setHours(now.getHours() + hour);
|
||||
return now;
|
||||
}
|
||||
|
||||
function formatTime(hours) {
|
||||
const days = Math.floor(hours / 24);
|
||||
const remainingHours = hours % 24;
|
||||
const time = calculateTimestamp(hours);
|
||||
|
||||
return `+${days}d ${remainingHours}h (${time.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})})`;
|
||||
}
|
||||
|
||||
// Очистка при размонтировании
|
||||
onDestroy(() => {
|
||||
clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="timeline-container">
|
||||
<div class="timeline-controls">
|
||||
<button
|
||||
on:click={() => {
|
||||
currentHour = Math.max(0, currentHour - updateInterval);
|
||||
dispatchTimeUpdate();
|
||||
}}
|
||||
disabled={currentHour <= 0}
|
||||
class="control-button"
|
||||
>
|
||||
← Назад
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={togglePlay}
|
||||
class="control-button play-button {isPlaying ? 'playing' : ''}"
|
||||
>
|
||||
{isPlaying ? '❚❚' : '▶'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
currentHour = Math.min(totalHours, currentHour + updateInterval);
|
||||
dispatchTimeUpdate();
|
||||
}}
|
||||
disabled={currentHour >= totalHours}
|
||||
class="control-button"
|
||||
>
|
||||
Вперёд →
|
||||
</button>
|
||||
|
||||
<div class="time-display">
|
||||
{formatTime(currentHour)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={totalHours}
|
||||
step={updateInterval}
|
||||
bind:value={currentHour}
|
||||
on:input={() => dispatchTimeUpdate()}
|
||||
class="timeline-slider"
|
||||
/>
|
||||
|
||||
<div class="time-marks">
|
||||
{#each Array(Math.floor(totalHours / updateInterval) + 1) as _, i}
|
||||
<div
|
||||
class="time-mark {currentHour === i * updateInterval ? 'active' : ''}"
|
||||
on:click={() => {
|
||||
currentHour = i * updateInterval;
|
||||
dispatchTimeUpdate();
|
||||
}}
|
||||
>
|
||||
{i * updateInterval % 24 === 0 ? `День ${Math.floor(i * updateInterval / 24) + 1}` : ''}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline-container {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 8px;
|
||||
padding: 10px 15px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.timeline-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.control-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
width: 40px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.play-button.playing {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
font-weight: bold;
|
||||
min-width: 180px;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.timeline-slider {
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time-marks {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.time-mark {
|
||||
cursor: pointer;
|
||||
padding: 2px 5px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.time-mark:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.time-mark.active {
|
||||
font-weight: bold;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.time-mark.active:before {
|
||||
background: #2196F3;
|
||||
height: 15px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue