Skip to content
64 changes: 64 additions & 0 deletions src/Lepiter-Core-Examples/LeExampleCustomPageTypeExamples.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Class {
#name : #LeExampleCustomPageTypeExamples,
#superclass : #Object,
#category : #'Lepiter-Core-Examples-Custom Page Types'
}

{ #category : #example }
LeExampleCustomPageTypeExamples >> databaseWithExampleCustomPage [
<gtExample>
| database |
database := LeDatabaseWithLocalMonitorExamples new databaseWithMonitor.
database monitor
storage: (LeMockLocalJsonV4StorageWithMissingDeserializers new
deserializersToRemove: {LeExampleCustomPageType}).
database addPage: LeExampleCustomPageType samplePage.
self assert: database pages size = 1.
^ database
]

{ #category : #example }
LeExampleCustomPageTypeExamples >> deregisterCustomPageTypeAndReloadPage [
<gtExample>
| database leJsonV4 reader |
database := self reloadPage.
leJsonV4 := database monitor storage leJsonV4.
leJsonV4 disableDeserializers.
reader := leJsonV4 newReader.
self assert: (reader mappings keys includes: LeExampleCustomPageType) not.
database detachPageButKeepFile: database pages first.
database monitor reload.
self
assert: (database pages first type isKindOf: LeUnknownNamedPageType)
description: 'A page with a title and an unknown page type most be loaded into the database as a page with type `LeUnknownNamedPageType`'.
^ database
]

{ #category : #example }
LeExampleCustomPageTypeExamples >> reloadPage [
<gtExample>
| database |
database := self databaseWithExampleCustomPage.
database detachPageButKeepFile: database pages first.
database monitor reload.
self assert: (database pages first type isKindOf: LeExampleCustomPageType).
^ database
]

{ #category : #example }
LeExampleCustomPageTypeExamples >> reregisterCustomPageTypeAndReloadPage [
<gtExample>
| database leJsonV4 reader |
database := self deregisterCustomPageTypeAndReloadPage.
leJsonV4 := database monitor storage leJsonV4.
leJsonV4 enableDeserializers.
reader := leJsonV4 newReader.
self assert: (reader mappings keys includes: LeExampleCustomPageType).
database detachPageButKeepFile: database pages first.
database monitor reload.
self flag: #TODO. "Check page hash optimization. unknown page types should be excluded and tried to be reloaded."
self
assert: (database pages first type isKindOf: LeExampleCustomPageType)
description: 'If code that handles custom page types is loaded and database reloaded, page should get reloaded as the correct page type.'.
^ database
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
Class {
#name : #LeExampleUnnamedCustomPageTypeExamples,
#superclass : #Object,
#category : #'Lepiter-Core-Examples-Custom Page Types'
}

{ #category : #example }
LeExampleUnnamedCustomPageTypeExamples >> databaseWithExampleCustomPage [
<gtExample>
| database |
database := LeDatabaseWithLocalMonitorExamples new databaseWithMonitor.
database monitor
storage: (LeMockLocalJsonV4StorageWithMissingDeserializers new
deserializersToRemove: {LeExampleUnnamedCustomPageType}).
database addPage: LeExampleUnnamedCustomPageType samplePage.
self assert: database pages size = 1.
^ database
]

{ #category : #example }
LeExampleUnnamedCustomPageTypeExamples >> deregisterCustomPageTypeAndReloadPage [
<gtExample>
| database leJsonV4 reader |
database := self reloadPage.
leJsonV4 := database monitor storage leJsonV4.
leJsonV4 disableDeserializers.
reader := leJsonV4 newReader.
self assert: (reader mappings keys includes: LeExampleUnnamedCustomPageType) not.
database detachPageButKeepFile: database pages first.
database monitor reload.
self
assert: (database pages first type isKindOf: LeUnknownUnnamedPageType)
description: 'A page with a title and an unknown page type most be loaded into the database as a page with type `LeUnknownNamedPageType`'.
^ database
]

{ #category : #example }
LeExampleUnnamedCustomPageTypeExamples >> modifyAndReloadPage [
<gtExample>
| database page pageJson |
database := self reregisterCustomPageTypeAndReloadPage.
page := database pages first.
page type theAnswer: page type theAnswer + 1.
page announceTreeChanged: page.
database monitor reload.
pageJson := NeoJSONReader
fromString: (LeJsonV4 uniqueInstance serializePretty: page).
self assert: (pageJson at: #pageType at: #theAnswer) = 43.
^ database
]

{ #category : #example }
LeExampleUnnamedCustomPageTypeExamples >> reloadPage [
<gtExample>
| database |
database := self databaseWithExampleCustomPage.
database detachPageButKeepFile: database pages first.
database monitor reload.
self assert: (database pages first type isKindOf: LeExampleUnnamedCustomPageType).
^ database
]

{ #category : #example }
LeExampleUnnamedCustomPageTypeExamples >> reregisterCustomPageTypeAndReloadPage [
<gtExample>
| database leJsonV4 reader |
database := self deregisterCustomPageTypeAndReloadPage.
leJsonV4 := database monitor storage leJsonV4.
leJsonV4 enableDeserializers.
reader := leJsonV4 newReader.
self assert: (reader mappings keys includes: LeExampleUnnamedCustomPageType).
database detachPageButKeepFile: database pages first.
database monitor reload.
self flag: #TODO. "Check page hash optimization. unknown page types should be excluded and tried to be reloaded."
self
assert: (database pages first type isKindOf: LeExampleUnnamedCustomPageType)
description: 'If code that handles custom page types is loaded and database reloaded, page should get reloaded as the correct page type.'.
^ database
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Class {
#name : #LeMockJsonV4WithMissingDeserializers,
#superclass : #LeJsonV4,
#instVars : [
'deserializersToDisable',
'disableDeserializers'
],
#category : #'Lepiter-Core-Examples-Mocks'
}

{ #category : #accessing }
LeMockJsonV4WithMissingDeserializers >> deserializersToDisable [
^ deserializersToDisable
]

{ #category : #accessing }
LeMockJsonV4WithMissingDeserializers >> deserializersToDisable: aCollection [
deserializersToDisable := aCollection
]

{ #category : #accessing }
LeMockJsonV4WithMissingDeserializers >> disableDeserializers [
disableDeserializers := true
]

{ #category : #accessing }
LeMockJsonV4WithMissingDeserializers >> enableDeserializers [
disableDeserializers := false
]

{ #category : #initialization }
LeMockJsonV4WithMissingDeserializers >> initialize [
super initialize.
disableDeserializers := false
]

{ #category : #initialization }
LeMockJsonV4WithMissingDeserializers >> newReader [
| aReader |
mutex
critical: [ aReader := LeJsonV4Reader new.
self allClassMappingsFor: aReader.
disableDeserializers
ifTrue: [ self deserializersToDisable
do: [ :each |
aReader mappings removeKey: each.
(aReader instVarNamed: #typeMap) removeKey: each leJsonV4Name ] ] ].
^ aReader
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Class {
#name : #LeMockLocalJsonV4StorageWithMissingDeserializers,
#superclass : #LeLocalJsonV4Storage,
#instVars : [
'deserializersToRemove',
'leJsonV4'
],
#category : #'Lepiter-Core-Examples-Mocks'
}

{ #category : #accessing }
LeMockLocalJsonV4StorageWithMissingDeserializers >> deserializersToRemove [
^ deserializersToRemove
]

{ #category : #accessing }
LeMockLocalJsonV4StorageWithMissingDeserializers >> deserializersToRemove: aCollection [
deserializersToRemove := aCollection
]

{ #category : #loading }
LeMockLocalJsonV4StorageWithMissingDeserializers >> leJsonV4 [
^ leJsonV4
ifNil: [ leJsonV4 := LeMockJsonV4WithMissingDeserializers new
deserializersToDisable: self deserializersToRemove ]
]
15 changes: 15 additions & 0 deletions src/Lepiter-Core/GtSpotterReturnAllItemsFilter.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Class {
#name : #GtSpotterReturnAllItemsFilter,
#superclass : #GtSpotterSubstringFilter,
#category : #'Lepiter-Core-Search'
}

{ #category : #evaluating }
GtSpotterReturnAllItemsFilter >> applyInScope: aStream context: aSpotterContext [
"Returns true no matter what is in the search query."

^ (GtSpotterSubstringFilterStream
forStream: aStream
search: aSpotterContext searchQuery)
itemString: [ :_ | aSpotterContext searchQuery ]
]
36 changes: 33 additions & 3 deletions src/Lepiter-Core/LeDatabase.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,23 @@ LeDatabase >> detachPage: aPage [
page: aPage)
]

{ #category : #'api - adding / removing' }
LeDatabase >> detachPageButKeepFile: aPage [

"I detach the page from the give database. The page could remain deleted or be later added to
another database."

self assertNotReadOnly.

(pagesByType at: aPage pageTypeClass)
removeKey: aPage databaseKey
ifAbsent: [ "do nothing" ].
pagesByUuid removeKey: aPage uid ifAbsent: [ "do nothing" ].

aPage removedFromDatabase: self.
self updateSortedCollectionsDueToRemoval: aPage.
]

{ #category : #'api - enumerating' }
LeDatabase >> do: aBlock [
"Evaluate aBlock for every page in the receiver"
Expand Down Expand Up @@ -689,6 +706,11 @@ LeDatabase >> initialize [
readOnly := false.
pagesByType := IdentityDictionary new.
pagesByUuid := Dictionary new.
"Populate all the page types"
LePageType allSubclassesDo: [ :pageType |
pagesByType
at: pageType pageTypeClass
ifAbsentPut: [ GtStringContentDictionary new ] ].
blocksByUID := Dictionary new.

self populatePageTypes.
Expand Down Expand Up @@ -1029,10 +1051,18 @@ LeDatabase >> pagesByDateToShow [

{ #category : #'private - accessing' }
LeDatabase >> pagesByName [
"Answer the pages by name.
This is internal structure that may change."
| namedPageClasses |
namedPageClasses := (pagesByType associations
select: [ :each | each key = LeNamedPageType or: [ each key inheritsFrom: LeNamedPageType ] ])
sorted: [ :each | each value size ] descending.
^ namedPageClasses allButFirst
inject: namedPageClasses first value
into: [ :acc :each | acc , each value ]
]

^ pagesByType at: LeNamedPageType
{ #category : #'private - accessing' }
LeDatabase >> pagesByType [
^ pagesByType
]

{ #category : #'private - accessing' }
Expand Down
93 changes: 93 additions & 0 deletions src/Lepiter-Core/LeExampleCustomPageType.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"
This is an example of a custom page type. It doesn't really do anything, but it is used in tests and helps users understand what methods need to be overriden/created, etc. when creating their own custom page types. To inspect a sample page from this class, you can evaluate such method or click on the 'eg' class action on the op right of this class.

The best approach for creating a new custom page type is to copy {{gtClass:LeExampleCustomPageType}} (this class) in your own package, including all it's methods, then start modifying the below methods accordingly.

- {{gtMethod:LeExampleCustomPageType class>>#initialize}} : In your custom page type class's `initialize` method, you need to 'register' that class on all the databases in the system. This will automatically get done in the `super initialize` call but you **DO** need to define an initialize class method and make the super call, this will **NOT** happen automatically. When you are testing locally, before code is loaded through Metacello or other means, you will need to evaluate this method manually for your class to be able to be recognized by Lepiter databases and serializers/deserializers.

- {{gtMethod:LeExampleCustomPageType>>#printOn:}} controls how your class is displayed and should overriden or you might think a page has been defined with the incorrect page type as it will display the superclass 'Named Page:' string.

- {{gtMethod:LeExampleCustomPageType class>>#niceClassName}}: this is a 'nice' human readable name of your page type, something like 'Something Page'. This will come up in the global spotter to be able to create a new page of your custom type.

- {{gtMethod:LeExampleCustomPageType class>>#leJsonV4Name}}: needs a unique name for lepiter JSON serialization.

- {{gtMethod:LeExampleUnnamedCustomPageType class>>#leJsonV4AttributeMapping}} (note this is a different class) needs to be modified if your custom page type will have slots and/or values that need to be serialized to the file system as a Lepiter page/JSON file.

- {{gtMethod:LeExampleCustomPageType class>>samplePage}} should be overriden and have an example page to potentially help users understand how to use the custom page type.

- Crucially, {{gtMethod:LeExampleCustomPageType>>defaultPhlowTool}}: can be overriden to display the custom page type in a different graphical format than the normal Lepiter page. Custom page types will have a 'composite' tool with the default tool as the first/main tool. Two different inspectors will also show up, one for the page type class, and one for the Lepiter page itself. One can add custom views on the page type that will not appear on arbitrary Lepiter pages, only pages of the specific page type.

- If one wants to completely override the phlow/composite tools displayed and not include the page type and page inspectors, or do any other more advanced custom logic, one can instead override {{gtMethod:LePageType>>asLepiterPagePhlowTool}} on their own class for added flexibility.

- {{gtMethod:LeExampleCustomPageType>>#asPreviewElement}} should be modified if you want to control what shows up in the global spotter when one selects a custom page type. This class implements a sane default you can use in your own custom page type.

- {{gtMethod:LePageType >>#pageTypeRepo}} **MUST** be implemented in your custom page type (even though it is not in this class). Read the comments in the superclass for the reasoning behind this. Defining this metadata enables being able to share Lepiter databases with people that don't have your custom page type logic yet and enables loading that code in the future.
"
Class {
#name : #LeExampleCustomPageType,
#superclass : #LeNamedPageType,
#category : #'Lepiter-Core-Model'
}

{ #category : #initialization }
LeExampleCustomPageType class >> initialize [
super initialize.
]

{ #category : #'as yet unclassified' }
LeExampleCustomPageType class >> leJsonV4AttributeMapping [

^ super leJsonV4AttributeMapping
]

{ #category : #accesing }
LeExampleCustomPageType class >> leJsonV4Name [

^ 'exampleCustomPage'
]

{ #category : #printing }
LeExampleCustomPageType class >> niceClassName [
^ 'Example Custom Page'
]

{ #category : #example }
LeExampleCustomPageType class >> samplePage [
| page |
page := LePage new.
page
type: (LeExampleCustomPageType new
title: 'Testing';
page: page).
^ page
]

{ #category : #ui }
LeExampleCustomPageType >> asPreviewElement [
^ self defaultPhlowTool asElement
]

{ #category : #converting }
LeExampleCustomPageType >> defaultPhlowTool [
^ GtPhlowExplicitTool new withIconAptitude
name: self page title;
icon: BrGlamorousVectorIcons gt;
stencil: [ BrFrame new matchParent
addChild: (BrGlamorousVectorIcons perform: #largeGt) create asScalableElement;
when: BlDoubleClickEvent
do: [ :anEvent | anEvent currentTarget phlow spawnObject: 42 ] ]
]

{ #category : #printing }
LeExampleCustomPageType >> printOn: aStream [

aStream
nextPutAll: 'Example Custom Page: ';
print: title
]

{ #category : #example }
LeExampleCustomPageType >> samplePage [
<gtExample>
^ self class samplePage
]
Loading