1- import React , { useState } from "react" ;
2- import Widget from "./Widget" ;
1+ import React , { createContext , useEffect , useState } from "react" ;
2+ import Widget , { WidgetState } from "./Widget" ;
3+ import WidgetMap from "./WidgetMap" ;
4+ import Header from "./Header" ;
35import Menu from "./Menu" ;
46import { Grid } from "./Grid" ;
5- import { Weather } from "./widgets/Weather" ;
6- import { Clock } from "./widgets/Clock" ;
7- import { ToDoList } from "./widgets/ToDoList" ;
8- import { Notepad } from "./widgets/Notepad" ;
9- import { Search } from "./widgets/Search" ;
10- import { Shortcut } from "./widgets/Shortcut" ;
7+ import { nanoid } from "nanoid" ;
118import styles from "./App.css" ;
12- import BatteryWidget from "./widgets/BatteryWidget" ;
13- import {
14- PencilSimpleIcon ,
15- CheckIcon ,
16- XIcon ,
17- ListIcon ,
18- } from "@phosphor-icons/react" ;
19-
20- const TestBox = ( ) => {
21- return < div className = { styles . container } > </ div > ;
22- } ;
9+
10+ interface AppContextType {
11+ widgets : WidgetState < any > [ ] ;
12+ editing : boolean ;
13+ deleting : boolean ;
14+ menuOpen : boolean ;
15+
16+ setWidgets : React . Dispatch < React . SetStateAction < WidgetState < any > [ ] > > ;
17+ setEditing : React . Dispatch < React . SetStateAction < boolean > > ;
18+ setDeleting : React . Dispatch < React . SetStateAction < boolean > > ;
19+ setMenuOpen : React . Dispatch < React . SetStateAction < boolean > > ;
20+
21+ saveTemplate : ( ) => void ;
22+ loadTemplate : ( ) => boolean ;
23+
24+ addWidget : ( type : string ) => WidgetState < any > ;
25+ removeWidget : ( id : string ) => void ;
26+ }
27+
28+ export const AppContext = createContext < AppContextType > ( null ) ;
29+
30+ const FallbackTemplate : WidgetState < any > [ ] = [
31+ {
32+ id : nanoid ( 6 ) ,
33+ type : "clock" ,
34+ size : { ...WidgetMap [ "clock" ] . size } ,
35+ position : { gridX : 10 , gridY : 1 } ,
36+ settings : { ...WidgetMap [ "clock" ] . settings } ,
37+ } ,
38+ {
39+ id : nanoid ( 6 ) ,
40+ type : "search" ,
41+ size : { ...WidgetMap [ "search" ] . size } ,
42+ position : { gridX : 8 , gridY : 3 } ,
43+ settings : { ...WidgetMap [ "search" ] . settings } ,
44+ } ,
45+ ] ;
2346
2447const App = ( ) => {
2548 const [ editing , setEditing ] = useState ( false ) ;
49+ const [ deleting , setDeleting ] = useState ( false ) ;
2650 const [ menuOpen , setMenuOpen ] = useState ( false ) ;
51+ const [ widgets , setWidgets ] = useState < WidgetState < any > [ ] > ( [ ] ) ;
52+
53+ // TODO: This system will eventually keep an
54+ // array of templates and store the index of
55+ // the active template. For the time being,
56+ // this uses a single template structure.
57+
58+ function saveTemplate ( ) {
59+ localStorage . setItem ( "template" , JSON . stringify ( widgets ) ) ;
60+ }
61+
62+ function loadTemplate ( ) : boolean {
63+ const template = localStorage . getItem ( "template" ) ;
64+
65+ if ( template === null ) {
66+ console . warn ( "No template stored" ) ;
67+ return false ;
68+ }
69+
70+ setWidgets ( JSON . parse ( template ) as WidgetState < any > [ ] ) ;
71+ return true ;
72+ }
73+
74+ function addWidget ( type : string ) {
75+ if ( ! Object . keys ( WidgetMap ) . includes ( type ) ) {
76+ console . error ( "Invalid widget type" ) ;
77+ return ;
78+ }
79+
80+ const widget : WidgetState < any > = {
81+ id : nanoid ( 6 ) ,
82+ type : type ,
83+ size : { ...WidgetMap [ type ] . size } ,
84+ position : { gridX : 0 , gridY : 0 } ,
85+ settings : { ...WidgetMap [ type ] . settings } ,
86+ } ;
87+
88+ setWidgets ( [ ...widgets , widget ] ) ;
89+ return widget ;
90+ }
91+
92+ function removeWidget ( id : string ) {
93+ setWidgets ( widgets . filter ( ( w ) => w . id !== id ) ) ;
94+ }
95+
96+ useEffect ( ( ) => {
97+ if ( ! loadTemplate ( ) ) {
98+ setWidgets ( FallbackTemplate ) ;
99+ }
100+ } , [ ] ) ;
27101
28102 return (
29- < div className = { styles . content } >
30- < div className = { styles . header } >
31- < div className = { styles . row } >
32- { editing && (
33- < button
34- className = { [ styles . container , styles . button ] . join ( " " ) }
35- onClick = { ( ) => {
36- setEditing ( ! editing ) ;
37- } }
38- >
39- < XIcon weight = "bold" > </ XIcon >
40- Cancel
41- </ button >
42- ) }
43-
44- < button
45- className = { [ styles . container , styles . button ] . join ( " " ) }
46- onClick = { ( ) => {
47- setEditing ( ! editing ) ;
48- } }
49- >
50- { editing && < CheckIcon weight = "bold" > </ CheckIcon > }
51- { ! editing && < PencilSimpleIcon weight = "bold" > </ PencilSimpleIcon > }
52- { editing ? "Done" : "Edit" }
53- </ button >
54-
55- < button
56- className = { [ styles . container , styles . button ] . join ( " " ) }
57- onClick = { ( ) => {
58- setMenuOpen ( ! menuOpen ) ;
59- } }
60- >
61- < ListIcon weight = "bold" > </ ListIcon >
62- Settings
63- </ button >
64- </ div >
65- </ div >
66-
67- < Grid width = { 24 } height = { 12 } editing = { editing } >
68- < Widget
69- size = { { width : 4 , height : 2 } }
70- position = { { gridX : 10 , gridY : 1 } }
71- >
72- < Clock > </ Clock >
73- </ Widget >
74-
75- < Widget
76- size = { { width : 5 , height : 5 } }
77- position = { { gridX : 18 , gridY : 1 } }
78- >
79- < ToDoList > </ ToDoList >
80- </ Widget >
81-
82- < Widget
83- size = { { width : 5 , height : 5 } }
84- position = { { gridX : 1 , gridY : 1 } }
85- >
86- < Notepad > </ Notepad >
87- </ Widget >
88-
89- < Widget
90- size = { { width : 8 , height : 1 } }
91- position = { { gridX : 8 , gridY : 3 } }
92- >
93- < Search > </ Search >
94- </ Widget >
95-
96- < Widget
97- size = { { width : 6 , height : 1 } }
98- position = { { gridX : 9 , gridY : 4 } }
99- >
100- < Weather > </ Weather >
101- </ Widget >
102-
103- < Widget
104- size = { { width : 1 , height : 1 } }
105- position = { { gridX : 10 , gridY : 5 } }
106- resizeable = { false }
107- >
108- < Shortcut url = "https://canvas.csuchico.edu" > </ Shortcut >
109- </ Widget >
110-
111- < Widget
112- size = { { width : 1 , height : 1 } }
113- position = { { gridX : 11 , gridY : 5 } }
114- resizeable = { false }
115- >
116- < Shortcut url = "https://outlook.com" > </ Shortcut >
117- </ Widget >
118-
119- < Widget
120- size = { { width : 1 , height : 1 } }
121- position = { { gridX : 12 , gridY : 5 } }
122- resizeable = { false }
123- >
124- < Shortcut url = "https://github.com" > </ Shortcut >
125- </ Widget >
126-
127- < Widget
128- size = { { width : 1 , height : 1 } }
129- position = { { gridX : 13 , gridY : 5 } }
130- resizeable = { false }
131- >
132- < Shortcut url = "https://stackoverflow.com" > </ Shortcut >
133- </ Widget >
134-
135- < Widget
136- size = { { width : 2 , height : 1 } }
137- position = { { gridX : 20 , gridY : 0 } }
138- >
139- < BatteryWidget />
140- </ Widget >
141- </ Grid >
142-
143- < Menu active = { menuOpen } > </ Menu >
103+ < div
104+ className = { styles . content }
105+ onClick = { ( e ) => {
106+ if ( e . target === e . currentTarget ) setMenuOpen ( false ) ;
107+ } }
108+ >
109+ < AppContext . Provider
110+ value = { {
111+ widgets,
112+ editing,
113+ deleting,
114+ menuOpen,
115+
116+ setWidgets,
117+ setEditing,
118+ setDeleting,
119+ setMenuOpen,
120+
121+ saveTemplate,
122+ loadTemplate,
123+
124+ addWidget,
125+ removeWidget,
126+ } }
127+ >
128+ < Header > </ Header >
129+ < Grid width = { 24 } height = { 12 } >
130+ { widgets . map ( ( state ) => {
131+ const map = WidgetMap [ state . type ] ;
132+ const Component = map . component ;
133+ return (
134+ < Widget
135+ size = { state . size || map . size }
136+ position = { state . position }
137+ resizeable = { map . resizable }
138+ id = { state . id }
139+ key = { state . id }
140+ >
141+ < Component { ...state } > </ Component >
142+ </ Widget >
143+ ) ;
144+ } ) }
145+ </ Grid >
146+
147+ < Menu active = { menuOpen } > </ Menu >
148+ </ AppContext . Provider >
144149 </ div >
145150 ) ;
146151} ;
147152
148- export default App ;
153+ export default App ;
0 commit comments