-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
-
445
-
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-
460
-
461
-
462
-
463
-
464
-
465
-
466
-
467
-
468
-
469
-
470
-
471
-
472
-
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
482
-
483
-
484
-
485
-
486
-
487
-
488
-
489
-
490
-
491
-
492
-
493
-
494
-
495
-
496
-
497
-
498
-
499
-
500
-
501
-
502
-
503
-
504
-
505
-
506
-
507
-
508
-
509
-
510
-
511
-
512
-
513
-
514
-
515
-
516
-
517
-
518
-
519
-
520
-
521
-
522
-
523
-
524
-
525
-
526
-
527
-
528
-
529
-
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
-
549
-
550
-
551
-
552
-
553
-
554
-
555
-
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
-
574
-
575
-
576
-
577
-
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
-
586
-
587
-
588
-
589
-
590
-
591
-
592
-
593
-
594
-
595
-
596
-
597
-
598
-
599
-
600
-
601
-
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
-
619
-
620
-
621
-
622
-
623
-
624
-
625
-
626
-
627
-
628
-
629
-
630
-
631
-
632
-
633
-
634
-
635
-
636
-
637
-
638
-
639
-
640
-
641
-
642
-
643
-
644
-
645
-
646
-
647
-
648
-
649
-
650
-
651
-
652
-
653
-
654
-
655
-
656
-
657
-
658
-
659
-
660
-
661
-
662
-
663
-
664
-
665
-
666
-
667
-
668
-
669
-
670
-
671
-
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
-
680
-
681
-
682
-
683
-
684
-
685
-
686
-
687
-
688
-
689
-
690
-
691
-
692
-
693
-
694
-
695
-
696
-
697
-
698
-
699
-
700
-
701
-
702
-
703
-
704
-
705
-
706
-
707
-
708
-
709
-
710
-
711
-
712
-
713
-
714
-
715
-
716
-
717
-
718
-
719
-
720
-
721
-
722
-
723
-
724
-
725
-
726
-
727
-
728
-
729
-
730
-
731
-
732
-
733
-
734
-
735
-
736
-
737
-
738
-
739
-
740
-
741
-
742
-
743
-
744
-
745
-
746
-
747
-
748
-
749
-
750
-
751
-
752
-
753
-
754
-
755
-
756
-
757
-
758
-
759
-
760
-
761
-
762
-
763
-
764
-
765
-
766
-
767
-
768
-
769
-
770
-
771
-
772
-
773
-
774
-
775
-
776
-
777
-
778
-
779
-
780
-
781
-
782
-
783
-- Copyright 2026 Shota FUJI
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
--
-- SPDX-License-Identifier: MPL-2.0
module Parameters.App exposing (Model, Msg, init, update, view)
import Browser.Navigation
import Dict
import Html exposing (hr, input, label, node, p, span, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onBlur, onFocus, onInput)
import Html.LivingStandard exposing (..)
import Length exposing (Length, mm, toMM)
import Parameters exposing (ColorSchema(..), LoopStyle(..), Parameters, ParametersDict, colorSchemaToString, getKey, hasKey, loopStyleToString, profileKind)
import Parameters.Constraints exposing (NumberConstraints, constraints)
import Parameters.Key as Key exposing (Key(..))
import Parameters.Parser exposing (..)
import Platform.Cmd as Cmd
import Process
import Task
import Time
import Url exposing (Url)
import Url.SearchParams
-- MODEL
type UrlUpdate
= Idle
| Throttled Time.Posix
| Scheduled
type alias Model =
{ fields : ParametersDict
, errors : Errors
, parameters : Parameters
, highlighting : Maybe Key
, navigation : Browser.Navigation.Key
, baseUrl : Url
, currentUrl : Url
, urlUpdate : UrlUpdate
}
init : Url -> Browser.Navigation.Key -> Parameters -> ( Model, Cmd Msg )
init baseUrl key params =
let
fields =
Dict.union
(Parameters.toDict params)
Parameters.fallbackValues
in
( { fields = fields
, errors =
case parse fields of
Ok _ ->
Dict.empty
Err errors ->
errors
, parameters = params
, highlighting = Nothing
, navigation = key
, baseUrl = baseUrl
, currentUrl = baseUrl
, urlUpdate = Idle
}
, Task.perform UrlRewriteRequested Time.now
)
-- UPDATE
type Msg
= FieldChanged Key String
| FieldUnset Key
| Highlight Key
| Unhighlight Key
| UrlRewriteTriggered
| UrlRewriteRequested Time.Posix
| RunThrottledUrlRewrite
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FieldChanged key value ->
let
fields =
Dict.insert (Key.toString key) value model.fields
in
case parse fields of
Ok newParams ->
( { model
| fields = fields
, errors = Dict.empty
, parameters = newParams
}
, Task.perform UrlRewriteRequested Time.now
)
Err errors ->
( { model | fields = fields, errors = errors }, Cmd.none )
FieldUnset key ->
let
fields =
Dict.remove (Key.toString key) model.fields
in
case parse fields of
Ok newParams ->
( { model
| fields = fields
, errors = Dict.empty
, parameters = newParams
}
, Task.perform UrlRewriteRequested Time.now
)
Err errors ->
( { model | fields = fields, errors = errors }, Cmd.none )
Highlight key ->
( { model | highlighting = Just key }, Cmd.none )
Unhighlight key ->
if model.highlighting == Just key then
( { model | highlighting = Nothing }, Cmd.none )
else
( model, Cmd.none )
UrlRewriteRequested now ->
let
{ baseUrl } =
model
query =
model.parameters
|> Parameters.toDict
|> Url.SearchParams.build
url =
{ baseUrl | query = Just query }
next =
{ model | currentUrl = url }
throttledUntil =
now
|> Time.posixToMillis
|> (+) 200
|> Time.millisToPosix
in
case model.urlUpdate of
Idle ->
update UrlRewriteTriggered { next | urlUpdate = Throttled throttledUntil }
Scheduled ->
( next, Cmd.none )
Throttled until ->
let
timeout =
Time.posixToMillis until - Time.posixToMillis now
in
if timeout <= 0 then
update UrlRewriteTriggered { next | urlUpdate = Throttled throttledUntil }
else
( { next | urlUpdate = Scheduled }
, Task.perform (\_ -> RunThrottledUrlRewrite) (Process.sleep (toFloat timeout))
)
RunThrottledUrlRewrite ->
update UrlRewriteTriggered { model | urlUpdate = Idle }
UrlRewriteTriggered ->
( model
, Browser.Navigation.replaceUrl model.navigation (Url.toString model.currentUrl)
)
-- VIEW
lengthFieldAttrs : NumberConstraints Length -> List (Html.Attribute msg)
lengthFieldAttrs { min, max } =
[ case min of
Just x ->
toMM x
|> String.fromFloat
|> Html.Attributes.min
Nothing ->
class ""
, case max of
Just x ->
toMM x
|> String.fromFloat
|> Html.Attributes.max
Nothing ->
class ""
, attribute "inputmode" "numeric"
, required True
]
intFieldAttrs : NumberConstraints Int -> List (Html.Attribute msg)
intFieldAttrs { min, max } =
[ case min of
Just x ->
Html.Attributes.min (String.fromInt x)
Nothing ->
class ""
, case max of
Just x ->
Html.Attributes.max (String.fromInt x)
Nothing ->
class ""
, attribute "inputmode" "numeric"
, required True
]
ariaInvalid : Bool -> Html.Attribute msg
ariaInvalid invalid =
attribute "aria-invalid"
(if invalid then
"true"
else
"false"
)
ariaDescribedBy : List String -> Html.Attribute msg
ariaDescribedBy ids =
case ids of
[] ->
class ""
_ ->
ids
|> String.join " "
|> attribute "aria-describedby"
errorId : Key -> String
errorId key =
Key.toString key ++ "_error"
errorText : Error -> List (Html.Html msg)
errorText e =
case e of
MissingValue ->
[ text "This field is required." ]
BelowMin n ->
[ text ("Value must be greater than or equals to " ++ String.fromFloat n ++ ".") ]
AboveMax n ->
[ text ("Value must be less than or equals to " ++ String.fromFloat n ++ ".") ]
NotALength ->
[ text "Invalid length value. Set an integer or a floating point number." ]
NotAnInt ->
[ text "Invalid integer value. This field only accepts integer value." ]
NotABool ->
[ text "Invalid bool value. Value must be either \"true\" or \"false\"." ]
NonexistentVariant str ->
[ text ("\"" ++ str ++ "\" is not a valid choice. Did you modify program's source code?") ]
UnsupportedParametersVersion str ->
[ text ("Parameters version \"" ++ str ++ "\" is not supported.") ]
descriptionId : Key -> String
descriptionId key =
Key.toString key ++ "_description"
type alias NumberFieldProps msg =
{ key : Key
, title : List (Html.Html msg)
, description : List (Html.Html msg)
, unit : Maybe String
, attrs : List (Html.Attribute msg)
, disabled : Bool
}
numberField : Model -> NumberFieldProps Msg -> Html.Html Msg
numberField model { key, title, description, attrs, unit, disabled } =
node "x-field"
[ if disabled then
attribute "disabled" ""
else
class ""
]
[ label [ for (Key.toString key), slot "title" ] title
, p
[ id (descriptionId key), slot "description" ]
description
, node "x-number-input"
[]
[ input
(id (Key.toString key)
:: value (model.fields |> getKey key |> Maybe.withDefault "")
:: onInput (FieldChanged key)
:: onFocus (Highlight key)
:: onBlur (Unhighlight key)
:: ariaInvalid (not (Dict.get (Key.toString key) model.errors == Nothing))
:: ariaDescribedBy [ errorId key, descriptionId key ]
:: Html.Attributes.disabled disabled
:: attrs
)
[]
, case unit of
Just u ->
span [ slot "unit" ] [ text u ]
Nothing ->
text ""
]
, p
[ id (errorId key), slot "error" ]
(Dict.get (Key.toString key) model.errors
|> Maybe.map errorText
|> Maybe.withDefault []
)
]
type alias ChoiceFieldProps msg =
{ key : Key
, title : List (Html.Html msg)
, description : List (Html.Html msg)
, attrs : List (Html.Attribute msg)
, disabled : Bool
, choices :
List
{ value : String
, label : List (Html.Html msg)
, description : List (Html.Html msg)
}
}
choiceField : Model -> ChoiceFieldProps Msg -> Html.Html Msg
choiceField model { key, title, disabled, description, attrs, choices } =
node "x-field"
[ if disabled then
attribute "disabled" ""
else
class ""
]
(span [ slot "title" ] title
:: p [ slot "description" ] description
:: List.map
(\choice ->
let
id =
Key.toString key ++ "_" ++ choice.value
choiceDescriptionId =
id ++ "__description"
checked =
getKey key model.fields
|> Maybe.withDefault ""
|> (==) choice.value
in
node "x-radio-box"
[ if checked then
attribute "checked" ""
else
class ""
]
[ Html.label [ for id, slot "label" ] choice.label
, Html.p [ Html.Attributes.id choiceDescriptionId, slot "description" ] choice.description
, input
(type_ "radio"
:: Html.Attributes.id id
:: Html.Attributes.name (Key.toString key)
:: Html.Attributes.value choice.value
:: Html.Attributes.checked checked
:: Html.Attributes.disabled disabled
:: Html.Events.onCheck (\_ -> FieldChanged key choice.value)
:: ariaDescribedBy [ choiceDescriptionId ]
:: attrs
)
[]
]
)
choices
)
type alias BoolFieldProps msg =
{ key : Key
, title : List (Html.Html msg)
, description : List (Html.Html msg)
, attrs : List (Html.Attribute msg)
, disabled : Bool
, true : { label : List (Html.Html msg), description : List (Html.Html msg) }
, false : { label : List (Html.Html msg), description : List (Html.Html msg) }
}
boolField : Model -> BoolFieldProps Msg -> Html.Html Msg
boolField model { key, title, disabled, description, attrs, true, false } =
let
isTrue =
hasKey key model.fields
id : Bool -> String
id v =
Key.toString key
++ "_"
++ (if v then
"true"
else
"false"
)
choiceDescriptionId : Bool -> String
choiceDescriptionId v =
id v ++ "_description"
in
node "x-field"
[ if disabled then
attribute "disabled" ""
else
class ""
]
[ span [ slot "title" ] title
, p [ slot "description" ] description
, node "x-radio-box"
[ if isTrue then
attribute "checked" ""
else
class ""
]
[ Html.label [ for (id True), slot "label" ] true.label
, Html.p [ Html.Attributes.id (choiceDescriptionId True), slot "description" ] true.description
, input
(type_ "radio"
:: Html.Attributes.id (id True)
:: Html.Attributes.name (Key.toString key)
:: Html.Attributes.value "true"
:: Html.Attributes.checked isTrue
:: Html.Attributes.disabled disabled
:: Html.Events.onCheck (\_ -> FieldChanged key "")
:: ariaDescribedBy [ choiceDescriptionId True ]
:: attrs
)
[]
]
, node "x-radio-box"
[ if not isTrue then
attribute "checked" ""
else
class ""
]
[ Html.label [ for (id False), slot "label" ] false.label
, Html.p [ Html.Attributes.id (choiceDescriptionId False), slot "description" ] false.description
, input
(type_ "radio"
:: Html.Attributes.id (id False)
:: Html.Attributes.name (Key.toString key)
:: Html.Attributes.value "false"
:: Html.Attributes.checked (not isTrue)
:: Html.Attributes.disabled disabled
:: Html.Events.onCheck (\_ -> FieldUnset key)
:: ariaDescribedBy [ choiceDescriptionId False ]
:: attrs
)
[]
]
]
type alias GroupProps msg =
{ title : List (Html.Html msg)
, description : Maybe (List (Html.Html msg))
}
group : GroupProps Msg -> List (Html.Html Msg) -> Html.Html Msg
group { title, description } children =
node "x-field-group"
[]
(span [ slot "title" ] title
:: (description |> Maybe.map (span [ slot "description" ]) |> Maybe.withDefault (text ""))
:: children
)
view : Model -> List (Html.Html Msg)
view model =
[ group
{ title = [ text "General" ], description = Nothing }
[ numberField model
{ key = ShoulderWidth
, title = [ text "Shoulder Width" ]
, description =
[ text "Width of your strap. "
, text "Should be same as or wider than your watch's lug width."
]
, unit = Just "mm"
, disabled = False
, attrs = step "1.0" :: lengthFieldAttrs constraints.shoulderWidth
}
, choiceField model
{ key = Profile
, title = [ text "Profile" ]
, description = [ text "Shape of the straps." ]
, disabled = False
, attrs = []
, choices =
[ { label = [ text "Straight" ]
, description = [ text "Shoulder width, tip width and buckle-edge width shares the same size." ]
, value = profileKind Parameters.Straight
}
, { label = [ text "Tapered" ]
, description = [ text "Narrows down to the specified width." ]
, value = profileKind (Parameters.Tapered (mm 0))
}
]
}
, numberField model
{ key = TaperTo
, title = [ text "Taper To" ]
, description = [ text "Width of the tip and buckle-edge." ]
, unit = Just "mm"
, disabled = model.parameters.profile == Parameters.Straight
, attrs = step "1.0" :: lengthFieldAttrs constraints.taperTo
}
, numberField model
{ key = LongPieceLength
, title = [ text "Length (Tail Side)" ]
, description =
[ text "Length of the tail-side piece, which is usually the longer one."
]
, unit = Just "mm"
, disabled = False
, attrs = step "1" :: lengthFieldAttrs constraints.longPiece.length
}
, numberField model
{ key = ShortPieceLength
, title = [ text "Length (Buckle Side)" ]
, description =
[ text "Length of the buckle-side piece, which is usually the shorter one."
]
, unit = Just "mm"
, disabled = False
, attrs = step "1" :: lengthFieldAttrs constraints.shortPiece.length
}
]
, hr [] []
, group
{ title = [ text "Buckle / Clasp" ], description = Nothing }
[ numberField model
{ key = BuckleHoleDistance
, title = [ text "Center Hole Distance" ]
, description =
[ text "Distance between case-side strap end and the center hole." ]
, unit = Just "mm"
, disabled = False
, attrs =
step "1.0" :: lengthFieldAttrs constraints.longPiece.buckleHole.distance
}
, numberField model
{ key = BuckleHoleAdjustments
, title = [ text "Hole Adjustment Level" ]
, description = [ text "Number of adjustment holes top and bottom of the center hole." ]
, unit = Nothing
, disabled = False
, attrs = step "1" :: intFieldAttrs constraints.longPiece.buckleHole.adjustments
}
, numberField model
{ key = BuckleHoleDiameter
, title = [ text "Hole Diameter" ]
, description = [ text "Diameter of the buckle holes." ]
, unit = Just "mm"
, disabled = False
, attrs =
step "1.0" :: lengthFieldAttrs constraints.longPiece.buckleHole.diameter
}
, numberField model
{ key = BuckleHoleInterval
, title = [ text "Hole Interval" ]
, description =
[ text "Center-to-center interval between buckle holes." ]
, unit = Just "mm"
, disabled = False
, attrs =
step "0.5" :: lengthFieldAttrs constraints.longPiece.buckleHole.interval
}
]
, hr [] []
, group
{ title = [ text "Loops" ], description = Nothing }
[ choiceField model
{ key = LoopStyle
, title = [ text "Loop Style" ]
, description = [ text "Style of the fixed loop and the free loop." ]
, disabled =
not (hasKey HasFixedLoop model.fields)
&& not (hasKey HasFreeLoop model.fields)
, attrs = []
, choices =
[ { label = [ text "Simple" ]
, description = [ text "Simply use a strip of leather as a loop." ]
, value = loopStyleToString Simple
}
, { label = [ text "Folded" ]
, description = [ text "Fold a strip of leather to hide edges (fibers.)" ]
, value = loopStyleToString Folded
}
]
}
, boolField model
{ key = HasFixedLoop
, title = [ text "Fixed Loop" ]
, description = [ text "Whether to draw fixed loop. Certain kind of deployant buckles don't require a fixed loop." ]
, attrs = []
, disabled = False
, true =
{ label = [ text "Enabled" ]
, description = [ text "Output fixed loop." ]
}
, false =
{ label = [ text "Disabled" ]
, description = [ text "Do not output fixed loop." ]
}
}
, numberField model
{ key = FixedLoopWidth
, title = [ text "Fixed Loop Width" ]
, description = [ text "Width of the fixed loop." ]
, unit = Just "mm"
, disabled = not (hasKey HasFixedLoop model.fields)
, attrs =
step "0.1" :: lengthFieldAttrs constraints.shortPiece.loops.fixed.width
}
, numberField model
{ key = FixedLoopLength
, title = [ text "Fixed Loop Length" ]
, description = [ text "Length of the fixed loop. This should be at least double the strap width and thickness." ]
, unit = Just "mm"
, disabled = not (hasKey HasFixedLoop model.fields)
, attrs =
step "0.1" :: lengthFieldAttrs constraints.shortPiece.loops.fixed.length
}
, boolField model
{ key = HasFreeLoop
, title = [ text "Free Loop" ]
, description = [ text "Whether to draw free loop. Certain kind of deployant buckles don't require a free loop." ]
, attrs = []
, disabled = False
, true =
{ label = [ text "Enabled" ]
, description = [ text "Output free loop." ]
}
, false =
{ label = [ text "Disabled" ]
, description = [ text "Do not output free loop." ]
}
}
, numberField model
{ key = FreeLoopWidth
, title = [ text "Free Loop Width" ]
, description = [ text "Width of the free loop." ]
, unit = Just "mm"
, disabled = not (hasKey HasFreeLoop model.fields)
, attrs =
step "0.1" :: lengthFieldAttrs constraints.shortPiece.loops.free.width
}
, numberField model
{ key = FreeLoopLength
, title = [ text "Free Loop Length" ]
, description = [ text "Length of the free loop. This should be at least double the strap width and thickness." ]
, unit = Just "mm"
, disabled = not (hasKey HasFreeLoop model.fields)
, attrs =
step "0.1" :: lengthFieldAttrs constraints.shortPiece.loops.free.length
}
, numberField model
{ key = FreeLoopOverlap
, title = [ text "Free Loop Overlap" ]
, description = [ text "Length of overlapping section of the free loop for gluing and/or stitching." ]
, unit = Just "mm"
, disabled = not (hasKey HasFreeLoop model.fields)
, attrs =
step "0.1" :: lengthFieldAttrs constraints.shortPiece.loops.free.overlap
}
]
, hr [] []
, group
{ title = [ text "Rendering" ], description = Nothing }
[ numberField model
{ key = CanvasMargin
, title = [ text "Print Margin" ]
, description =
[ text "Lower values can cause printing problems depending on your printer." ]
, unit = Just "mm"
, disabled = False
, attrs = step "1.0" :: lengthFieldAttrs constraints.rendering.margin
}
, numberField model
{ key = LineWidth
, title = [ text "Line Width" ]
, description =
[ text "Stroke width (thickness) of the cutting lines and seam lines." ]
, unit = Just "mm"
, disabled = False
, attrs = step "0.1" :: lengthFieldAttrs constraints.rendering.lineWidth
}
, choiceField model
{ key = ColorSchema
, title = [ text "Color Schema" ]
, description = [ text "Color schema of the template." ]
, attrs = []
, disabled = False
, choices =
[ { label = [ text "Black on White" ]
, description = [ text "Select this if you print the template with a regular printer." ]
, value = colorSchemaToString BlackOnWhite
}
, { label = [ text "White on Black" ]
, description = [ text "For digital output. Printing this with an inkjet printer is not a good idea." ]
, value = colorSchemaToString WhiteOnBlack
}
]
}
, boolField model
{ key = QRCode
, title = [ text "QR code" ]
, description =
[ text "Add QR code points to this page's URL to the output. "
, text "As the QR code generation is computationally heavy process, I recommend you to disable "
, text "this while editing parameters."
]
, attrs = []
, disabled = False
, true =
{ label = [ text "Enabled" ]
, description = [ text "Output QR code." ]
}
, false =
{ label = [ text "Disabled" ]
, description = [ text "Do not output QR code." ]
}
}
]
]