diva.js 6.0.2 → 7.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.clang-format +7 -0
  2. package/.github/workflows/npm-publish.yml +45 -0
  3. package/LICENSE +55 -0
  4. package/Makefile +75 -0
  5. package/README.md +15 -114
  6. package/elm.json +32 -0
  7. package/package.json +12 -59
  8. package/review/elm.json +52 -0
  9. package/review/src/ReviewConfig.elm +87 -0
  10. package/scripts/elm-esm.sh +40 -0
  11. package/scripts/minify-css.mjs +31 -0
  12. package/src/Filters.elm +1044 -0
  13. package/src/Main.elm +1217 -0
  14. package/src/Model.elm +213 -0
  15. package/src/Msg.elm +59 -0
  16. package/src/Utilities.elm +46 -0
  17. package/src/View/CollectionExplorer.elm +172 -0
  18. package/src/View/Helpers.elm +86 -0
  19. package/src/View/HtmlRenderer.elm +136 -0
  20. package/src/View/Icons.elm +159 -0
  21. package/src/View/ManifestInfoModal.elm +363 -0
  22. package/src/View/PageViewModal.elm +1046 -0
  23. package/src/View/Sidebar.elm +786 -0
  24. package/src/View/Toolbar.elm +189 -0
  25. package/src/View.elm +244 -0
  26. package/src/diva.ts +802 -0
  27. package/src/filters.ts +1843 -0
  28. package/src/styles/app.css +328 -0
  29. package/src/styles/collection.css +75 -0
  30. package/src/styles/modal.css +388 -0
  31. package/src/styles/sidebar.css +215 -0
  32. package/src/styles/theme.css +39 -0
  33. package/src/styles/toolbar.css +154 -0
  34. package/src/viewer-element.ts +1307 -0
  35. package/testing/index.html +52 -0
  36. package/testing/testing.html +231 -0
  37. package/tsconfig.json +12 -0
  38. package/AUTHORS +0 -22
  39. package/build/diva.css +0 -554
  40. package/build/diva.css.map +0 -1
  41. package/build/diva.js +0 -9
  42. package/build/diva.js.map +0 -1
  43. package/build/plugins/download.js +0 -2
  44. package/build/plugins/download.js.map +0 -1
  45. package/build/plugins/manipulation.js +0 -2
  46. package/build/plugins/manipulation.js.map +0 -1
  47. package/build/plugins/metadata.js +0 -2
  48. package/build/plugins/metadata.js.map +0 -1
  49. package/index.html +0 -28
  50. package/karma.conf.js +0 -87
  51. package/source/css/_mixins.scss +0 -43
  52. package/source/css/_variables.scss +0 -50
  53. package/source/css/_viewer.scss +0 -462
  54. package/source/css/diva.scss +0 -15
  55. package/source/css/plugins/_manipulation.scss +0 -228
  56. package/source/css/plugins/_metadata.scss +0 -31
  57. package/source/img/adjust.svg +0 -11
  58. package/source/img/book-view.svg +0 -6
  59. package/source/img/close.svg +0 -6
  60. package/source/img/download.svg +0 -6
  61. package/source/img/from-fullscreen.svg +0 -8
  62. package/source/img/grid-fewer.svg +0 -6
  63. package/source/img/grid-more.svg +0 -6
  64. package/source/img/grid-view.svg +0 -6
  65. package/source/img/link.svg +0 -6
  66. package/source/img/metadata.svg +0 -9
  67. package/source/img/page-view.svg +0 -6
  68. package/source/img/to-fullscreen.svg +0 -11
  69. package/source/img/zoom-in.svg +0 -6
  70. package/source/img/zoom-out.svg +0 -7
  71. package/source/js/composite-image.js +0 -174
  72. package/source/js/diva-global.js +0 -7
  73. package/source/js/diva.js +0 -1543
  74. package/source/js/document-handler.js +0 -180
  75. package/source/js/document-layout.js +0 -286
  76. package/source/js/exceptions.js +0 -26
  77. package/source/js/gesture-events.js +0 -190
  78. package/source/js/grid-handler.js +0 -122
  79. package/source/js/iiif-source-adapter.js +0 -63
  80. package/source/js/image-cache.js +0 -113
  81. package/source/js/image-manifest.js +0 -157
  82. package/source/js/image-request-handler.js +0 -76
  83. package/source/js/interpolate-animation.js +0 -122
  84. package/source/js/page-layouts/book-layout.js +0 -161
  85. package/source/js/page-layouts/grid-layout.js +0 -97
  86. package/source/js/page-layouts/index.js +0 -38
  87. package/source/js/page-layouts/page-dimensions.js +0 -9
  88. package/source/js/page-layouts/singles-layout.js +0 -27
  89. package/source/js/page-overlay-manager.js +0 -102
  90. package/source/js/page-tools-overlay.js +0 -95
  91. package/source/js/parse-iiif-manifest.js +0 -302
  92. package/source/js/plugins/_filters.js +0 -679
  93. package/source/js/plugins/download.js +0 -83
  94. package/source/js/plugins/manipulation.js +0 -837
  95. package/source/js/plugins/metadata.js +0 -190
  96. package/source/js/renderer.js +0 -584
  97. package/source/js/settings-view.js +0 -30
  98. package/source/js/tile-coverage-map.js +0 -25
  99. package/source/js/toolbar.js +0 -573
  100. package/source/js/utils/dragscroll.js +0 -106
  101. package/source/js/utils/elt.js +0 -94
  102. package/source/js/utils/events.js +0 -190
  103. package/source/js/utils/get-scrollbar-width.js +0 -29
  104. package/source/js/utils/hash-params.js +0 -86
  105. package/source/js/utils/parse-label-value.js +0 -34
  106. package/source/js/utils/vanilla.kinetic.js +0 -527
  107. package/source/js/validation-runner.js +0 -177
  108. package/source/js/viewer-core.js +0 -1514
  109. package/source/js/viewport.js +0 -143
  110. package/test/_setup.js +0 -13
  111. package/test/composite-image_test.js +0 -94
  112. package/test/diva_test.js +0 -43
  113. package/test/hash-params_test.js +0 -221
  114. package/test/image-cache_test.js +0 -106
  115. package/test/main.js +0 -6
  116. package/test/manifests/beromunsterManifest.json +0 -15514
  117. package/test/manifests/iiifv2.json +0 -11032
  118. package/test/manifests/iiifv2pages.json +0 -30437
  119. package/test/manifests/iiifv3.json +0 -10965
  120. package/test/navigation_test.js +0 -355
  121. package/test/parse-iiif-manifest_test.js +0 -68
  122. package/test/public_test.js +0 -881
  123. package/test/settings_test.js +0 -487
  124. package/test/utils/book-layout_test.js +0 -148
  125. package/test/utils/elt_test.js +0 -102
  126. package/test/utils/events_test.js +0 -245
  127. package/test/utils/hash-params_test.js +0 -79
  128. package/test/utils/parse-label-value_test.js +0 -45
  129. package/test/z_plugins_test.js +0 -180
  130. package/webpack.config.js +0 -58
  131. package/webpack.config.test.js +0 -45
@@ -0,0 +1,1046 @@
1
+ module View.PageViewModal exposing (viewPageViewModal)
2
+
3
+ import Filters exposing (FilterFloatValue(..), FilterIntValue(..), FilterStringValue(..), FilterToggle(..))
4
+ import Html exposing (Html, button, div, img, input, label, option, select, span, text, textarea)
5
+ import Html.Attributes as HA exposing (alt, checked, classList, id, rows, src, type_, value)
6
+ import Html.Events exposing (onCheck, onClick, onInput)
7
+ import Html.Lazy as Lazy
8
+ import IIIF.Language exposing (extractLabelFromLanguageMap)
9
+ import IIIF.Presentation exposing (toLabel)
10
+ import Model exposing (Model, PageImage, currentManifest, getPageAt)
11
+ import Msg exposing (Msg(..))
12
+ import Set
13
+ import Utilities exposing (disabledIf)
14
+ import View.Helpers exposing (emptyHtml, viewButton)
15
+ import View.Icons as Icons
16
+
17
+
18
+ viewPageViewModal : Model -> Html Msg
19
+ viewPageViewModal model =
20
+ if model.pageViewOpen then
21
+ div
22
+ [ classList
23
+ [ ( "modal-overlay", True )
24
+ , ( "is-fullscreen", model.pageViewFullscreen )
25
+ ]
26
+ ]
27
+ [ div
28
+ [ classList
29
+ [ ( "modal", True )
30
+ , ( "is-fullscreen", model.pageViewFullscreen )
31
+ , ( "is-page-view", not model.pageViewFullscreen )
32
+ ]
33
+ ]
34
+ [ viewModalHeader model
35
+ , viewModalBody model
36
+ ]
37
+ ]
38
+
39
+ else
40
+ emptyHtml
41
+
42
+
43
+ adaptiveOffsetRange : Model -> RangeRowConfig
44
+ adaptiveOffsetRange model =
45
+ { label = "Offset"
46
+ , min = "-50"
47
+ , max = "50"
48
+ , step = Just "1"
49
+ , value = String.fromInt model.filters.adaptiveOffset
50
+ , display = String.fromInt model.filters.adaptiveOffset
51
+ , onInput = UserUpdatedFilterInt IntAdaptiveOffset
52
+ }
53
+
54
+
55
+ blueChannelConfig : Model -> ChannelConfig
56
+ blueChannelConfig model =
57
+ { gammaEnabled = model.filters.altBlueGammaEnabled
58
+ , gamma = model.filters.altBlueGamma
59
+ , gammaToggle = ToggleAltBlueGamma
60
+ , gammaInput = IntAltBlueGamma
61
+ , sigmoidEnabled = model.filters.altBlueSigmoidEnabled
62
+ , sigmoid = model.filters.altBlueSigmoid
63
+ , sigmoidToggle = ToggleAltBlueSigmoid
64
+ , sigmoidInput = IntAltBlueSigmoid
65
+ , hueEnabled = model.filters.altBlueHueEnabled
66
+ , hue = model.filters.altBlueHue
67
+ , hueToggle = ToggleAltBlueHue
68
+ , hueInput = IntAltBlueHue
69
+ , hueWindow = model.filters.altBlueHueWindow
70
+ , hueWindowInput = IntAltBlueHueWindow
71
+ , vibranceEnabled = model.filters.altBlueVibranceEnabled
72
+ , vibrance = model.filters.altBlueVibrance
73
+ , vibranceToggle = ToggleAltBlueVibrance
74
+ , vibranceInput = IntAltBlueVibrance
75
+ }
76
+
77
+
78
+ type alias ChannelConfig =
79
+ { gammaEnabled : Bool
80
+ , gamma : Int
81
+ , gammaToggle : FilterToggle
82
+ , gammaInput : FilterIntValue
83
+ , sigmoidEnabled : Bool
84
+ , sigmoid : Int
85
+ , sigmoidToggle : FilterToggle
86
+ , sigmoidInput : FilterIntValue
87
+ , hueEnabled : Bool
88
+ , hue : Int
89
+ , hueToggle : FilterToggle
90
+ , hueInput : FilterIntValue
91
+ , hueWindow : Int
92
+ , hueWindowInput : FilterIntValue
93
+ , vibranceEnabled : Bool
94
+ , vibrance : Int
95
+ , vibranceToggle : FilterToggle
96
+ , vibranceInput : FilterIntValue
97
+ }
98
+
99
+
100
+ colourAdjustToggleRanges : Model -> List ToggleRangeRowConfig
101
+ colourAdjustToggleRanges model =
102
+ [ { label = "Brightness"
103
+ , checked = model.filters.brightnessEnabled
104
+ , onToggle = ToggleBrightness
105
+ , min = "-255"
106
+ , max = "255"
107
+ , step = Nothing
108
+ , value = String.fromInt model.filters.brightness
109
+ , display = String.fromInt model.filters.brightness
110
+ , onInput = UserUpdatedFilterInt IntBrightness
111
+ }
112
+ , { label = "Contrast"
113
+ , checked = model.filters.contrastEnabled
114
+ , onToggle = ToggleContrast
115
+ , min = "0"
116
+ , max = "4"
117
+ , step = Just "0.1"
118
+ , value = String.fromFloat model.filters.contrast
119
+ , display = String.fromFloat model.filters.contrast
120
+ , onInput = UserUpdatedFilterFloat FloatContrast
121
+ }
122
+ , { label = "Gamma"
123
+ , checked = model.filters.gammaEnabled
124
+ , onToggle = ToggleGamma
125
+ , min = "0.1"
126
+ , max = "4"
127
+ , step = Just "0.1"
128
+ , value = String.fromFloat model.filters.gamma
129
+ , display = String.fromFloat model.filters.gamma
130
+ , onInput = UserUpdatedFilterFloat FloatGamma
131
+ }
132
+ , { label = "Saturation"
133
+ , checked = model.filters.saturationEnabled
134
+ , onToggle = ToggleSaturation
135
+ , min = "-100"
136
+ , max = "100"
137
+ , step = Nothing
138
+ , value = String.fromInt model.filters.saturation
139
+ , display = String.fromInt model.filters.saturation
140
+ , onInput = UserUpdatedFilterInt IntSaturation
141
+ }
142
+ , { label = "Vibrance"
143
+ , checked = model.filters.vibranceEnabled
144
+ , onToggle = ToggleVibrance
145
+ , min = "-100"
146
+ , max = "100"
147
+ , step = Nothing
148
+ , value = String.fromInt model.filters.vibrance
149
+ , display = String.fromInt model.filters.vibrance
150
+ , onInput = UserUpdatedFilterInt IntVibrance
151
+ }
152
+ , { label = "Hue"
153
+ , checked = model.filters.hueEnabled
154
+ , onToggle = ToggleHue
155
+ , min = "-100"
156
+ , max = "100"
157
+ , step = Nothing
158
+ , value = String.fromInt model.filters.hue
159
+ , display = String.fromInt model.filters.hue
160
+ , onInput = UserUpdatedFilterInt IntHue
161
+ }
162
+ , { label = "Red"
163
+ , checked = model.filters.ccRedEnabled
164
+ , onToggle = ToggleCcRed
165
+ , min = "-100"
166
+ , max = "100"
167
+ , step = Nothing
168
+ , value = String.fromInt model.filters.ccRed
169
+ , display = String.fromInt model.filters.ccRed
170
+ , onInput = UserUpdatedFilterInt IntCcRed
171
+ }
172
+ , { label = "Green"
173
+ , checked = model.filters.ccGreenEnabled
174
+ , onToggle = ToggleCcGreen
175
+ , min = "-100"
176
+ , max = "100"
177
+ , step = Nothing
178
+ , value = String.fromInt model.filters.ccGreen
179
+ , display = String.fromInt model.filters.ccGreen
180
+ , onInput = UserUpdatedFilterInt IntCcGreen
181
+ }
182
+ , { label = "Blue"
183
+ , checked = model.filters.ccBlueEnabled
184
+ , onToggle = ToggleCcBlue
185
+ , min = "-100"
186
+ , max = "100"
187
+ , step = Nothing
188
+ , value = String.fromInt model.filters.ccBlue
189
+ , display = String.fromInt model.filters.ccBlue
190
+ , onInput = UserUpdatedFilterInt IntCcBlue
191
+ }
192
+ ]
193
+
194
+
195
+ colourmapPresets : List (Html Msg)
196
+ colourmapPresets =
197
+ [ option [ value "gray" ] [ text "Gray" ]
198
+ , option [ value "hot" ] [ text "Hot" ]
199
+ , option [ value "cool" ] [ text "Cool" ]
200
+ ]
201
+
202
+
203
+ convolutionPresets : List (Html Msg)
204
+ convolutionPresets =
205
+ [ option [ value "sharpen" ] [ text "Sharpen" ]
206
+ , option [ value "blur" ] [ text "Blur" ]
207
+ , option [ value "edge" ] [ text "Edge" ]
208
+ , option [ value "emboss" ] [ text "Emboss" ]
209
+ ]
210
+
211
+
212
+ currentPageLabelFor : Model -> String
213
+ currentPageLabelFor model =
214
+ model.selectedIndex
215
+ |> Maybe.andThen (\index -> getPageAt index model.pages)
216
+ |> Maybe.map .label
217
+ |> Maybe.withDefault ""
218
+
219
+
220
+ enhancementToggleRanges : Model -> List ToggleRangeRowConfig
221
+ enhancementToggleRanges model =
222
+ [ { label = "Normalize"
223
+ , checked = model.filters.normalizeEnabled
224
+ , onToggle = ToggleNormalize
225
+ , min = "0"
226
+ , max = "2"
227
+ , step = Just "0.1"
228
+ , value = String.fromFloat model.filters.normalizeStrength
229
+ , display = String.fromFloat model.filters.normalizeStrength
230
+ , onInput = UserUpdatedFilterFloat FloatNormalizeStrength
231
+ }
232
+ , { label = "Unsharp"
233
+ , checked = model.filters.unsharpEnabled
234
+ , onToggle = ToggleUnsharp
235
+ , min = "0"
236
+ , max = "3"
237
+ , step = Just "0.1"
238
+ , value = String.fromFloat model.filters.unsharpAmount
239
+ , display = String.fromFloat model.filters.unsharpAmount
240
+ , onInput = UserUpdatedFilterFloat FloatUnsharpAmount
241
+ }
242
+ , { label = "Adaptive Threshold"
243
+ , checked = model.filters.adaptiveEnabled
244
+ , onToggle = ToggleAdaptive
245
+ , min = "3"
246
+ , max = "51"
247
+ , step = Just "2"
248
+ , value = String.fromInt model.filters.adaptiveWindow
249
+ , display = String.fromInt model.filters.adaptiveWindow
250
+ , onInput = UserUpdatedFilterInt IntAdaptiveWindow
251
+ }
252
+ ]
253
+
254
+
255
+ greenChannelConfig : Model -> ChannelConfig
256
+ greenChannelConfig model =
257
+ { gammaEnabled = model.filters.altGreenGammaEnabled
258
+ , gamma = model.filters.altGreenGamma
259
+ , gammaToggle = ToggleAltGreenGamma
260
+ , gammaInput = IntAltGreenGamma
261
+ , sigmoidEnabled = model.filters.altGreenSigmoidEnabled
262
+ , sigmoid = model.filters.altGreenSigmoid
263
+ , sigmoidToggle = ToggleAltGreenSigmoid
264
+ , sigmoidInput = IntAltGreenSigmoid
265
+ , hueEnabled = model.filters.altGreenHueEnabled
266
+ , hue = model.filters.altGreenHue
267
+ , hueToggle = ToggleAltGreenHue
268
+ , hueInput = IntAltGreenHue
269
+ , hueWindow = model.filters.altGreenHueWindow
270
+ , hueWindowInput = IntAltGreenHueWindow
271
+ , vibranceEnabled = model.filters.altGreenVibranceEnabled
272
+ , vibrance = model.filters.altGreenVibrance
273
+ , vibranceToggle = ToggleAltGreenVibrance
274
+ , vibranceInput = IntAltGreenVibrance
275
+ }
276
+
277
+
278
+ manifestTitleFor : Model -> String
279
+ manifestTitleFor model =
280
+ currentManifest model
281
+ |> Maybe.map (\m -> toLabel m |> extractLabelFromLanguageMap model.detectedLanguage)
282
+ |> Maybe.withDefault ""
283
+
284
+
285
+ morphKernelOptions : List (Html Msg)
286
+ morphKernelOptions =
287
+ [ option [ value "3" ] [ text "3x3" ]
288
+ , option [ value "5" ] [ text "5x5" ]
289
+ , option [ value "7" ] [ text "7x7" ]
290
+ ]
291
+
292
+
293
+ morphOperationOptions : List (Html Msg)
294
+ morphOperationOptions =
295
+ [ option [ value "erode" ] [ text "Erode" ]
296
+ , option [ value "dilate" ] [ text "Dilate" ]
297
+ ]
298
+
299
+
300
+ pcaModes : List (Html Msg)
301
+ pcaModes =
302
+ [ option [ value "pca-rgb" ] [ text "PCA (RGB)" ]
303
+ , option [ value "pca1" ] [ text "PCA Component 1" ]
304
+ , option [ value "pca2" ] [ text "PCA Component 2" ]
305
+ , option [ value "pca3" ] [ text "PCA Component 3" ]
306
+ ]
307
+
308
+
309
+ pseudoColourModes : List (Html Msg)
310
+ pseudoColourModes =
311
+ [ option [ value "rg" ] [ text "Red–Green Diff" ]
312
+ , option [ value "gb" ] [ text "Green–Blue Diff" ]
313
+ , option [ value "rb" ] [ text "Red–Blue Diff" ]
314
+ , option [ value "luma" ] [ text "Luma False Colour" ]
315
+ , option [ value "cmy" ] [ text "CMY False Colour" ]
316
+ , option [ value "heat" ] [ text "Heat Map" ]
317
+ ]
318
+
319
+
320
+ type alias RangeRowConfig =
321
+ { label : String
322
+ , min : String
323
+ , max : String
324
+ , step : Maybe String
325
+ , value : String
326
+ , display : String
327
+ , onInput : String -> Msg
328
+ }
329
+
330
+
331
+ redChannelConfig : Model -> ChannelConfig
332
+ redChannelConfig model =
333
+ { gammaEnabled = model.filters.altRedGammaEnabled
334
+ , gamma = model.filters.altRedGamma
335
+ , gammaToggle = ToggleAltRedGamma
336
+ , gammaInput = IntAltRedGamma
337
+ , sigmoidEnabled = model.filters.altRedSigmoidEnabled
338
+ , sigmoid = model.filters.altRedSigmoid
339
+ , sigmoidToggle = ToggleAltRedSigmoid
340
+ , sigmoidInput = IntAltRedSigmoid
341
+ , hueEnabled = model.filters.altRedHueEnabled
342
+ , hue = model.filters.altRedHue
343
+ , hueToggle = ToggleAltRedHue
344
+ , hueInput = IntAltRedHue
345
+ , hueWindow = model.filters.altRedHueWindow
346
+ , hueWindowInput = IntAltRedHueWindow
347
+ , vibranceEnabled = model.filters.altRedVibranceEnabled
348
+ , vibrance = model.filters.altRedVibrance
349
+ , vibranceToggle = ToggleAltRedVibrance
350
+ , vibranceInput = IntAltRedVibrance
351
+ }
352
+
353
+
354
+ type alias ToggleRangeRowConfig =
355
+ { label : String
356
+ , checked : Bool
357
+ , onToggle : FilterToggle
358
+ , min : String
359
+ , max : String
360
+ , step : Maybe String
361
+ , value : String
362
+ , display : String
363
+ , onInput : String -> Msg
364
+ }
365
+
366
+
367
+ toneToggleRanges : Model -> List ToggleRangeRowConfig
368
+ toneToggleRanges model =
369
+ [ { label = "Threshold"
370
+ , checked = model.filters.thresholdEnabled
371
+ , onToggle = ToggleThreshold
372
+ , min = "0"
373
+ , max = "255"
374
+ , step = Nothing
375
+ , value = String.fromInt model.filters.threshold
376
+ , display = String.fromInt model.filters.threshold
377
+ , onInput = UserUpdatedFilterInt IntThreshold
378
+ }
379
+ ]
380
+
381
+
382
+ viewAdvancedColourAdjustGroup : Model -> Html Msg
383
+ viewAdvancedColourAdjustGroup model =
384
+ viewFilterGroup model
385
+ "advanced-colour-adjust"
386
+ "Advanced colour adjust"
387
+ (viewFilterRow
388
+ [ button
389
+ [ HA.class "filter-reset"
390
+ , type_ "button"
391
+ , onClick UserResetAltColourAdjust
392
+ ]
393
+ [ text "Reset sliders" ]
394
+ ]
395
+ :: viewChannelRows "Red" (redChannelConfig model)
396
+ ++ viewChannelRows "Green" (greenChannelConfig model)
397
+ ++ viewChannelRows "Blue" (blueChannelConfig model)
398
+ )
399
+
400
+
401
+ viewChannelRows : String -> ChannelConfig -> List (Html Msg)
402
+ viewChannelRows channelName config =
403
+ [ viewToggleRangeRow
404
+ { label = channelName ++ " Gamma"
405
+ , checked = config.gammaEnabled
406
+ , onToggle = config.gammaToggle
407
+ , min = "0"
408
+ , max = "100"
409
+ , step = Just "1"
410
+ , value = String.fromInt config.gamma
411
+ , display = String.fromInt config.gamma
412
+ , onInput = UserUpdatedFilterInt config.gammaInput
413
+ }
414
+ , viewToggleRangeRow
415
+ { label = channelName ++ " Sigmoid"
416
+ , checked = config.sigmoidEnabled
417
+ , onToggle = config.sigmoidToggle
418
+ , min = "0"
419
+ , max = "100"
420
+ , step = Just "1"
421
+ , value = String.fromInt config.sigmoid
422
+ , display = String.fromInt config.sigmoid
423
+ , onInput = UserUpdatedFilterInt config.sigmoidInput
424
+ }
425
+ , viewToggleRangeRow
426
+ { label = channelName ++ " Hue Boost"
427
+ , checked = config.hueEnabled
428
+ , onToggle = config.hueToggle
429
+ , min = "-100"
430
+ , max = "100"
431
+ , step = Just "1"
432
+ , value = String.fromInt config.hue
433
+ , display = String.fromInt config.hue
434
+ , onInput = UserUpdatedFilterInt config.hueInput
435
+ }
436
+ , viewRangeRow
437
+ { label = channelName ++ " Hue Window"
438
+ , min = "2"
439
+ , max = "30"
440
+ , step = Just "1"
441
+ , value = String.fromInt config.hueWindow
442
+ , display = String.fromInt config.hueWindow
443
+ , onInput = UserUpdatedFilterInt config.hueWindowInput
444
+ }
445
+ , viewToggleRangeRow
446
+ { label = channelName ++ " Vibrance"
447
+ , checked = config.vibranceEnabled
448
+ , onToggle = config.vibranceToggle
449
+ , min = "0"
450
+ , max = "100"
451
+ , step = Just "1"
452
+ , value = String.fromInt config.vibrance
453
+ , display = String.fromInt config.vibrance
454
+ , onInput = UserUpdatedFilterInt config.vibranceInput
455
+ }
456
+ ]
457
+
458
+
459
+ viewColourAdjustGroup : Model -> Html Msg
460
+ viewColourAdjustGroup model =
461
+ viewFilterGroup model
462
+ "colour-adjust"
463
+ "Colour Adjust"
464
+ (List.map viewToggleRangeRow (colourAdjustToggleRanges model))
465
+
466
+
467
+ viewColourInput : String -> (String -> Msg) -> Html Msg
468
+ viewColourInput colourValue onChange =
469
+ input
470
+ [ HA.class "filter-color-input"
471
+ , type_ "color"
472
+ , value colourValue
473
+ , onInput onChange
474
+ ]
475
+ []
476
+
477
+
478
+ viewColourmapGroup : Model -> Html Msg
479
+ viewColourmapGroup model =
480
+ viewFilterGroup model
481
+ "colourmap"
482
+ "Colourmap"
483
+ [ viewFilterRow
484
+ [ viewToggle "Colourmap" model.filters.colourmapEnabled ToggleColourmap
485
+ , viewSelect model.filters.colourmapPreset (UserUpdatedFilterString StringColourmapPreset) colourmapPresets
486
+ ]
487
+ , viewRangeRow
488
+ { label = "Center"
489
+ , min = "0"
490
+ , max = "255"
491
+ , step = Nothing
492
+ , value = String.fromInt model.filters.colourmapCenter
493
+ , display = String.fromInt model.filters.colourmapCenter
494
+ , onInput = UserUpdatedFilterInt IntColourmapCenter
495
+ }
496
+ ]
497
+
498
+
499
+ viewConvolutionGroup : Model -> Html Msg
500
+ viewConvolutionGroup model =
501
+ viewFilterGroup model
502
+ "convolution"
503
+ "Convolution"
504
+ [ viewFilterRow
505
+ [ viewToggle "Kernel" model.filters.convolutionEnabled ToggleConvolution
506
+ , viewSelect model.filters.convolutionPreset (UserUpdatedFilterString StringConvolutionPreset) convolutionPresets
507
+ ]
508
+ ]
509
+
510
+
511
+ viewEnhancementGroup : Model -> Html Msg
512
+ viewEnhancementGroup model =
513
+ viewFilterGroup model
514
+ "enhancement"
515
+ "Enhancement"
516
+ (List.map viewToggleRangeRow (enhancementToggleRanges model)
517
+ ++ [ viewRangeRow (adaptiveOffsetRange model) ]
518
+ )
519
+
520
+
521
+ viewFilterGroup : Model -> String -> String -> List (Html Msg) -> Html Msg
522
+ viewFilterGroup model groupId title items =
523
+ let
524
+ isExpanded =
525
+ Set.member groupId model.filterGroupExpanded
526
+ in
527
+ div [ HA.class "filter-group" ]
528
+ (button
529
+ [ classList
530
+ [ ( "filter-title-button", True )
531
+ , ( "is-collapsed", not isExpanded )
532
+ ]
533
+ , onClick (UserToggledFilterGroup groupId)
534
+ ]
535
+ [ span
536
+ [ classList
537
+ [ ( "filter-title-icon", True )
538
+ , ( "is-expanded", isExpanded )
539
+ ]
540
+ ]
541
+ []
542
+ , span [] [ text title ]
543
+ ]
544
+ :: (if isExpanded then
545
+ items
546
+
547
+ else
548
+ []
549
+ )
550
+ )
551
+
552
+
553
+ viewFilterJsonGroup : Model -> Html Msg
554
+ viewFilterJsonGroup model =
555
+ viewFilterGroup model
556
+ "filter-json"
557
+ "Import / Export Filter Settings"
558
+ [ viewFilterRow
559
+ [ button
560
+ [ HA.class "filter-reset"
561
+ , type_ "button"
562
+ , onClick UserCopiedFilterJson
563
+ ]
564
+ [ text "Show JSON" ]
565
+ , button
566
+ [ HA.class "filter-reset"
567
+ , type_ "button"
568
+ , onClick UserAppliedFilterJson
569
+ ]
570
+ [ text "Apply" ]
571
+ ]
572
+ , textarea
573
+ [ HA.class "filter-json"
574
+ , value model.filtersJsonInput
575
+ , onInput UserUpdatedFilterJsonInput
576
+ , rows 6
577
+ ]
578
+ []
579
+ , case model.filtersJsonError of
580
+ Just err ->
581
+ div [ HA.class "filter-json-error" ] [ text err ]
582
+
583
+ Nothing ->
584
+ emptyHtml
585
+ ]
586
+
587
+
588
+ viewFilterRow : List (Html Msg) -> Html Msg
589
+ viewFilterRow items =
590
+ div [ HA.class "filter-row" ] items
591
+
592
+
593
+ viewImageChoiceItem : Int -> Int -> PageImage -> Html Msg
594
+ viewImageChoiceItem selectedIndex index image =
595
+ let
596
+ isActive =
597
+ index == selectedIndex
598
+ in
599
+ button
600
+ [ classList
601
+ [ ( "page-view-choice", True )
602
+ , ( "ui-card", True )
603
+ , ( "ui-card--dark", True )
604
+ , ( "is-active", isActive )
605
+ ]
606
+ , type_ "button"
607
+ , onClick (UserClickedPageViewImageChoice index)
608
+ ]
609
+ [ img
610
+ [ HA.class "page-view-choice-thumb"
611
+ , src image.thumbUrl
612
+ , alt image.label
613
+ ]
614
+ []
615
+ , span
616
+ [ HA.class "page-view-choice-label" ]
617
+ [ text image.label ]
618
+ ]
619
+
620
+
621
+ viewImageChoicesSidebar : List PageImage -> Int -> Html Msg
622
+ viewImageChoicesSidebar images selectedIndex =
623
+ div
624
+ [ HA.class "page-view-choices" ]
625
+ (List.indexedMap (\index image -> Lazy.lazy3 viewImageChoiceItem selectedIndex index image) images)
626
+
627
+
628
+ viewMirrorRow : Model -> Html Msg
629
+ viewMirrorRow model =
630
+ viewFilterRow
631
+ [ viewToggle "Mirror" model.filters.flip ToggleFlip ]
632
+
633
+
634
+ viewModalBody : Model -> Html Msg
635
+ viewModalBody model =
636
+ let
637
+ currentPage =
638
+ model.selectedIndex
639
+ |> Maybe.andThen (\index -> getPageAt index model.pages)
640
+
641
+ hasChoices =
642
+ Maybe.map (\page -> List.length page.images > 1) currentPage
643
+ |> Maybe.withDefault False
644
+ in
645
+ div
646
+ [ classList
647
+ [ ( "modal-body", True )
648
+ , ( "is-no-gap", True )
649
+ , ( "is-fullscreen", model.pageViewFullscreen )
650
+ , ( "is-with-choices", hasChoices )
651
+ , ( "is-no-sidebar", not model.pageViewSidebarVisible )
652
+ , ( "is-with-choices-no-sidebar", hasChoices && not model.pageViewSidebarVisible )
653
+ ]
654
+ ]
655
+ (case ( hasChoices, currentPage, model.pageViewSidebarVisible ) of
656
+ ( True, Just page, True ) ->
657
+ [ viewImageChoicesSidebar page.images model.pageViewImageIndex
658
+ , viewModalViewer model.pageViewFullscreen False
659
+ , viewModalSidebar model
660
+ ]
661
+
662
+ ( True, Just page, False ) ->
663
+ [ viewImageChoicesSidebar page.images model.pageViewImageIndex
664
+ , viewModalViewer model.pageViewFullscreen False
665
+ ]
666
+
667
+ ( _, _, True ) ->
668
+ [ viewModalViewer model.pageViewFullscreen True
669
+ , viewModalSidebar model
670
+ ]
671
+
672
+ _ ->
673
+ [ viewModalViewer model.pageViewFullscreen True
674
+ ]
675
+ )
676
+
677
+
678
+ viewModalHeader : Model -> Html Msg
679
+ viewModalHeader model =
680
+ let
681
+ manifestTitle =
682
+ manifestTitleFor model
683
+
684
+ pageLabel =
685
+ currentPageLabelFor model
686
+
687
+ ( prevDisabled, nextDisabled ) =
688
+ case model.selectedIndex of
689
+ Just index ->
690
+ ( index <= 0
691
+ , index >= (List.length model.pages - 1)
692
+ )
693
+
694
+ Nothing ->
695
+ ( True
696
+ , True
697
+ )
698
+
699
+ ( fullscreenIcon, fullscreenLabel ) =
700
+ if model.pageViewFullscreen then
701
+ ( Icons.fromFullscreen, "Exit fullscreen" )
702
+
703
+ else
704
+ ( Icons.toFullscreen, "Fullscreen" )
705
+
706
+ ( sidebarIcon, sidebarLabel ) =
707
+ if model.pageViewSidebarVisible then
708
+ ( Icons.hideSidebar, "Hide filters" )
709
+
710
+ else
711
+ ( Icons.showSidebar, "Show filters" )
712
+ in
713
+ div
714
+ [ HA.class "modal-header" ]
715
+ [ div
716
+ [ HA.class "modal-title-stack" ]
717
+ (div [ HA.class "modal-title" ] [ text "Page View" ]
718
+ :: (if String.isEmpty manifestTitle then
719
+ []
720
+
721
+ else
722
+ [ div [ HA.class "modal-subtitle" ] [ text manifestTitle ] ]
723
+ )
724
+ ++ (if String.isEmpty pageLabel then
725
+ []
726
+
727
+ else
728
+ [ div [ HA.class "modal-subtitle is-muted" ] [ text pageLabel ] ]
729
+ )
730
+ )
731
+ , div
732
+ [ HA.class "modal-actions" ]
733
+ [ viewButton
734
+ { label = "Previous Page"
735
+ , icon = Icons.prevPage
736
+ , onClickMsg = disabledIf prevDisabled UserClickedPageViewPrev
737
+ , isFullscreen = model.fullscreen
738
+ }
739
+ , viewButton
740
+ { label = "Next Page"
741
+ , icon = Icons.nextPage
742
+ , onClickMsg = disabledIf nextDisabled UserClickedPageViewNext
743
+ , isFullscreen = model.fullscreen
744
+ }
745
+ , viewButton
746
+ { label = "Save view"
747
+ , icon = Icons.downloadSelection
748
+ , onClickMsg = Just UserClickedSaveFilteredImage
749
+ , isFullscreen = model.fullscreen
750
+ }
751
+ , viewButton
752
+ { label = "Reset Filters"
753
+ , icon = Icons.reset
754
+ , onClickMsg = Just UserResetAllFilters
755
+ , isFullscreen = model.fullscreen
756
+ }
757
+ , viewButton
758
+ { label = sidebarLabel
759
+ , icon = sidebarIcon
760
+ , onClickMsg = Just UserToggledPageViewSidebar
761
+ , isFullscreen = model.fullscreen
762
+ }
763
+ , viewButton
764
+ { label = fullscreenLabel
765
+ , icon = fullscreenIcon
766
+ , onClickMsg = Just UserToggledPageViewFullscreen
767
+ , isFullscreen = model.fullscreen
768
+ }
769
+ , div
770
+ [ HA.class "modal-close-action" ]
771
+ [ viewButton
772
+ { label = ""
773
+ , icon = Icons.close
774
+ , onClickMsg = Just UserClickedClosePageView
775
+ , isFullscreen = model.fullscreen
776
+ }
777
+ ]
778
+ ]
779
+ ]
780
+
781
+
782
+ viewModalSidebar : Model -> Html Msg
783
+ viewModalSidebar model =
784
+ div
785
+ [ HA.class "modal-sidebar" ]
786
+ [ Lazy.lazy viewTransformGroup model
787
+ , Lazy.lazy viewToneGroup model
788
+ , Lazy.lazy viewColourAdjustGroup model
789
+ , Lazy.lazy viewMorphologyGroup model
790
+ , Lazy.lazy viewConvolutionGroup model
791
+ , Lazy.lazy viewColourmapGroup model
792
+ , Lazy.lazy viewPseudoColourGroup model
793
+ , Lazy.lazy viewPcaGroup model
794
+ , Lazy.lazy viewAdvancedColourAdjustGroup model
795
+ , Lazy.lazy viewEnhancementGroup model
796
+ , Lazy.lazy viewFilterJsonGroup model
797
+ ]
798
+
799
+
800
+ viewModalViewer : Bool -> Bool -> Html Msg
801
+ viewModalViewer fullscreen isOuterLeft =
802
+ div
803
+ [ classList
804
+ [ ( "modal-viewer", True )
805
+ , ( "is-fullscreen", fullscreen )
806
+ , ( "is-outer-left", isOuterLeft )
807
+ ]
808
+ ]
809
+ [ div
810
+ [ HA.class "modal-canvas"
811
+ , id "filter-viewer"
812
+ ]
813
+ []
814
+ ]
815
+
816
+
817
+ viewMorphologyGroup : Model -> Html Msg
818
+ viewMorphologyGroup model =
819
+ viewFilterGroup model
820
+ "morphology"
821
+ "Morphology"
822
+ [ viewFilterRow
823
+ [ viewToggle "Morph" model.filters.morphEnabled ToggleMorph
824
+ , viewSelect model.filters.morphOperation (UserUpdatedFilterString StringMorphOperation) morphOperationOptions
825
+ , viewSelect (String.fromInt model.filters.morphKernel) (UserUpdatedFilterInt IntMorphKernel) morphKernelOptions
826
+ ]
827
+ ]
828
+
829
+
830
+ viewPcaGroup : Model -> Html Msg
831
+ viewPcaGroup model =
832
+ viewFilterGroup model
833
+ "pca"
834
+ "Visible area PCA"
835
+ [ viewFilterRow
836
+ [ viewToggle "Visible area PCA" model.filters.globalPcaEnabled ToggleGlobalPca
837
+ , viewSelect model.filters.pcaMode (UserUpdatedFilterString StringPcaMode) pcaModes
838
+ ]
839
+ , viewRangeRow
840
+ { label = "Hue Rotation"
841
+ , min = "-180"
842
+ , max = "180"
843
+ , step = Just "1"
844
+ , value = String.fromInt model.filters.pcaHue
845
+ , display = String.fromInt model.filters.pcaHue ++ "deg"
846
+ , onInput = UserUpdatedFilterInt IntPcaHue
847
+ }
848
+ ]
849
+
850
+
851
+ viewPseudoColourGroup : Model -> Html Msg
852
+ viewPseudoColourGroup model =
853
+ viewFilterGroup model
854
+ "pseudo-colour"
855
+ "Pseudo Colour"
856
+ [ viewFilterRow
857
+ [ viewToggle "Pseudo-colour" model.filters.pseudoColourEnabled TogglePseudoColour
858
+ , viewSelect model.filters.pseudoColourMode (UserUpdatedFilterString StringPseudoColourMode) pseudoColourModes
859
+ ]
860
+ , viewRangeRow
861
+ { label = "Red Weight"
862
+ , min = "0"
863
+ , max = "2"
864
+ , step = Just "0.01"
865
+ , value = String.fromFloat model.filters.pseudoColourRed
866
+ , display = String.fromFloat model.filters.pseudoColourRed
867
+ , onInput = UserUpdatedFilterFloat FloatPseudoColourRed
868
+ }
869
+ , viewRangeRow
870
+ { label = "Green Weight"
871
+ , min = "0"
872
+ , max = "2"
873
+ , step = Just "0.01"
874
+ , value = String.fromFloat model.filters.pseudoColourGreen
875
+ , display = String.fromFloat model.filters.pseudoColourGreen
876
+ , onInput = UserUpdatedFilterFloat FloatPseudoColourGreen
877
+ }
878
+ , viewRangeRow
879
+ { label = "Blue Weight"
880
+ , min = "0"
881
+ , max = "2"
882
+ , step = Just "0.01"
883
+ , value = String.fromFloat model.filters.pseudoColourBlue
884
+ , display = String.fromFloat model.filters.pseudoColourBlue
885
+ , onInput = UserUpdatedFilterFloat FloatPseudoColourBlue
886
+ }
887
+ , viewFilterRow
888
+ [ viewToggle "Replace Colour" model.filters.colourReplaceEnabled ToggleColourReplace ]
889
+ , viewFilterRow
890
+ [ span [ HA.class "filter-label" ] [ text "Source" ]
891
+ , viewColourInput model.filters.colourReplaceSource (UserUpdatedFilterString StringColourReplaceSource)
892
+ , span [ HA.class "filter-label" ] [ text "Target" ]
893
+ , viewColourInput model.filters.colourReplaceTarget (UserUpdatedFilterString StringColourReplaceTarget)
894
+ ]
895
+ , viewRangeRow
896
+ { label = "Tolerance"
897
+ , min = "0"
898
+ , max = "255"
899
+ , step = Just "1"
900
+ , value = String.fromInt model.filters.colourReplaceTolerance
901
+ , display = String.fromInt model.filters.colourReplaceTolerance
902
+ , onInput = UserUpdatedFilterInt IntColourReplaceTolerance
903
+ }
904
+ , viewRangeRow
905
+ { label = "Strength"
906
+ , min = "0"
907
+ , max = "1"
908
+ , step = Just "0.01"
909
+ , value = String.fromFloat model.filters.colourReplaceBlend
910
+ , display = String.fromFloat model.filters.colourReplaceBlend
911
+ , onInput = UserUpdatedFilterFloat FloatColourReplaceBlend
912
+ }
913
+ , viewFilterRow
914
+ [ viewToggle "Preserve Luminance" model.filters.colourReplacePreserveLum ToggleColourReplacePreserveLum ]
915
+ ]
916
+
917
+
918
+ viewRangeInput : String -> String -> Maybe String -> String -> (String -> Msg) -> Html Msg
919
+ viewRangeInput minValue maxValue stepValue currentValue onChange =
920
+ let
921
+ baseAttrs =
922
+ [ type_ "range"
923
+ , HA.min minValue
924
+ , HA.max maxValue
925
+ , value currentValue
926
+ , onInput onChange
927
+ ]
928
+
929
+ attrs =
930
+ case stepValue of
931
+ Just stepSize ->
932
+ HA.step stepSize :: baseAttrs
933
+
934
+ Nothing ->
935
+ baseAttrs
936
+ in
937
+ input (HA.class "filter-range-input" :: attrs) []
938
+
939
+
940
+ viewRangeRow : RangeRowConfig -> Html Msg
941
+ viewRangeRow config =
942
+ div [ HA.class "filter-range-group" ]
943
+ [ div [ HA.class "filter-range-header" ]
944
+ [ span [ HA.class "filter-label" ] [ text config.label ]
945
+ , span [ HA.class "filter-value" ] [ text config.display ]
946
+ ]
947
+ , viewRangeInput config.min config.max config.step config.value config.onInput
948
+ ]
949
+
950
+
951
+ viewRotationRow : Model -> Html Msg
952
+ viewRotationRow model =
953
+ div [ HA.class "filter-range-group" ]
954
+ [ div [ HA.class "filter-range-header" ]
955
+ [ span [ HA.class "filter-label" ] [ text "Rotation" ]
956
+ , span [ HA.class "filter-range-header-right" ]
957
+ [ span [ HA.class "filter-value" ]
958
+ [ text (String.fromInt model.filters.rotation ++ "°") ]
959
+ , button
960
+ [ HA.class "filter-reset"
961
+ , type_ "button"
962
+ , onClick (UserUpdatedFilterInt IntRotation "0")
963
+ ]
964
+ [ text "Reset" ]
965
+ ]
966
+ ]
967
+ , viewRangeInput "-180"
968
+ "180"
969
+ (Just "1")
970
+ (String.fromInt model.filters.rotation)
971
+ (UserUpdatedFilterInt IntRotation)
972
+ ]
973
+
974
+
975
+ viewSelect : String -> (String -> Msg) -> List (Html Msg) -> Html Msg
976
+ viewSelect currentValue onChange options =
977
+ select
978
+ [ HA.class "filter-select"
979
+ , onInput onChange
980
+ , value currentValue
981
+ ]
982
+ options
983
+
984
+
985
+ viewToggle : String -> Bool -> FilterToggle -> Html Msg
986
+ viewToggle labelText isChecked toggle =
987
+ label [ HA.class "filter-toggle" ]
988
+ [ input
989
+ [ type_ "checkbox"
990
+ , checked isChecked
991
+ , onCheck (UserToggledFilter toggle)
992
+ ]
993
+ []
994
+ , text labelText
995
+ ]
996
+
997
+
998
+ viewToggleRangeRow : ToggleRangeRowConfig -> Html Msg
999
+ viewToggleRangeRow config =
1000
+ div [ HA.class "filter-range-group" ]
1001
+ [ div [ HA.class "filter-range-header" ]
1002
+ [ label [ HA.class "filter-toggle is-inline" ]
1003
+ [ input
1004
+ [ type_ "checkbox"
1005
+ , checked config.checked
1006
+ , onCheck (UserToggledFilter config.onToggle)
1007
+ ]
1008
+ []
1009
+ , text config.label
1010
+ ]
1011
+ , span [ HA.class "filter-value" ] [ text config.display ]
1012
+ ]
1013
+ , viewRangeInput config.min config.max config.step config.value config.onInput
1014
+ ]
1015
+
1016
+
1017
+ viewToggleRows : List ( String, Bool, FilterToggle ) -> List (Html Msg)
1018
+ viewToggleRows items =
1019
+ List.map
1020
+ (\( labelText, isChecked, toggle ) ->
1021
+ viewToggle labelText isChecked toggle
1022
+ )
1023
+ items
1024
+
1025
+
1026
+ viewToneGroup : Model -> Html Msg
1027
+ viewToneGroup model =
1028
+ viewFilterGroup model
1029
+ "tone"
1030
+ "Tone"
1031
+ (viewToggleRows
1032
+ [ ( "Grayscale", model.filters.grayscale, ToggleGrayscale )
1033
+ , ( "Invert", model.filters.invert, ToggleInvert )
1034
+ ]
1035
+ ++ List.map viewToggleRangeRow (toneToggleRanges model)
1036
+ )
1037
+
1038
+
1039
+ viewTransformGroup : Model -> Html Msg
1040
+ viewTransformGroup model =
1041
+ viewFilterGroup model
1042
+ "transform"
1043
+ "Transform"
1044
+ [ viewRotationRow model
1045
+ , viewMirrorRow model
1046
+ ]