diff --git a/README.md b/README.md index deb330e..7436b2a 100644 --- a/README.md +++ b/README.md @@ -439,7 +439,8 @@ while it is read is left out, and the rest of the scrape continues. `CanopyBrowseHtmlHandler` wraps it. Requests without `text/html` in `Accept` are passed through unchanged. A browser gets a table per branch, with links into child branches and an editable field -with a Save button per leaf. +with a Save button per leaf. A dropdown below the title reloads the page every 1 to 60 seconds; the +choice is kept in the browser, and no reload happens while an input has the focus. ```smalltalk Canopy registerSystemMetrics. diff --git a/source/Canopy-Core-Tests/CanopyBrowseHtmlHandlerTest.class.st b/source/Canopy-Core-Tests/CanopyBrowseHtmlHandlerTest.class.st index 3aefa81..4779ee0 100644 --- a/source/Canopy-Core-Tests/CanopyBrowseHtmlHandlerTest.class.st +++ b/source/Canopy-Core-Tests/CanopyBrowseHtmlHandlerTest.class.st @@ -81,11 +81,33 @@ CanopyBrowseHtmlHandlerTest >> testHandleRequestEscapesHtmlSpecialCharactersInVa ] ensure: [ Canopy reset ] ] +{ #category : 'tests' } +CanopyBrowseHtmlHandlerTest >> testHandleRequestRendersAutoRefreshControlOnBranchPage [ + "The dropdown sits right below the title, so it is the same place on every page." + | body | + [ self registerBrowseTestDomain. + body := (CanopyBrowseHtmlHandler new handleRequest: (self requestFor: #GET url: 'http://localhost/browseTest' accept: 'text/html' body: nil)) entity contents. + self assert: (body includesSubstring: '

') + ] ensure: [ Canopy reset ] +] + { #category : 'tests' } CanopyBrowseHtmlHandlerTest >> testHandleRequestRendersHtmlPageForLeafWhenAcceptIsHtml [ [ self registerBrowseTestDomain. self assert: (CanopyBrowseHtmlHandler new handleRequest: (self requestFor: #GET url: 'http://localhost/browseTest/a' accept: 'text/html' body: nil)) entity contents - equals: '

/browseTest/a

Value: "x"

' + equals: '

/browseTest/a

' , CanopyBrowseHtmlHandler new refreshControl , '

Value: "x"

' ] ensure: [ Canopy reset ] ] diff --git a/source/Canopy-Core/CanopyBrowseHtmlHandler.class.st b/source/Canopy-Core/CanopyBrowseHtmlHandler.class.st index 757e84d..c3c5337 100644 --- a/source/Canopy-Core/CanopyBrowseHtmlHandler.class.st +++ b/source/Canopy-Core/CanopyBrowseHtmlHandler.class.st @@ -45,6 +45,7 @@ CanopyBrowseHtmlHandler >> branchPageFor: node path: pathString [ ^ String streamContents: [ :s | s nextPutAll: ''. s nextPutAll: '

/'; nextPutAll: (self htmlEscape: pathString); nextPutAll: '

'. + s nextPutAll: self refreshControl. s nextPutAll: ''. node keysAndValuesDo: [ :key :child | | href | @@ -97,12 +98,22 @@ CanopyBrowseHtmlHandler >> pageFor: node path: pathString [ ^ String streamContents: [ :s | s nextPutAll: ''. s nextPutAll: '

/'; nextPutAll: (self htmlEscape: pathString); nextPutAll: '

'. + s nextPutAll: self refreshControl. s nextPutAll: '

Value: '; nextPutAll: (self htmlEscape: currentJson); nextPutAll: '

'. s nextPutAll: ''. s nextPutAll: ''. s nextPutAll: '' ] ] +{ #category : 'private' } +CanopyBrowseHtmlHandler >> refreshControl [ + "A dropdown that reloads the page every n seconds, for watching metrics move. The choice is + kept in the browser's localStorage, so it survives the reload and a click into a child - and + stays with the viewer rather than with the tree. While an input has the focus the reload + waits for the next round, or a value being typed would be thrown away." + ^ '

' +] + { #category : 'accessing' } CanopyBrowseHtmlHandler >> restHandler [ ^ restHandler ifNil: [ restHandler := CanopyBrowseHandler new ]