gdk-toogle 0.7.0 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/config/toogle_manifest.js +1 -0
- data/app/assets/javascript/toogle/application.js +117 -0
- data/app/assets/stylesheets/toogle/application.css +0 -2
- data/app/assets/stylesheets/toogle/components/card.css +6 -2
- data/app/assets/stylesheets/toogle/components/scrollbox.css +18 -5
- data/app/assets/stylesheets/toogle/elements.css +37 -13
- data/app/assets/stylesheets/toogle/layout.css +12 -0
- data/app/assets/stylesheets/toogle/variables.css +22 -2
- data/app/controllers/toogle/features_controller.rb +15 -1
- data/app/models/toogle/definition.rb +11 -2
- data/app/models/toogle/feature.rb +6 -5
- data/app/views/layouts/toogle/application.html.haml +4 -3
- data/app/views/toogle/definitions/index.html.haml +1 -1
- data/app/views/toogle/features/_dialog.html.haml +1 -1
- data/app/views/toogle/features/index.html.haml +67 -42
- data/lib/toogle/version.rb +1 -1
- metadata +3 -5
- data/app/assets/stylesheets/toogle/dark-mode.css +0 -25
- data/app/views/toogle/application/_alpine_components.html.haml +0 -56
- data/app/views/toogle/features/_remove.html.haml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b41975a3c2a35c12eecaad91aa7bfd949d68d0132576d1f68ecca48987abf641
|
4
|
+
data.tar.gz: ad9ede84ae8f37f9286699d33163f360a04854a00abb3fbfb4ec6b07e3ec386f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20a49dff67060c27ab608f9851570494726856f34e0deb11ad3c60266b72a3af13260384c63ddf3afe3284449e9984e03f3fd345a9302c6e132760a515c124eb
|
7
|
+
data.tar.gz: 1d372ec1b321404ad287154a027aef0a1732116ffedd449602bcd57f57e4d4d77b0a0e5ea11f2e5ed5e7c5caf3901ae99569485f3db8c0e50c7bc5f581ba5957
|
@@ -0,0 +1,117 @@
|
|
1
|
+
document.addEventListener("alpine:init", () => {
|
2
|
+
Alpine.data("features", (indexUrl) => ({
|
3
|
+
features: [],
|
4
|
+
indexUrl,
|
5
|
+
|
6
|
+
init() {
|
7
|
+
fetch(this.indexUrl, {
|
8
|
+
method: "GET",
|
9
|
+
headers: {
|
10
|
+
Accept: "application/json",
|
11
|
+
},
|
12
|
+
})
|
13
|
+
.then((response) => response.json())
|
14
|
+
.then((data) => {
|
15
|
+
this.features = data;
|
16
|
+
})
|
17
|
+
.catch((error) => console.error("Error fetching data:", error));
|
18
|
+
},
|
19
|
+
|
20
|
+
toggleFeature(feature) {
|
21
|
+
const newState = feature.state === "enabled" ? "disabled" : "enabled";
|
22
|
+
fetch(`${this.indexUrl}/${feature.name}`, {
|
23
|
+
method: "PUT",
|
24
|
+
headers: {
|
25
|
+
Accept: "application/json",
|
26
|
+
"Content-Type": "application/json",
|
27
|
+
},
|
28
|
+
body: JSON.stringify({
|
29
|
+
state: newState,
|
30
|
+
}),
|
31
|
+
}).then(() => {
|
32
|
+
this.features = this.features.map((f) => {
|
33
|
+
return f === feature ? { ...f, state: newState } : f;
|
34
|
+
});
|
35
|
+
});
|
36
|
+
},
|
37
|
+
|
38
|
+
deleteFeature(featureName) {
|
39
|
+
const csrfToken = document.head.querySelector(
|
40
|
+
"meta[name=csrf-token]"
|
41
|
+
)?.content;
|
42
|
+
|
43
|
+
fetch(`${this.indexUrl}/${featureName}`, {
|
44
|
+
method: "DELETE",
|
45
|
+
headers: {
|
46
|
+
Accept: "application/json",
|
47
|
+
"X-CSRF-Token": csrfToken,
|
48
|
+
},
|
49
|
+
}).then(() => {
|
50
|
+
this.features = this.features.filter((f) => f.name !== featureName);
|
51
|
+
});
|
52
|
+
},
|
53
|
+
}));
|
54
|
+
|
55
|
+
Alpine.data("toggle", (featureName, isChecked, indexUrl) => ({
|
56
|
+
name: featureName,
|
57
|
+
checked: isChecked,
|
58
|
+
indexUrl,
|
59
|
+
|
60
|
+
input: {
|
61
|
+
["@change"]() {
|
62
|
+
const csrfToken = document.head.querySelector(
|
63
|
+
"meta[name=csrf-token]"
|
64
|
+
)?.content;
|
65
|
+
|
66
|
+
fetch(`${this.indexUrl}/${this.name}`, {
|
67
|
+
method: "PUT",
|
68
|
+
headers: {
|
69
|
+
"Content-Type": "application/json",
|
70
|
+
"X-CSRF-Token": csrfToken,
|
71
|
+
},
|
72
|
+
body: JSON.stringify({
|
73
|
+
state: this.checked ? "disabled" : "enabled",
|
74
|
+
}),
|
75
|
+
}).then(() => {
|
76
|
+
window.location = this.indexUrl;
|
77
|
+
});
|
78
|
+
},
|
79
|
+
},
|
80
|
+
}));
|
81
|
+
|
82
|
+
Alpine.data("darkModeSwitcher", () => ({
|
83
|
+
isDark: undefined,
|
84
|
+
|
85
|
+
init() {
|
86
|
+
const cookieValue = this.getCookieValue();
|
87
|
+
if (cookieValue) {
|
88
|
+
this.isDark = cookieValue === "true";
|
89
|
+
} else {
|
90
|
+
this.isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
91
|
+
}
|
92
|
+
},
|
93
|
+
|
94
|
+
getCookieValue() {
|
95
|
+
return (
|
96
|
+
document.cookie
|
97
|
+
.match("(^|;)\\s*" + "toogle-dark" + "\\s*=\\s*([^;]+)")
|
98
|
+
?.pop() || ""
|
99
|
+
);
|
100
|
+
},
|
101
|
+
|
102
|
+
button: {
|
103
|
+
["@click"]() {
|
104
|
+
this.isDark = !this.isDark;
|
105
|
+
document.cookie = `toogle-dark=${this.isDark}; SameSite=Strict`;
|
106
|
+
if (this.isDark) {
|
107
|
+
document.documentElement.classList.remove("light-mode");
|
108
|
+
} else {
|
109
|
+
document.documentElement.classList.add("light-mode");
|
110
|
+
}
|
111
|
+
},
|
112
|
+
["x-bind:title"]() {
|
113
|
+
return `Switch to ${this.isDark ? "Light Mode" : "Dark Mode"}`;
|
114
|
+
},
|
115
|
+
},
|
116
|
+
}));
|
117
|
+
});
|
@@ -1,6 +1,10 @@
|
|
1
1
|
.card {
|
2
|
-
background-color:
|
2
|
+
background-color: hsl(
|
3
|
+
var(--hue-primary),
|
4
|
+
var(--saturation-bg),
|
5
|
+
var(--lightness-bg1)
|
6
|
+
);
|
3
7
|
border-radius: 5px;
|
4
8
|
padding: 2rem;
|
5
|
-
box-shadow: 0
|
9
|
+
box-shadow: 0 10px 16px var(--color-shadow);
|
6
10
|
}
|
@@ -1,13 +1,26 @@
|
|
1
1
|
.scrollbox {
|
2
2
|
max-height: 20ch;
|
3
3
|
overflow: auto;
|
4
|
-
padding:
|
4
|
+
padding: 1rem 0;
|
5
5
|
word-break: break-all;
|
6
6
|
|
7
|
-
background-color:
|
8
|
-
|
7
|
+
background-color: hsl(
|
8
|
+
var(--hue-primary),
|
9
|
+
var(--saturation-bg),
|
10
|
+
var(--lightness-bg2)
|
11
|
+
);
|
12
|
+
border: 2px solid
|
13
|
+
hsl(var(--hue-primary), var(--saturation-bg), var(--lightness-bg1));
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
border-style: ridge;
|
16
|
+
|
17
|
+
box-shadow: 0 5px 10px var(--color-shadow);
|
18
|
+
|
19
|
+
li {
|
20
|
+
padding: 0 1rem;
|
21
|
+
|
22
|
+
&:hover {
|
23
|
+
background-color: hsl(var(--hue-primary), 60%, var(--lightness-bg1));
|
24
|
+
}
|
12
25
|
}
|
13
26
|
}
|
@@ -1,8 +1,11 @@
|
|
1
1
|
html {
|
2
|
-
--
|
3
|
-
|
4
|
-
background-color: var(--page-bg);
|
2
|
+
color: var(--color-text);
|
5
3
|
transition: 0.2s;
|
4
|
+
background-image: radial-gradient(
|
5
|
+
circle at 10vh 10vw,
|
6
|
+
hsl(var(--hue-primary), var(--saturation-bg), var(--lightness-bg1)),
|
7
|
+
hsl(var(--hue-primary), var(--saturation-bg), var(--lightness-bg0))
|
8
|
+
);
|
6
9
|
}
|
7
10
|
|
8
11
|
body {
|
@@ -11,7 +14,6 @@ body {
|
|
11
14
|
> header {
|
12
15
|
position: sticky;
|
13
16
|
top: 0;
|
14
|
-
background-color: var(--page-bg);
|
15
17
|
transition: 0.2s;
|
16
18
|
}
|
17
19
|
}
|
@@ -27,34 +29,58 @@ p {
|
|
27
29
|
}
|
28
30
|
|
29
31
|
a {
|
30
|
-
color:
|
32
|
+
color: var(--color-primary);
|
31
33
|
font-weight: bold;
|
32
34
|
text-decoration: none;
|
33
|
-
padding: 2px;
|
34
35
|
border-radius: 2px;
|
35
36
|
transition: outline 0.2s;
|
36
37
|
|
37
38
|
&:focus {
|
38
|
-
outline:
|
39
|
+
outline: 2px solid var(--color-primary);
|
39
40
|
}
|
40
41
|
}
|
41
42
|
|
43
|
+
footer a {
|
44
|
+
color: var(--color-text);
|
45
|
+
}
|
46
|
+
|
42
47
|
input[type="submit"]:not(.small) {
|
43
48
|
font-size: large;
|
44
49
|
padding: 0.25rem;
|
45
50
|
}
|
46
51
|
|
47
|
-
input[type="search"]
|
52
|
+
input[type="search"],
|
53
|
+
input[type="text"] {
|
48
54
|
margin: 0.25rem 0;
|
49
55
|
padding: 0.75rem;
|
50
56
|
font-family: monospace;
|
51
57
|
border-radius: 0.5rem;
|
52
58
|
|
59
|
+
&:not(:read-only):focus {
|
60
|
+
outline: 2px solid var(--color-primary);
|
61
|
+
}
|
62
|
+
|
63
|
+
&:read-only:focus {
|
64
|
+
outline: none;
|
65
|
+
}
|
66
|
+
|
53
67
|
&.large {
|
54
68
|
font-size: large;
|
55
69
|
}
|
56
70
|
}
|
57
71
|
|
72
|
+
input[type="search"],
|
73
|
+
input[type="text"] {
|
74
|
+
color: hsl(var(--hue-primary), var(--saturation-bg), var(--lightness-fg));
|
75
|
+
background-color: hsl(
|
76
|
+
var(--hue-primary),
|
77
|
+
var(--saturation-bg),
|
78
|
+
var(--lightness-bg2)
|
79
|
+
);
|
80
|
+
border: none;
|
81
|
+
box-shadow: inset 1px 1px 2px var(--color-shadow);
|
82
|
+
}
|
83
|
+
|
58
84
|
hr {
|
59
85
|
margin: 1.5rem 0;
|
60
86
|
}
|
@@ -64,6 +90,7 @@ code {
|
|
64
90
|
}
|
65
91
|
|
66
92
|
button {
|
93
|
+
color: var(--color-text);
|
67
94
|
cursor: pointer;
|
68
95
|
background-color: transparent;
|
69
96
|
border: none;
|
@@ -84,13 +111,9 @@ button {
|
|
84
111
|
}
|
85
112
|
|
86
113
|
&:focus {
|
87
|
-
outline:
|
114
|
+
outline: 2px solid var(--color-primary);
|
88
115
|
opacity: 1 !important;
|
89
116
|
}
|
90
|
-
|
91
|
-
> svg + span {
|
92
|
-
margin-left: 0.25em;
|
93
|
-
}
|
94
117
|
}
|
95
118
|
|
96
119
|
ul {
|
@@ -108,6 +131,7 @@ ul {
|
|
108
131
|
|
109
132
|
dialog {
|
110
133
|
background-color: transparent;
|
134
|
+
color: var(--color-text);
|
111
135
|
border: none;
|
112
136
|
outline: none;
|
113
137
|
|
@@ -26,6 +26,8 @@ body {
|
|
26
26
|
> main {
|
27
27
|
display: flex;
|
28
28
|
flex-direction: column-reverse;
|
29
|
+
align-items: stretch;
|
30
|
+
justify-content: start;
|
29
31
|
width: 100ch;
|
30
32
|
max-width: 95%;
|
31
33
|
margin: auto;
|
@@ -64,6 +66,16 @@ li.feature-toggle {
|
|
64
66
|
padding: 0.5rem;
|
65
67
|
}
|
66
68
|
|
69
|
+
.more-info {
|
70
|
+
padding-left: 68px; /* toggle width */
|
71
|
+
}
|
72
|
+
|
73
|
+
.metadata {
|
74
|
+
display: grid;
|
75
|
+
grid-template-columns: auto 1fr;
|
76
|
+
gap: 0.5rem;
|
77
|
+
}
|
78
|
+
|
67
79
|
&:hover {
|
68
80
|
background-color: rgba(153, 153, 153, 0.1);
|
69
81
|
|
@@ -1,3 +1,23 @@
|
|
1
|
-
|
2
|
-
--
|
1
|
+
html {
|
2
|
+
--hue-primary: 35;
|
3
|
+
--saturation-bg: 5%;
|
4
|
+
--lightness-bg0: 80%;
|
5
|
+
--lightness-bg1: 88%;
|
6
|
+
--lightness-bg2: 96%;
|
7
|
+
--lightness-fg: 10%;
|
8
|
+
--lightness-shadow: 50%;
|
9
|
+
|
10
|
+
--color-primary: hsl(var(--hue-primary), 90%, 50%);
|
11
|
+
--color-text: hsl(var(--hue-primary), 0%, var(--lightness-fg));
|
12
|
+
--color-shadow: hsl(0, var(--saturation-bg), var(--lightness-shadow));
|
13
|
+
}
|
14
|
+
|
15
|
+
@media (prefers-color-scheme: dark) {
|
16
|
+
html:not(.light-mode) {
|
17
|
+
--lightness-bg0: 8%;
|
18
|
+
--lightness-bg1: 16%;
|
19
|
+
--lightness-bg2: 24%;
|
20
|
+
--lightness-fg: 90%;
|
21
|
+
--lightness-shadow: 4%;
|
22
|
+
}
|
3
23
|
}
|
@@ -2,6 +2,11 @@ module Toogle
|
|
2
2
|
class FeaturesController < ApplicationController
|
3
3
|
def index
|
4
4
|
@features = Toogle::Feature.all
|
5
|
+
|
6
|
+
respond_to do |format|
|
7
|
+
format.html
|
8
|
+
format.json { render json: @features }
|
9
|
+
end
|
5
10
|
end
|
6
11
|
|
7
12
|
def show
|
@@ -35,7 +40,16 @@ module Toogle
|
|
35
40
|
|
36
41
|
def destroy
|
37
42
|
::Feature.remove(params[:id])
|
38
|
-
|
43
|
+
|
44
|
+
respond_to do |format|
|
45
|
+
format.html do
|
46
|
+
redirect_to features_url, status: :see_other
|
47
|
+
end
|
48
|
+
|
49
|
+
format.json do
|
50
|
+
head :ok
|
51
|
+
end
|
52
|
+
end
|
39
53
|
end
|
40
54
|
end
|
41
55
|
end
|
@@ -6,7 +6,13 @@ module Toogle
|
|
6
6
|
|
7
7
|
def self.all
|
8
8
|
::Feature::Definition.definitions.map do |definition|
|
9
|
-
new(
|
9
|
+
new(
|
10
|
+
name: definition[0].to_s,
|
11
|
+
default_enabled: definition[1].default_enabled,
|
12
|
+
milestone: definition[1].milestone,
|
13
|
+
introduced_by_url: definition[1].introduced_by_url,
|
14
|
+
rollout_issue_url: definition[1].rollout_issue_url,
|
15
|
+
)
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
@@ -19,9 +25,12 @@ module Toogle
|
|
19
25
|
all.find { |definition| definition.name == name }
|
20
26
|
end
|
21
27
|
|
22
|
-
def initialize(name:, default_enabled:)
|
28
|
+
def initialize(name:, default_enabled:, milestone: nil, introduced_by_url: nil, rollout_issue_url: nil)
|
23
29
|
@name = name
|
24
30
|
@default_enabled = default_enabled
|
31
|
+
@milestone = milestone
|
32
|
+
@introduced_by_url = introduced_by_url
|
33
|
+
@rollout_issue_url = rollout_issue_url
|
25
34
|
end
|
26
35
|
end
|
27
36
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Toogle
|
4
4
|
class Feature
|
5
|
-
attr_accessor :name, :state
|
5
|
+
attr_accessor :name, :state, :definition
|
6
6
|
|
7
7
|
def self.all
|
8
8
|
definitions = Definition.all
|
@@ -11,21 +11,22 @@ module Toogle
|
|
11
11
|
feature_class: ::Feature::FlipperFeature,
|
12
12
|
gate_class: ::Feature::FlipperGate
|
13
13
|
).get_all.map do |feature_name, feature_values|
|
14
|
-
|
15
|
-
feature_state = if
|
14
|
+
feature_definition = Definition.find(feature_name)
|
15
|
+
feature_state = if feature_definition
|
16
16
|
feature_values[:boolean] ? :enabled: :disabled
|
17
17
|
else
|
18
18
|
# This usually happens when switching back from an unmerged feature
|
19
19
|
# branch that introduces a new flag, or when a flag got deleted.
|
20
20
|
:unknown
|
21
21
|
end
|
22
|
-
new(name: feature_name, state: feature_state)
|
22
|
+
new(name: feature_name, state: feature_state, definition: feature_definition)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def initialize(name:, state:)
|
26
|
+
def initialize(name:, state:, definition:)
|
27
27
|
@name = name
|
28
28
|
@state = state
|
29
|
+
@definition = definition
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -7,8 +7,10 @@
|
|
7
7
|
= csrf_meta_tags
|
8
8
|
= csp_meta_tag
|
9
9
|
= stylesheet_link_tag "toogle/application", media: "all"
|
10
|
+
= javascript_include_tag 'toogle/application'
|
10
11
|
%script(defer src="https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.x.x/dist/cdn.min.js")
|
11
12
|
%script(defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js")
|
13
|
+
|
12
14
|
%body
|
13
15
|
%header
|
14
16
|
%nav.d-flex.gap
|
@@ -38,6 +40,7 @@
|
|
38
40
|
%path(d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z")
|
39
41
|
|
40
42
|
%main
|
43
|
+
= yield
|
41
44
|
- if notice.present?
|
42
45
|
#notice.d-flex.row.center.gap
|
43
46
|
%svg(width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round")
|
@@ -45,9 +48,7 @@
|
|
45
48
|
%line(x1="12" y1="16" x2="12" y2="12")
|
46
49
|
%line(x1="12" y1="8" x2="12.01" y2="8")
|
47
50
|
%span.grow= notice
|
48
|
-
|
51
|
+
|
49
52
|
%footer
|
50
53
|
%a(href="https://gitlab.com/thutterer/toogle" target="_blank")
|
51
54
|
= Toogle::VERSION
|
52
|
-
|
53
|
-
= render "alpine_components"
|
@@ -3,7 +3,7 @@
|
|
3
3
|
- name = definition.name
|
4
4
|
- enabled = definition.default_enabled
|
5
5
|
%li.d-flex.center.row.nowrap(x-show="$el.textContent.includes(query) || query == ''")
|
6
|
-
%label.toggle(x-data="toggle('#{name}', #{enabled})")
|
6
|
+
%label.toggle(x-data="toggle('#{name}', #{enabled}, '#{features_url}')")
|
7
7
|
%input(type="checkbox" x-bind="input" x-model="checked"){checked: enabled}
|
8
8
|
%span.handle.round(:title="checked ? 'Enabled by default. Click to disable.' : 'Disabled by default. Click to enable.'")
|
9
9
|
%code.grow= name
|
@@ -2,7 +2,7 @@
|
|
2
2
|
.card{"x-on:click.outside": "$refs.dialog.close()"}
|
3
3
|
%header
|
4
4
|
%h2
|
5
|
-
%label.toggle(x-data="toggle(feature, isAlreadyEnabled)")
|
5
|
+
%label.toggle(x-data="toggle(feature, isAlreadyEnabled, '#{features_url}')")
|
6
6
|
%input(type="checkbox" x-bind="input" x-model="checked")
|
7
7
|
%span.handle.round
|
8
8
|
|
@@ -1,46 +1,71 @@
|
|
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
|
-
|
1
|
+
.card
|
2
|
+
%h3 My feature flags
|
3
|
+
%small These features already use custom settings in this GDK.
|
4
|
+
|
5
|
+
%ul(x-data="features('#{features_url}')")
|
6
|
+
%template(x-for="feature in features" :key="feature.name")
|
7
|
+
%li.feature-toggle(x-data="{ showMore: false }"){"@click.outside": "showMore = false"}
|
8
|
+
.d-flex.row
|
9
|
+
%template(x-if="feature.state === 'unknown'")
|
10
|
+
%label.toggle(title="This feature does not exist on the current branch.")
|
11
|
+
%input(type="checkbox" disabled :name="feature.name")
|
12
|
+
%span.handle.round
|
13
|
+
%template(x-if="feature.state !== 'unknown'")
|
14
|
+
%label.toggle
|
15
|
+
%input(type="checkbox" x-on:change="toggleFeature(feature)" :checked="feature.state === 'enabled'" :name="feature.name")
|
16
|
+
%span.handle.round
|
17
|
+
|
18
|
+
%code.grow(x-text="feature.name")
|
19
|
+
|
20
|
+
%button(title="Show more" x-on:click="showMore = !showMore")
|
21
|
+
%svg(xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round")
|
22
|
+
%circle(cx="12" cy="12" r="1")
|
23
|
+
%circle(cx="12" cy="5" r="1")
|
24
|
+
%circle(cx="12" cy="19" r="1")
|
25
|
+
|
26
|
+
%button(title="Forget this setting and use default" x-on:click="deleteFeature(feature.name)")
|
27
|
+
%svg(xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round")
|
28
|
+
%line(x1="18" y1="6" x2="6" y2="18")
|
29
|
+
%line(x1="6" y1="6" x2="18" y2="18")
|
30
|
+
|
31
|
+
.d-flex.col.more-info{ "x-show": "showMore" }
|
32
|
+
.metadata
|
33
|
+
%strong Milestone
|
34
|
+
%span(x-text="feature.definition.milestone || '-'")
|
35
|
+
%strong Introduced by
|
36
|
+
%a(x-show="feature.definition.introduced_by_url"
|
37
|
+
x-text="`!${feature.definition.introduced_by_url?.split('/').at(-1)}`"
|
38
|
+
:href="feature.definition.introduced_by_url"
|
39
|
+
target="_blank")
|
40
|
+
%span(x-show="!feature.definition.introduced_by_url") -
|
41
|
+
%strong Rollout issue
|
42
|
+
%a(x-show="feature.definition.rollout_issue_url"
|
43
|
+
x-text="`#${feature.definition.rollout_issue_url?.split('/').at(-1)}`"
|
44
|
+
:href="feature.definition.rollout_issue_url"
|
45
|
+
target="_blank")
|
46
|
+
%span(x-show="!feature.definition.rollout_issue_url") -
|
47
|
+
|
48
|
+
%hr
|
49
|
+
%small Share this URL with MR reviewers.
|
50
|
+
.d-flex.row
|
51
|
+
%input.grow(x-ref="copyText" :value="`${indexUrl}${feature.name}`" type="text" readonly="readonly")
|
52
|
+
%button(x-on:click="$refs.copyText.select(); document.execCommand('copy');") Copy
|
53
|
+
%p(x-show="!features.length") No active feature flags in this GDK yet.
|
54
|
+
|
55
|
+
.card(style="margin: 8vh 0 5vh;")
|
56
|
+
%h3 Search and toggle feature flags
|
57
|
+
.d-flex.col{
|
58
|
+
"x-data": "{query: '', show: false}",
|
59
|
+
"@click.outside": "show = false",
|
60
|
+
"@keyup.esc": "show = false"}
|
61
|
+
%input.large.w-100#search{
|
62
|
+
"type": "search",
|
63
|
+
"x-model.debounce.400ms": "query",
|
64
|
+
"x-ref": "search",
|
65
|
+
"x-on:input": "show = true",
|
66
|
+
"x-on:focus": "show = true"}
|
67
|
+
%template(x-if="show")
|
42
68
|
.d-flex.col.stretch{
|
43
|
-
"x-show": "show",
|
44
69
|
"x-anchor.bottom-start.offset.5" => "$refs.search",
|
45
70
|
"x-transition": nil}
|
46
71
|
.grow{
|
data/lib/toogle/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gdk-toogle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Hutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -92,11 +92,11 @@ files:
|
|
92
92
|
- README.md
|
93
93
|
- Rakefile
|
94
94
|
- app/assets/config/toogle_manifest.js
|
95
|
+
- app/assets/javascript/toogle/application.js
|
95
96
|
- app/assets/stylesheets/toogle/application.css
|
96
97
|
- app/assets/stylesheets/toogle/components/card.css
|
97
98
|
- app/assets/stylesheets/toogle/components/scrollbox.css
|
98
99
|
- app/assets/stylesheets/toogle/components/toggle.css
|
99
|
-
- app/assets/stylesheets/toogle/dark-mode.css
|
100
100
|
- app/assets/stylesheets/toogle/elements.css
|
101
101
|
- app/assets/stylesheets/toogle/layout.css
|
102
102
|
- app/assets/stylesheets/toogle/utilities.css
|
@@ -107,10 +107,8 @@ files:
|
|
107
107
|
- app/models/toogle/definition.rb
|
108
108
|
- app/models/toogle/feature.rb
|
109
109
|
- app/views/layouts/toogle/application.html.haml
|
110
|
-
- app/views/toogle/application/_alpine_components.html.haml
|
111
110
|
- app/views/toogle/definitions/index.html.haml
|
112
111
|
- app/views/toogle/features/_dialog.html.haml
|
113
|
-
- app/views/toogle/features/_remove.html.haml
|
114
112
|
- app/views/toogle/features/_toggle.html.haml
|
115
113
|
- app/views/toogle/features/index.html.haml
|
116
114
|
- config/routes.rb
|
@@ -1,25 +0,0 @@
|
|
1
|
-
@media (prefers-color-scheme: dark) {
|
2
|
-
html:not(.light-mode) {
|
3
|
-
--page-bg: #181822;
|
4
|
-
|
5
|
-
color: #eee;
|
6
|
-
|
7
|
-
a {
|
8
|
-
color: #ddd;
|
9
|
-
}
|
10
|
-
|
11
|
-
button,
|
12
|
-
dialog {
|
13
|
-
color: #eee;
|
14
|
-
}
|
15
|
-
|
16
|
-
.card {
|
17
|
-
background-color: #282833;
|
18
|
-
box-shadow: 0 0.5rem 1rem #111;
|
19
|
-
}
|
20
|
-
|
21
|
-
.scrollbox {
|
22
|
-
background-color: #444;
|
23
|
-
}
|
24
|
-
}
|
25
|
-
}
|
@@ -1,56 +0,0 @@
|
|
1
|
-
:javascript
|
2
|
-
document.addEventListener("alpine:init", () => {
|
3
|
-
Alpine.data("toggle", (featureName, isChecked) => ({
|
4
|
-
name: featureName,
|
5
|
-
checked: isChecked,
|
6
|
-
|
7
|
-
input: {
|
8
|
-
['@change']() {
|
9
|
-
fetch(`./${this.name}.json`, {
|
10
|
-
method: "PUT",
|
11
|
-
headers: {
|
12
|
-
'Content-Type': 'application/json'
|
13
|
-
},
|
14
|
-
body: JSON.stringify({state: this.checked ? 'disabled' : 'enabled'})
|
15
|
-
}).then(() => {
|
16
|
-
// Always reload the page after change as a lazy way to move newly
|
17
|
-
// enabled feature flags into the top section.
|
18
|
-
// TODO: Either make this a classic form element or go full Alpine.
|
19
|
-
window.location = '#{features_url}'
|
20
|
-
})
|
21
|
-
},
|
22
|
-
},
|
23
|
-
}));
|
24
|
-
|
25
|
-
Alpine.data("darkModeSwitcher", () => ({
|
26
|
-
isDark: undefined,
|
27
|
-
|
28
|
-
init() {
|
29
|
-
const cookieValue = this.getCookieValue()
|
30
|
-
if (cookieValue) {
|
31
|
-
this.isDark = cookieValue === "true"
|
32
|
-
} else {
|
33
|
-
this.isDark = window.matchMedia("(prefers-color-scheme: dark)").matches
|
34
|
-
}
|
35
|
-
},
|
36
|
-
|
37
|
-
getCookieValue() {
|
38
|
-
return document.cookie.match('(^|;)\\s*' + 'toogle-dark' + '\\s*=\\s*([^;]+)')?.pop() || ''
|
39
|
-
},
|
40
|
-
|
41
|
-
button: {
|
42
|
-
['@click']() {
|
43
|
-
this.isDark = !this.isDark
|
44
|
-
document.cookie = `toogle-dark=${this.isDark}; SameSite=Strict`
|
45
|
-
if(this.isDark) {
|
46
|
-
document.documentElement.classList.remove('light-mode');
|
47
|
-
} else {
|
48
|
-
document.documentElement.classList.add('light-mode');
|
49
|
-
}
|
50
|
-
},
|
51
|
-
['x-bind:title']() {
|
52
|
-
return `Switch to ${this.isDark ? 'Light Mode' : 'Dark Mode'}`
|
53
|
-
}
|
54
|
-
},
|
55
|
-
}));
|
56
|
-
});
|
@@ -1,4 +0,0 @@
|
|
1
|
-
= button_to feature_path(feature.name), method: :delete, title: "Forget this setting and use default" do
|
2
|
-
%svg(xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round")
|
3
|
-
%line(x1="18" y1="6" x2="6" y2="18")
|
4
|
-
%line(x1="6" y1="6" x2="18" y2="18")
|