@7365admin1/layer-common 3.2.1-staging.70 → 3.2.1-staging.72
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.
- package/components/FeedbackMain.vue +7 -7
- package/components/Layout/NavigationDrawer.vue +39 -10
- package/components/NavigationItem.vue +9 -0
- package/components/VisitorManagement.vue +1 -1
- package/composables/useComment.ts +3 -3
- package/package.json +1 -1
- package/utils/theme.test.ts +101 -21
- package/utils/theme.ts +46 -11
|
@@ -721,9 +721,9 @@ const {
|
|
|
721
721
|
_getFeedbacks({
|
|
722
722
|
page: page.value,
|
|
723
723
|
site: route.params.site as string,
|
|
724
|
-
provider: currentUser.value
|
|
725
|
-
...(currentUser.value
|
|
726
|
-
userId: currentUser.value
|
|
724
|
+
provider: currentUser.value?.serviceProvider,
|
|
725
|
+
...(currentUser.value?.type != "site" && {
|
|
726
|
+
userId: currentUser.value?._id,
|
|
727
727
|
}),
|
|
728
728
|
service: "Security",
|
|
729
729
|
dateFrom: moment(startDate.value, "DD/MM/YYYY").startOf("day"),
|
|
@@ -998,9 +998,9 @@ async function handleAccept() {
|
|
|
998
998
|
_id: feedbackData.value._id,
|
|
999
999
|
statusUpdate: {
|
|
1000
1000
|
status: "In-Progress",
|
|
1001
|
-
updatedById: currentUser.value
|
|
1002
|
-
updatedByName: `${currentUser.value
|
|
1003
|
-
assignee: currentUser.value
|
|
1001
|
+
updatedById: currentUser.value?._id,
|
|
1002
|
+
updatedByName: `${currentUser.value?.givenName} ${currentUser.value?.surname}`,
|
|
1003
|
+
assignee: currentUser.value?._id,
|
|
1004
1004
|
provider: currentUser.value?.serviceProvider,
|
|
1005
1005
|
},
|
|
1006
1006
|
};
|
|
@@ -1158,7 +1158,7 @@ async function submit() {
|
|
|
1158
1158
|
isSubmitting.value = true;
|
|
1159
1159
|
|
|
1160
1160
|
const result = await createFeedback({
|
|
1161
|
-
createdBy: currentUser.value
|
|
1161
|
+
createdBy: currentUser.value?._id,
|
|
1162
1162
|
description: _feedback.value.description,
|
|
1163
1163
|
attachments: _feedback.value.attachments,
|
|
1164
1164
|
site: route.params.site as string,
|
|
@@ -3,19 +3,39 @@
|
|
|
3
3
|
<!--
|
|
4
4
|
`bg-brand-surface`, not `bg-primary`: `primary` is a foreground colour on a
|
|
5
5
|
dark page (it has to carry `class="text-primary"` sentences at 4.5:1), and a
|
|
6
|
-
fill that carries a
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
and
|
|
13
|
-
|
|
6
|
+
fill that carries a label cannot also be that. `brand-surface` is a real
|
|
7
|
+
surface in each theme - see `utils/theme.ts` - so the drawer belongs to the
|
|
8
|
+
application it is in rather than being the one panel the theme switch never
|
|
9
|
+
reached. Its label reads 15.43:1 light, 13.21:1 dark.
|
|
10
|
+
|
|
11
|
+
NO HARDCODED LABEL COLOUR ANYWHERE IN HERE. Vuetify paints the drawer with
|
|
12
|
+
`on-brand-surface` and the list inside it inherits that through
|
|
13
|
+
`bg-transparent`, which the drawer supplies to every `v-list` it contains.
|
|
14
|
+
A `text-white` in here (or in an app's `#action` / `#services` slot) is
|
|
15
|
+
white on a light rail at 1.13:1.
|
|
16
|
+
|
|
17
|
+
`floating` dropped so the drawer draws its own edge, and the three
|
|
18
|
+
opacities below are raised on this element only:
|
|
19
|
+
|
|
20
|
+
- `border-opacity` 0.45. A surface is a low-contrast panel by design
|
|
21
|
+
(1.13:1 light, 1.19:1 dark against the page), so the edge is what carries
|
|
22
|
+
the boundary: 3.27:1 light, 4.33:1 dark. The theme-wide value stays where
|
|
23
|
+
it is; raising that would redraw every table and card edge in eleven
|
|
24
|
+
applications.
|
|
25
|
+
- `hover-opacity` 0.08. Material's 0.04 is 1.11:1 on this rail, which is
|
|
26
|
+
not feedback. 0.08 is 1.17:1 light, 1.24:1 dark.
|
|
27
|
+
- `activated-opacity` 0.18. The active item is `primary` text on a
|
|
28
|
+
primary-tinted fill (see `NavigationItem.vue`); 0.12 left the fill at
|
|
29
|
+
1.15:1, 0.18 is 1.44:1 light, 1.33:1 dark.
|
|
14
30
|
-->
|
|
15
|
-
<v-navigation-drawer
|
|
31
|
+
<v-navigation-drawer
|
|
32
|
+
v-model="drawer"
|
|
33
|
+
permanent
|
|
34
|
+
class="bg-brand-surface brand-drawer"
|
|
35
|
+
>
|
|
16
36
|
<v-list>
|
|
17
37
|
<v-list-item>
|
|
18
|
-
<v-list-item-title class="text-h6
|
|
38
|
+
<v-list-item-title class="text-h6">
|
|
19
39
|
{{ props.title || APP_NAME }}
|
|
20
40
|
</v-list-item-title>
|
|
21
41
|
</v-list-item>
|
|
@@ -103,3 +123,12 @@ const handleClick = (item: any) => {
|
|
|
103
123
|
emit("navigate", item);
|
|
104
124
|
};
|
|
105
125
|
</script>
|
|
126
|
+
|
|
127
|
+
<style scoped>
|
|
128
|
+
/* Scoped to the drawer: none of these reach the rest of the application. */
|
|
129
|
+
.brand-drawer {
|
|
130
|
+
--v-border-opacity: 0.45;
|
|
131
|
+
--v-hover-opacity: 0.08;
|
|
132
|
+
--v-activated-opacity: 0.18;
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
`color="primary"` is applied by Vuetify only while the item is active, so the
|
|
3
|
+
page you are on is named by colour and not just by a 1.15:1 tint you cannot
|
|
4
|
+
see. `primary` reads 14.56:1 on the light rail and 5.38:1 on the dark one,
|
|
5
|
+
and it is the one brand colour that is a foreground in both themes.
|
|
6
|
+
-->
|
|
1
7
|
<template>
|
|
2
8
|
<v-list-group v-if="children && children.length">
|
|
3
9
|
<template #activator="{ props: groupProps }">
|
|
4
10
|
<v-list-item
|
|
5
11
|
v-bind="groupProps"
|
|
6
12
|
:prepend-icon="icon"
|
|
13
|
+
color="primary"
|
|
7
14
|
class="text-subtitle-2"
|
|
8
15
|
@click.stop="onParentClick"
|
|
9
16
|
>
|
|
@@ -25,6 +32,7 @@
|
|
|
25
32
|
v-else-if="routeTo"
|
|
26
33
|
:prepend-icon="icon"
|
|
27
34
|
:to="routeTo"
|
|
35
|
+
color="primary"
|
|
28
36
|
class="text-subtitle-2"
|
|
29
37
|
>
|
|
30
38
|
{{ title }}
|
|
@@ -34,6 +42,7 @@
|
|
|
34
42
|
v-else-if="props.link"
|
|
35
43
|
:prepend-icon="icon"
|
|
36
44
|
:href="props.link"
|
|
45
|
+
color="primary"
|
|
37
46
|
class="text-subtitle-2"
|
|
38
47
|
>
|
|
39
48
|
{{ title }}
|
|
@@ -1707,7 +1707,7 @@ async function handleVisitorDataFromScannedQRCodeCheckInOut(
|
|
|
1707
1707
|
site: visitorData.site ?? siteId,
|
|
1708
1708
|
},
|
|
1709
1709
|
update: {
|
|
1710
|
-
updatedBy: currentUser.value
|
|
1710
|
+
updatedBy: currentUser.value?._id,
|
|
1711
1711
|
visitorPass: visitorPass.find(
|
|
1712
1712
|
(i: Record<string, any>) => i.keyId === null
|
|
1713
1713
|
)
|
|
@@ -89,13 +89,13 @@ export default function useComment() {
|
|
|
89
89
|
|
|
90
90
|
comments.value = _comments.items
|
|
91
91
|
.map((comment: any) => {
|
|
92
|
-
if (comment.createdBy === currentUser.value
|
|
92
|
+
if (comment.createdBy === currentUser.value?._id) {
|
|
93
93
|
comment.justify = "end";
|
|
94
94
|
} else {
|
|
95
95
|
comment.justify = "start";
|
|
96
96
|
if (
|
|
97
97
|
((Array.isArray(comment.seenBy) &&
|
|
98
|
-
!comment.seenBy.includes(currentUser.value
|
|
98
|
+
!comment.seenBy.includes(currentUser.value?._id)) ||
|
|
99
99
|
!comment?.seenBy) &&
|
|
100
100
|
comment?._id
|
|
101
101
|
) {
|
|
@@ -111,7 +111,7 @@ export default function useComment() {
|
|
|
111
111
|
);
|
|
112
112
|
|
|
113
113
|
if (Array.isArray(updateSeenIds) && updateSeenIds.length > 0) {
|
|
114
|
-
const seenBy = await updateSeenBy(updateSeenIds, currentUser.value
|
|
114
|
+
const seenBy = await updateSeenBy(updateSeenIds, currentUser.value?._id);
|
|
115
115
|
}
|
|
116
116
|
} catch (error) {
|
|
117
117
|
console.log("error :", error);
|
package/package.json
CHANGED
package/utils/theme.test.ts
CHANGED
|
@@ -24,6 +24,20 @@ const contrast = (a: string, b: string): number => {
|
|
|
24
24
|
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
/** `fg` at `alpha` composited over `bg` - an overlay is not its raw value. */
|
|
28
|
+
const over = (fg: string, bg: string, alpha: number): string =>
|
|
29
|
+
"#" +
|
|
30
|
+
[0, 2, 4]
|
|
31
|
+
.map((i) =>
|
|
32
|
+
Math.round(
|
|
33
|
+
parseInt(fg.slice(1 + i, 3 + i), 16) * alpha +
|
|
34
|
+
parseInt(bg.slice(1 + i, 3 + i), 16) * (1 - alpha)
|
|
35
|
+
)
|
|
36
|
+
.toString(16)
|
|
37
|
+
.padStart(2, "0")
|
|
38
|
+
)
|
|
39
|
+
.join("");
|
|
40
|
+
|
|
27
41
|
const WHITE = "#FFFFFF";
|
|
28
42
|
const dark = DARK_THEME.colors;
|
|
29
43
|
const light = LIGHT_THEME.colors;
|
|
@@ -65,21 +79,100 @@ test("text-primary is readable on the surface each theme puts it on", () => {
|
|
|
65
79
|
/**
|
|
66
80
|
* The drawer is the reason `primary` was moved in the first place. If someone
|
|
67
81
|
* points it back at `primary` this fails, because a foreground colour cannot
|
|
68
|
-
* carry
|
|
82
|
+
* carry the drawer's label.
|
|
69
83
|
*/
|
|
70
|
-
test("the nav drawer fill carries
|
|
84
|
+
test("the nav drawer fill carries its own label in both themes", () => {
|
|
71
85
|
assert.ok(
|
|
72
|
-
contrast(
|
|
73
|
-
`
|
|
74
|
-
|
|
86
|
+
contrast(dark["on-brand-surface"], dark["brand-surface"]) >= 4.5,
|
|
87
|
+
`dark drawer label: ${contrast(
|
|
88
|
+
dark["on-brand-surface"],
|
|
75
89
|
dark["brand-surface"]
|
|
76
90
|
).toFixed(2)}`
|
|
77
91
|
);
|
|
78
|
-
assert.ok(
|
|
92
|
+
assert.ok(
|
|
93
|
+
contrast(light["on-brand-surface"], light["brand-surface"]) >= 4.5,
|
|
94
|
+
`light drawer label: ${contrast(
|
|
95
|
+
light["on-brand-surface"],
|
|
96
|
+
light["brand-surface"]
|
|
97
|
+
).toFixed(2)}`
|
|
98
|
+
);
|
|
79
99
|
});
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
/**
|
|
102
|
+
* The defect this replaced: `brand-surface` was the same deep navy in both
|
|
103
|
+
* themes, so the drawer was the one panel the theme switch did not reach. A
|
|
104
|
+
* light-theme surface has to be light, a dark-theme surface has to be dark,
|
|
105
|
+
* and if the two are ever within 3:1 of each other the switch is invisible on
|
|
106
|
+
* the drawer again.
|
|
107
|
+
*/
|
|
108
|
+
test("the drawer belongs to its theme, and the switch visibly changes it", () => {
|
|
109
|
+
assert.ok(
|
|
110
|
+
contrast(light["brand-surface"], WHITE) < 1.5,
|
|
111
|
+
"the light drawer must be a light surface, not a dark slab on a white page"
|
|
112
|
+
);
|
|
113
|
+
assert.ok(
|
|
114
|
+
contrast(dark["brand-surface"], dark.background) < 1.5,
|
|
115
|
+
"the dark drawer must sit with the dark page, not float above it"
|
|
116
|
+
);
|
|
117
|
+
assert.ok(
|
|
118
|
+
contrast(light["brand-surface"], dark["brand-surface"]) >= 3,
|
|
119
|
+
`light and dark drawers only ${contrast(
|
|
120
|
+
light["brand-surface"],
|
|
121
|
+
dark["brand-surface"]
|
|
122
|
+
).toFixed(2)} apart - the switch would look like it does nothing`
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The drawer's fill is deliberately close to the page, so the edge is the
|
|
128
|
+
* boundary and it is the thing that has to reach 3:1. 0.45 is the value
|
|
129
|
+
* `NavigationDrawer.vue` sets on itself; the theme-wide `border-opacity` is
|
|
130
|
+
* left alone.
|
|
131
|
+
*/
|
|
132
|
+
test("the drawer's own edge clears the 3:1 a boundary wants", () => {
|
|
133
|
+
const edge = (fg: string, bg: string) => over(fg, bg, 0.45);
|
|
134
|
+
|
|
135
|
+
assert.ok(
|
|
136
|
+
contrast(edge("#000000", light["brand-surface"]), WHITE) >= 3,
|
|
137
|
+
`light drawer edge vs page: ${contrast(
|
|
138
|
+
edge("#000000", light["brand-surface"]),
|
|
139
|
+
WHITE
|
|
140
|
+
).toFixed(2)}`
|
|
141
|
+
);
|
|
142
|
+
assert.ok(
|
|
143
|
+
contrast(edge(WHITE, dark["brand-surface"]), dark.background) >= 3,
|
|
144
|
+
`dark drawer edge vs page: ${contrast(
|
|
145
|
+
edge(WHITE, dark["brand-surface"]),
|
|
146
|
+
dark.background
|
|
147
|
+
).toFixed(2)}`
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/** The active item is named by colour, so `primary` has to be readable on it. */
|
|
152
|
+
test("the active nav item is readable on the drawer in both themes", () => {
|
|
153
|
+
assert.ok(
|
|
154
|
+
contrast(light.primary, light["brand-surface"]) >= 4.5,
|
|
155
|
+
`light: ${contrast(light.primary, light["brand-surface"]).toFixed(2)}`
|
|
156
|
+
);
|
|
157
|
+
assert.ok(
|
|
158
|
+
contrast(dark.primary, dark["brand-surface"]) >= 4.5,
|
|
159
|
+
`dark: ${contrast(dark.primary, dark["brand-surface"]).toFixed(2)}`
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* `v-list-subheader` is the one thing in the drawer that does NOT inherit the
|
|
165
|
+
* drawer's label colour - Vuetify paints it `on-surface` at medium emphasis.
|
|
166
|
+
* That is why apps used to hardcode `text-white` on it. It has to work
|
|
167
|
+
* unaided now, or those hardcodes come back.
|
|
168
|
+
*/
|
|
169
|
+
test("an unstyled subheader is readable on the drawer in both themes", () => {
|
|
170
|
+
assert.ok(
|
|
171
|
+
contrast(over("#000000", light["brand-surface"], 0.6), light["brand-surface"]) >= 4.5
|
|
172
|
+
);
|
|
173
|
+
assert.ok(
|
|
174
|
+
contrast(over(WHITE, dark["brand-surface"], 0.6), dark["brand-surface"]) >= 4.5
|
|
175
|
+
);
|
|
83
176
|
});
|
|
84
177
|
|
|
85
178
|
test("primary-button carries a white label in both themes", () => {
|
|
@@ -92,19 +185,6 @@ test("primary-button carries a white label in both themes", () => {
|
|
|
92
185
|
* apart between them is a bug nobody sees until they flip the switch.
|
|
93
186
|
*/
|
|
94
187
|
test("disabled text clears 4.5:1 on the surface each theme puts it on", () => {
|
|
95
|
-
const over = (fg: string, bg: string, alpha: number) =>
|
|
96
|
-
"#" +
|
|
97
|
-
[0, 2, 4]
|
|
98
|
-
.map((i) =>
|
|
99
|
-
Math.round(
|
|
100
|
-
parseInt(fg.slice(1 + i, 3 + i), 16) * alpha +
|
|
101
|
-
parseInt(bg.slice(1 + i, 3 + i), 16) * (1 - alpha)
|
|
102
|
-
)
|
|
103
|
-
.toString(16)
|
|
104
|
-
.padStart(2, "0")
|
|
105
|
-
)
|
|
106
|
-
.join("");
|
|
107
|
-
|
|
108
188
|
assert.equal(LIGHT_THEME.variables["disabled-opacity"], 0.55);
|
|
109
189
|
assert.equal(DARK_THEME.variables["disabled-opacity"], 0.55);
|
|
110
190
|
assert.ok(contrast(over("#000000", WHITE, 0.55), WHITE) >= 4.5);
|
package/utils/theme.ts
CHANGED
|
@@ -32,22 +32,49 @@
|
|
|
32
32
|
* There is no overlap - a value light enough for 4.5:1 text on `surface`
|
|
33
33
|
* (#1B242F) can carry white at 3.48:1 at the very best.
|
|
34
34
|
*
|
|
35
|
-
* So the fill is not `primary` any more. `brand-surface` below is the
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
35
|
+
* So the fill is not `primary` any more. `brand-surface` below is what the
|
|
36
|
+
* navigation drawer is painted with, and `primary` is free to be a foreground
|
|
37
|
+
* colour. The foreground uses outnumber the fill uses by roughly an order of
|
|
38
|
+
* magnitude, which is why the split falls this way round.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `brand-surface` IS A SURFACE, AND A SURFACE BELONGS TO ITS THEME.
|
|
43
|
+
*
|
|
44
|
+
* It was first set to the deep brand navy in BOTH themes, which made the
|
|
45
|
+
* navigation drawer the one part of the product the theme switch did not
|
|
46
|
+
* reach: a near-black slab bolted onto a white application in light mode, and
|
|
47
|
+
* a lighter blue block sitting 1.80:1 off the cards beside it in dark mode. It
|
|
48
|
+
* belonged to neither.
|
|
49
|
+
*
|
|
50
|
+
* It is now a real surface in each theme - a light navy-tinted rail on the
|
|
51
|
+
* light page, the same `surface` the app bar and the cards use on the dark
|
|
52
|
+
* page - so the drawer reads as part of the application it is in and the
|
|
53
|
+
* switch visibly changes it. The brand is carried by the title and by the
|
|
54
|
+
* active item, which is `primary` in both themes, rather than by painting the
|
|
55
|
+
* whole rail.
|
|
56
|
+
*
|
|
57
|
+
* The drawer is a low-contrast panel against the page by design (1.13:1 light,
|
|
58
|
+
* 1.19:1 dark, exactly like every other surface in Material), so its edge is
|
|
59
|
+
* what carries the boundary. `NavigationDrawer.vue` raises `border-opacity` on
|
|
60
|
+
* itself alone to reach 3:1 there without redrawing every table and card edge
|
|
61
|
+
* in eleven applications.
|
|
62
|
+
*
|
|
63
|
+
* `on-brand-surface` is set explicitly rather than left to Vuetify's black or
|
|
64
|
+
* white, so the label is the same near-black / near-white the rest of the
|
|
65
|
+
* theme reads in.
|
|
40
66
|
*/
|
|
41
67
|
export const LIGHT_THEME = {
|
|
42
68
|
dark: false,
|
|
43
69
|
colors: {
|
|
44
70
|
primary: "#042134",
|
|
45
71
|
/**
|
|
46
|
-
* The navigation drawer's own fill
|
|
47
|
-
*
|
|
48
|
-
* White label on it: 16.51:1.
|
|
72
|
+
* The navigation drawer's own fill: a navy-tinted light rail, 1.13:1
|
|
73
|
+
* against the white page and carried by its own 3.27:1 edge.
|
|
49
74
|
*/
|
|
50
|
-
"brand-surface": "#
|
|
75
|
+
"brand-surface": "#EDF1F7",
|
|
76
|
+
/** The drawer's label colour. On the rail: 15.43:1. */
|
|
77
|
+
"on-brand-surface": "#0B1B27",
|
|
51
78
|
"primary-button": "#1867C0",
|
|
52
79
|
"text-primary": "#052439",
|
|
53
80
|
},
|
|
@@ -87,8 +114,16 @@ export const DARK_THEME = {
|
|
|
87
114
|
* that still do that.
|
|
88
115
|
*/
|
|
89
116
|
primary: "#5B9BE0",
|
|
90
|
-
/**
|
|
91
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The navigation drawer's own fill. Deliberately the same value as
|
|
119
|
+
* `surface`: on a dark page the drawer, the app bar and the cards are the
|
|
120
|
+
* same material, which is what makes the rail read as part of the
|
|
121
|
+
* application instead of a blue block laid on top of it. The token stays
|
|
122
|
+
* separate from `surface` so the drawer can be moved on its own later.
|
|
123
|
+
*/
|
|
124
|
+
"brand-surface": "#1B242F",
|
|
125
|
+
/** The drawer's label colour. On the rail: 13.21:1. */
|
|
126
|
+
"on-brand-surface": "#E8ECF1",
|
|
92
127
|
/** Unchanged on purpose: it already passes in both themes. */
|
|
93
128
|
"primary-button": "#1867C0",
|
|
94
129
|
/** #052439 on a dark page measures 1.18:1. This is 15.72:1. */
|