Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
658 changes: 71 additions & 587 deletions apps/web/src/lib/themes/standard-autumn/ItineraryView.svelte

Large diffs are not rendered by default.

39 changes: 16 additions & 23 deletions apps/web/src/lib/themes/standard-autumn/StepList.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<script lang="ts">
import type { Step } from "@tabitabi/types";
import { marked } from "marked";
import { renderMarkdown } from "./utils/markdown";
import {
EditIcon,
DeleteIcon,
LocationIcon,
LockIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "./components/icons/index.svelte";

interface Props {
steps: Step[];
Expand Down Expand Up @@ -32,7 +40,6 @@
let editStepHour = $state("09");
let editStepMinute = $state("00");

// Carousel management
let activeIndex = $state(0);
let trackEl = $state<HTMLDivElement | null>(null);
let touchStartX = $state<number | null>(null);
Expand All @@ -44,7 +51,6 @@
}
});

// Initialize activeIndex to the closest date to today
$effect(() => {
const groups = groupedSteps();
if (groups.length === 0) return;
Expand All @@ -66,15 +72,13 @@
activeIndex = closestIndex;
});

// Update focusedDate when activeIndex changes
$effect(() => {
const groups = groupedSteps();
if (groups.length > 0 && groups[activeIndex]) {
focusedDate = groups[activeIndex][0];
}
});

// Group steps by date
const groupedSteps = $derived(() => {
const groups = new Map<string, Step[]>();
for (const step of steps) {
Expand All @@ -96,33 +100,34 @@
if (i >= total) return total - 1;
return i;
}

function goTo(i: number) {
activeIndex = clampIndex(i);
}

function next() {
goTo(activeIndex + 1);
}

function prev() {
goTo(activeIndex - 1);
}

function handleKey(e: KeyboardEvent) {
if (e.key === "ArrowRight") {
next();
}
if (e.key === "ArrowLeft") {
prev();
}
if (e.key === "ArrowRight") next();
if (e.key === "ArrowLeft") prev();
}

function onTouchStart(e: TouchEvent) {
touchStartX = e.touches[0].clientX;
touchDeltaX = 0;
}

function onTouchMove(e: TouchEvent) {
if (touchStartX == null) return;
touchDeltaX = e.touches[0].clientX - touchStartX;
}

function onTouchEnd() {
if (touchStartX == null) return;
if (touchDeltaX < -50) next();
Expand All @@ -131,7 +136,6 @@
touchDeltaX = 0;
}

// Navigate to card on click
function handleCardClick(index: number) {
if (index !== activeIndex) {
goTo(index);
Expand Down Expand Up @@ -189,17 +193,6 @@
await onDeleteStep(stepId);
}
}

// Configure marked for safe rendering
marked.setOptions({
breaks: true, // Convert line breaks to <br>
gfm: true, // Enable GitHub Flavored Markdown
});

// Function to render markdown safely
function renderMarkdown(text: string): string {
return marked.parse(text, { async: false }) as string;
}
</script>

{#if steps.length === 0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<script lang="ts">
interface Props {
newStep: {
title: string;
date: string;
time: string;
location: string;
notes: string;
};
newStepHour: string;
newStepMinute: string;
onSubmit: () => void;
onCancel: () => void;
}

let {
newStep = $bindable(),
newStepHour = $bindable(),
newStepMinute = $bindable(),
onSubmit,
onCancel,
}: Props = $props();

$effect(() => {
newStep.time = `${newStepHour}:${newStepMinute}`;
});

function handleSubmit(e: Event) {
e.preventDefault();
onSubmit();
}
</script>

<form class="standard-autumn-form" onsubmit={handleSubmit}>
<h3 class="standard-autumn-form-title">新しい予定を追加</h3>
<div class="standard-autumn-form-grid">
<input
type="text"
bind:value={newStep.title}
placeholder="予定のタイトル *"
class="standard-autumn-input"
required
/>
<div class="standard-autumn-datetime">
<input
type="date"
bind:value={newStep.date}
class="standard-autumn-input"
required
/>
<div class="standard-autumn-time-picker">
<select
bind:value={newStepHour}
class="standard-autumn-select-time"
required
>
{#each Array.from( { length: 24 }, (_, i) => String(i).padStart(2, "0"), ) as hour}
<option value={hour}>{hour}</option>
{/each}
</select>
<span class="standard-autumn-time-separator">:</span>
<select
bind:value={newStepMinute}
class="standard-autumn-select-time"
required
>
<option value="00">00</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>
</div>
</div>
<input
type="text"
bind:value={newStep.location}
placeholder="場所 (任意)"
class="standard-autumn-input"
/>
<textarea
bind:value={newStep.notes}
placeholder="メモ (任意)"
class="standard-autumn-textarea"
rows="3"
></textarea>
</div>
<div class="standard-autumn-form-actions">
<button
type="submit"
class="standard-autumn-btn standard-autumn-btn-primary">追加する</button
>
<button
type="button"
onclick={onCancel}
class="standard-autumn-btn standard-autumn-btn-secondary"
>キャンセル</button
>
</div>
</form>
162 changes: 162 additions & 0 deletions apps/web/src/lib/themes/standard-autumn/components/BottomNav.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<script lang="ts">
import { goto } from "$app/navigation";
import {
HomeIcon,
WalicaIcon,
ViewIcon,
EditIcon,
SettingsIcon,
} from "./icons/index.svelte";
import SettingsMenu from "./SettingsMenu.svelte";

interface ThemeOption {
id: string;
name: string;
description?: string;
}

interface Props {
hasEditPermission: boolean;
walicaId?: string | null;
themes: ThemeOption[];
selectedThemeId: string;
secretModeEnabled: boolean;
secretModeOffset: number;
walicaUrl: string;
onEditModeToggle: () => void;
onThemeChange: (themeId: string) => void;
onSecretModeChange: (enabled: boolean, offset: number) => void;
onWalicaUpdate: (url: string) => void;
onWalicaOpen: () => void;
}

let {
hasEditPermission,
walicaId,
themes,
selectedThemeId,
secretModeEnabled,
secretModeOffset,
walicaUrl,
onEditModeToggle,
onThemeChange,
onSecretModeChange,
onWalicaUpdate,
onWalicaOpen,
}: Props = $props();

let showSettingsMenu = $state(false);
let showThemeSelect = $state(false);

function handleSettingsClick() {
showSettingsMenu = !showSettingsMenu;
showThemeSelect = false;
}

function handleShowThemeSelect() {
showThemeSelect = true;
showSettingsMenu = false;
}

function handleThemeChange(themeId: string) {
showThemeSelect = false;
onThemeChange(themeId);
}

function handleEditModeToggle() {
showSettingsMenu = false;
onEditModeToggle();
}
</script>

<nav class="standard-autumn-bottom-nav" aria-label="フッターメニュー">
<button
class="standard-autumn-bottom-btn"
title="ホーム"
aria-label="ホーム"
onclick={() => goto("/")}
>
{@html HomeIcon}
<span>Home</span>
</button>

{#if walicaId}
<button
class="standard-autumn-bottom-btn"
title="Walica"
aria-label="Walica"
onclick={onWalicaOpen}
>
{@html WalicaIcon}
<span>Walica</span>
</button>
{/if}

<div class="standard-autumn-btn-wrapper">
<button
class="standard-autumn-bottom-btn"
title={hasEditPermission
? "閲覧モードに切り替え"
: "編集モードに切り替え"}
aria-label={hasEditPermission
? "閲覧モードに切り替え"
: "編集モードに切り替え"}
onclick={handleEditModeToggle}
>
{#if hasEditPermission}
{@html ViewIcon}
<span>View</span>
{:else}
{@html EditIcon}
<span>Edit</span>
{/if}
</button>
</div>

{#if hasEditPermission}
<div class="standard-autumn-btn-wrapper">
<button
class="standard-autumn-bottom-btn"
title="設定"
aria-label="設定"
onclick={handleSettingsClick}
>
{@html SettingsIcon}
<span>Settings</span>
</button>
{#if showSettingsMenu}
<SettingsMenu
{themes}
{selectedThemeId}
{secretModeEnabled}
{secretModeOffset}
{walicaUrl}
{showThemeSelect}
onThemeChange={handleThemeChange}
{onSecretModeChange}
{onWalicaUpdate}
onShowThemeSelect={handleShowThemeSelect}
onClose={() => (showSettingsMenu = false)}
/>
{/if}
{#if showThemeSelect}
<div class="standard-autumn-theme-select-popup">
<label for="theme-select" class="standard-autumn-theme-select-label"
>テーマを選択</label
>
<select
id="theme-select"
value={selectedThemeId}
onchange={(e) =>
handleThemeChange((e.target as HTMLSelectElement).value)}
class="standard-autumn-theme-select-input"
>
{#each themes as theme}
<option value={theme.id}>{theme.name}</option>
{/each}
</select>
</div>
{/if}
</div>
{/if}
</nav>
Loading