@@ -19,14 +19,17 @@ export type SelectEditingTask = {tag: "selectEditingTask"; id: string};
1919export type DragId = { type : "task" ; id : string } ;
2020export type DropId = { type : "filter" ; id : FilterId } | { type : "list" ; target : Tasks . DropTargetHandle } ;
2121
22+ export type FilterBarEvent = { tag : "filterBar" ; type : "set" ; id : string ; state : "include" | "exclude" } ;
23+
2224export type Event =
2325 | CheckEvent
2426 | TextFieldEvent < TextFieldId >
2527 | SelectEditingTask
2628 | SelectFilterEvent
2729 | TaskEditor . Event
2830 | Drag . DragEvent < DragId , DropId >
29- | Storage . Event ;
31+ | Storage . Event
32+ | FilterBarEvent ;
3033
3134export type Effect =
3235 | { type : "fileDownload" ; name : string ; contents : string }
@@ -41,6 +44,7 @@ export type State = {
4144 textFields : TextFieldStates < TextFieldId > ;
4245 editor : TaskEditor . State ;
4346 taskDrag : Drag . DragState < DragId , DropId > ;
47+ subtaskFilters : Tasks . SubtaskFilters ;
4448} ;
4549
4650export const empty : State = {
@@ -49,6 +53,7 @@ export const empty: State = {
4953 editor : TaskEditor . empty ,
5054 filter : "ready" ,
5155 taskDrag : { dragging : null , hovering : null } ,
56+ subtaskFilters : [ ] ,
5257} ;
5358
5459export type FilterIndicator = null | { text : string ; color : "red" | "orange" | "green" } | { } ;
@@ -65,24 +70,48 @@ export type SideBarSectionView = {title: string; filter: FilterId; filters: Filt
6570
6671export type FileControlsView = "saveLoad" | null ;
6772
73+ export type FilterBarView = {
74+ filters : { id : string ; label : string ; state : "neutral" | "include" | "exclude" } [ ] ;
75+ } ;
76+
6877export type View = {
6978 fileControls : FileControlsView ;
7079 addTask : { value : string } ;
7180 sideBar : SideBarSectionView [ ] ;
7281 taskList : TaskListView ;
82+ filterBar : FilterBarView ;
7383 editor : TaskEditor . View ;
7484} ;
7585
86+ function viewFilterBar ( app : State , args : { today : Date } ) : FilterBarView {
87+ const list = Tasks . view ( { ...app , today : args . today } ) ;
88+
89+ const anyPaused = list . some ( ( r ) => r . rows . some ( ( t ) => t . type === "task" && t . paused ) ) ;
90+ const anyUnpaused = list . some ( ( r ) => r . rows . some ( ( t ) => t . type === "task" && ! t . paused ) ) ;
91+
92+ function filterState ( id : string ) : "neutral" | "include" | "exclude" {
93+ const filter = app . subtaskFilters . find ( ( f ) => f . id === id ) ;
94+ if ( filter === undefined ) {
95+ return "neutral" ;
96+ }
97+ return filter . state ;
98+ }
99+
100+ if ( app . subtaskFilters . find ( ( f ) => f . id === "paused" ) || ( anyPaused && anyUnpaused ) )
101+ return { filters : [ { id : "paused" , label : "Paused" , state : filterState ( "paused" ) } ] } ;
102+ else return { filters : [ ] } ;
103+ }
104+
76105export function view ( app : State , args : { today : Date } ) : View {
77- const activeProjects = Tasks . activeProjects ( app . tasks ) ;
106+ const activeProjects = Tasks . activeProjects ( { ... app , ... args } ) ;
78107
79108 function filterView (
80109 filter : FilterId ,
81110 opts ?: { counter : "small" | "red" | "orange" | "green" ; count ?: number } ,
82111 ) : FilterView {
83112 function indicator ( ) {
84113 if ( ! opts ?. counter ) return null ;
85- const count = opts . count ?? Tasks . count ( app . tasks , filter , args ) ;
114+ const count = opts . count ?? Tasks . count ( { ... app , ... args } , filter ) ;
86115 if ( count === 0 ) return null ;
87116 if ( opts . counter === "small" ) return { } ;
88117 return { text : count . toString ( ) , color : opts . counter } ;
@@ -91,7 +120,7 @@ export function view(app: State, args: {today: Date}): View {
91120 return {
92121 label : Tasks . filterTitle ( app . tasks , filter ) ,
93122 filter,
94- selected : Tasks . isSubfilter ( app . tasks , app . filter , filter ) ,
123+ selected : Tasks . isSubfilter ( { ... app , ... args } , app . filter , filter ) ,
95124 dropTarget : { type : "filter" , id : filter } ,
96125 indicator : indicator ( ) ,
97126 } ;
@@ -128,6 +157,7 @@ export function view(app: State, args: {today: Date}): View {
128157 filters : [ filterView ( "archive" ) ] ,
129158 } ,
130159 ] ,
160+ filterBar : viewFilterBar ( app , args ) ,
131161 taskList : Tasks . view ( { ...app , today : args . today } ) ,
132162 editor : TaskEditor . view ( app . editor ) ,
133163 } ;
@@ -169,10 +199,10 @@ export function updateApp(app: State, ev: Event, args: {today: Date}): State {
169199 const [ drag , drop ] = dropped_ ;
170200
171201 if ( drop . type === "filter" ) {
172- const app_ = { ...app , tasks : edit ( app , drag . id , [ { type : "moveToFilter" , filter : drop . id } ] , args ) } ;
202+ const app_ = { ...app , tasks : edit ( { ... app , ... args } , drag . id , [ { type : "moveToFilter" , filter : drop . id } ] ) } ;
173203 return { ...app_ , editor : TaskEditor . reload ( app_ ) } ;
174204 } else if ( drop . type === "list" ) {
175- return { ...app , tasks : edit ( app , drag . id , [ { type : "move" , target : drop . target } ] , args ) } ;
205+ return { ...app , tasks : edit ( { ... app , ... args } , drag . id , [ { type : "move" , target : drop . target } ] ) } ;
176206 } else {
177207 const unreachable : never = drop ;
178208 return unreachable ;
@@ -189,26 +219,46 @@ export function updateApp(app: State, ev: Event, args: {today: Date}): State {
189219 return { ...app , filter : ev . filter } ;
190220 }
191221
222+ function handleFilterBar ( app : State , ev : Event ) {
223+ if ( ev . tag !== "filterBar" ) return app ;
224+
225+ let subtaskFilters = app . subtaskFilters ;
226+ const currentState = subtaskFilters . find ( ( f ) => f . id === ev . id ) ;
227+ if ( currentState === undefined ) {
228+ if ( ev . id === "paused" ) subtaskFilters = [ ...subtaskFilters , { id : ev . id , state : ev . state } ] ;
229+ else console . error ( "Invalid filter ID" , ev ) ;
230+ } else {
231+ subtaskFilters = subtaskFilters . flatMap ( ( f ) =>
232+ f . id === ev . id ? ( f . state === ev . state ? [ ] : [ { ...f , state : ev . state } ] ) : [ f ] ,
233+ ) ;
234+ }
235+
236+ return {
237+ ...app ,
238+ subtaskFilters,
239+ } ;
240+ }
241+
192242 function handleTextField ( app : State , ev : Event ) {
193243 if ( ev . tag !== "textField" ) return app ;
194244 const result = { ...app , textFields : updateTextFields ( app . textFields , ev ) } ;
195245 if ( ev . type === "submit" ) {
196- return { ...result , tasks : add ( app , { title : textFieldValue ( app . textFields , "addTitle" ) } , args ) } ;
246+ return { ...result , tasks : add ( { ... app , ... args } , { title : textFieldValue ( app . textFields , "addTitle" ) } ) } ;
197247 } else {
198248 return result ;
199249 }
200250 }
201251
202252 function handleEdit ( app : State , ev : Event ) {
203253 if ( ev . tag !== "editor" ) return app ;
204- const tasks = edit ( app , ev . component . id . taskId , TaskEditor . editOperationsFor ( app . editor , ev ) , args ) ;
254+ const tasks = edit ( { ... app , ... args } , ev . component . id . taskId , TaskEditor . editOperationsFor ( app . editor , ev ) ) ;
205255 return { ...app , editor : TaskEditor . load ( { tasks} , app . editor ! . id ) , tasks} ;
206256 }
207257
208258 function handleCheck ( app : State , ev : Event ) {
209259 if ( ev . tag !== "check" ) return app ;
210260 const value = Tasks . find ( app . tasks , ev . id ) ?. status === "done" ? "active" : "done" ;
211- const tasks = edit ( app , ev . id , [ { type : "set" , property : "status" , value} ] , args ) ;
261+ const tasks = edit ( { ... app , ... args } , ev . id , [ { type : "set" , property : "status" , value} ] ) ;
212262 return { ...app , tasks, editor : TaskEditor . reload ( { ...app , tasks} ) } ;
213263 }
214264
@@ -233,6 +283,7 @@ export function updateApp(app: State, ev: Event, args: {today: Date}): State {
233283 ( app ) => handleCheck ( app , ev ) ,
234284 ( app ) => handleEdit ( app , ev ) ,
235285 ( app ) => handleSelectFilter ( app , ev ) ,
286+ ( app ) => handleFilterBar ( app , ev ) ,
236287 ( app ) => handleSelectEditingTask ( app , ev ) ,
237288 ( app ) => handleDrop ( app , ev ) ,
238289 ( app ) => handleDragState ( app , ev ) ,
0 commit comments