Skip to content

Commit 7fb1d46

Browse files
Merge pull request #139 from TigerAppsOrg/adv-search-improvements
Adv search improvements
2 parents 3251733 + 54ab0c8 commit 7fb1d46

File tree

10 files changed

+523
-294
lines changed

10 files changed

+523
-294
lines changed

apps/web/src/lib/components/general/style/ThemePanel.svelte

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,24 @@
3333
"Pixel"
3434
]);
3535
36+
const LAST_THEME_KEY = "recal-last-selected-theme";
37+
3638
// Track the last selected theme for "Reset to Theme" functionality
39+
// Load from localStorage on init
3740
let lastSelectedTheme: { name: string; colors: CalColors } | null = null;
3841
42+
// Load last selected theme from localStorage on mount
43+
if (typeof window !== "undefined") {
44+
const stored = localStorage.getItem(LAST_THEME_KEY);
45+
if (stored) {
46+
try {
47+
lastSelectedTheme = JSON.parse(stored);
48+
} catch {
49+
localStorage.removeItem(LAST_THEME_KEY);
50+
}
51+
}
52+
}
53+
3954
/**
4055
* Apply a preset palette
4156
*/
@@ -50,6 +65,14 @@
5065
calColors.set(hslColors);
5166
lastSelectedTheme = { name, colors: hslColors };
5267
68+
// Persist to localStorage
69+
if (typeof window !== "undefined") {
70+
localStorage.setItem(
71+
LAST_THEME_KEY,
72+
JSON.stringify(lastSelectedTheme)
73+
);
74+
}
75+
5376
// Auto-toggle dark mode for dark palettes
5477
if (DARK_PALETTES.has(name)) {
5578
darkTheme.set(true);
@@ -82,6 +105,11 @@
82105
calColors.set(DEFAULT_RCARD_COLORS);
83106
darkTheme.set(false);
84107
lastSelectedTheme = null;
108+
109+
// Remove from localStorage
110+
if (typeof window !== "undefined") {
111+
localStorage.removeItem(LAST_THEME_KEY);
112+
}
85113
};
86114
87115
/**

apps/web/src/lib/components/recal/Calendar.svelte

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { browser } from "$app/environment";
23
import { currentTerm } from "$lib/changeme";
34
import {
45
type BoxParam,
@@ -22,6 +23,24 @@
2223
import { slide } from "svelte/transition";
2324
import CalBox from "./calendar/CalBox.svelte";
2425
26+
const TIME_MARKS_KEY = "showTimeMarks";
27+
28+
// Load preference from localStorage on init
29+
if (browser) {
30+
const saved = localStorage.getItem(TIME_MARKS_KEY);
31+
if (saved !== null) {
32+
$searchSettings.style["Show Time Marks"] = saved === "true";
33+
}
34+
}
35+
36+
// Save preference to localStorage when it changes
37+
$: if (browser) {
38+
localStorage.setItem(
39+
TIME_MARKS_KEY,
40+
String($searchSettings.style["Show Time Marks"])
41+
);
42+
}
43+
2544
let toRender: BoxParam[] = [];
2645
2746
let prevSchedule: number = -1;
@@ -347,22 +366,40 @@
347366
<!--!------------------------------------------------------------------>
348367

349368
<div class="h-full">
350-
<div class="h-full w-full std-area flex rounded-md">
369+
<div class="h-full w-full std-area flex rounded-md relative">
370+
<!-- Time marks toggle button (absolutely positioned) -->
371+
<button
372+
class="absolute top-0 left-0 z-10 h-[4%] w-7 flex items-center justify-center
373+
dark:text-zinc-100 hover:text-zinc-600 hover:dark:text-zinc-300 hover:duration-150"
374+
on:click={() =>
375+
($searchSettings.style["Show Time Marks"] =
376+
!$searchSettings.style["Show Time Marks"])}
377+
title="Toggle time marks">
378+
<svg
379+
xmlns="http://www.w3.org/2000/svg"
380+
viewBox="0 0 20 20"
381+
fill="currentColor"
382+
class="w-4 h-4">
383+
<path
384+
d="M2 4.5A.5.5 0 012.5 4h6a.5.5 0 010 1h-6a.5.5 0 01-.5-.5zm0 5A.5.5 0 012.5 9h4a.5.5 0 010 1h-4a.5.5 0 01-.5-.5zm0 5a.5.5 0 01.5-.5h6a.5.5 0 010 1h-6a.5.5 0 01-.5-.5z" />
385+
</svg>
386+
</button>
387+
388+
<!-- Time marks column (only when enabled) -->
351389
{#if $searchSettings.style["Show Time Marks"]}
352390
<div
353-
class="w-10 h-full"
354-
transition:slide={{ axis: "x", duration: 150, easing: linear }}>
391+
class="h-full flex flex-col shrink-0 w-8"
392+
transition:slide={{ axis: "x", duration: 75, easing: linear }}>
355393
<div
356-
class="h-[4%] outline outline-[0.5px] outline-zinc-200
357-
dark:outline-zinc-700 overflow-hidden">
394+
class="h-[4%] outline outline-[0.5px] outline-zinc-200 dark:outline-zinc-700">
358395
</div>
359-
<div class="h-[96%] grid grid-cols-1">
396+
<div class="flex-1 grid grid-cols-1">
360397
{#each MARKERS as marker}
361398
<div
362-
class="text-xs font-light
363-
outline outline-[0.5px] outline-zinc-200
364-
dark:outline-zinc-700 pt-[1px] pl-[1px]
365-
overflow-hidden">
399+
class="text-[10px] font-light
400+
outline outline-[0.5px] outline-zinc-200
401+
dark:outline-zinc-700 pt-[1px] pl-[2px]
402+
overflow-hidden text-zinc-500 dark:text-zinc-400">
366403
{marker}
367404
</div>
368405
{/each}

apps/web/src/lib/components/recal/calendar/CalBox.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@
183183
{params.section.title}
184184
</div>
185185

186-
{#if ($searchSettings.style["Always Show Rooms"] || hovered) && isCourseBox(params) && params.section.room}
186+
{#if ($searchSettings.style["Always Show Calendar Box Info"] || hovered) && isCourseBox(params) && params.section.room}
187187
<div class="font-light text-2xs leading-3 pt-[1px]">
188188
{params.section.room}
189189
</div>
190190
{/if}
191191

192-
{#if ($searchSettings.style["Always Show Enrollments"] || hovered) && isCourseBox(params)}
192+
{#if ($searchSettings.style["Always Show Calendar Box Info"] || hovered) && isCourseBox(params)}
193193
<div class="font-light text-2xs leading-3 pt-[1px]">
194194
Enrollment: {params.section.tot}/{params.section.cap === 999
195195
? ""

apps/web/src/lib/components/recal/left/Saved.svelte

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@
4444
</span>
4545
<button
4646
on:click={() => modalStore.push("exportCal")}
47-
class="flex items-center gap-[1px] text-sm">
47+
class="flex items-center gap-1 text-sm">
4848
<svg
4949
xmlns="http://www.w3.org/2000/svg"
50-
viewBox="0 0 20 20"
51-
fill="currentColor"
52-
class="w-5 h-5 calbut">
50+
fill="none"
51+
viewBox="0 0 24 24"
52+
stroke-width="1.5"
53+
stroke="currentColor"
54+
class="w-4 h-4 calbut">
5355
<path
54-
fill-rule="evenodd"
55-
d="M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z"
56-
clip-rule="evenodd" />
56+
stroke-linecap="round"
57+
stroke-linejoin="round"
58+
d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z" />
5759
</svg>
5860

5961
<p class="calbut">Export</p>

apps/web/src/lib/components/recal/left/SearchBar.svelte

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@
1212
searchSettings
1313
} from "$lib/stores/recal";
1414
import { sectionData } from "$lib/stores/rsections";
15-
import { getStyles } from "$lib/stores/styles";
15+
import { calColors, darkTheme, getStyles } from "$lib/stores/styles";
16+
17+
// Adjust HSL lightness: positive = darker, negative = lighter
18+
const adjustLightness = (hsl: string, amount: number): string => {
19+
const match = hsl.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/);
20+
if (!match) return hsl;
21+
const [, h, s, l] = match;
22+
const newL = Math.max(0, Math.min(100, parseInt(l) - amount));
23+
return `hsl(${h}, ${s}%, ${newL}%)`;
24+
};
1625
import { toastStore } from "$lib/stores/toast";
1726
import type { SupabaseClient } from "@supabase/supabase-js";
1827
import { getContext } from "svelte";
@@ -21,6 +30,7 @@
2130
const supabase = getContext("supabase") as SupabaseClient;
2231
2332
let inputBar: HTMLInputElement;
33+
let searchFocused = false;
2434
2535
// Number of results, under which sections are added automatically
2636
const THRESHOLD = 20;
@@ -55,7 +65,19 @@
5565
sectionData.add(supabase, $currentTerm, $searchResults[i].id);
5666
};
5767
58-
$: cssVarStyles = getStyles("2");
68+
// Re-run when calColors changes (getStyles uses get() internally)
69+
let cssVarStyles: string;
70+
$: $calColors, (cssVarStyles = getStyles("2"));
71+
72+
// Adjust gradient colors: darken in light mode, lighten in dark mode
73+
$: adj = $darkTheme ? -25 : 15;
74+
$: gradColors = [
75+
adjustLightness($calColors["0"], adj),
76+
adjustLightness($calColors["1"], adj),
77+
adjustLightness($calColors["2"], adj),
78+
adjustLightness($calColors["4"], adj),
79+
adjustLightness($calColors["5"], adj)
80+
];
5981
</script>
6082

6183
<div class="flex flex-col justify-between h-16" style={cssVarStyles}>
@@ -87,9 +109,11 @@
87109
placeholder="Search"
88110
class="search-input std-area rounded-md"
89111
bind:this={inputBar}
90-
on:input={triggerSearch} />
112+
on:input={triggerSearch}
113+
on:focus={() => (searchFocused = true)}
114+
on:blur={() => (searchFocused = false)} />
91115
<button
92-
class="adv-search"
116+
class="adv-search {searchFocused ? 'focused' : ''}"
93117
on:click={() => {
94118
if (!$ready)
95119
toastStore.add("error", "Please wait for the data to load");
@@ -100,8 +124,38 @@
100124
fill="none"
101125
viewBox="0 0 24 24"
102126
stroke-width="1.5"
103-
stroke="currentColor"
104-
class="w-6 h-6">
127+
stroke={searchFocused ? "url(#theme-gradient)" : "currentColor"}
128+
class="w-6 h-6 gear-icon">
129+
<defs>
130+
<linearGradient
131+
id="theme-gradient"
132+
x1="0%"
133+
y1="0%"
134+
x2="100%"
135+
y2="100%">
136+
<stop offset="0%" stop-color={gradColors[0]}>
137+
<animate
138+
attributeName="stop-color"
139+
values={`${gradColors[0]};${gradColors[1]};${gradColors[2]};${gradColors[3]};${gradColors[4]};${gradColors[0]}`}
140+
dur="3s"
141+
repeatCount="indefinite" />
142+
</stop>
143+
<stop offset="50%" stop-color={gradColors[2]}>
144+
<animate
145+
attributeName="stop-color"
146+
values={`${gradColors[2]};${gradColors[3]};${gradColors[4]};${gradColors[0]};${gradColors[1]};${gradColors[2]}`}
147+
dur="3s"
148+
repeatCount="indefinite" />
149+
</stop>
150+
<stop offset="100%" stop-color={gradColors[4]}>
151+
<animate
152+
attributeName="stop-color"
153+
values={`${gradColors[4]};${gradColors[0]};${gradColors[1]};${gradColors[2]};${gradColors[3]};${gradColors[4]}`}
154+
dur="3s"
155+
repeatCount="indefinite" />
156+
</stop>
157+
</linearGradient>
158+
</defs>
105159
<path
106160
stroke-linecap="round"
107161
stroke-linejoin="round"
@@ -121,12 +175,21 @@
121175
}
122176
123177
.adv-search {
124-
@apply h-10 w-10 flex justify-center items-center
125-
dark:text-zinc-100;
178+
@apply h-10 w-10 flex justify-center items-center
179+
dark:text-zinc-100;
180+
}
181+
182+
.adv-search:hover svg {
183+
animation: spin 0.8s linear infinite;
126184
}
127185
128-
.adv-search:hover {
129-
@apply text-zinc-600 dark:text-zinc-300 duration-150;
186+
@keyframes spin {
187+
from {
188+
transform: rotate(0deg);
189+
}
190+
to {
191+
transform: rotate(360deg);
192+
}
130193
}
131194
132195
.togglebutton {

apps/web/src/lib/components/recal/left/elements/CourseCard.svelte

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -64,39 +64,17 @@
6464
}
6565
};
6666
67-
// Color by rating
67+
// Search result styling
6868
if (category === "search") {
6969
styles.stripes = "";
70-
if ($searchSettings.style["Color by Rating"]) {
71-
if (!course.rating) {
72-
styles.color = "hsl(0, 0%, 50%)";
73-
fillStyles();
74-
} else if (course.rating >= 4.5) {
75-
styles.color = "hsl(120, 52%, 75%)";
76-
fillStyles();
77-
} else if (course.rating >= 4.0) {
78-
styles.color = "hsl(197, 34%, 72%)";
79-
fillStyles();
80-
} else if (course.rating >= 3.5) {
81-
styles.color = "hsl(60, 96%, 74%)";
82-
fillStyles();
83-
} else if (course.rating >= 3.0) {
84-
styles.color = "hsl(35, 99%, 65%)";
85-
fillStyles();
86-
} else {
87-
styles.color = "hsl(1, 100%, 69%)";
88-
fillStyles();
89-
}
70+
if ($darkTheme) {
71+
styles.color = "hsl(0, 0%, 10%)";
72+
styles.text = "hsl(0, 0%, 90%)";
73+
styles.hoverColor = "hsl(0, 0%, 10%)";
74+
styles.hoverText = "hsl(0, 0%, 100%)";
9075
} else {
91-
if ($darkTheme) {
92-
styles.color = "hsl(0, 0%, 10%)";
93-
styles.text = "hsl(0, 0%, 90%)";
94-
styles.hoverColor = "hsl(0, 0%, 10%)";
95-
styles.hoverText = "hsl(0, 0%, 100%)";
96-
} else {
97-
styles.color = "hsl(0, 0%,100%)";
98-
fillStyles();
99-
}
76+
styles.color = "hsl(0, 0%,100%)";
77+
fillStyles();
10078
}
10179
10280
// Dynamic color (saved courses)
@@ -193,9 +171,31 @@
193171
{#if $searchSettings.style["Show Weighted Rating"]}
194172
[{course.adj_rating} adj]
195173
{/if}
174+
{#if $searchSettings.style["Show Enrollment"]}
175+
{@const sections =
176+
$sectionData[$currentTerm]?.[course.id] || []}
177+
{@const priority = ["L", "S", "C", "P", "B", "D", "U"]}
178+
{@const mainCat = priority.find(cat =>
179+
sections.some(s => s.category === cat)
180+
)}
181+
{@const mainSections = mainCat
182+
? sections.filter(s => s.category === mainCat)
183+
: []}
184+
{@const totalEnroll = mainSections.reduce(
185+
(sum, s) => sum + s.tot,
186+
0
187+
)}
188+
{@const totalCap = mainSections.reduce(
189+
(sum, s) => sum + (s.cap !== 999 ? s.cap : 0),
190+
0
191+
)}
192+
Enrollment: {totalCap > 0
193+
? `${totalEnroll}/${totalCap}`
194+
: "N/A"}
195+
{/if}
196196
</div>
197197

198-
{#if $searchSettings.style["Show Instructor(s)"]}
198+
{#if $searchSettings.style["Show Instructors"]}
199199
{#if course.instructors && course.instructors.length > 0}
200200
{#each course.instructors as instructor}
201201
<div class="text-xs italic font-light">

0 commit comments

Comments
 (0)