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;
-webkit-box-sizing: border-box;
box-sizing: border-box;
z-index: 0;
}
/* Styles for the close button */
@@ -82,18 +83,12 @@ body {
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 */
.clock-time {
color: #ffffff;
font-size: 48px;
font-family: 'IBM Plex Mono', monospace;
text-align: center;
margin-bottom: 10px;
}
/* SearchWidget form */
@@ -107,51 +102,11 @@ body {
padding: 5px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 4px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
background: rgba(255, 255, 255, 1);
color: #000;
}
/* Widget settings modal */
.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 {
.accept-button {
margin-top: 10px;
padding: 5px 10px;
background: #007bff;
@@ -161,7 +116,7 @@ body {
cursor: pointer;
}
.widget-modal-content button:hover {
.accept-button:hover {
background: #0056b3;
}
@@ -204,7 +159,6 @@ body {
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
@@ -305,3 +259,9 @@ body {
width: 100%;
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 <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) => {
+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 { loadableBackgroundImageState } from '../state/backgroundImage.state';
import { settingsService } from '../services/settingsService';
import { ModalOverlay } from './ModalOverlay';
const GRID_COLS = 36;
const GRID_ROWS = 18;
@@ -41,14 +42,21 @@ export const NewTab: React.FC = () => {
);
setWidgets(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 (
<>
<ModalOverlay />
<SettingsPanel
backgroundImage={backgroundImage}
setBackgroundImage={setBackgroundImage}
addWidget={addWidget}
/>
{showGrid && (
<div className="grid-overlay">
+49 -55
View File
@@ -1,76 +1,70 @@
import React, { useState } from 'react';
import { CogwheelIcon } from './CogwheelIcon';
import { useAtom } from 'jotai';
import { draggableState } from '../state/draggable.state';
import { useAtom, useSetAtom } from 'jotai';
import { editState } from '../state/edit.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 {
backgroundImage: string,
setBackgroundImage: (url: string) => void
setBackgroundImage: (url: string) => void,
addWidget: (widget: WidgetData) => void,
}
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, setBackgroundImage }) => {
//const [backgroundUrl, setBackgroundUrl] = useAtom(backgroundImageState);
const [dragEnabled, setDragEnabled] = useAtom(draggableState);
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ backgroundImage, setBackgroundImage, addWidget }) => {
const [editEnabled, setEditEnabled] = useAtom(editState);
const [showGrid, setShowGrid] = useAtom(showGridState);
const [isOpen, setIsOpen] = useState(false);
const [isModalShowing, setModalShowing] = useState(false);
const handleSave = () => {
//settingsService.saveSetting('backgroundUrl', backgroundUrl)
settingsService.saveSetting('dragEnabled', dragEnabled)
settingsService.saveSetting('showGrid', showGrid)
setIsOpen(false);
const createWidget = (type: WidgetTypes) => {
const newWidgetData = intitialWidgetData(type);
addWidget(newWidgetData)
};
const addWidget = (type: string) => {
//const newWidget = createBaseWidget(type, Math.floor(gridCols / 2), Math.floor(gridRows / 2), dragEnabled);
//saveSettings({ widgets: [...settings.widgets, newWidget] });
};
return (
<>
<button className="settings-toggle" onClick={() => setIsOpen(!isOpen)}>
<button className="settings-toggle" onClick={() => setModalShowing(true)}>
<CogwheelIcon size={24} />
</button>
{isOpen && (
<div className="settings-modal">
<div className="modal-content">
<h2>Settings</h2>
<label>
Background URL:
<input
type="text"
value={backgroundImage}
onChange={(e) => setBackgroundImage(e.target.value)}
placeholder="Enter image URL"
/>
</label>
<label>
Drag Enabled:
<input
type="checkbox"
checked={dragEnabled}
onChange={(e) => setDragEnabled(e.target.checked)}
/>
</label>
<label>
Show Grid:
<input
type="checkbox"
checked={showGrid}
onChange={(e) => setShowGrid(e.target.checked)}
/>
</label>
<div className="widget-buttons">
<button onClick={() => addWidget('clock')}>Add Clock</button>
<button onClick={() => addWidget('search')}>Add Search</button>
</div>
<button className="close-modal" onClick={handleSave}>Save & Close</button>
</div>
<ModalOverlay
setVisible={setModalShowing}
visible={isModalShowing}
>
<h2>Settings</h2>
<label>
Background URL:
<input
type="text"
value={backgroundImage}
onChange={(e) => setBackgroundImage(e.target.value)}
placeholder="Enter image URL"
/>
</label>
<label>
Drag Enabled:
<input
type="checkbox"
checked={editEnabled}
onChange={() => setEditEnabled(b => !b)}
/>
</label>
<label>
Show Grid:
<input
type="checkbox"
checked={showGrid}
onChange={() => setShowGrid(b => !b)}
/>
</label>
<div className="widget-buttons">
<button onClick={() => createWidget('clock')}>Add Clock</button>
<button onClick={() => createWidget('search')}>Add Search</button>
</div>
)}
</ModalOverlay >
</>
);
};
+70 -57
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 { CogwheelIcon } from './CogwheelIcon';
import { useAtom, useAtomValue } from 'jotai';
import { draggableState } from '../state/draggable.state';
import { useAtomValue } from 'jotai';
import { editState } from '../state/edit.state';
import { CloseButton } from './Closebutton';
import { ModalOverlay } from './ModalOverlay';
interface WidgetProps {
widget: WidgetData;
gridSize: { width: number; height: number };
children: React.ReactNode;
settingsContent?: React.ReactNode;
settingsContent: ReactElement;
updateWidget: (id: string, data: Partial<WidgetData>) => void
}
@@ -17,19 +19,21 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
const modalRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
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 [heightInput, setHeightInput] = useState(widget.gridHeight);
const isDraggable = useAtomValue(draggableState)
const isDraggable = useAtomValue(editState)
const snapToGrid = (pos: number, grid: number) => Math.round(pos / grid);
const handleMouseDown = (e: React.MouseEvent) => {
if(!isDraggable || isSettingsOpen){
if (!isDraggable || isSettingsOpen) {
return
}
if (widgetRef.current && e.target === widgetRef.current) {
e.stopPropagation()
if (!!widgetRef.current) {
setIsDragging(true);
const rect = widgetRef.current.getBoundingClientRect();
setDragOffset({
@@ -62,22 +66,16 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
const handleClickOutside = (e: MouseEvent) => {
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 newHeight = Math.max(1, heightInput);
updateWidget(widget.id, { gridWidth: newWidth, gridHeight: newHeight });
setWidthInput(newWidth);
setHeightInput(newHeight);
setIsSettingsOpen(false);
};
}, [heightInput, widthInput])
useEffect(() => {
if (isDragging) {
@@ -110,55 +108,70 @@ export const Widget: React.FC<WidgetProps> = ({ widget, gridSize, children, sett
}
}, [widget.gridX, widget.gridY, widget.gridWidth, widget.gridHeight, gridSize, isDragging]);
const genericSettingsContent = (
<>
<label>
Width (grid cells):
<input
type="number"
min="1"
value={widthInput}
onChange={(e) => setWidthInput(parseInt(e.target.value) || 1)}
/>
</label>
<label>
Height (grid cells):
<input
type="number"
min="1"
value={heightInput}
onChange={(e) => setHeightInput(parseInt(e.target.value) || 1)}
/>
</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}
<ModalOverlay
setVisible={setSettingsOpen}
visible={isSettingsOpen}
>
<CogwheelIcon size={24} />
</button>
<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}
{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>
);
};
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>
Width (grid cells):
<input
type="number"
min="1"
value={widthInput}
onChange={(e) => { setWidthInput(parseInt(e.target.value)); }}
/>
</label>
<label>
Height (grid cells):
<input
type="number"
min="1"
value={heightInput}
onChange={(e) => { setHeightInput(parseInt(e.target.value)); }}
/>
</label>
</>
}
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { WidgetData, SearchWidgetData, ClockWidgetData, WidgetExport, WidgetTypes } from '../types';
import { WidgetData, WidgetExport, WidgetTypes } from '../types';
import { Widget } from './Widget';
import { SearchWidget } from './SearchWidget';
import { ClockWidget } from './ClockWidget';
+31 -27
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 = {
backgroundUrl: '',
widgets: [
{
id: 'clock-1',
type: 'clock',
gridX: 15,
gridY: 7,
gridWidth: 6, // Clock: 6x3
gridHeight: 3,
dragEnabled: true,
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,
dragEnabled: true,
engine: 'Google'
} as SearchWidgetData
],
dragEnabled: true,
showGrid: false
widgets: [],
editEnabled: true,
showGrid: true
};
export const intitialWidgetData = (type: WidgetTypes): WidgetData => {
switch (type) {
case 'clock':
return {
gridHeight: 2,
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'
} as SearchWidgetData
}
}
-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 { 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 {
backgroundUrl: string;
widgets: WidgetData[];
dragEnabled: boolean;
editEnabled: boolean;
showGrid: boolean;
}