glib-web 4.42.4 → 4.43.0

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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/glib/json_ui/action_builder/browsers.rb +10 -0
  3. data/app/views/json_ui/garage/forms/selects.json.jbuilder +240 -174
  4. data/app/views/json_ui/garage/tables/bulk_edit.json.jbuilder +0 -24
  5. data/app/views/json_ui/garage/test_page/_header.json.jbuilder +25 -15
  6. data/app/views/json_ui/garage/test_page/auth.json.jbuilder +113 -0
  7. data/app/views/json_ui/garage/test_page/browsers.json.jbuilder +44 -0
  8. data/app/views/json_ui/garage/test_page/commands.json.jbuilder +30 -0
  9. data/app/views/json_ui/garage/test_page/cookies.json.jbuilder +12 -4
  10. data/app/views/json_ui/garage/test_page/fields.json.jbuilder +160 -0
  11. data/app/views/json_ui/garage/test_page/fields_dynamicSelect.json.jbuilder +177 -0
  12. data/app/views/json_ui/garage/test_page/fields_select.json.jbuilder +152 -0
  13. data/app/views/json_ui/garage/test_page/fields_sign.json.jbuilder +153 -0
  14. data/app/views/json_ui/garage/test_page/fields_timer.json.jbuilder +148 -0
  15. data/app/views/json_ui/garage/test_page/fields_upload.json.jbuilder +129 -0
  16. data/app/views/json_ui/garage/test_page/forms.json.jbuilder +6 -2
  17. data/app/views/json_ui/garage/test_page/list.json.jbuilder +110 -69
  18. data/app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder +240 -0
  19. data/app/views/json_ui/garage/test_page/popovers.json.jbuilder +6 -2
  20. data/app/views/json_ui/garage/test_page/progressCircle.json.jbuilder +74 -65
  21. data/app/views/json_ui/garage/test_page/table.json.jbuilder +157 -88
  22. metadata +9 -2
  23. data/app/views/json_ui/garage/test_page/file_upload_new.json.jbuilder +0 -158
@@ -0,0 +1,152 @@
1
+ json.title 'Test Page (Fields Select Options)'
2
+
3
+ page = json_ui_page json
4
+
5
+ page.body(
6
+ childViews: ->(body) do
7
+ render 'json_ui/garage/test_page/header', view: body
8
+
9
+ body.panels_responsive(
10
+ padding: glib_json_padding_body,
11
+ childViews: ->(res) do
12
+ res.h2 text: 'Fields Select Options'
13
+ res.spacer height: 8
14
+ res.label text: 'Use icon or image on each option in fields_select.'
15
+ res.spacer height: 12
16
+
17
+ res.panels_form(
18
+ url: json_ui_garage_url(path: 'forms/generic_post'),
19
+ method: 'post',
20
+ childViews: ->(form) do
21
+ base_options = [
22
+ { text: 'Option 1', value: 'option1' },
23
+ { text: 'Option 2', value: 'option2' },
24
+ { text: 'Option 3', value: 'option3' },
25
+ { text: 'Option 4', value: 'option4' }
26
+ ]
27
+
28
+ icon_names = ['star', 'bolt', 'event', 'place']
29
+ icon_options = base_options.map.with_index do |option, index|
30
+ option.merge(icon: { name: icon_names[index] })
31
+ end
32
+ image_options = base_options.map.with_index do |option, index|
33
+ option.merge(imageUrl: "https://picsum.photos/seed/select-#{index + 1}/32/24")
34
+ end
35
+
36
+ form.h2 text: 'Basic example'
37
+ form.spacer height: 8
38
+ form.fields_select(
39
+ id: 'select_media',
40
+ name: 'user[select_media]',
41
+ label: 'Select with icon',
42
+ width: 'matchParent',
43
+ clearable: true,
44
+ options: icon_options,
45
+ value: 'option1',
46
+ onChange: ->(action) do
47
+ action.logics_set targetId: 'select_status', data: { text: 'Status: changed' }
48
+ end
49
+ )
50
+
51
+ form.spacer height: 12
52
+ form.hr width: 'matchParent'
53
+ form.spacer height: 12
54
+
55
+ form.h2 text: 'Variants/props'
56
+ form.spacer height: 8
57
+ form.label text: 'Swap options between icon and image styles.'
58
+ form.spacer height: 8
59
+ form.panels_flow(
60
+ innerPadding: { bottom: 0 },
61
+ width: 'matchParent',
62
+ childViews: ->(flow) do
63
+ flow.button(
64
+ text: 'Use icon options',
65
+ onClick: ->(action) do
66
+ action.components_set(
67
+ targetId: 'select_media',
68
+ data: { options: icon_options, value: 'option1', multiple: nil, useChips: nil }
69
+ )
70
+ end
71
+ )
72
+ flow.spacer width: 8
73
+ flow.button(
74
+ text: 'Use image options',
75
+ onClick: ->(action) do
76
+ action.components_set(
77
+ targetId: 'select_media',
78
+ data: { options: image_options, value: 'option2', multiple: nil, useChips: nil }
79
+ )
80
+ end
81
+ )
82
+ flow.spacer width: 8
83
+ flow.button(
84
+ text: 'Text only',
85
+ onClick: ->(action) do
86
+ action.components_set(
87
+ targetId: 'select_media',
88
+ data: { options: base_options, value: 'option3', multiple: nil, useChips: nil }
89
+ )
90
+ end
91
+ )
92
+ flow.spacer width: 8
93
+ flow.button(
94
+ text: 'Multiple + chips',
95
+ onClick: ->(action) do
96
+ action.components_set(
97
+ targetId: 'select_media',
98
+ data: { options: icon_options, value: %w[option1 option3], multiple: true, useChips: true }
99
+ )
100
+ end
101
+ )
102
+ end
103
+ )
104
+
105
+ form.spacer height: 12
106
+ form.hr width: 'matchParent'
107
+ form.spacer height: 12
108
+
109
+ form.h2 text: 'Edge/advanced'
110
+ form.spacer height: 8
111
+ form.panels_flow(
112
+ innerPadding: { bottom: 0 },
113
+ width: 'matchParent',
114
+ childViews: ->(flow) do
115
+ flow.button(
116
+ text: 'Disable select',
117
+ onClick: ->(action) do
118
+ action.components_set targetId: 'select_media', data: { disabled: true }
119
+ end
120
+ )
121
+ flow.spacer width: 8
122
+ flow.button(
123
+ text: 'Enable select',
124
+ onClick: ->(action) do
125
+ action.components_set targetId: 'select_media', data: { disabled: nil }
126
+ end
127
+ )
128
+ flow.spacer width: 8
129
+ flow.button(
130
+ text: 'Empty options',
131
+ onClick: ->(action) do
132
+ action.components_set targetId: 'select_media', data: { options: [], value: nil }
133
+ end
134
+ )
135
+ flow.spacer width: 8
136
+ flow.button(
137
+ text: 'Restore defaults',
138
+ onClick: ->(action) do
139
+ action.components_set(
140
+ targetId: 'select_media',
141
+ data: { options: icon_options, value: 'option1', disabled: nil, multiple: nil, useChips: nil }
142
+ )
143
+ end
144
+ )
145
+ end
146
+ )
147
+ end
148
+ )
149
+ end
150
+ )
151
+ end
152
+ )
@@ -0,0 +1,153 @@
1
+ json.title 'Test Page (Fields Sign)'
2
+
3
+ page = json_ui_page json
4
+
5
+ page.body(
6
+ childViews: ->(body) do
7
+ render 'json_ui/garage/test_page/header', view: body
8
+
9
+ body.panels_responsive(
10
+ padding: glib_json_padding_body,
11
+ childViews: ->(res) do
12
+ res.panels_form(
13
+ url: json_ui_garage_url(path: 'forms/generic_post'),
14
+ method: 'post',
15
+ childViews: ->(form) do
16
+ form.h2 text: 'Overview'
17
+ form.label text: 'Collects a drawn signature and uploads it via direct upload.'
18
+ form.spacer height: 12
19
+ form.hr width: 'matchParent'
20
+ form.spacer height: 12
21
+
22
+ form.h2 text: 'Basic example'
23
+ form.spacer height: 8
24
+ form.fields_sign(
25
+ id: 'signature_main',
26
+ name: 'user[signature]',
27
+ directUploadUrl: glib_direct_uploads_url,
28
+ width: 320,
29
+ height: 160,
30
+ validation: { required: { message: 'add your signature!' } }
31
+ )
32
+ form.spacer height: 8
33
+ form.label id: 'signature_status', text: 'Status: ready'
34
+
35
+ form.spacer height: 12
36
+ form.hr width: 'matchParent'
37
+ form.spacer height: 12
38
+
39
+ form.h2 text: 'Variants/props'
40
+ form.label text: 'Swap sizes, validation rules, and enabled states.'
41
+ form.spacer height: 8
42
+ form.panels_flow(
43
+ innerPadding: { bottom: 0 },
44
+ width: 'matchParent',
45
+ childViews: ->(flow) do
46
+ flow.button(
47
+ text: 'Large (480x220)',
48
+ onClick: ->(action) do
49
+ action.components_set targetId: 'signature_main', data: { width: 480, height: 220 }
50
+ end
51
+ )
52
+ flow.spacer width: 8
53
+ flow.button(
54
+ text: 'Compact (260x120)',
55
+ onClick: ->(action) do
56
+ action.components_set targetId: 'signature_main', data: { width: 260, height: 120 }
57
+ end
58
+ )
59
+ flow.spacer width: 8
60
+ flow.button(
61
+ text: 'Required',
62
+ onClick: ->(action) do
63
+ action.components_set targetId: 'signature_main',
64
+ data: { validation: { required: { message: 'signature is required' } } }
65
+ end
66
+ )
67
+ flow.spacer width: 8
68
+ flow.button(
69
+ text: 'Optional',
70
+ onClick: ->(action) do
71
+ action.components_set targetId: 'signature_main', data: { validation: nil }
72
+ end
73
+ )
74
+ flow.spacer width: 8
75
+ flow.button(
76
+ text: 'Disable',
77
+ onClick: ->(action) do
78
+ action.components_set targetId: 'signature_main', data: { disabled: true }
79
+ end
80
+ )
81
+ flow.spacer width: 8
82
+ flow.button(
83
+ text: 'Enable',
84
+ onClick: ->(action) do
85
+ action.components_set targetId: 'signature_main', data: { disabled: nil }
86
+ end
87
+ )
88
+ end
89
+ )
90
+
91
+ form.spacer height: 12
92
+ form.hr width: 'matchParent'
93
+ form.spacer height: 12
94
+
95
+ form.h2 text: 'Actions/events'
96
+ form.spacer height: 8
97
+ form.panels_flow(
98
+ innerPadding: { bottom: 0 },
99
+ width: 'matchParent',
100
+ childViews: ->(flow) do
101
+ flow.button(
102
+ text: 'Reset signature',
103
+ onClick: ->(action) do
104
+ action.runMultiple childActions: ->(multiple) do
105
+ multiple.fields_reset targetId: 'signature_main'
106
+ multiple.logics_set targetId: 'signature_status', data: { text: 'Status: reset' }
107
+ end
108
+ end
109
+ )
110
+ flow.spacer width: 8
111
+ flow.button(
112
+ text: 'Clear value',
113
+ onClick: ->(action) do
114
+ action.runMultiple childActions: ->(multiple) do
115
+ multiple.components_set targetId: 'signature_main', data: { value: nil }
116
+ multiple.logics_set targetId: 'signature_status', data: { text: 'Status: cleared' }
117
+ end
118
+ end
119
+ )
120
+ flow.spacer width: 8
121
+ flow.button(
122
+ text: 'Submit form',
123
+ onClick: ->(action) do
124
+ action.runMultiple childActions: ->(multiple) do
125
+ multiple.forms_submit
126
+ multiple.logics_set targetId: 'signature_status', data: { text: 'Status: submitted' }
127
+ end
128
+ end
129
+ )
130
+ end
131
+ )
132
+
133
+ form.spacer height: 12
134
+ form.hr width: 'matchParent'
135
+ form.spacer height: 12
136
+
137
+ form.h2 text: 'Edge/advanced'
138
+ form.spacer height: 8
139
+ form.label text: 'Use a small canvas to test tighter layouts.'
140
+ form.spacer height: 8
141
+ form.fields_sign(
142
+ id: 'signature_compact',
143
+ name: 'user[signature_compact]',
144
+ directUploadUrl: glib_direct_uploads_url,
145
+ width: 220,
146
+ height: 100
147
+ )
148
+ end
149
+ )
150
+ end
151
+ )
152
+ end
153
+ )
@@ -0,0 +1,148 @@
1
+ json.title 'Test Page (Fields Timer)'
2
+
3
+ page = json_ui_page json
4
+
5
+ page.body(
6
+ childViews: ->(body) do
7
+ render 'json_ui/garage/test_page/header', view: body
8
+
9
+ body.panels_responsive(
10
+ padding: glib_json_padding_body,
11
+ childViews: ->(res) do
12
+ res.h2 text: 'Fields Timer'
13
+ res.spacer height: 8
14
+ res.label text: 'Countdown and stopwatch timer fields with min/max constraints.'
15
+ res.spacer height: 12
16
+
17
+ res.panels_form(
18
+ url: json_ui_garage_url(path: 'forms/generic_post'),
19
+ method: 'post',
20
+ childViews: ->(form) do
21
+ form.h2 text: 'Basic usage'
22
+ form.spacer height: 8
23
+ form.fields_timer(
24
+ id: 'timer_basic',
25
+ width: 180,
26
+ styleClasses: ['outlined'],
27
+ label: 'Timer',
28
+ name: 'user[timer_basic]',
29
+ value: 90,
30
+ min: 30
31
+ )
32
+
33
+ form.spacer height: 12
34
+ form.hr width: 'matchParent'
35
+ form.spacer height: 12
36
+
37
+ form.h2 text: 'Variants/props'
38
+ form.spacer height: 8
39
+ form.label text: 'Stopwatch (forward)'
40
+ form.spacer height: 4
41
+ form.fields_timer(
42
+ id: 'timer_forward',
43
+ width: 180,
44
+ styleClasses: ['outlined'],
45
+ label: 'Stop Watch',
46
+ name: 'user[timer_forward]',
47
+ value: 10,
48
+ max: 45,
49
+ forward: true
50
+ )
51
+
52
+ form.spacer height: 12
53
+ form.label text: 'Days style class'
54
+ form.spacer height: 4
55
+ form.fields_timer(
56
+ id: 'timer_days',
57
+ name: 'user[timer_days]',
58
+ value: 1.days.from_now,
59
+ styleClasses: ['days']
60
+ )
61
+
62
+ form.spacer height: 12
63
+ form.hr width: 'matchParent'
64
+ form.spacer height: 12
65
+
66
+ form.h2 text: 'Actions/events'
67
+ form.spacer height: 8
68
+ form.label id: 'timer_status', text: 'Status: idle'
69
+ form.spacer height: 8
70
+ form.panels_flow(
71
+ innerPadding: { bottom: 0 },
72
+ width: 'matchParent',
73
+ childViews: ->(flow) do
74
+ flow.button(
75
+ text: 'Set 2 minutes',
76
+ onClick: ->(action) do
77
+ action.components_set targetId: 'timer_basic', data: { value: 120 }
78
+ end
79
+ )
80
+ flow.spacer width: 8
81
+ flow.button(
82
+ text: 'Set 45 seconds',
83
+ onClick: ->(action) do
84
+ action.components_set targetId: 'timer_basic', data: { value: 45 }
85
+ end
86
+ )
87
+ flow.spacer width: 8
88
+ flow.button(
89
+ text: 'Swap to stopwatch',
90
+ onClick: ->(action) do
91
+ action.runMultiple childActions: ->(multiple) do
92
+ multiple.components_set targetId: 'timer_basic', data: { value: 5, min: nil, max: 30, forward: true }
93
+ multiple.logics_set targetId: 'timer_status', data: { text: 'Status: forward (max 30s)' }
94
+ end
95
+ end
96
+ )
97
+ end
98
+ )
99
+
100
+ form.spacer height: 12
101
+ form.hr width: 'matchParent'
102
+ form.spacer height: 12
103
+
104
+ form.h2 text: 'Edge/advanced'
105
+ form.spacer height: 8
106
+ form.fields_timer(
107
+ id: 'timer_limits',
108
+ width: 180,
109
+ styleClasses: ['outlined'],
110
+ label: 'Min/Max clamp',
111
+ name: 'user[timer_limits]',
112
+ value: 6,
113
+ min: 5,
114
+ max: 10
115
+ )
116
+ form.spacer height: 8
117
+ form.panels_flow(
118
+ innerPadding: { bottom: 0 },
119
+ width: 'matchParent',
120
+ childViews: ->(flow) do
121
+ flow.button(
122
+ text: 'Set 2s (below min)',
123
+ onClick: ->(action) do
124
+ action.components_set targetId: 'timer_limits', data: { value: 2 }
125
+ end
126
+ )
127
+ flow.spacer width: 8
128
+ flow.button(
129
+ text: 'Set 7s',
130
+ onClick: ->(action) do
131
+ action.components_set targetId: 'timer_limits', data: { value: 7 }
132
+ end
133
+ )
134
+ flow.spacer width: 8
135
+ flow.button(
136
+ text: 'Set 15s (above max)',
137
+ onClick: ->(action) do
138
+ action.components_set targetId: 'timer_limits', data: { value: 15 }
139
+ end
140
+ )
141
+ end
142
+ )
143
+ end
144
+ )
145
+ end
146
+ )
147
+ end
148
+ )
@@ -0,0 +1,129 @@
1
+ json.title 'Test Page (Fields Upload)'
2
+
3
+ page = json_ui_page json
4
+
5
+ page.body(
6
+ childViews: ->(body) do
7
+ render 'json_ui/garage/test_page/header', view: body
8
+
9
+ image = OpenStruct.new(
10
+ url: 'https://picsum.photos/id/11/100/60',
11
+ signed_id: 'test',
12
+ filename: 'upload.png'
13
+ )
14
+
15
+ accepts = {
16
+ fileType: 'image',
17
+ maxFileSize: 10,
18
+ maxFileLength: 2
19
+ }
20
+
21
+ base_props = {
22
+ accepts: accepts,
23
+ directUploadUrl: glib_direct_uploads_url,
24
+ styleClass: 'pb-2',
25
+ value: image&.signed_id
26
+ }
27
+
28
+ base_props[:onFinishUpload] = ->(action) do
29
+ action.snackbars_alert message: 'Get the signed ids!'
30
+ end
31
+
32
+ body.panels_responsive(
33
+ padding: glib_json_padding_body,
34
+ childViews: ->(res) do
35
+ res.h2 text: 'Fields Upload'
36
+ res.spacer height: 8
37
+ res.label text: 'Use fields_upload for direct uploads with placeholder and inputView customization.'
38
+ res.spacer height: 12
39
+
40
+ res.panels_form(
41
+ url: json_ui_garage_url(path: 'forms/generic_post'),
42
+ method: 'post',
43
+ childViews: ->(form) do
44
+ form.h2 text: 'Basic usage'
45
+ form.spacer height: 8
46
+ form.panels_flow(
47
+ styleClass: 'align-center',
48
+ xs: { gap: { all: 4 } },
49
+ childViews: ->(flow) do
50
+ flow.fields_upload(
51
+ base_props.merge(
52
+ name: 'user[file_basic]',
53
+ id: 'fields_upload_basic',
54
+ label: 'Upload file',
55
+ width: 320,
56
+ placeholderView: { type: 'image', url: image&.url, width: 144, height: 144 }
57
+ )
58
+ )
59
+ flow.button(
60
+ text: 'Trigger',
61
+ onClick: ->(action) { action.components_invoke targetId: 'fields_upload_basic', name: 'trigger' }
62
+ )
63
+ flow.button(
64
+ text: 'Reset',
65
+ onClick: ->(action) { action.components_invoke targetId: 'fields_upload_basic', name: 'reset' }
66
+ )
67
+ end
68
+ )
69
+
70
+ form.spacer height: 12
71
+ form.hr width: 'matchParent'
72
+ form.spacer height: 12
73
+
74
+ form.h2 text: 'InputView variants'
75
+ form.spacer height: 8
76
+ form.label text: 'Outlined text-field input'
77
+ form.spacer height: 4
78
+ form.fields_upload(
79
+ base_props.merge(
80
+ name: 'user[file_input_variant]',
81
+ id: 'fields_upload_variant',
82
+ label: 'Choose file',
83
+ width: 320,
84
+ inputView: { variant: 'outlined' },
85
+ multiple: false
86
+ )
87
+ )
88
+
89
+ form.spacer height: 12
90
+ form.label text: 'Button trigger (files array)'
91
+ form.spacer height: 4
92
+ form.button(
93
+ text: 'Select file',
94
+ onClick: ->(action) { action.components_invoke targetId: 'fields_upload_files', name: 'trigger' }
95
+ )
96
+ form.fields_upload(
97
+ base_props.merge(
98
+ name: 'user[file_input_files][]',
99
+ id: 'fields_upload_files',
100
+ label: 'Upload file',
101
+ width: 320,
102
+ inputView: { files: [] },
103
+ styleClass: 'pb-2 d-none',
104
+ multiple: true
105
+ )
106
+ )
107
+
108
+ form.spacer height: 12
109
+ form.label text: 'Multiple selection input'
110
+ form.spacer height: 4
111
+ form.fields_upload(
112
+ base_props.merge(
113
+ name: 'user[file_input_multiple][]',
114
+ id: 'fields_upload_multiple',
115
+ label: 'Choose files',
116
+ width: 320,
117
+ inputView: { multiple: true },
118
+ multiple: true
119
+ )
120
+ )
121
+
122
+ form.spacer height: 12
123
+ form.fields_submit text: 'submit'
124
+ end
125
+ )
126
+ end
127
+ )
128
+ end
129
+ )
@@ -72,8 +72,12 @@ page.body(
72
72
  res.button(
73
73
  text: 'forms/submit with status update',
74
74
  onClick: ->(action) do
75
- action.logics_set targetId: 'forms_status', data: { text: 'Status: submitting' }
76
- action.forms_submit targetId: 'forms_basic'
75
+ action.runMultiple(
76
+ childActions: ->(multi) do
77
+ multi.logics_set targetId: 'forms_status', data: { text: 'Status: submitting' }
78
+ multi.forms_submit targetId: 'forms_basic'
79
+ end
80
+ )
77
81
  end
78
82
  )
79
83