@jskit-ai/users-core 0.1.15 → 0.1.17
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/package.descriptor.mjs +6 -6
- package/package.json +5 -5
- package/src/server/common/formatters/workspaceFormatter.js +12 -13
- package/src/server/common/repositories/workspacesRepository.js +2 -19
- package/src/shared/index.js +24 -18
- package/src/shared/settings.js +77 -89
- package/templates/migrations/users_core_initial.cjs +9 -5
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +111 -54
- package/test/workspaceSettingsRepository.test.js +71 -35
- package/test/workspaceSettingsResource.test.js +36 -20
- package/test/workspaceSettingsService.test.js +36 -20
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/users-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.17",
|
|
5
5
|
description: "Users/workspace domain runtime plus HTTP routes for workspace, account, and console features.",
|
|
6
6
|
dependsOn: [
|
|
7
7
|
"@jskit-ai/auth-core",
|
|
@@ -203,10 +203,10 @@ export default Object.freeze({
|
|
|
203
203
|
mutations: {
|
|
204
204
|
dependencies: {
|
|
205
205
|
runtime: {
|
|
206
|
-
"@jskit-ai/auth-core": "0.1.
|
|
207
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
208
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
209
|
-
"@jskit-ai/kernel": "0.1.
|
|
206
|
+
"@jskit-ai/auth-core": "0.1.12",
|
|
207
|
+
"@jskit-ai/database-runtime": "0.1.13",
|
|
208
|
+
"@jskit-ai/http-runtime": "0.1.12",
|
|
209
|
+
"@jskit-ai/kernel": "0.1.12",
|
|
210
210
|
"@fastify/multipart": "^9.4.0",
|
|
211
211
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
212
212
|
"typebox": "^1.0.81"
|
|
@@ -436,7 +436,7 @@ export default Object.freeze({
|
|
|
436
436
|
file: "config/server.js",
|
|
437
437
|
position: "bottom",
|
|
438
438
|
skipIfContains: "config.workspaceColor =",
|
|
439
|
-
value: "\nconfig.workspaceColor = \"
|
|
439
|
+
value: "\nconfig.workspaceColor = \"#1867C0\";\n",
|
|
440
440
|
reason: "Append default server-only users/workspace settings into app-owned config.",
|
|
441
441
|
category: "users-core",
|
|
442
442
|
id: "users-core-server-config"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"./shared/resources/consoleSettingsFields": "./src/shared/resources/consoleSettingsFields.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jskit-ai/auth-core": "0.1.
|
|
28
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
29
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
30
|
-
"@jskit-ai/kernel": "0.1.
|
|
27
|
+
"@jskit-ai/auth-core": "0.1.12",
|
|
28
|
+
"@jskit-ai/database-runtime": "0.1.13",
|
|
29
|
+
"@jskit-ai/http-runtime": "0.1.12",
|
|
30
|
+
"@jskit-ai/kernel": "0.1.12",
|
|
31
31
|
"@fastify/multipart": "^9.4.0",
|
|
32
32
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
33
33
|
"typebox": "^1.0.81"
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
coerceWorkspaceColor,
|
|
3
|
-
|
|
4
|
-
coerceWorkspaceSurfaceColor,
|
|
5
|
-
coerceWorkspaceSurfaceVariantColor
|
|
3
|
+
resolveWorkspaceThemePalettes
|
|
6
4
|
} from "../../../shared/settings.js";
|
|
7
5
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
8
6
|
|
|
9
7
|
function mapWorkspaceSummary(workspace, membership) {
|
|
10
|
-
const color = coerceWorkspaceColor(workspace.color);
|
|
11
8
|
return {
|
|
12
9
|
id: Number(workspace.id),
|
|
13
10
|
slug: normalizeText(workspace.slug),
|
|
14
11
|
name: normalizeText(workspace.name),
|
|
15
|
-
color,
|
|
16
|
-
secondaryColor: coerceWorkspaceSecondaryColor(workspace.secondaryColor, { color }),
|
|
17
|
-
surfaceColor: coerceWorkspaceSurfaceColor(workspace.surfaceColor, { color }),
|
|
18
|
-
surfaceVariantColor: coerceWorkspaceSurfaceVariantColor(workspace.surfaceVariantColor, { color }),
|
|
12
|
+
color: coerceWorkspaceColor(workspace.color),
|
|
19
13
|
avatarUrl: normalizeText(workspace.avatarUrl),
|
|
20
14
|
roleId: normalizeLowerText(membership?.roleId || "member") || "member",
|
|
21
15
|
isAccessible: normalizeLowerText(membership?.status || "active") === "active"
|
|
@@ -26,13 +20,18 @@ function mapWorkspaceSettingsPublic(workspaceSettings, { workspaceInvitationsEna
|
|
|
26
20
|
const source = workspaceSettings && typeof workspaceSettings === "object" ? workspaceSettings : {};
|
|
27
21
|
const invitesAvailable = workspaceInvitationsEnabled === true;
|
|
28
22
|
const invitesEnabled = invitesAvailable && source.invitesEnabled !== false;
|
|
29
|
-
const
|
|
23
|
+
const themePalettes = resolveWorkspaceThemePalettes(source);
|
|
24
|
+
|
|
30
25
|
return {
|
|
31
26
|
name: normalizeText(source.name),
|
|
32
|
-
color,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
lightPrimaryColor: themePalettes.light.color,
|
|
28
|
+
lightSecondaryColor: themePalettes.light.secondaryColor,
|
|
29
|
+
lightSurfaceColor: themePalettes.light.surfaceColor,
|
|
30
|
+
lightSurfaceVariantColor: themePalettes.light.surfaceVariantColor,
|
|
31
|
+
darkPrimaryColor: themePalettes.dark.color,
|
|
32
|
+
darkSecondaryColor: themePalettes.dark.secondaryColor,
|
|
33
|
+
darkSurfaceColor: themePalettes.dark.surfaceColor,
|
|
34
|
+
darkSurfaceVariantColor: themePalettes.dark.surfaceVariantColor,
|
|
36
35
|
avatarUrl: normalizeText(source.avatarUrl),
|
|
37
36
|
invitesEnabled,
|
|
38
37
|
invitesAvailable,
|
|
@@ -6,12 +6,7 @@ import {
|
|
|
6
6
|
nowDb,
|
|
7
7
|
isDuplicateEntryError
|
|
8
8
|
} from "./repositoryUtils.js";
|
|
9
|
-
import {
|
|
10
|
-
coerceWorkspaceColor,
|
|
11
|
-
coerceWorkspaceSecondaryColor,
|
|
12
|
-
coerceWorkspaceSurfaceColor,
|
|
13
|
-
coerceWorkspaceSurfaceVariantColor
|
|
14
|
-
} from "../../../shared/settings.js";
|
|
9
|
+
import { coerceWorkspaceColor } from "../../../shared/settings.js";
|
|
15
10
|
|
|
16
11
|
function mapRow(row) {
|
|
17
12
|
if (!row) {
|
|
@@ -27,15 +22,6 @@ function mapRow(row) {
|
|
|
27
22
|
isPersonal: Boolean(row.is_personal),
|
|
28
23
|
avatarUrl: row.avatar_url ? normalizeText(row.avatar_url) : "",
|
|
29
24
|
color,
|
|
30
|
-
secondaryColor: coerceWorkspaceSecondaryColor(row.secondary_color, {
|
|
31
|
-
color
|
|
32
|
-
}),
|
|
33
|
-
surfaceColor: coerceWorkspaceSurfaceColor(row.surface_color, {
|
|
34
|
-
color
|
|
35
|
-
}),
|
|
36
|
-
surfaceVariantColor: coerceWorkspaceSurfaceVariantColor(row.surface_variant_color, {
|
|
37
|
-
color
|
|
38
|
-
}),
|
|
39
25
|
createdAt: toIsoString(row.created_at),
|
|
40
26
|
updatedAt: toIsoString(row.updated_at),
|
|
41
27
|
deletedAt: toNullableIso(row.deleted_at)
|
|
@@ -67,10 +53,7 @@ function createRepository(knex) {
|
|
|
67
53
|
"w.owner_user_id",
|
|
68
54
|
"w.is_personal",
|
|
69
55
|
client.raw("COALESCE(ws.avatar_url, w.avatar_url) as avatar_url"),
|
|
70
|
-
|
|
71
|
-
client.raw("COALESCE(ws.secondary_color, ws.color, w.color) as secondary_color"),
|
|
72
|
-
client.raw("COALESCE(ws.surface_color, ws.color, w.color) as surface_color"),
|
|
73
|
-
client.raw("COALESCE(ws.surface_variant_color, ws.color, w.color) as surface_variant_color"),
|
|
56
|
+
"w.color",
|
|
74
57
|
"w.created_at",
|
|
75
58
|
"w.updated_at",
|
|
76
59
|
"w.deleted_at"
|
package/src/shared/index.js
CHANGED
|
@@ -8,19 +8,21 @@ import {
|
|
|
8
8
|
} from "./roles.js";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
12
|
+
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
12
13
|
DEFAULT_WORKSPACE_COLOR,
|
|
13
14
|
DEFAULT_USER_SETTINGS,
|
|
14
15
|
coerceWorkspaceColor,
|
|
16
|
+
coerceWorkspaceThemeColor,
|
|
15
17
|
coerceWorkspaceSecondaryColor,
|
|
16
18
|
coerceWorkspaceSurfaceColor,
|
|
17
19
|
coerceWorkspaceSurfaceVariantColor,
|
|
18
|
-
deriveWorkspaceBackgroundColor,
|
|
19
|
-
deriveWorkspaceSecondaryColor,
|
|
20
|
-
deriveWorkspaceSurfaceColor,
|
|
21
|
-
deriveWorkspaceSurfaceVariantColor,
|
|
22
|
-
mixHexColors,
|
|
23
20
|
normalizeWorkspaceHexColor,
|
|
21
|
+
normalizeWorkspaceThemeMode,
|
|
22
|
+
resolveWorkspaceThemeDefaultPalette,
|
|
23
|
+
resolveWorkspaceThemePalettes,
|
|
24
|
+
WORKSPACE_THEME_MODE_DARK,
|
|
25
|
+
WORKSPACE_THEME_MODE_LIGHT,
|
|
24
26
|
resolveWorkspaceThemePalette
|
|
25
27
|
} from "./settings.js";
|
|
26
28
|
import {
|
|
@@ -51,19 +53,21 @@ const USERS_SHARED_API = Object.freeze({
|
|
|
51
53
|
resolveRolePermissions,
|
|
52
54
|
listRoleDescriptors,
|
|
53
55
|
hasPermission,
|
|
56
|
+
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
57
|
+
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
54
58
|
DEFAULT_WORKSPACE_COLOR,
|
|
55
59
|
DEFAULT_USER_SETTINGS,
|
|
56
60
|
coerceWorkspaceColor,
|
|
61
|
+
coerceWorkspaceThemeColor,
|
|
57
62
|
coerceWorkspaceSecondaryColor,
|
|
58
63
|
coerceWorkspaceSurfaceColor,
|
|
59
64
|
coerceWorkspaceSurfaceVariantColor,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
WORKSPACE_THEME_MODE_LIGHT,
|
|
66
|
+
WORKSPACE_THEME_MODE_DARK,
|
|
67
|
+
normalizeWorkspaceThemeMode,
|
|
68
|
+
resolveWorkspaceThemeDefaultPalette,
|
|
69
|
+
resolveWorkspaceThemePalettes,
|
|
65
70
|
normalizeWorkspaceHexColor,
|
|
66
|
-
mixHexColors,
|
|
67
71
|
resolveWorkspaceThemePalette,
|
|
68
72
|
TENANCY_MODE_NONE,
|
|
69
73
|
TENANCY_MODE_PERSONAL,
|
|
@@ -90,19 +94,21 @@ export {
|
|
|
90
94
|
resolveRolePermissions,
|
|
91
95
|
listRoleDescriptors,
|
|
92
96
|
hasPermission,
|
|
97
|
+
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
98
|
+
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
93
99
|
DEFAULT_WORKSPACE_COLOR,
|
|
94
100
|
DEFAULT_USER_SETTINGS,
|
|
95
101
|
coerceWorkspaceColor,
|
|
102
|
+
coerceWorkspaceThemeColor,
|
|
96
103
|
coerceWorkspaceSecondaryColor,
|
|
97
104
|
coerceWorkspaceSurfaceColor,
|
|
98
105
|
coerceWorkspaceSurfaceVariantColor,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
WORKSPACE_THEME_MODE_LIGHT,
|
|
107
|
+
WORKSPACE_THEME_MODE_DARK,
|
|
108
|
+
normalizeWorkspaceThemeMode,
|
|
109
|
+
resolveWorkspaceThemeDefaultPalette,
|
|
110
|
+
resolveWorkspaceThemePalettes,
|
|
104
111
|
normalizeWorkspaceHexColor,
|
|
105
|
-
mixHexColors,
|
|
106
112
|
resolveWorkspaceThemePalette,
|
|
107
113
|
TENANCY_MODE_NONE,
|
|
108
114
|
TENANCY_MODE_PERSONAL,
|
package/src/shared/settings.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
const
|
|
1
|
+
const WORKSPACE_THEME_MODE_LIGHT = "light";
|
|
2
|
+
const WORKSPACE_THEME_MODE_DARK = "dark";
|
|
2
3
|
const HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
3
4
|
|
|
5
|
+
const DEFAULT_WORKSPACE_LIGHT_PALETTE = Object.freeze({
|
|
6
|
+
color: "#1867C0",
|
|
7
|
+
secondaryColor: "#48A9A6",
|
|
8
|
+
surfaceColor: "#FFFFFF",
|
|
9
|
+
surfaceVariantColor: "#424242"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const DEFAULT_WORKSPACE_DARK_PALETTE = Object.freeze({
|
|
13
|
+
color: "#2196F3",
|
|
14
|
+
secondaryColor: "#54B6B2",
|
|
15
|
+
surfaceColor: "#212121",
|
|
16
|
+
surfaceVariantColor: "#C8C8C8"
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const DEFAULT_WORKSPACE_COLOR = DEFAULT_WORKSPACE_LIGHT_PALETTE.color;
|
|
20
|
+
|
|
4
21
|
const DEFAULT_USER_SETTINGS = Object.freeze({
|
|
5
22
|
theme: "system",
|
|
6
23
|
locale: "en",
|
|
@@ -25,124 +42,95 @@ function normalizeWorkspaceHexColor(value) {
|
|
|
25
42
|
return normalized.toUpperCase();
|
|
26
43
|
}
|
|
27
44
|
|
|
28
|
-
function clampChannel(value) {
|
|
29
|
-
if (!Number.isFinite(value)) {
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
if (value < 0) {
|
|
33
|
-
return 0;
|
|
34
|
-
}
|
|
35
|
-
if (value > 255) {
|
|
36
|
-
return 255;
|
|
37
|
-
}
|
|
38
|
-
return Math.round(value);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function toHexChannel(value) {
|
|
42
|
-
return clampChannel(value).toString(16).padStart(2, "0").toUpperCase();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseHexColor(value) {
|
|
46
|
-
const normalized = normalizeWorkspaceHexColor(value);
|
|
47
|
-
if (!normalized) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
red: Number.parseInt(normalized.slice(1, 3), 16),
|
|
52
|
-
green: Number.parseInt(normalized.slice(3, 5), 16),
|
|
53
|
-
blue: Number.parseInt(normalized.slice(5, 7), 16)
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function mixHexColors(baseColor, mixColor, mixRatio = 0) {
|
|
58
|
-
const base = parseHexColor(baseColor);
|
|
59
|
-
const mixed = parseHexColor(mixColor);
|
|
60
|
-
if (!base || !mixed) {
|
|
61
|
-
return "";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const ratio = Math.min(1, Math.max(0, Number(mixRatio) || 0));
|
|
65
|
-
const keepRatio = 1 - ratio;
|
|
66
|
-
return `#${toHexChannel(base.red * keepRatio + mixed.red * ratio)}${toHexChannel(
|
|
67
|
-
base.green * keepRatio + mixed.green * ratio
|
|
68
|
-
)}${toHexChannel(base.blue * keepRatio + mixed.blue * ratio)}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
45
|
function coerceWorkspaceColor(value) {
|
|
72
46
|
return normalizeWorkspaceHexColor(value) || DEFAULT_WORKSPACE_COLOR;
|
|
73
47
|
}
|
|
74
48
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function deriveWorkspaceSurfaceVariantColor(workspaceColor = DEFAULT_WORKSPACE_COLOR) {
|
|
84
|
-
return mixHexColors(coerceWorkspaceColor(workspaceColor), "#FFFFFF", 0.86) || "#E2E8F1";
|
|
49
|
+
function normalizeWorkspaceThemeMode(value = "") {
|
|
50
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
51
|
+
if (normalized === WORKSPACE_THEME_MODE_DARK) {
|
|
52
|
+
return WORKSPACE_THEME_MODE_DARK;
|
|
53
|
+
}
|
|
54
|
+
return WORKSPACE_THEME_MODE_LIGHT;
|
|
85
55
|
}
|
|
86
56
|
|
|
87
|
-
function
|
|
88
|
-
const
|
|
89
|
-
return
|
|
57
|
+
function resolveWorkspaceThemeDefaultPalette(mode = WORKSPACE_THEME_MODE_LIGHT) {
|
|
58
|
+
const normalizedMode = normalizeWorkspaceThemeMode(mode);
|
|
59
|
+
return normalizedMode === WORKSPACE_THEME_MODE_DARK
|
|
60
|
+
? DEFAULT_WORKSPACE_DARK_PALETTE
|
|
61
|
+
: DEFAULT_WORKSPACE_LIGHT_PALETTE;
|
|
90
62
|
}
|
|
91
63
|
|
|
92
|
-
function
|
|
93
|
-
return normalizeWorkspaceHexColor(value) ||
|
|
64
|
+
function coerceWorkspaceThemeColor(value, fallbackColor = DEFAULT_WORKSPACE_COLOR) {
|
|
65
|
+
return normalizeWorkspaceHexColor(value) || normalizeWorkspaceHexColor(fallbackColor) || DEFAULT_WORKSPACE_COLOR;
|
|
94
66
|
}
|
|
95
67
|
|
|
96
|
-
function
|
|
97
|
-
return
|
|
68
|
+
function coerceWorkspaceSecondaryColor(value, { mode = WORKSPACE_THEME_MODE_LIGHT } = {}) {
|
|
69
|
+
return coerceWorkspaceThemeColor(value, resolveWorkspaceThemeDefaultPalette(mode).secondaryColor);
|
|
98
70
|
}
|
|
99
71
|
|
|
100
|
-
function
|
|
101
|
-
return
|
|
72
|
+
function coerceWorkspaceSurfaceColor(value, { mode = WORKSPACE_THEME_MODE_LIGHT } = {}) {
|
|
73
|
+
return coerceWorkspaceThemeColor(value, resolveWorkspaceThemeDefaultPalette(mode).surfaceColor);
|
|
102
74
|
}
|
|
103
75
|
|
|
104
|
-
function
|
|
105
|
-
return
|
|
76
|
+
function coerceWorkspaceSurfaceVariantColor(value, { mode = WORKSPACE_THEME_MODE_LIGHT } = {}) {
|
|
77
|
+
return coerceWorkspaceThemeColor(value, resolveWorkspaceThemeDefaultPalette(mode).surfaceVariantColor);
|
|
106
78
|
}
|
|
107
79
|
|
|
108
|
-
function resolveWorkspaceThemePalette(input = {}) {
|
|
80
|
+
function resolveWorkspaceThemePalette(input = {}, { mode = WORKSPACE_THEME_MODE_LIGHT } = {}) {
|
|
109
81
|
const source = input && typeof input === "object" ? input : {};
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
82
|
+
const normalizedMode = normalizeWorkspaceThemeMode(mode);
|
|
83
|
+
const paletteDefaults = resolveWorkspaceThemeDefaultPalette(normalizedMode);
|
|
84
|
+
|
|
85
|
+
if (normalizedMode === WORKSPACE_THEME_MODE_DARK) {
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
color: coerceWorkspaceThemeColor(source.darkPrimaryColor, paletteDefaults.color),
|
|
88
|
+
secondaryColor: coerceWorkspaceThemeColor(source.darkSecondaryColor, paletteDefaults.secondaryColor),
|
|
89
|
+
surfaceColor: coerceWorkspaceThemeColor(source.darkSurfaceColor, paletteDefaults.surfaceColor),
|
|
90
|
+
surfaceVariantColor: coerceWorkspaceThemeColor(
|
|
91
|
+
source.darkSurfaceVariantColor,
|
|
92
|
+
paletteDefaults.surfaceVariantColor
|
|
93
|
+
)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
color: coerceWorkspaceThemeColor(source.lightPrimaryColor, paletteDefaults.color),
|
|
99
|
+
secondaryColor: coerceWorkspaceThemeColor(source.lightSecondaryColor, paletteDefaults.secondaryColor),
|
|
100
|
+
surfaceColor: coerceWorkspaceThemeColor(source.lightSurfaceColor, paletteDefaults.surfaceColor),
|
|
101
|
+
surfaceVariantColor: coerceWorkspaceThemeColor(
|
|
102
|
+
source.lightSurfaceVariantColor,
|
|
103
|
+
paletteDefaults.surfaceVariantColor
|
|
104
|
+
)
|
|
122
105
|
});
|
|
106
|
+
}
|
|
123
107
|
|
|
108
|
+
function resolveWorkspaceThemePalettes(input = {}) {
|
|
124
109
|
return Object.freeze({
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
110
|
+
light: resolveWorkspaceThemePalette(input, {
|
|
111
|
+
mode: WORKSPACE_THEME_MODE_LIGHT
|
|
112
|
+
}),
|
|
113
|
+
dark: resolveWorkspaceThemePalette(input, {
|
|
114
|
+
mode: WORKSPACE_THEME_MODE_DARK
|
|
115
|
+
})
|
|
130
116
|
});
|
|
131
117
|
}
|
|
132
118
|
|
|
133
119
|
export {
|
|
120
|
+
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
121
|
+
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
134
122
|
DEFAULT_WORKSPACE_COLOR,
|
|
135
123
|
DEFAULT_USER_SETTINGS,
|
|
136
|
-
coerceWorkspaceBackgroundColor,
|
|
137
124
|
coerceWorkspaceColor,
|
|
125
|
+
coerceWorkspaceThemeColor,
|
|
138
126
|
coerceWorkspaceSecondaryColor,
|
|
139
127
|
coerceWorkspaceSurfaceColor,
|
|
140
128
|
coerceWorkspaceSurfaceVariantColor,
|
|
141
|
-
deriveWorkspaceBackgroundColor,
|
|
142
|
-
deriveWorkspaceSecondaryColor,
|
|
143
|
-
deriveWorkspaceSurfaceColor,
|
|
144
|
-
deriveWorkspaceSurfaceVariantColor,
|
|
145
|
-
mixHexColors,
|
|
146
129
|
normalizeWorkspaceHexColor,
|
|
130
|
+
normalizeWorkspaceThemeMode,
|
|
131
|
+
resolveWorkspaceThemeDefaultPalette,
|
|
132
|
+
resolveWorkspaceThemePalettes,
|
|
133
|
+
WORKSPACE_THEME_MODE_DARK,
|
|
134
|
+
WORKSPACE_THEME_MODE_LIGHT,
|
|
147
135
|
resolveWorkspaceThemePalette
|
|
148
136
|
};
|
|
@@ -25,7 +25,7 @@ exports.up = async function up(knex) {
|
|
|
25
25
|
table.integer("owner_user_id").unsigned().notNullable().references("id").inTable("user_profiles").onDelete("CASCADE");
|
|
26
26
|
table.boolean("is_personal").notNullable().defaultTo(true);
|
|
27
27
|
table.string("avatar_url", 512).notNullable().defaultTo("");
|
|
28
|
-
table.string("color", 7).notNullable().defaultTo("#
|
|
28
|
+
table.string("color", 7).notNullable().defaultTo("#1867C0");
|
|
29
29
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
30
30
|
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
31
31
|
table.timestamp("deleted_at", { useTz: false }).nullable();
|
|
@@ -46,10 +46,14 @@ exports.up = async function up(knex) {
|
|
|
46
46
|
table.integer("workspace_id").unsigned().primary().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
47
47
|
table.string("name", 160).notNullable().defaultTo("Workspace");
|
|
48
48
|
table.string("avatar_url", 512).notNullable().defaultTo("");
|
|
49
|
-
table.string("
|
|
50
|
-
table.string("
|
|
51
|
-
table.string("
|
|
52
|
-
table.string("
|
|
49
|
+
table.string("light_primary_color", 7).notNullable().defaultTo("#1867C0");
|
|
50
|
+
table.string("light_secondary_color", 7).notNullable().defaultTo("#48A9A6");
|
|
51
|
+
table.string("light_surface_color", 7).notNullable().defaultTo("#FFFFFF");
|
|
52
|
+
table.string("light_surface_variant_color", 7).notNullable().defaultTo("#424242");
|
|
53
|
+
table.string("dark_primary_color", 7).notNullable().defaultTo("#2196F3");
|
|
54
|
+
table.string("dark_secondary_color", 7).notNullable().defaultTo("#54B6B2");
|
|
55
|
+
table.string("dark_surface_color", 7).notNullable().defaultTo("#212121");
|
|
56
|
+
table.string("dark_surface_variant_color", 7).notNullable().defaultTo("#C8C8C8");
|
|
53
57
|
table.boolean("invites_enabled").notNullable().defaultTo(true);
|
|
54
58
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
55
59
|
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
coerceWorkspaceSurfaceVariantColor
|
|
7
|
+
DEFAULT_WORKSPACE_DARK_PALETTE,
|
|
8
|
+
DEFAULT_WORKSPACE_LIGHT_PALETTE,
|
|
9
|
+
coerceWorkspaceThemeColor
|
|
11
10
|
} from "@jskit-ai/users-core/shared/settings";
|
|
12
11
|
import {
|
|
13
12
|
defineField,
|
|
@@ -34,10 +33,6 @@ function normalizeHexColor(value) {
|
|
|
34
33
|
return /^#[0-9A-Fa-f]{6}$/.test(color) ? color.toUpperCase() : null;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
function resolveThemeBaseColor({ workspace = {}, settings = {} } = {}) {
|
|
38
|
-
return normalizeText(settings.color || workspace.color);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
resetWorkspaceSettingsFields();
|
|
42
37
|
|
|
43
38
|
defineField({
|
|
@@ -78,101 +73,163 @@ defineField({
|
|
|
78
73
|
});
|
|
79
74
|
|
|
80
75
|
defineField({
|
|
81
|
-
key: "
|
|
82
|
-
dbColumn: "
|
|
76
|
+
key: "lightPrimaryColor",
|
|
77
|
+
dbColumn: "light_primary_color",
|
|
78
|
+
required: true,
|
|
79
|
+
inputSchema: Type.String({
|
|
80
|
+
minLength: 7,
|
|
81
|
+
maxLength: 7,
|
|
82
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
83
|
+
messages: {
|
|
84
|
+
required: "Light primary color is required.",
|
|
85
|
+
pattern: "Light primary color must be a hex color like #1867C0.",
|
|
86
|
+
default: "Light primary color must be a hex color like #1867C0."
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
90
|
+
normalizeInput: normalizeHexColor,
|
|
91
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_LIGHT_PALETTE.color),
|
|
92
|
+
resolveDefault: () => DEFAULT_WORKSPACE_LIGHT_PALETTE.color
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
defineField({
|
|
96
|
+
key: "lightSecondaryColor",
|
|
97
|
+
dbColumn: "light_secondary_color",
|
|
98
|
+
required: true,
|
|
99
|
+
inputSchema: Type.String({
|
|
100
|
+
minLength: 7,
|
|
101
|
+
maxLength: 7,
|
|
102
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
103
|
+
messages: {
|
|
104
|
+
required: "Light secondary color is required.",
|
|
105
|
+
pattern: "Light secondary color must be a hex color like #48A9A6.",
|
|
106
|
+
default: "Light secondary color must be a hex color like #48A9A6."
|
|
107
|
+
}
|
|
108
|
+
}),
|
|
109
|
+
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
110
|
+
normalizeInput: normalizeHexColor,
|
|
111
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_LIGHT_PALETTE.secondaryColor),
|
|
112
|
+
resolveDefault: () => DEFAULT_WORKSPACE_LIGHT_PALETTE.secondaryColor
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
defineField({
|
|
116
|
+
key: "lightSurfaceColor",
|
|
117
|
+
dbColumn: "light_surface_color",
|
|
118
|
+
required: true,
|
|
119
|
+
inputSchema: Type.String({
|
|
120
|
+
minLength: 7,
|
|
121
|
+
maxLength: 7,
|
|
122
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
123
|
+
messages: {
|
|
124
|
+
required: "Light surface color is required.",
|
|
125
|
+
pattern: "Light surface color must be a hex color like #FFFFFF.",
|
|
126
|
+
default: "Light surface color must be a hex color like #FFFFFF."
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
130
|
+
normalizeInput: normalizeHexColor,
|
|
131
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceColor),
|
|
132
|
+
resolveDefault: () => DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceColor
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
defineField({
|
|
136
|
+
key: "lightSurfaceVariantColor",
|
|
137
|
+
dbColumn: "light_surface_variant_color",
|
|
138
|
+
required: true,
|
|
139
|
+
inputSchema: Type.String({
|
|
140
|
+
minLength: 7,
|
|
141
|
+
maxLength: 7,
|
|
142
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
143
|
+
messages: {
|
|
144
|
+
required: "Light surface variant color is required.",
|
|
145
|
+
pattern: "Light surface variant color must be a hex color like #424242.",
|
|
146
|
+
default: "Light surface variant color must be a hex color like #424242."
|
|
147
|
+
}
|
|
148
|
+
}),
|
|
149
|
+
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
150
|
+
normalizeInput: normalizeHexColor,
|
|
151
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceVariantColor),
|
|
152
|
+
resolveDefault: () => DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceVariantColor
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
defineField({
|
|
156
|
+
key: "darkPrimaryColor",
|
|
157
|
+
dbColumn: "dark_primary_color",
|
|
83
158
|
required: true,
|
|
84
159
|
inputSchema: Type.String({
|
|
85
160
|
minLength: 7,
|
|
86
161
|
maxLength: 7,
|
|
87
162
|
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
88
163
|
messages: {
|
|
89
|
-
required: "
|
|
90
|
-
pattern: "
|
|
91
|
-
default: "
|
|
164
|
+
required: "Dark primary color is required.",
|
|
165
|
+
pattern: "Dark primary color must be a hex color like #2196F3.",
|
|
166
|
+
default: "Dark primary color must be a hex color like #2196F3."
|
|
92
167
|
}
|
|
93
168
|
}),
|
|
94
169
|
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
95
170
|
normalizeInput: normalizeHexColor,
|
|
96
|
-
normalizeOutput: (value) =>
|
|
97
|
-
resolveDefault: (
|
|
171
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_DARK_PALETTE.color),
|
|
172
|
+
resolveDefault: () => DEFAULT_WORKSPACE_DARK_PALETTE.color
|
|
98
173
|
});
|
|
99
174
|
|
|
100
175
|
defineField({
|
|
101
|
-
key: "
|
|
102
|
-
dbColumn: "
|
|
176
|
+
key: "darkSecondaryColor",
|
|
177
|
+
dbColumn: "dark_secondary_color",
|
|
103
178
|
required: true,
|
|
104
179
|
inputSchema: Type.String({
|
|
105
180
|
minLength: 7,
|
|
106
181
|
maxLength: 7,
|
|
107
182
|
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
108
183
|
messages: {
|
|
109
|
-
required: "
|
|
110
|
-
pattern: "
|
|
111
|
-
default: "
|
|
184
|
+
required: "Dark secondary color is required.",
|
|
185
|
+
pattern: "Dark secondary color must be a hex color like #54B6B2.",
|
|
186
|
+
default: "Dark secondary color must be a hex color like #54B6B2."
|
|
112
187
|
}
|
|
113
188
|
}),
|
|
114
189
|
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
115
190
|
normalizeInput: normalizeHexColor,
|
|
116
|
-
normalizeOutput: (value
|
|
117
|
-
|
|
118
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
119
|
-
}),
|
|
120
|
-
resolveDefault: ({ workspace = {}, settings = {} } = {}) =>
|
|
121
|
-
coerceWorkspaceSecondaryColor(workspace.secondaryColor, {
|
|
122
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
123
|
-
})
|
|
191
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_DARK_PALETTE.secondaryColor),
|
|
192
|
+
resolveDefault: () => DEFAULT_WORKSPACE_DARK_PALETTE.secondaryColor
|
|
124
193
|
});
|
|
125
194
|
|
|
126
195
|
defineField({
|
|
127
|
-
key: "
|
|
128
|
-
dbColumn: "
|
|
196
|
+
key: "darkSurfaceColor",
|
|
197
|
+
dbColumn: "dark_surface_color",
|
|
129
198
|
required: true,
|
|
130
199
|
inputSchema: Type.String({
|
|
131
200
|
minLength: 7,
|
|
132
201
|
maxLength: 7,
|
|
133
202
|
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
134
203
|
messages: {
|
|
135
|
-
required: "
|
|
136
|
-
pattern: "
|
|
137
|
-
default: "
|
|
204
|
+
required: "Dark surface color is required.",
|
|
205
|
+
pattern: "Dark surface color must be a hex color like #212121.",
|
|
206
|
+
default: "Dark surface color must be a hex color like #212121."
|
|
138
207
|
}
|
|
139
208
|
}),
|
|
140
209
|
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
141
210
|
normalizeInput: normalizeHexColor,
|
|
142
|
-
normalizeOutput: (value
|
|
143
|
-
|
|
144
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
145
|
-
}),
|
|
146
|
-
resolveDefault: ({ workspace = {}, settings = {} } = {}) =>
|
|
147
|
-
coerceWorkspaceSurfaceColor(workspace.surfaceColor, {
|
|
148
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
149
|
-
})
|
|
211
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_DARK_PALETTE.surfaceColor),
|
|
212
|
+
resolveDefault: () => DEFAULT_WORKSPACE_DARK_PALETTE.surfaceColor
|
|
150
213
|
});
|
|
151
214
|
|
|
152
215
|
defineField({
|
|
153
|
-
key: "
|
|
154
|
-
dbColumn: "
|
|
216
|
+
key: "darkSurfaceVariantColor",
|
|
217
|
+
dbColumn: "dark_surface_variant_color",
|
|
155
218
|
required: true,
|
|
156
219
|
inputSchema: Type.String({
|
|
157
220
|
minLength: 7,
|
|
158
221
|
maxLength: 7,
|
|
159
222
|
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
160
223
|
messages: {
|
|
161
|
-
required: "
|
|
162
|
-
pattern: "
|
|
163
|
-
default: "
|
|
224
|
+
required: "Dark surface variant color is required.",
|
|
225
|
+
pattern: "Dark surface variant color must be a hex color like #C8C8C8.",
|
|
226
|
+
default: "Dark surface variant color must be a hex color like #C8C8C8."
|
|
164
227
|
}
|
|
165
228
|
}),
|
|
166
229
|
outputSchema: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
167
230
|
normalizeInput: normalizeHexColor,
|
|
168
|
-
normalizeOutput: (value
|
|
169
|
-
|
|
170
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
171
|
-
}),
|
|
172
|
-
resolveDefault: ({ workspace = {}, settings = {} } = {}) =>
|
|
173
|
-
coerceWorkspaceSurfaceVariantColor(workspace.surfaceVariantColor, {
|
|
174
|
-
color: resolveThemeBaseColor({ workspace, settings })
|
|
175
|
-
})
|
|
231
|
+
normalizeOutput: (value) => coerceWorkspaceThemeColor(value, DEFAULT_WORKSPACE_DARK_PALETTE.surfaceVariantColor),
|
|
232
|
+
resolveDefault: () => DEFAULT_WORKSPACE_DARK_PALETTE.surfaceVariantColor
|
|
176
233
|
});
|
|
177
234
|
|
|
178
235
|
defineField({
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveWorkspaceThemePalettes } from "../src/shared/settings.js";
|
|
5
5
|
import { createRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
|
|
6
6
|
|
|
7
7
|
function createDefaultWorkspaceSettings() {
|
|
8
8
|
return true;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const DEFAULT_WORKSPACE_THEME =
|
|
12
|
-
color: "#2F5D9E"
|
|
13
|
-
});
|
|
11
|
+
const DEFAULT_WORKSPACE_THEME = resolveWorkspaceThemePalettes({});
|
|
14
12
|
|
|
15
13
|
function createKnexStub(rowOverrides = {}) {
|
|
16
14
|
const state = {
|
|
@@ -20,10 +18,14 @@ function createKnexStub(rowOverrides = {}) {
|
|
|
20
18
|
workspace_id: 1,
|
|
21
19
|
name: "Workspace",
|
|
22
20
|
avatar_url: "",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
light_primary_color: DEFAULT_WORKSPACE_THEME.light.color,
|
|
22
|
+
light_secondary_color: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
|
|
23
|
+
light_surface_color: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
|
|
24
|
+
light_surface_variant_color: DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor,
|
|
25
|
+
dark_primary_color: DEFAULT_WORKSPACE_THEME.dark.color,
|
|
26
|
+
dark_secondary_color: DEFAULT_WORKSPACE_THEME.dark.secondaryColor,
|
|
27
|
+
dark_surface_color: DEFAULT_WORKSPACE_THEME.dark.surfaceColor,
|
|
28
|
+
dark_surface_variant_color: DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor,
|
|
27
29
|
invites_enabled: 1,
|
|
28
30
|
created_at: "2026-03-09 00:26:35.710",
|
|
29
31
|
updated_at: "2026-03-09 00:26:35.710",
|
|
@@ -41,10 +43,14 @@ function createKnexStub(rowOverrides = {}) {
|
|
|
41
43
|
workspace_id: payload.workspace_id,
|
|
42
44
|
name: payload.name,
|
|
43
45
|
avatar_url: payload.avatar_url,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
light_primary_color: payload.light_primary_color,
|
|
47
|
+
light_secondary_color: payload.light_secondary_color,
|
|
48
|
+
light_surface_color: payload.light_surface_color,
|
|
49
|
+
light_surface_variant_color: payload.light_surface_variant_color,
|
|
50
|
+
dark_primary_color: payload.dark_primary_color,
|
|
51
|
+
dark_secondary_color: payload.dark_secondary_color,
|
|
52
|
+
dark_surface_color: payload.dark_surface_color,
|
|
53
|
+
dark_surface_variant_color: payload.dark_surface_variant_color,
|
|
48
54
|
invites_enabled: payload.invites_enabled,
|
|
49
55
|
created_at: "2026-03-10 00:00:00.000",
|
|
50
56
|
updated_at: "2026-03-10 00:00:00.000"
|
|
@@ -69,17 +75,29 @@ function createKnexStub(rowOverrides = {}) {
|
|
|
69
75
|
if (Object.hasOwn(payload, "avatar_url")) {
|
|
70
76
|
state.row.avatar_url = payload.avatar_url;
|
|
71
77
|
}
|
|
72
|
-
if (Object.hasOwn(payload, "
|
|
73
|
-
state.row.
|
|
78
|
+
if (Object.hasOwn(payload, "light_primary_color")) {
|
|
79
|
+
state.row.light_primary_color = payload.light_primary_color;
|
|
80
|
+
}
|
|
81
|
+
if (Object.hasOwn(payload, "light_secondary_color")) {
|
|
82
|
+
state.row.light_secondary_color = payload.light_secondary_color;
|
|
83
|
+
}
|
|
84
|
+
if (Object.hasOwn(payload, "light_surface_color")) {
|
|
85
|
+
state.row.light_surface_color = payload.light_surface_color;
|
|
86
|
+
}
|
|
87
|
+
if (Object.hasOwn(payload, "light_surface_variant_color")) {
|
|
88
|
+
state.row.light_surface_variant_color = payload.light_surface_variant_color;
|
|
89
|
+
}
|
|
90
|
+
if (Object.hasOwn(payload, "dark_primary_color")) {
|
|
91
|
+
state.row.dark_primary_color = payload.dark_primary_color;
|
|
74
92
|
}
|
|
75
|
-
if (Object.hasOwn(payload, "
|
|
76
|
-
state.row.
|
|
93
|
+
if (Object.hasOwn(payload, "dark_secondary_color")) {
|
|
94
|
+
state.row.dark_secondary_color = payload.dark_secondary_color;
|
|
77
95
|
}
|
|
78
|
-
if (Object.hasOwn(payload, "
|
|
79
|
-
state.row.
|
|
96
|
+
if (Object.hasOwn(payload, "dark_surface_color")) {
|
|
97
|
+
state.row.dark_surface_color = payload.dark_surface_color;
|
|
80
98
|
}
|
|
81
|
-
if (Object.hasOwn(payload, "
|
|
82
|
-
state.row.
|
|
99
|
+
if (Object.hasOwn(payload, "dark_surface_variant_color")) {
|
|
100
|
+
state.row.dark_surface_variant_color = payload.dark_surface_variant_color;
|
|
83
101
|
}
|
|
84
102
|
if (Object.hasOwn(payload, "updated_at")) {
|
|
85
103
|
state.row.updated_at = payload.updated_at;
|
|
@@ -106,10 +124,14 @@ test("workspaceSettingsRepository.findByWorkspaceId maps the stored row", async
|
|
|
106
124
|
workspaceId: 1,
|
|
107
125
|
name: "Workspace",
|
|
108
126
|
avatarUrl: "",
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
lightPrimaryColor: DEFAULT_WORKSPACE_THEME.light.color,
|
|
128
|
+
lightSecondaryColor: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
|
|
129
|
+
lightSurfaceColor: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
|
|
130
|
+
lightSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor,
|
|
131
|
+
darkPrimaryColor: DEFAULT_WORKSPACE_THEME.dark.color,
|
|
132
|
+
darkSecondaryColor: DEFAULT_WORKSPACE_THEME.dark.secondaryColor,
|
|
133
|
+
darkSurfaceColor: DEFAULT_WORKSPACE_THEME.dark.surfaceColor,
|
|
134
|
+
darkSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor,
|
|
113
135
|
invitesEnabled: true,
|
|
114
136
|
createdAt: "2026-03-08T16:26:35.710Z",
|
|
115
137
|
updatedAt: "2026-03-08T16:26:35.710Z"
|
|
@@ -142,17 +164,31 @@ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defa
|
|
|
142
164
|
assert.equal(state.insertedRow.workspace_id, 5);
|
|
143
165
|
assert.equal(state.insertedRow.name, "Workspace");
|
|
144
166
|
assert.equal(state.insertedRow.avatar_url, "");
|
|
145
|
-
assert.equal(state.insertedRow.
|
|
146
|
-
assert.equal(state.insertedRow.
|
|
147
|
-
assert.equal(state.insertedRow.
|
|
148
|
-
assert.equal(
|
|
167
|
+
assert.equal(state.insertedRow.light_primary_color, DEFAULT_WORKSPACE_THEME.light.color);
|
|
168
|
+
assert.equal(state.insertedRow.light_secondary_color, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
|
|
169
|
+
assert.equal(state.insertedRow.light_surface_color, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
|
|
170
|
+
assert.equal(
|
|
171
|
+
state.insertedRow.light_surface_variant_color,
|
|
172
|
+
DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor
|
|
173
|
+
);
|
|
174
|
+
assert.equal(state.insertedRow.dark_primary_color, DEFAULT_WORKSPACE_THEME.dark.color);
|
|
175
|
+
assert.equal(state.insertedRow.dark_secondary_color, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
|
|
176
|
+
assert.equal(state.insertedRow.dark_surface_color, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
|
|
177
|
+
assert.equal(
|
|
178
|
+
state.insertedRow.dark_surface_variant_color,
|
|
179
|
+
DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor
|
|
180
|
+
);
|
|
149
181
|
assert.equal(state.insertedRow.invites_enabled, false);
|
|
150
182
|
assert.equal(record.name, "Workspace");
|
|
151
183
|
assert.equal(record.avatarUrl, "");
|
|
152
|
-
assert.equal(record.
|
|
153
|
-
assert.equal(record.
|
|
154
|
-
assert.equal(record.
|
|
155
|
-
assert.equal(record.
|
|
184
|
+
assert.equal(record.lightPrimaryColor, DEFAULT_WORKSPACE_THEME.light.color);
|
|
185
|
+
assert.equal(record.lightSecondaryColor, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
|
|
186
|
+
assert.equal(record.lightSurfaceColor, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
|
|
187
|
+
assert.equal(record.lightSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor);
|
|
188
|
+
assert.equal(record.darkPrimaryColor, DEFAULT_WORKSPACE_THEME.dark.color);
|
|
189
|
+
assert.equal(record.darkSecondaryColor, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
|
|
190
|
+
assert.equal(record.darkSurfaceColor, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
|
|
191
|
+
assert.equal(record.darkSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor);
|
|
156
192
|
assert.equal(record.invitesEnabled, false);
|
|
157
193
|
});
|
|
158
194
|
|
|
@@ -165,15 +201,15 @@ test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates name/avata
|
|
|
165
201
|
const updated = await repository.updateSettingsByWorkspaceId(1, {
|
|
166
202
|
name: "New name",
|
|
167
203
|
avatarUrl: "https://example.com/avatar.png",
|
|
168
|
-
|
|
204
|
+
lightPrimaryColor: "#123abc"
|
|
169
205
|
});
|
|
170
206
|
|
|
171
207
|
assert.equal(state.updatePayload.name, "New name");
|
|
172
208
|
assert.equal(state.updatePayload.avatar_url, "https://example.com/avatar.png");
|
|
173
|
-
assert.equal(state.updatePayload.
|
|
209
|
+
assert.equal(state.updatePayload.light_primary_color, "#123ABC");
|
|
174
210
|
assert.equal(updated.name, "New name");
|
|
175
211
|
assert.equal(updated.avatarUrl, "https://example.com/avatar.png");
|
|
176
|
-
assert.equal(updated.
|
|
212
|
+
assert.equal(updated.lightPrimaryColor, "#123ABC");
|
|
177
213
|
});
|
|
178
214
|
|
|
179
215
|
test("workspaceSettingsRepository can be constructed without validating app config shape", () => {
|
|
@@ -2,7 +2,7 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
4
4
|
import "../test-support/registerDefaultSettingsFields.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveWorkspaceThemePalettes } from "../src/shared/settings.js";
|
|
6
6
|
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
7
7
|
import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
|
|
8
8
|
|
|
@@ -48,10 +48,14 @@ test("workspace settings patch body normalizes valid payload before validation",
|
|
|
48
48
|
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
49
49
|
name: " Team Mercury ",
|
|
50
50
|
avatarUrl: "https://example.com/avatar.png",
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
lightPrimaryColor: "#0f6b54",
|
|
52
|
+
lightSecondaryColor: "#0b4d3c",
|
|
53
|
+
lightSurfaceColor: "#eef5f3",
|
|
54
|
+
lightSurfaceVariantColor: "#ddeae7",
|
|
55
|
+
darkPrimaryColor: "#123456",
|
|
56
|
+
darkSecondaryColor: "#234567",
|
|
57
|
+
darkSurfaceColor: "#345678",
|
|
58
|
+
darkSurfaceVariantColor: "#456789",
|
|
55
59
|
invitesEnabled: false
|
|
56
60
|
});
|
|
57
61
|
|
|
@@ -60,10 +64,14 @@ test("workspace settings patch body normalizes valid payload before validation",
|
|
|
60
64
|
assert.deepEqual(parsed.value, {
|
|
61
65
|
name: "Team Mercury",
|
|
62
66
|
avatarUrl: "https://example.com/avatar.png",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
lightPrimaryColor: "#0F6B54",
|
|
68
|
+
lightSecondaryColor: "#0B4D3C",
|
|
69
|
+
lightSurfaceColor: "#EEF5F3",
|
|
70
|
+
lightSurfaceVariantColor: "#DDEAE7",
|
|
71
|
+
darkPrimaryColor: "#123456",
|
|
72
|
+
darkSecondaryColor: "#234567",
|
|
73
|
+
darkSurfaceColor: "#345678",
|
|
74
|
+
darkSurfaceVariantColor: "#456789",
|
|
67
75
|
invitesEnabled: false
|
|
68
76
|
});
|
|
69
77
|
});
|
|
@@ -95,16 +103,20 @@ test("workspace settings create body requires full-write fields", () => {
|
|
|
95
103
|
});
|
|
96
104
|
|
|
97
105
|
assert.equal(parsed.ok, false);
|
|
98
|
-
assert.equal(parsed.fieldErrors.
|
|
99
|
-
assert.equal(parsed.fieldErrors.
|
|
100
|
-
assert.equal(parsed.fieldErrors.
|
|
101
|
-
assert.equal(parsed.fieldErrors.
|
|
106
|
+
assert.equal(parsed.fieldErrors.lightPrimaryColor, "Light primary color is required.");
|
|
107
|
+
assert.equal(parsed.fieldErrors.lightSecondaryColor, "Light secondary color is required.");
|
|
108
|
+
assert.equal(parsed.fieldErrors.lightSurfaceColor, "Light surface color is required.");
|
|
109
|
+
assert.equal(parsed.fieldErrors.lightSurfaceVariantColor, "Light surface variant color is required.");
|
|
110
|
+
assert.equal(parsed.fieldErrors.darkPrimaryColor, "Dark primary color is required.");
|
|
111
|
+
assert.equal(parsed.fieldErrors.darkSecondaryColor, "Dark secondary color is required.");
|
|
112
|
+
assert.equal(parsed.fieldErrors.darkSurfaceColor, "Dark surface color is required.");
|
|
113
|
+
assert.equal(parsed.fieldErrors.darkSurfaceVariantColor, "Dark surface variant color is required.");
|
|
102
114
|
assert.equal(parsed.fieldErrors.invitesEnabled, "invitesEnabled is required.");
|
|
103
115
|
});
|
|
104
116
|
|
|
105
117
|
test("workspace settings output normalizes raw service payloads", () => {
|
|
106
|
-
const expectedTheme =
|
|
107
|
-
|
|
118
|
+
const expectedTheme = resolveWorkspaceThemePalettes({
|
|
119
|
+
lightPrimaryColor: "#0F6B54"
|
|
108
120
|
});
|
|
109
121
|
const normalized = workspaceSettingsResource.operations.view.outputValidator.normalize({
|
|
110
122
|
workspace: {
|
|
@@ -115,7 +127,7 @@ test("workspace settings output normalizes raw service payloads", () => {
|
|
|
115
127
|
settings: {
|
|
116
128
|
name: " Mercury Workspace ",
|
|
117
129
|
avatarUrl: " https://example.com/avatar.png ",
|
|
118
|
-
|
|
130
|
+
lightPrimaryColor: "#0f6b54",
|
|
119
131
|
invitesEnabled: false
|
|
120
132
|
},
|
|
121
133
|
roleCatalog: createRoleCatalog()
|
|
@@ -130,10 +142,14 @@ test("workspace settings output normalizes raw service payloads", () => {
|
|
|
130
142
|
settings: {
|
|
131
143
|
name: "Mercury Workspace",
|
|
132
144
|
avatarUrl: "https://example.com/avatar.png",
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
lightPrimaryColor: "#0F6B54",
|
|
146
|
+
lightSecondaryColor: expectedTheme.light.secondaryColor,
|
|
147
|
+
lightSurfaceColor: expectedTheme.light.surfaceColor,
|
|
148
|
+
lightSurfaceVariantColor: expectedTheme.light.surfaceVariantColor,
|
|
149
|
+
darkPrimaryColor: expectedTheme.dark.color,
|
|
150
|
+
darkSecondaryColor: expectedTheme.dark.secondaryColor,
|
|
151
|
+
darkSurfaceColor: expectedTheme.dark.surfaceColor,
|
|
152
|
+
darkSurfaceVariantColor: expectedTheme.dark.surfaceVariantColor,
|
|
137
153
|
invitesEnabled: false,
|
|
138
154
|
invitesAvailable: true,
|
|
139
155
|
invitesEffective: false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveWorkspaceThemePalettes } from "../src/shared/settings.js";
|
|
5
5
|
import { createService } from "../src/server/workspaceSettings/workspaceSettingsService.js";
|
|
6
6
|
|
|
7
7
|
function authorizedOptions(permissions = []) {
|
|
@@ -16,8 +16,8 @@ function authorizedOptions(permissions = []) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function createFixture({ workspaceInvitationsEnabled = true } = {}) {
|
|
19
|
-
const defaultTheme =
|
|
20
|
-
|
|
19
|
+
const defaultTheme = resolveWorkspaceThemePalettes({
|
|
20
|
+
lightPrimaryColor: "#0F6B54"
|
|
21
21
|
});
|
|
22
22
|
const state = {
|
|
23
23
|
settingsPatch: null,
|
|
@@ -27,15 +27,19 @@ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
|
|
|
27
27
|
name: "TonyMobily3",
|
|
28
28
|
ownerUserId: 9,
|
|
29
29
|
avatarUrl: "",
|
|
30
|
-
color: defaultTheme.color
|
|
30
|
+
color: defaultTheme.light.color
|
|
31
31
|
},
|
|
32
32
|
settings: {
|
|
33
33
|
name: "TonyMobily3",
|
|
34
34
|
avatarUrl: "",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
lightPrimaryColor: defaultTheme.light.color,
|
|
36
|
+
lightSecondaryColor: defaultTheme.light.secondaryColor,
|
|
37
|
+
lightSurfaceColor: defaultTheme.light.surfaceColor,
|
|
38
|
+
lightSurfaceVariantColor: defaultTheme.light.surfaceVariantColor,
|
|
39
|
+
darkPrimaryColor: defaultTheme.dark.color,
|
|
40
|
+
darkSecondaryColor: defaultTheme.dark.secondaryColor,
|
|
41
|
+
darkSurfaceColor: defaultTheme.dark.surfaceColor,
|
|
42
|
+
darkSurfaceVariantColor: defaultTheme.dark.surfaceVariantColor,
|
|
39
43
|
invitesEnabled: true
|
|
40
44
|
}
|
|
41
45
|
};
|
|
@@ -73,10 +77,14 @@ test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEn
|
|
|
73
77
|
assert.deepEqual(response.settings, {
|
|
74
78
|
name: "TonyMobily3",
|
|
75
79
|
avatarUrl: "",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
lightPrimaryColor: "#0F6B54",
|
|
81
|
+
lightSecondaryColor: "#48A9A6",
|
|
82
|
+
lightSurfaceColor: "#FFFFFF",
|
|
83
|
+
lightSurfaceVariantColor: "#424242",
|
|
84
|
+
darkPrimaryColor: "#2196F3",
|
|
85
|
+
darkSecondaryColor: "#54B6B2",
|
|
86
|
+
darkSurfaceColor: "#212121",
|
|
87
|
+
darkSurfaceVariantColor: "#C8C8C8",
|
|
80
88
|
invitesEnabled: true,
|
|
81
89
|
invitesAvailable: true,
|
|
82
90
|
invitesEffective: true
|
|
@@ -102,10 +110,14 @@ test("workspaceSettingsService.updateWorkspaceSettings writes editable fields th
|
|
|
102
110
|
assert.deepEqual(response.settings, {
|
|
103
111
|
name: "New Name",
|
|
104
112
|
avatarUrl: "",
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
lightPrimaryColor: "#0F6B54",
|
|
114
|
+
lightSecondaryColor: "#48A9A6",
|
|
115
|
+
lightSurfaceColor: "#FFFFFF",
|
|
116
|
+
lightSurfaceVariantColor: "#424242",
|
|
117
|
+
darkPrimaryColor: "#2196F3",
|
|
118
|
+
darkSecondaryColor: "#54B6B2",
|
|
119
|
+
darkSurfaceColor: "#212121",
|
|
120
|
+
darkSurfaceVariantColor: "#C8C8C8",
|
|
109
121
|
invitesEnabled: false,
|
|
110
122
|
invitesAvailable: true,
|
|
111
123
|
invitesEffective: false
|
|
@@ -125,10 +137,14 @@ test("workspaceSettingsService disables invite settings in output when app polic
|
|
|
125
137
|
assert.deepEqual(response.settings, {
|
|
126
138
|
name: "TonyMobily3",
|
|
127
139
|
avatarUrl: "",
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
lightPrimaryColor: "#0F6B54",
|
|
141
|
+
lightSecondaryColor: "#48A9A6",
|
|
142
|
+
lightSurfaceColor: "#FFFFFF",
|
|
143
|
+
lightSurfaceVariantColor: "#424242",
|
|
144
|
+
darkPrimaryColor: "#2196F3",
|
|
145
|
+
darkSecondaryColor: "#54B6B2",
|
|
146
|
+
darkSurfaceColor: "#212121",
|
|
147
|
+
darkSurfaceVariantColor: "#C8C8C8",
|
|
132
148
|
invitesEnabled: false,
|
|
133
149
|
invitesAvailable: false,
|
|
134
150
|
invitesEffective: false
|