Skip to content

Commit b6caaff

Browse files
committed
resolved comments
1 parent 5d6133e commit b6caaff

9 files changed

Lines changed: 217 additions & 45 deletions

File tree

bg.png

608 KB
Loading

extension/public/bg.png

608 KB
Loading

extension/src/App.css

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,27 @@ h3, h4 {
339339

340340
.command-center-widgets {
341341
display: grid;
342-
grid-template-columns: repeat(2, minmax(0, 1fr));
342+
grid-template-columns: repeat(3, minmax(0, 1fr));
343343
align-items: start;
344344
gap: 20px;
345345
width: 100%;
346-
max-width: 1120px;
346+
max-width: 1520px;
347347
margin: 0 auto;
348348
padding: 0 20px 48px 20px;
349349
position: relative;
350350
z-index: 10;
351351
}
352352

353+
.command-center-widgets.columns-2 {
354+
grid-template-columns: repeat(2, minmax(0, 1fr));
355+
max-width: 1120px;
356+
}
357+
358+
.command-center-widgets.columns-3 {
359+
grid-template-columns: repeat(3, minmax(0, 1fr));
360+
max-width: 1520px;
361+
}
362+
353363
.widget-arrange-toolbar {
354364
width: 100%;
355365
max-width: 1120px;
@@ -445,10 +455,10 @@ h3, h4 {
445455
position: relative;
446456
border-radius: var(--radius-lg);
447457
transition:
448-
transform 320ms var(--ease-apple),
449-
box-shadow 320ms var(--ease-apple),
450-
border-color 320ms var(--ease-apple),
451-
background 320ms var(--ease-apple);
458+
transform 220ms var(--ease-apple),
459+
box-shadow 220ms var(--ease-apple),
460+
border-color 220ms var(--ease-apple),
461+
background 220ms var(--ease-apple);
452462
}
453463

454464
.widget-slot.is-dragging {
@@ -462,7 +472,7 @@ h3, h4 {
462472
}
463473

464474
.widget-slot.is-snapped {
465-
animation: widgetSnap 420ms var(--ease-heavy);
475+
animation: widgetSnap 280ms var(--ease-heavy);
466476
}
467477

468478
.widget-slot-badge {
@@ -589,8 +599,13 @@ button:disabled {
589599

590600
body {
591601
background-color: var(--bg-main);
592-
background-image: radial-gradient(ellipse at 50% 0%, rgba(231, 117, 0, 0.06) 0%, transparent 60%);
593-
background-size: 100vw 100vh;
602+
background-image:
603+
linear-gradient(180deg, rgba(8, 8, 10, 0.72) 0%, rgba(8, 8, 10, 0.84) 100%),
604+
radial-gradient(ellipse at 50% 0%, rgba(231, 117, 0, 0.14) 0%, rgba(0, 0, 0, 0) 58%),
605+
url("../public/bg.png");
606+
background-size: cover;
607+
background-position: center center;
608+
background-repeat: no-repeat;
594609
background-attachment: fixed;
595610
color: var(--text-primary) !important;
596611
min-height: 100vh;
@@ -654,6 +669,20 @@ body {
654669
min-height: 220px;
655670
}
656671

672+
.today-news-source {
673+
align-self: flex-start;
674+
margin-bottom: 12px;
675+
padding: 4px 10px;
676+
border-radius: 999px;
677+
border: 1px solid rgba(231, 117, 0, 0.35);
678+
background: rgba(231, 117, 0, 0.12);
679+
color: rgba(255, 231, 207, 0.95);
680+
font-size: 11px;
681+
font-weight: 700;
682+
letter-spacing: 0.04em;
683+
text-transform: uppercase;
684+
}
685+
657686
.today-news-link {
658687
display: inline-block;
659688
color: var(--text-primary);

extension/src/App.tsx

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,70 @@ const AVAILABLE_WIDGETS = [
2626

2727
const DEFAULT_WIDGETS = ["dhall", "weather", "today"];
2828
const ALLOWED_WIDGET_IDS = new Set(AVAILABLE_WIDGETS.map((widget) => widget.id));
29-
type WidgetColumn = "left" | "right";
29+
const ALL_WIDGET_COLUMNS = ["left", "middle", "right"] as const;
30+
const MIN_WIDGET_COLUMNS = 2;
31+
const MAX_WIDGET_COLUMNS = 3;
32+
33+
type WidgetColumn = (typeof ALL_WIDGET_COLUMNS)[number];
3034
type WidgetLayout = Record<WidgetColumn, string[]>;
3135

32-
const WIDGET_COLUMNS: WidgetColumn[] = ["left", "right"];
36+
const getActiveColumns = (columnCount: number): WidgetColumn[] => {
37+
const safeCount = Math.max(MIN_WIDGET_COLUMNS, Math.min(MAX_WIDGET_COLUMNS, columnCount));
38+
return safeCount === 2 ? ["left", "right"] : ["left", "middle", "right"];
39+
};
40+
41+
const createEmptyLayout = (): WidgetLayout => ({
42+
left: [],
43+
middle: [],
44+
right: [],
45+
});
3346

34-
const buildDefaultLayout = (widgets: string[]): WidgetLayout => {
35-
const layout: WidgetLayout = { left: [], right: [] };
47+
const buildDefaultLayout = (widgets: string[], columnCount: number): WidgetLayout => {
48+
const layout = createEmptyLayout();
49+
const activeColumns = getActiveColumns(columnCount);
3650
widgets.forEach((widgetId, index) => {
37-
const targetColumn: WidgetColumn = index % 2 === 0 ? "left" : "right";
51+
const targetColumn = activeColumns[index % activeColumns.length];
3852
layout[targetColumn].push(widgetId);
3953
});
4054
return layout;
4155
};
4256

4357
const normalizeWidgetLayout = (
4458
layout: Partial<WidgetLayout> | null,
45-
activeWidgets: string[]
59+
activeWidgets: string[],
60+
columnCount: number
4661
): WidgetLayout => {
62+
const activeColumns = getActiveColumns(columnCount);
4763
const activeSet = new Set(activeWidgets);
4864
const seen = new Set<string>();
4965

50-
const sanitize = (ids?: string[]) =>
51-
(ids || []).filter((id) => {
66+
const normalized = createEmptyLayout();
67+
68+
// Keep explicit user ordering for currently active columns.
69+
activeColumns.forEach((column) => {
70+
normalized[column] = (layout?.[column] || []).filter((id) => {
5271
if (!activeSet.has(id) || seen.has(id)) return false;
5372
seen.add(id);
5473
return true;
5574
});
75+
});
5676

57-
const normalized: WidgetLayout = {
58-
left: sanitize(layout?.left),
59-
right: sanitize(layout?.right),
60-
};
77+
// Re-home widgets from inactive columns + any new/missing widgets.
78+
const extras = ALL_WIDGET_COLUMNS.filter((column) => !activeColumns.includes(column))
79+
.flatMap((column) => layout?.[column] || [])
80+
.filter((id) => activeSet.has(id) && !seen.has(id));
6181

6282
activeWidgets.forEach((id) => {
83+
if (!seen.has(id)) {
84+
extras.push(id);
85+
}
86+
});
87+
88+
extras.forEach((id) => {
6389
if (seen.has(id)) return;
64-
const targetColumn =
65-
normalized.left.length <= normalized.right.length ? "left" : "right";
90+
const targetColumn = activeColumns.reduce((shortest, candidate) =>
91+
normalized[candidate].length < normalized[shortest].length ? candidate : shortest
92+
);
6693
normalized[targetColumn].push(id);
6794
seen.add(id);
6895
});
@@ -79,6 +106,10 @@ function App() {
79106
const [draggingWidgetId, setDraggingWidgetId] = useState<string | null>(null);
80107
const [dropTargetKey, setDropTargetKey] = useState<string | null>(null);
81108
const [snappedWidgetId, setSnappedWidgetId] = useState<string | null>(null);
109+
const [columnCount, setColumnCount] = useState<2 | 3>(() => {
110+
const saved = storage.getLocalStorage(StorageKeys.WIDGET_COLUMN_COUNT);
111+
return saved === "2" ? 2 : 3;
112+
});
82113

83114
const [activeWidgets, setActiveWidgets] = useState<string[]>(() => {
84115
const saved = storage.getLocalStorage(StorageKeys.ACTIVE_WIDGETS);
@@ -95,13 +126,13 @@ function App() {
95126
});
96127
const [widgetLayout, setWidgetLayout] = useState<WidgetLayout>(() => {
97128
const saved = storage.getLocalStorage(StorageKeys.WIDGET_LAYOUT);
98-
if (!saved) return buildDefaultLayout(activeWidgets);
129+
if (!saved) return buildDefaultLayout(activeWidgets, columnCount);
99130

100131
try {
101132
const parsed = JSON.parse(saved) as Partial<WidgetLayout>;
102-
return normalizeWidgetLayout(parsed, activeWidgets);
133+
return normalizeWidgetLayout(parsed, activeWidgets, columnCount);
103134
} catch {
104-
return buildDefaultLayout(activeWidgets);
135+
return buildDefaultLayout(activeWidgets, columnCount);
105136
}
106137
});
107138

@@ -130,13 +161,17 @@ function App() {
130161
}, [activeWidgets]);
131162

132163
useEffect(() => {
133-
setWidgetLayout((prev) => normalizeWidgetLayout(prev, activeWidgets));
134-
}, [activeWidgets]);
164+
setWidgetLayout((prev) => normalizeWidgetLayout(prev, activeWidgets, columnCount));
165+
}, [activeWidgets, columnCount]);
135166

136167
useEffect(() => {
137168
storage.setLocalStorage(StorageKeys.WIDGET_LAYOUT, JSON.stringify(widgetLayout));
138169
}, [widgetLayout]);
139170

171+
useEffect(() => {
172+
storage.setLocalStorage(StorageKeys.WIDGET_COLUMN_COUNT, String(columnCount));
173+
}, [columnCount]);
174+
140175
const toggleWidget = (id: string) => {
141176
if (!ALLOWED_WIDGET_IDS.has(id)) return;
142177
setActiveWidgets(prev =>
@@ -163,14 +198,26 @@ function App() {
163198
if (!fromId) return;
164199

165200
setWidgetLayout((prev) => {
166-
const next = normalizeWidgetLayout(prev, activeWidgets);
167-
next.left = next.left.filter((id) => id !== fromId);
168-
next.right = next.right.filter((id) => id !== fromId);
169-
170-
const insertionIndex = Math.max(
171-
0,
172-
Math.min(toIndex, next[toColumn].length)
173-
);
201+
const next = normalizeWidgetLayout(prev, activeWidgets, columnCount);
202+
let fromColumn: WidgetColumn | null = null;
203+
let fromIndex = -1;
204+
205+
for (const column of ALL_WIDGET_COLUMNS) {
206+
const index = next[column].indexOf(fromId);
207+
if (index === -1 || fromColumn) continue;
208+
fromColumn = column;
209+
fromIndex = index;
210+
}
211+
212+
if (!fromColumn) return next;
213+
214+
next[fromColumn] = next[fromColumn].filter((id) => id !== fromId);
215+
216+
let insertionIndex = Math.max(0, Math.min(toIndex, next[toColumn].length));
217+
if (fromColumn === toColumn && fromIndex < toIndex) {
218+
insertionIndex = Math.max(0, insertionIndex - 1);
219+
}
220+
174221
next[toColumn].splice(insertionIndex, 0, fromId);
175222
return next;
176223
});
@@ -180,6 +227,7 @@ function App() {
180227

181228
const feedbackMailto =
182229
"mailto:aj5828@princeton.edu?subject=TodayCustom%20Feedback%20%28Free%20Coffee%29&body=Hey%20Khan%2C%0A%0AHere%20is%20my%20feedback%20for%20TodayCustom%3A%0A-%20%0A-%20%0A%0AThanks!";
230+
const activeColumns = getActiveColumns(columnCount);
183231

184232
return (
185233
<div className="App">
@@ -216,8 +264,10 @@ function App() {
216264
</div>
217265
)}
218266

219-
<div className={`command-center-widgets ${isArrangeMode ? "is-arranging" : ""}`}>
220-
{WIDGET_COLUMNS.map((column) => (
267+
<div
268+
className={`command-center-widgets columns-${columnCount} ${isArrangeMode ? "is-arranging" : ""}`}
269+
>
270+
{activeColumns.map((column) => (
221271
<div
222272
key={column}
223273
className={[
@@ -264,16 +314,20 @@ function App() {
264314
onDragOver={(e) => {
265315
if (!isArrangeMode) return;
266316
e.preventDefault();
317+
e.stopPropagation();
267318
if (dropTargetKey !== `${column}:${widgetId}`) {
268319
setDropTargetKey(`${column}:${widgetId}`);
269320
}
270321
}}
271322
onDrop={(e) => {
272323
if (!isArrangeMode) return;
273324
e.preventDefault();
325+
e.stopPropagation();
274326
const draggedId = e.dataTransfer.getData("text/plain");
275327
const targetIndex = widgetLayout[column].indexOf(widgetId);
276-
moveWidget(draggedId, column, targetIndex);
328+
const slotRect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
329+
const isDropAfter = e.clientY >= slotRect.top + slotRect.height / 2;
330+
moveWidget(draggedId, column, targetIndex + (isDropAfter ? 1 : 0));
277331
setDraggingWidgetId(null);
278332
setDropTargetKey(null);
279333
}}
@@ -305,6 +359,8 @@ function App() {
305359
activeWidgets={activeWidgets}
306360
toggleWidget={toggleWidget}
307361
availableWidgets={AVAILABLE_WIDGETS}
362+
columnCount={columnCount}
363+
onColumnCountChange={setColumnCount}
308364
/>
309365

310366
<a

extension/src/components/SettingsModal.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,43 @@
207207
transform: translateX(20px);
208208
}
209209

210+
.settings-layout-toggle {
211+
display: grid;
212+
grid-template-columns: 1fr 1fr;
213+
gap: 8px;
214+
border: 1px solid rgba(255, 255, 255, 0.12);
215+
border-radius: 14px;
216+
background: rgba(255, 255, 255, 0.04);
217+
padding: 8px;
218+
}
219+
220+
.settings-layout-option {
221+
border: 1px solid rgba(255, 255, 255, 0.14);
222+
border-radius: 10px;
223+
padding: 10px 12px;
224+
background: rgba(255, 255, 255, 0.04);
225+
color: var(--text-secondary, #a1a1aa);
226+
font-size: 13px;
227+
font-weight: 700;
228+
cursor: pointer;
229+
transition: background 160ms var(--ease-apple), border-color 160ms var(--ease-apple), color 160ms var(--ease-apple), transform 160ms var(--ease-apple);
230+
}
231+
232+
.settings-layout-option:hover {
233+
background: rgba(255, 255, 255, 0.1);
234+
color: var(--text-primary, #ffffff);
235+
}
236+
237+
.settings-layout-option:active {
238+
transform: scale(0.98);
239+
}
240+
241+
.settings-layout-option.active {
242+
background: rgba(231, 117, 0, 0.2);
243+
border-color: rgba(231, 117, 0, 0.7);
244+
color: #fff;
245+
}
246+
210247
.settings-upload-card {
211248
border: 1px solid rgba(255, 255, 255, 0.14);
212249
border-radius: 14px;

0 commit comments

Comments
 (0)