Functional but settings modals position and size badly

This commit is contained in:
Your Name
2025-03-16 18:46:30 -04:00
parent ab5380a399
commit 3e4105aca3
14 changed files with 220 additions and 201 deletions
+11 -51
View File
@@ -26,6 +26,7 @@ body {
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
z-index: 0;
} }
/* Styles for the close button */ /* Styles for the close button */
@@ -82,18 +83,12 @@ body {
background: #333; background: #333;
} }
/* Ensure container contents don't overlap the buttons */
.container > *:not(.close-button):not(.settings-button) {
margin-top: 20px;
}
/* Styles for ClockWidget time display */ /* Styles for ClockWidget time display */
.clock-time { .clock-time {
color: #ffffff; color: #ffffff;
font-size: 48px; font-size: 48px;
font-family: 'IBM Plex Mono', monospace; font-family: 'IBM Plex Mono', monospace;
text-align: center; text-align: center;
margin-bottom: 10px;
} }
/* SearchWidget form */ /* SearchWidget form */
@@ -107,51 +102,11 @@ body {
padding: 5px; padding: 5px;
border: 1px solid rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 4px; border-radius: 4px;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 1);
color: #fff; color: #000;
} }
/* Widget settings modal */ .accept-button {
.widget-settings-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.widget-modal-content {
position: relative;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
max-width: 300px;
width: 90%;
text-align: center;
}
.widget-modal-content label {
display: block;
margin: 10px 0;
color: #333;
}
.widget-modal-content select,
.widget-modal-content input[type="number"] {
width: 100%;
padding: 5px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}
.widget-modal-content button {
margin-top: 10px; margin-top: 10px;
padding: 5px 10px; padding: 5px 10px;
background: #007bff; background: #007bff;
@@ -161,7 +116,7 @@ body {
cursor: pointer; cursor: pointer;
} }
.widget-modal-content button:hover { .accept-button:hover {
background: #0056b3; background: #0056b3;
} }
@@ -204,7 +159,6 @@ body {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -305,3 +259,9 @@ body {
width: 100%; width: 100%;
height: 1px; height: 1px;
} }
.timezone-display{
font-size: small;
text-align: center;
color: #ffffff;
}
+4 -1
View File
@@ -27,7 +27,10 @@ export const ClockWidgetComponent: React.FC<ClockWidgetProps> = ({ widget, updat
return new Intl.DateTimeFormat('en-US', options).format(time); return new Intl.DateTimeFormat('en-US', options).format(time);
}; };
return <div className="clock-time">{formatTime()}</div>; return <>
<div className="clock-time">{formatTime()}</div>
<div className="timezone-display">{widget.timezone}</div>
</>;
}; };
export const ClockWidgetSettings = ({ widget, updateWidget }: ClockWidgetProps) => { export const ClockWidgetSettings = ({ widget, updateWidget }: ClockWidgetProps) => {
+11
View File
@@ -0,0 +1,11 @@
import React, { MouseEvent } from "react"
type ButtonProps = {
onClick: React.MouseEventHandler<HTMLButtonElement>
}
export function CloseButton({ onClick }: ButtonProps) {
return (
<button className="close-button" onClick={onClick}>×</button>
)
}
+19
View File
@@ -0,0 +1,19 @@
import { ReactElement, ReactNode, useRef, useState } from "react";
interface ModalOverlayProps {
visible: boolean;
setVisible: (b: boolean) => void;
children: ReactElement | ReactElement[];
}
export function ModalOverlay({visible, setVisible, children}: ModalOverlayProps) {
const modalRef = useRef<HTMLDivElement>(null);
return visible && <div className="settings-modal" ref={modalRef}>
<div className="modal-content">
{children}
<button className="close-modal" onClick={() => setVisible(false)}>Close</button>
</div>
</div>
}
+9 -1
View File
@@ -7,6 +7,7 @@ import { showGridState } from '../state/showGrid.state';
import { WidgetData } from '../types'; import { WidgetData } from '../types';
import { loadableBackgroundImageState } from '../state/backgroundImage.state'; import { loadableBackgroundImageState } from '../state/backgroundImage.state';
import { settingsService } from '../services/settingsService'; import { settingsService } from '../services/settingsService';
import { ModalOverlay } from './ModalOverlay';
const GRID_COLS = 36; const GRID_COLS = 36;
const GRID_ROWS = 18; const GRID_ROWS = 18;
@@ -41,14 +42,21 @@ export const NewTab: React.FC = () => {
); );
setWidgets(updatedWidgets); setWidgets(updatedWidgets);
settingsService.saveSetting('widgets', updatedWidgets) settingsService.saveSetting('widgets', updatedWidgets)
console.log('Updated widget:', { id, updates })
}; };
const addWidget = (data: WidgetData) => {
const updatedWidgets = [...widgets, data]
setWidgets([...widgets, data])
settingsService.saveSetting('widgets', updatedWidgets)
}
return ( return (
<> <>
<ModalOverlay />
<SettingsPanel <SettingsPanel
backgroundImage={backgroundImage} backgroundImage={backgroundImage}
setBackgroundImage={setBackgroundImage} setBackgroundImage={setBackgroundImage}
addWidget={addWidget}
/> />
{showGrid && ( {showGrid && (
<div className="grid-overlay"> <div className="grid-overlay">
+25 -31
View File
@@ -1,42 +1,39 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { CogwheelIcon } from './CogwheelIcon'; import { CogwheelIcon } from './CogwheelIcon';
import { useAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { draggableState } from '../state/draggable.state'; import { editState } from '../state/edit.state';
import { showGridState } from '../state/showGrid.state'; import { showGridState } from '../state/showGrid.state';
import { settingsService } from '../services/settingsService'; import { intitialWidgetData } from '../defaults';
import { WidgetData, WidgetTypes } from '../types';
import { modalContent, modalVisible } from '../state/modalOverlay.state';
import { ModalOverlay } from './ModalOverlay';
interface SettingsPanelProps { interface SettingsPanelProps {
backgroundImage: string, backgroundImage: string,
setBackgroundImage: (url: string) => void setBackgroundImage: (url: string) => void,
addWidget: (widget: WidgetData) => void,
} }
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, setBackgroundImage }) => { export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, setBackgroundImage, addWidget }) => {
//const [backgroundUrl, setBackgroundUrl] = useAtom(backgroundImageState); const [editEnabled, setEditEnabled] = useAtom(editState);
const [dragEnabled, setDragEnabled] = useAtom(draggableState);
const [showGrid, setShowGrid] = useAtom(showGridState); const [showGrid, setShowGrid] = useAtom(showGridState);
const [isOpen, setIsOpen] = useState(false); const [isModalShowing, setModalShowing] = useState(false);
const handleSave = () => { const createWidget = (type: WidgetTypes) => {
//settingsService.saveSetting('backgroundUrl', backgroundUrl) const newWidgetData = intitialWidgetData(type);
settingsService.saveSetting('dragEnabled', dragEnabled) addWidget(newWidgetData)
settingsService.saveSetting('showGrid', showGrid)
setIsOpen(false);
}; };
const addWidget = (type: string) => {
//const newWidget = createBaseWidget(type, Math.floor(gridCols / 2), Math.floor(gridRows / 2), dragEnabled);
//saveSettings({ widgets: [...settings.widgets, newWidget] });
};
return ( return (
<> <>
<button className="settings-toggle" onClick={() => setIsOpen(!isOpen)}> <button className="settings-toggle" onClick={() => setModalShowing(true)}>
<CogwheelIcon size={24} /> <CogwheelIcon size={24} />
</button> </button>
{isOpen && ( <ModalOverlay
<div className="settings-modal"> setVisible={setModalShowing}
<div className="modal-content"> visible={isModalShowing}
>
<h2>Settings</h2> <h2>Settings</h2>
<label> <label>
Background URL: Background URL:
@@ -51,8 +48,8 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, s
Drag Enabled: Drag Enabled:
<input <input
type="checkbox" type="checkbox"
checked={dragEnabled} checked={editEnabled}
onChange={(e) => setDragEnabled(e.target.checked)} onChange={() => setEditEnabled(b => !b)}
/> />
</label> </label>
<label> <label>
@@ -60,17 +57,14 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, s
<input <input
type="checkbox" type="checkbox"
checked={showGrid} checked={showGrid}
onChange={(e) => setShowGrid(e.target.checked)} onChange={() => setShowGrid(b => !b)}
/> />
</label> </label>
<div className="widget-buttons"> <div className="widget-buttons">
<button onClick={() => addWidget('clock')}>Add Clock</button> <button onClick={() => createWidget('clock')}>Add Clock</button>
<button onClick={() => addWidget('search')}>Add Search</button> <button onClick={() => createWidget('search')}>Add Search</button>
</div> </div>
<button className="close-modal" onClick={handleSave}>Save & Close</button> </ModalOverlay >
</div>
</div>
)}
</> </>
); );
}; };
+64 -51
View File
@@ -1,14 +1,16 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect, ReactElement } from 'react';
import { WidgetData } from '../types'; import { WidgetData } from '../types';
import { CogwheelIcon } from './CogwheelIcon'; import { CogwheelIcon } from './CogwheelIcon';
import { useAtom, useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { draggableState } from '../state/draggable.state'; import { editState } from '../state/edit.state';
import { CloseButton } from './Closebutton';
import { ModalOverlay } from './ModalOverlay';
interface WidgetProps { interface WidgetProps {
widget: WidgetData; widget: WidgetData;
gridSize: { width: number; height: number }; gridSize: { width: number; height: number };
children: React.ReactNode; children: React.ReactNode;
settingsContent?: React.ReactNode; settingsContent: ReactElement;
updateWidget: (id: string, data: Partial<WidgetData>) => void updateWidget: (id: string, data: Partial<WidgetData>) => void
} }
@@ -17,11 +19,11 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSettingsOpen, setSettingsOpen] = useState(false);
const [widthInput, setWidthInput] = useState(widget.gridWidth); const [widthInput, setWidthInput] = useState(widget.gridWidth);
const [heightInput, setHeightInput] = useState(widget.gridHeight); const [heightInput, setHeightInput] = useState(widget.gridHeight);
const isDraggable = useAtomValue(draggableState) const isDraggable = useAtomValue(editState)
const snapToGrid = (pos: number, grid: number) => Math.round(pos / grid); const snapToGrid = (pos: number, grid: number) => Math.round(pos / grid);
const handleMouseDown = (e: React.MouseEvent) => { const handleMouseDown = (e: React.MouseEvent) => {
@@ -29,7 +31,9 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
return return
} }
if (widgetRef.current && e.target === widgetRef.current) { e.stopPropagation()
if (!!widgetRef.current) {
setIsDragging(true); setIsDragging(true);
const rect = widgetRef.current.getBoundingClientRect(); const rect = widgetRef.current.getBoundingClientRect();
setDragOffset({ setDragOffset({
@@ -62,22 +66,16 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) { if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
setIsSettingsOpen(false); setSettingsOpen(false);
} }
}; };
const stopPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
const handleSizeChange = () => { useEffect(() => {
const newWidth = Math.max(1, widthInput); const newWidth = Math.max(1, widthInput);
const newHeight = Math.max(1, heightInput); const newHeight = Math.max(1, heightInput);
updateWidget(widget.id, { gridWidth: newWidth, gridHeight: newHeight }); updateWidget(widget.id, { gridWidth: newWidth, gridHeight: newHeight });
setWidthInput(newWidth); }, [heightInput, widthInput])
setHeightInput(newHeight);
setIsSettingsOpen(false);
};
useEffect(() => { useEffect(() => {
if (isDragging) { if (isDragging) {
@@ -110,15 +108,60 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
} }
}, [widget.gridX, widget.gridY, widget.gridWidth, widget.gridHeight, gridSize, isDragging]); }, [widget.gridX, widget.gridY, widget.gridWidth, widget.gridHeight, gridSize, isDragging]);
const genericSettingsContent = ( return (
<>
<div
ref={widgetRef}
className="container"
onMouseDown={handleMouseDown}
style={{ cursor: isDraggable && !isSettingsOpen ? 'grab' : 'default' }}
>
<ModalOverlay
setVisible={setSettingsOpen}
visible={isSettingsOpen}
>
<SettingsComponent
heightInput={heightInput}
setHeightInput={setHeightInput}
setWidthInput={setWidthInput}
widthInput={widthInput}
>
{settingsContent}
</SettingsComponent>
</ModalOverlay>
{isDraggable && <>
<CloseButton onClick={() => undefined /*removeWidget(widget.id)*/} />
<button
className="settings-button"
onClick={() => setSettingsOpen(true)}
>
<CogwheelIcon size={24} />
</button>
</>}
{children}
</div>
);
};
type GenericSettingsProps = {
widthInput: number;
setWidthInput: (n: number) => void;
heightInput: number;
setHeightInput: (n: number) => void;
children: ReactElement | ReactElement[]
}
function SettingsComponent({ widthInput, setWidthInput, heightInput, setHeightInput, children }: GenericSettingsProps) {
return <>
{children}
<label> <label>
Width (grid cells): Width (grid cells):
<input <input
type="number" type="number"
min="1" min="1"
value={widthInput} value={widthInput}
onChange={(e) => setWidthInput(parseInt(e.target.value) || 1)} onChange={(e) => { setWidthInput(parseInt(e.target.value)); }}
/> />
</label> </label>
<label> <label>
@@ -127,38 +170,8 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
type="number" type="number"
min="1" min="1"
value={heightInput} value={heightInput}
onChange={(e) => setHeightInput(parseInt(e.target.value) || 1)} onChange={(e) => { setHeightInput(parseInt(e.target.value)); }}
/> />
</label> </label>
<button onClick={handleSizeChange}>Apply</button>
</> </>
); }
return (
<div
ref={widgetRef}
className="container"
onMouseDown={handleMouseDown}
style={{ cursor: isDraggable && !isSettingsOpen ? 'grab' : 'default' }}
>
<button className="close-button" onClick={() => undefined /*removeWidget(widget.id)*/} onMouseDown={stopPropagation}>×</button>
<button
className="settings-button"
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
onMouseDown={stopPropagation}
>
<CogwheelIcon size={24} />
</button>
{children}
{isSettingsOpen && (
<div className="widget-settings-modal" onMouseDown={stopPropagation}>
<div ref={modalRef} className="widget-modal-content">
<button className="close-button" onClick={() => setIsSettingsOpen(false)} onMouseDown={stopPropagation}>×</button>
{settingsContent}
{genericSettingsContent}
</div>
</div>
)}
</div>
);
};
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { WidgetData, SearchWidgetData, ClockWidgetData, WidgetExport, WidgetTypes } from '../types'; import { WidgetData, WidgetExport, WidgetTypes } from '../types';
import { Widget } from './Widget'; import { Widget } from './Widget';
import { SearchWidget } from './SearchWidget'; import { SearchWidget } from './SearchWidget';
import { ClockWidget } from './ClockWidget'; import { ClockWidget } from './ClockWidget';
+29 -25
View File
@@ -1,31 +1,35 @@
import { GlobalSettings, SearchWidgetData, ClockWidgetData } from './types'; import { GlobalSettings, SearchWidgetData, ClockWidgetData, WidgetTypes, WidgetData } from './types';
export const DEFAULT_SETTINGS: GlobalSettings = { export const DEFAULT_SETTINGS: GlobalSettings = {
backgroundUrl: '', backgroundUrl: '',
widgets: [ widgets: [],
{ editEnabled: true,
id: 'clock-1', showGrid: true
type: 'clock', };
gridX: 15,
gridY: 7, export const intitialWidgetData = (type: WidgetTypes): WidgetData => {
gridWidth: 6, // Clock: 6x3 switch (type) {
gridHeight: 3, case 'clock':
dragEnabled: true, return {
showSeconds: true,
use24Hour: false,
timezone: 'UTC' // Default timezone
} as ClockWidgetData,
{
id: 'search-1',
type: 'search',
gridX: 13,
gridY: 10,
gridWidth: 10, // Search: 10x2
gridHeight: 2, gridHeight: 2,
dragEnabled: true, gridWidth: 6,
gridX: 15,
gridY: 6,
timezone: 'UTC',
showSeconds: false,
use24Hour: true,
type: 'clock',
id: `${type}-${Date.now()}`
} as ClockWidgetData
case 'search':
return {
gridHeight: 2,
gridWidth: 10,
gridX: 13,
gridY: 6,
id: `${type}-${Date.now()}`,
type: 'search',
engine: 'Google' engine: 'Google'
} as SearchWidgetData } as SearchWidgetData
], }
dragEnabled: true, }
showGrid: false
};
-3
View File
@@ -1,3 +0,0 @@
import { atom } from "jotai";
export const draggableState = atom<boolean>(false)
+4
View File
@@ -0,0 +1,4 @@
import { atom } from "jotai";
import { DEFAULT_SETTINGS } from "../defaults";
export const editState = atom<boolean>(DEFAULT_SETTINGS.editEnabled)
+5
View File
@@ -0,0 +1,5 @@
import { atom } from "jotai";
import { ReactNode } from "react";
export const modalVisible = atom<boolean>(false)
export const modalContent = atom<ReactNode>(undefined)
+2 -1
View File
@@ -1,3 +1,4 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { DEFAULT_SETTINGS } from "../defaults";
export const showGridState = atom<boolean>(false) export const showGridState = atom<boolean>(DEFAULT_SETTINGS.showGrid)
+1 -1
View File
@@ -25,7 +25,7 @@ export type WidgetTypes = (SearchWidgetData | ClockWidgetData)['type']
export interface GlobalSettings { export interface GlobalSettings {
backgroundUrl: string; backgroundUrl: string;
widgets: WidgetData[]; widgets: WidgetData[];
dragEnabled: boolean; editEnabled: boolean;
showGrid: boolean; showGrid: boolean;
} }