@byline/admin 6.0.0 → 6.0.2
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/dist/modules/admin-preferences/index.d.ts +7 -3
- package/dist/modules/admin-preferences/index.js +1 -1
- package/dist/modules/admin-preferences/schemas.d.ts +19 -16
- package/dist/modules/admin-preferences/schemas.js +46 -2
- package/dist/modules/analytics/components/timeseries.d.ts +28 -0
- package/dist/modules/analytics/components/timeseries.js +107 -35
- package/dist/modules/analytics/components/timeseries.module.js +15 -0
- package/dist/modules/analytics/components/timeseries_module.css +110 -3
- package/package.json +7 -7
- package/src/modules/admin-preferences/index.ts +7 -2
- package/src/modules/admin-preferences/schemas.test.node.ts +74 -0
- package/src/modules/admin-preferences/schemas.ts +85 -8
- package/src/modules/analytics/components/dashboard.test.node.ts +51 -0
- package/src/modules/analytics/components/timeseries.module.css +176 -3
- package/src/modules/analytics/components/timeseries.tsx +153 -40
- package/dist/exports-parity.test.node.js +0 -37
- package/dist/fields/array/array-field.jsx +0 -209
- package/dist/fields/blocks/blocks-field.jsx +0 -248
- package/dist/fields/checkbox/checkbox-field.jsx +0 -24
- package/dist/fields/code/code-editor.jsx +0 -211
- package/dist/fields/code/code-field.jsx +0 -109
- package/dist/fields/column-formatter.jsx +0 -25
- package/dist/fields/date-time-formatter.jsx +0 -18
- package/dist/fields/datetime/datetime-field.jsx +0 -22
- package/dist/fields/draggable-context-menu.jsx +0 -50
- package/dist/fields/field-admin.test.node.js +0 -40
- package/dist/fields/field-renderer.jsx +0 -136
- package/dist/fields/field-services-context.jsx +0 -17
- package/dist/fields/file/file-field.jsx +0 -176
- package/dist/fields/file/file-upload-field.jsx +0 -89
- package/dist/fields/group/group-field.jsx +0 -46
- package/dist/fields/image/image-field.jsx +0 -183
- package/dist/fields/image/image-upload-field.jsx +0 -157
- package/dist/fields/local-date-time.jsx +0 -69
- package/dist/fields/locale-badge.jsx +0 -19
- package/dist/fields/numerical/numerical-field.jsx +0 -93
- package/dist/fields/relation/relation-column-formatter.jsx +0 -74
- package/dist/fields/relation/relation-display.jsx +0 -118
- package/dist/fields/relation/relation-field.jsx +0 -108
- package/dist/fields/relation/relation-many-field.jsx +0 -142
- package/dist/fields/relation/relation-picker.jsx +0 -240
- package/dist/fields/relation/relation-summary.jsx +0 -45
- package/dist/fields/select/select-field.jsx +0 -27
- package/dist/fields/sortable-item.jsx +0 -54
- package/dist/fields/text/text-field.jsx +0 -79
- package/dist/fields/text-area/text-area-field.jsx +0 -79
- package/dist/forms/available-locales-reconcile.test.node.js +0 -43
- package/dist/forms/available-locales-widget.jsx +0 -63
- package/dist/forms/document-actions.jsx +0 -486
- package/dist/forms/form-context.jsx +0 -598
- package/dist/forms/form-modals.jsx +0 -128
- package/dist/forms/form-renderer.jsx +0 -532
- package/dist/forms/form-status-display.jsx +0 -69
- package/dist/forms/navigation-guard.jsx +0 -68
- package/dist/forms/nested-path.test.node.js +0 -115
- package/dist/forms/path-widget.jsx +0 -125
- package/dist/forms/pending-uploads.test.node.js +0 -19
- package/dist/forms/repeating-items.test.node.js +0 -29
- package/dist/forms/scheduled-publication-control.jsx +0 -389
- package/dist/forms/scheduled-publication-state.test.node.js +0 -121
- package/dist/forms/scheduled-publication-time.test.node.js +0 -70
- package/dist/forms/status-transitions.test.node.js +0 -76
- package/dist/forms/tree-placement-widget.jsx +0 -160
- package/dist/forms/upload-executor.test.node.js +0 -368
- package/dist/forms/use-form-layout.test.node.js +0 -69
- package/dist/modules/admin-account/components/change-password.jsx +0 -169
- package/dist/modules/admin-account/components/container.jsx +0 -164
- package/dist/modules/admin-account/components/preferences.jsx +0 -148
- package/dist/modules/admin-account/components/theme-switch.jsx +0 -52
- package/dist/modules/admin-account/components/update.jsx +0 -186
- package/dist/modules/admin-permissions/components/inspector.jsx +0 -200
- package/dist/modules/admin-preferences/schemas.test.node.js +0 -48
- package/dist/modules/admin-roles/components/create.jsx +0 -155
- package/dist/modules/admin-roles/components/permissions.jsx +0 -239
- package/dist/modules/admin-roles/components/update.jsx +0 -148
- package/dist/modules/admin-users/components/create.jsx +0 -206
- package/dist/modules/admin-users/components/roles.jsx +0 -126
- package/dist/modules/admin-users/components/set-password.jsx +0 -133
- package/dist/modules/admin-users/components/update.jsx +0 -215
- package/dist/modules/analytics/components/dashboard.jsx +0 -208
- package/dist/modules/analytics/components/dashboard.test.node.js +0 -162
- package/dist/modules/analytics/components/timeseries.jsx +0 -193
- package/dist/modules/auth/components/sign-in-form.jsx +0 -89
- package/dist/modules/auth/safe-redirect.test.node.js +0 -40
- package/dist/modules/auth/sign-in-form-props.test.node.js +0 -7
- package/dist/presentation/group.jsx +0 -26
- package/dist/presentation/row.jsx +0 -23
- package/dist/presentation/tabs.jsx +0 -39
- package/dist/services/admin-services-context.jsx +0 -17
- package/dist/widgets/diff-viewer/diff-modal.jsx +0 -110
- package/dist/widgets/source-locale-badge/source-locale-badge.jsx +0 -28
- package/dist/widgets/status-badge/status-badge.jsx +0 -46
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Self-service like `@byline/admin/admin-account`: the actor IS the
|
|
13
13
|
* target, and there is no ability gate — authn-only. The `scope` string
|
|
14
|
-
* (e.g. `collections.docs.list`) is the
|
|
15
|
-
*
|
|
14
|
+
* (e.g. `collections.docs.list`, `analytics.dashboard`) is the
|
|
15
|
+
* generality lever: a new admin surface claims its own scope by
|
|
16
|
+
* registering a value schema for it in `schemas.ts`, which keeps each
|
|
17
|
+
* family's payload strictly validated and unregistered scopes
|
|
18
|
+
* unwritable. Storage itself is untouched — scopes share one JSON
|
|
19
|
+
* key-value table.
|
|
16
20
|
*/
|
|
17
21
|
export { getPreferenceCommand, setPreferenceCommand } from './commands.js';
|
|
18
|
-
export { getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, setPreferenceRequestSchema, } from './schemas.js';
|
|
22
|
+
export { analyticsViewPreferenceValueSchema, getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, setPreferenceRequestSchema, } from './schemas.js';
|
|
19
23
|
export { AdminPreferencesService } from './service.js';
|
|
20
24
|
export type { AdminPreferencesCommandDeps } from './commands.js';
|
|
21
25
|
export type { AdminPreferencesRepository, AdminUserPreferenceRow, } from './repository.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { getPreferenceCommand, setPreferenceCommand } from "./commands.js";
|
|
2
|
-
export { getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, setPreferenceRequestSchema } from "./schemas.js";
|
|
2
|
+
export { analyticsViewPreferenceValueSchema, getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, setPreferenceRequestSchema } from "./schemas.js";
|
|
3
3
|
export { AdminPreferencesService } from "./service.js";
|
|
@@ -5,17 +5,6 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* Zod schemas for the admin-preferences commands.
|
|
10
|
-
*
|
|
11
|
-
* Self-service, like admin-account: none of the request schemas accept a
|
|
12
|
-
* user id — the command resolves the target from `actor.id`.
|
|
13
|
-
*
|
|
14
|
-
* The `value` payload is validated against the list-view shape because
|
|
15
|
-
* that is the only registered scope family today
|
|
16
|
-
* (`collections.<path>.list`). When a second scope family arrives, this
|
|
17
|
-
* becomes a scope-keyed selection of value schemas.
|
|
18
|
-
*/
|
|
19
8
|
import { z } from 'zod';
|
|
20
9
|
/** Dot-separated segment key, e.g. `collections.docs.list`. */
|
|
21
10
|
export declare const preferenceScopeSchema: z.ZodString;
|
|
@@ -29,17 +18,31 @@ export declare const listViewPreferenceValueSchema: z.ZodObject<{
|
|
|
29
18
|
order: z.ZodOptional<z.ZodString>;
|
|
30
19
|
desc: z.ZodOptional<z.ZodBoolean>;
|
|
31
20
|
}, z.core.$strict>;
|
|
21
|
+
/**
|
|
22
|
+
* Sticky analytics-dashboard keys. The period list is sourced from
|
|
23
|
+
* `@byline/analytics/config` rather than restated, so the stored value
|
|
24
|
+
* can only ever be one the dashboard actually offers — adding a sixth
|
|
25
|
+
* period there admits it here with no edit.
|
|
26
|
+
*
|
|
27
|
+
* Note the stored `period` is the *parsed* value (`30`, `'ytd'`), not
|
|
28
|
+
* the `?period=30` URL string the route validates on the way in.
|
|
29
|
+
*/
|
|
30
|
+
export declare const analyticsViewPreferenceValueSchema: z.ZodObject<{
|
|
31
|
+
period: z.ZodUnion<readonly [z.ZodLiteral<7>, z.ZodLiteral<30>, z.ZodLiteral<90>, z.ZodLiteral<"ytd">, z.ZodLiteral<"all">]>;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
/**
|
|
34
|
+
* The value schema a scope selects, or `undefined` when the scope
|
|
35
|
+
* belongs to no registered family. Exported so callers (and tests) can
|
|
36
|
+
* ask the same question the write path asks.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolvePreferenceValueSchema(scope: string): z.ZodType | undefined;
|
|
32
39
|
export declare const getPreferenceRequestSchema: z.ZodObject<{
|
|
33
40
|
scope: z.ZodString;
|
|
34
41
|
}, z.core.$strip>;
|
|
35
42
|
export type GetPreferenceRequest = z.infer<typeof getPreferenceRequestSchema>;
|
|
36
43
|
export declare const setPreferenceRequestSchema: z.ZodObject<{
|
|
37
44
|
scope: z.ZodString;
|
|
38
|
-
value: z.
|
|
39
|
-
page_size: z.ZodOptional<z.ZodNumber>;
|
|
40
|
-
order: z.ZodOptional<z.ZodString>;
|
|
41
|
-
desc: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
-
}, z.core.$strict>;
|
|
45
|
+
value: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
43
46
|
}, z.core.$strip>;
|
|
44
47
|
export type SetPreferenceRequest = z.infer<typeof setPreferenceRequestSchema>;
|
|
45
48
|
/** `value` is `null` when the user has no stored preference for the scope. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ANALYTICS_DASHBOARD_PERIODS } from "@byline/analytics/config";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
const preferenceScopeSchema = z.string().min(1).max(255).regex(/^[a-z0-9_-]+(\.[a-z0-9_-]+)*$/i, {
|
|
3
4
|
message: 'scope must be dot-separated segments of [a-z0-9_-]'
|
|
@@ -9,15 +10,58 @@ const listViewPreferenceValueSchema = z.object({
|
|
|
9
10
|
}).strict().refine((v)=>Object.keys(v).length > 0, {
|
|
10
11
|
message: 'value cannot be empty'
|
|
11
12
|
});
|
|
13
|
+
const analyticsViewPreferenceValueSchema = z.object({
|
|
14
|
+
period: z.union([
|
|
15
|
+
z.literal(ANALYTICS_DASHBOARD_PERIODS["0"]),
|
|
16
|
+
z.literal(ANALYTICS_DASHBOARD_PERIODS["1"]),
|
|
17
|
+
z.literal(ANALYTICS_DASHBOARD_PERIODS["2"]),
|
|
18
|
+
z.literal(ANALYTICS_DASHBOARD_PERIODS["3"]),
|
|
19
|
+
z.literal(ANALYTICS_DASHBOARD_PERIODS["4"])
|
|
20
|
+
])
|
|
21
|
+
}).strict();
|
|
22
|
+
const PREFERENCE_VALUE_SCHEMAS = [
|
|
23
|
+
{
|
|
24
|
+
matches: (scope)=>{
|
|
25
|
+
const segments = scope.split('.');
|
|
26
|
+
return 3 === segments.length && 'collections' === segments[0] && 'list' === segments[2];
|
|
27
|
+
},
|
|
28
|
+
schema: listViewPreferenceValueSchema
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
matches: (scope)=>'analytics.dashboard' === scope,
|
|
32
|
+
schema: analyticsViewPreferenceValueSchema
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
function resolvePreferenceValueSchema(scope) {
|
|
36
|
+
return PREFERENCE_VALUE_SCHEMAS.find((family)=>family.matches(scope))?.schema;
|
|
37
|
+
}
|
|
12
38
|
const getPreferenceRequestSchema = z.object({
|
|
13
39
|
scope: preferenceScopeSchema
|
|
14
40
|
});
|
|
15
41
|
const setPreferenceRequestSchema = z.object({
|
|
16
42
|
scope: preferenceScopeSchema,
|
|
17
|
-
value:
|
|
43
|
+
value: z.record(z.string(), z.unknown())
|
|
44
|
+
}).superRefine((input, ctx)=>{
|
|
45
|
+
const schema = resolvePreferenceValueSchema(input.scope);
|
|
46
|
+
if (null == schema) return void ctx.addIssue({
|
|
47
|
+
code: 'custom',
|
|
48
|
+
path: [
|
|
49
|
+
'scope'
|
|
50
|
+
],
|
|
51
|
+
message: 'scope does not belong to a registered preference family'
|
|
52
|
+
});
|
|
53
|
+
const result = schema.safeParse(input.value);
|
|
54
|
+
if (result.success) return;
|
|
55
|
+
for (const issue of result.error.issues)ctx.addIssue({
|
|
56
|
+
...issue,
|
|
57
|
+
path: [
|
|
58
|
+
'value',
|
|
59
|
+
...issue.path
|
|
60
|
+
]
|
|
61
|
+
});
|
|
18
62
|
});
|
|
19
63
|
const preferenceResponseSchema = z.object({
|
|
20
64
|
scope: z.string(),
|
|
21
65
|
value: z.record(z.string(), z.unknown()).nullable()
|
|
22
66
|
});
|
|
23
|
-
export { getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, setPreferenceRequestSchema };
|
|
67
|
+
export { analyticsViewPreferenceValueSchema, getPreferenceRequestSchema, listViewPreferenceValueSchema, preferenceResponseSchema, preferenceScopeSchema, resolvePreferenceValueSchema, setPreferenceRequestSchema };
|
|
@@ -51,6 +51,34 @@ export declare function resolveAnalyticsChartGranularity(period: AnalyticsDashbo
|
|
|
51
51
|
export declare function bucketAnalyticsTimeseries(days: readonly AnalyticsSummaryDay[], granularity: AnalyticsChartGranularity): readonly AnalyticsChartBucket[];
|
|
52
52
|
/** Project reporting buckets into column geometry. */
|
|
53
53
|
export declare function buildAnalyticsColumns(buckets: readonly Pick<AnalyticsChartBucket, 'views' | 'visitors'>[]): readonly AnalyticsColumn[];
|
|
54
|
+
/**
|
|
55
|
+
* Where the hover card sits relative to the plot box.
|
|
56
|
+
*
|
|
57
|
+
* `leftPercent` / `topPercent` are percentages of the plot rather than user
|
|
58
|
+
* -space units because the card is HTML laid over a stretched SVG: the plot
|
|
59
|
+
* is drawn `preserveAspectRatio="none"`, so anything measured in viewBox
|
|
60
|
+
* units would skew with the container width (the same reason the axis labels
|
|
61
|
+
* are HTML). Percentages survive the stretch.
|
|
62
|
+
*/
|
|
63
|
+
export interface AnalyticsHoverCardPlacement {
|
|
64
|
+
/** Horizontal centre of the hovered column, as a percentage of plot width. */
|
|
65
|
+
leftPercent: number;
|
|
66
|
+
/** Top edge of the hovered column, as a percentage of plot height. */
|
|
67
|
+
topPercent: number;
|
|
68
|
+
/**
|
|
69
|
+
* How the card lines up with `leftPercent`. Columns near a plot edge anchor
|
|
70
|
+
* that edge instead of centring, so the card never overhangs the chart.
|
|
71
|
+
*/
|
|
72
|
+
align: 'start' | 'center' | 'end';
|
|
73
|
+
/**
|
|
74
|
+
* Which side of the column top the card occupies. A column tall enough that
|
|
75
|
+
* a card above it would clip the top of the plot takes the card below
|
|
76
|
+
* instead.
|
|
77
|
+
*/
|
|
78
|
+
side: 'above' | 'below';
|
|
79
|
+
}
|
|
80
|
+
/** Project one column's geometry into hover-card placement. */
|
|
81
|
+
export declare function resolveHoverCardPlacement(column: AnalyticsColumn): AnalyticsHoverCardPlacement;
|
|
54
82
|
export interface AnalyticsTimeseriesProps {
|
|
55
83
|
days: readonly AnalyticsSummaryDay[];
|
|
56
84
|
granularity: AnalyticsChartGranularity;
|
|
@@ -51,6 +51,18 @@ function buildAnalyticsColumns(buckets) {
|
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
const CARD_EDGE_MARGIN = 15;
|
|
55
|
+
const CARD_FLIP_THRESHOLD = 45;
|
|
56
|
+
function resolveHoverCardPlacement(column) {
|
|
57
|
+
const leftPercent = (column.hitX + column.hitWidth / 2) / VIEWBOX_WIDTH * 100;
|
|
58
|
+
const topPercent = column.y / VIEWBOX_HEIGHT * 100;
|
|
59
|
+
return {
|
|
60
|
+
leftPercent,
|
|
61
|
+
topPercent,
|
|
62
|
+
align: leftPercent < CARD_EDGE_MARGIN ? 'start' : leftPercent > 100 - CARD_EDGE_MARGIN ? 'end' : 'center',
|
|
63
|
+
side: topPercent < CARD_FLIP_THRESHOLD ? 'below' : 'above'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
54
66
|
function AnalyticsTimeseries({ days, granularity, locale }) {
|
|
55
67
|
const { t } = useTranslation('byline-admin');
|
|
56
68
|
const [hovered, setHovered] = useState(null);
|
|
@@ -76,6 +88,13 @@ function AnalyticsTimeseries({ days, granularity, locale }) {
|
|
|
76
88
|
const formatBucket = (bucket)=>1 === bucket.dayCount ? formatDay(bucket.from) : `${formatDay(bucket.from)} – ${formatDay(bucket.to)}`;
|
|
77
89
|
const visitorLabel = (bucket)=>'day' === bucket.granularity ? t('analytics.stats.dailyUniques') : t('analytics.stats.summedDailyUniques');
|
|
78
90
|
const active = null == hovered ? void 0 : buckets[hovered];
|
|
91
|
+
const activePlacement = null == hovered || null == columns[hovered] ? void 0 : resolveHoverCardPlacement(columns[hovered]);
|
|
92
|
+
const lastShown = useRef(null);
|
|
93
|
+
if (null != active && null != activePlacement) lastShown.current = {
|
|
94
|
+
bucket: active,
|
|
95
|
+
placement: activePlacement
|
|
96
|
+
};
|
|
97
|
+
const shown = lastShown.current;
|
|
79
98
|
const readBucket = (bucket)=>[
|
|
80
99
|
formatBucket(bucket),
|
|
81
100
|
`${t('analytics.stats.views')} ${numbers.format(bucket.views)}`,
|
|
@@ -116,46 +135,99 @@ function AnalyticsTimeseries({ days, granularity, locale }) {
|
|
|
116
135
|
onKeyDown: trackKey,
|
|
117
136
|
onBlur: ()=>setHovered(null),
|
|
118
137
|
children: [
|
|
119
|
-
/*#__PURE__*/ jsxs("
|
|
120
|
-
|
|
121
|
-
className: clsx('byline-analytics-chart', timeseries_module.chart),
|
|
122
|
-
viewBox: `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`,
|
|
123
|
-
preserveAspectRatio: "none",
|
|
124
|
-
"aria-hidden": "true",
|
|
138
|
+
/*#__PURE__*/ jsxs("div", {
|
|
139
|
+
className: clsx('byline-analytics-plot', timeseries_module.plot),
|
|
125
140
|
children: [
|
|
126
|
-
/*#__PURE__*/
|
|
127
|
-
|
|
141
|
+
/*#__PURE__*/ jsxs("svg", {
|
|
142
|
+
ref: plot,
|
|
143
|
+
className: clsx('byline-analytics-chart', timeseries_module.chart),
|
|
144
|
+
viewBox: `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`,
|
|
145
|
+
preserveAspectRatio: "none",
|
|
146
|
+
"aria-hidden": "true",
|
|
147
|
+
children: [
|
|
148
|
+
/*#__PURE__*/ jsx("title", {
|
|
149
|
+
children: t('analytics.chart.views')
|
|
150
|
+
}),
|
|
151
|
+
[
|
|
152
|
+
0.5,
|
|
153
|
+
1
|
|
154
|
+
].map((fraction)=>/*#__PURE__*/ jsx("line", {
|
|
155
|
+
className: timeseries_module.gridline,
|
|
156
|
+
x1: 0,
|
|
157
|
+
x2: VIEWBOX_WIDTH,
|
|
158
|
+
y1: VIEWBOX_HEIGHT - VIEWBOX_HEIGHT * fraction,
|
|
159
|
+
y2: VIEWBOX_HEIGHT - VIEWBOX_HEIGHT * fraction,
|
|
160
|
+
vectorEffect: "non-scaling-stroke"
|
|
161
|
+
}, fraction)),
|
|
162
|
+
columns.map((column)=>/*#__PURE__*/ jsxs("g", {
|
|
163
|
+
className: clsx(timeseries_module.column, hovered === column.index && timeseries_module.columnActive),
|
|
164
|
+
children: [
|
|
165
|
+
/*#__PURE__*/ jsx("rect", {
|
|
166
|
+
className: timeseries_module.views,
|
|
167
|
+
x: column.x,
|
|
168
|
+
width: column.width,
|
|
169
|
+
y: column.y,
|
|
170
|
+
height: column.height
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ jsx("rect", {
|
|
173
|
+
className: timeseries_module.visitors,
|
|
174
|
+
x: column.insetX,
|
|
175
|
+
width: column.insetWidth,
|
|
176
|
+
y: column.insetY,
|
|
177
|
+
height: column.insetHeight
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
}, buckets[column.index]?.from))
|
|
181
|
+
]
|
|
128
182
|
}),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
y2: VIEWBOX_HEIGHT - VIEWBOX_HEIGHT * fraction,
|
|
138
|
-
vectorEffect: "non-scaling-stroke"
|
|
139
|
-
}, fraction)),
|
|
140
|
-
columns.map((column)=>/*#__PURE__*/ jsxs("g", {
|
|
141
|
-
className: clsx(timeseries_module.column, hovered === column.index && timeseries_module.columnActive),
|
|
183
|
+
buckets.length > 0 && /*#__PURE__*/ jsx("div", {
|
|
184
|
+
className: clsx('byline-analytics-hovercard', timeseries_module.hoverCard, null != shown && timeseries_module[shown.placement.align], null != shown && ('below' === shown.placement.side ? timeseries_module.below : timeseries_module.above), null != active && timeseries_module.hoverCardVisible),
|
|
185
|
+
style: {
|
|
186
|
+
left: `${shown?.placement.leftPercent ?? 50}%`,
|
|
187
|
+
top: `${shown?.placement.topPercent ?? 100}%`
|
|
188
|
+
},
|
|
189
|
+
"aria-hidden": "true",
|
|
190
|
+
children: null != shown && /*#__PURE__*/ jsxs(Fragment, {
|
|
142
191
|
children: [
|
|
143
|
-
/*#__PURE__*/ jsx("
|
|
144
|
-
className: timeseries_module.
|
|
145
|
-
|
|
146
|
-
width: column.width,
|
|
147
|
-
y: column.y,
|
|
148
|
-
height: column.height
|
|
192
|
+
/*#__PURE__*/ jsx("span", {
|
|
193
|
+
className: timeseries_module.hoverCardDate,
|
|
194
|
+
children: formatBucket(shown.bucket)
|
|
149
195
|
}),
|
|
150
|
-
/*#__PURE__*/
|
|
151
|
-
className: timeseries_module.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
/*#__PURE__*/ jsxs("span", {
|
|
197
|
+
className: timeseries_module.hoverCardRow,
|
|
198
|
+
children: [
|
|
199
|
+
/*#__PURE__*/ jsx("span", {
|
|
200
|
+
className: clsx(timeseries_module.keyLine, timeseries_module.keyViews)
|
|
201
|
+
}),
|
|
202
|
+
/*#__PURE__*/ jsx("span", {
|
|
203
|
+
className: timeseries_module.hoverCardLabel,
|
|
204
|
+
children: t('analytics.stats.views')
|
|
205
|
+
}),
|
|
206
|
+
/*#__PURE__*/ jsx("span", {
|
|
207
|
+
className: timeseries_module.hoverCardValue,
|
|
208
|
+
children: numbers.format(shown.bucket.views)
|
|
209
|
+
})
|
|
210
|
+
]
|
|
211
|
+
}),
|
|
212
|
+
/*#__PURE__*/ jsxs("span", {
|
|
213
|
+
className: timeseries_module.hoverCardRow,
|
|
214
|
+
children: [
|
|
215
|
+
/*#__PURE__*/ jsx("span", {
|
|
216
|
+
className: clsx(timeseries_module.keyLine, timeseries_module.keyVisitors)
|
|
217
|
+
}),
|
|
218
|
+
/*#__PURE__*/ jsx("span", {
|
|
219
|
+
className: timeseries_module.hoverCardLabel,
|
|
220
|
+
children: visitorLabel(shown.bucket)
|
|
221
|
+
}),
|
|
222
|
+
/*#__PURE__*/ jsx("span", {
|
|
223
|
+
className: timeseries_module.hoverCardValue,
|
|
224
|
+
children: numbers.format(shown.bucket.visitors)
|
|
225
|
+
})
|
|
226
|
+
]
|
|
156
227
|
})
|
|
157
228
|
]
|
|
158
|
-
}
|
|
229
|
+
})
|
|
230
|
+
})
|
|
159
231
|
]
|
|
160
232
|
}),
|
|
161
233
|
/*#__PURE__*/ jsxs("div", {
|
|
@@ -250,4 +322,4 @@ function combineRows(from, to, granularity, rows) {
|
|
|
250
322
|
downloads
|
|
251
323
|
};
|
|
252
324
|
}
|
|
253
|
-
export { AnalyticsTimeseries, bucketAnalyticsTimeseries, buildAnalyticsColumns, resolveAnalyticsChartGranularity };
|
|
325
|
+
export { AnalyticsTimeseries, bucketAnalyticsTimeseries, buildAnalyticsColumns, resolveAnalyticsChartGranularity, resolveHoverCardPlacement };
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import "./timeseries_module.css";
|
|
2
2
|
const timeseries_module = {
|
|
3
3
|
root: "root-L2qL4D",
|
|
4
|
+
plot: "plot-hcQkYI",
|
|
4
5
|
chart: "chart-giYTrx",
|
|
6
|
+
hoverCard: "hoverCard-riiLLo",
|
|
7
|
+
hoverCardVisible: "hoverCardVisible-mYSsIo",
|
|
8
|
+
center: "center-WOF43a",
|
|
9
|
+
start: "start-rwR0vM",
|
|
10
|
+
end: "end-dDnFtK",
|
|
11
|
+
above: "above-V9eoE9",
|
|
12
|
+
below: "below-BMn9q_",
|
|
13
|
+
hoverCardDate: "hoverCardDate-Td91gd",
|
|
14
|
+
hoverCardRow: "hoverCardRow-fMGuNA",
|
|
15
|
+
keyLine: "keyLine-wvPvBg",
|
|
16
|
+
keyViews: "keyViews-G8H3zC",
|
|
17
|
+
keyVisitors: "keyVisitors-P1JVE9",
|
|
18
|
+
hoverCardLabel: "hoverCardLabel-JYa1xk",
|
|
19
|
+
hoverCardValue: "hoverCardValue-m_cylC",
|
|
5
20
|
gridline: "gridline-R_wAwP",
|
|
6
21
|
column: "column-_Cw2Ua",
|
|
7
22
|
views: "views-WHbm44",
|
|
@@ -10,12 +10,115 @@
|
|
|
10
10
|
outline-offset: 4px;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
:is(.plot-hcQkYI, .byline-analytics-plot) {
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
:is(.chart-giYTrx, .byline-analytics-chart) {
|
|
14
18
|
width: 100%;
|
|
15
19
|
height: 11rem;
|
|
16
20
|
display: block;
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
:is(.hoverCard-riiLLo, .byline-analytics-hovercard) {
|
|
24
|
+
z-index: 1;
|
|
25
|
+
background-color: var(--surface-panel-elevated);
|
|
26
|
+
border-width: var(--border-width-thin);
|
|
27
|
+
border-style: var(--border-style-solid);
|
|
28
|
+
border-color: var(--surface-panel-border);
|
|
29
|
+
border-radius: var(--border-radius-sm);
|
|
30
|
+
box-shadow: var(--shadow-sm);
|
|
31
|
+
font-variant-numeric: tabular-nums;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
gap: .125rem .5rem;
|
|
36
|
+
padding: .4rem .55rem;
|
|
37
|
+
font-size: .6875rem;
|
|
38
|
+
line-height: 1.35;
|
|
39
|
+
transition: opacity .16s ease-in, translate .16s ease-in;
|
|
40
|
+
display: grid;
|
|
41
|
+
position: absolute;
|
|
42
|
+
translate: 0 6px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.hoverCardVisible-mYSsIo {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
transition: opacity .24s ease-out, translate .24s ease-out;
|
|
48
|
+
translate: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.hoverCard-riiLLo.center-WOF43a {
|
|
52
|
+
transform: translateX(-50%);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.hoverCard-riiLLo.start-rwR0vM {
|
|
56
|
+
transform: translateX(-.5rem);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hoverCard-riiLLo.end-dDnFtK {
|
|
60
|
+
transform: translateX(calc(.5rem - 100%));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.hoverCard-riiLLo.above-V9eoE9 {
|
|
64
|
+
margin-top: -.5rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.hoverCard-riiLLo.above-V9eoE9.center-WOF43a {
|
|
68
|
+
transform: translate(-50%, -100%);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.hoverCard-riiLLo.above-V9eoE9.start-rwR0vM {
|
|
72
|
+
transform: translate(-.5rem, -100%);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.hoverCard-riiLLo.above-V9eoE9.end-dDnFtK {
|
|
76
|
+
transform: translate(calc(.5rem - 100%), -100%);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.hoverCard-riiLLo.below-BMn9q_ {
|
|
80
|
+
margin-top: .5rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.hoverCardDate-Td91gd {
|
|
84
|
+
color: var(--text);
|
|
85
|
+
letter-spacing: .01em;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.hoverCardRow-fMGuNA {
|
|
90
|
+
grid-template-columns: .625rem 1fr auto;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: .4rem;
|
|
93
|
+
display: grid;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.keyLine-wvPvBg {
|
|
97
|
+
border-radius: 1px;
|
|
98
|
+
width: .625rem;
|
|
99
|
+
height: 2px;
|
|
100
|
+
display: inline-block;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.keyViews-G8H3zC {
|
|
104
|
+
opacity: .62;
|
|
105
|
+
background-color: #38bdf8;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.keyVisitors-P1JVE9 {
|
|
109
|
+
background-color: #7c3aed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.hoverCardLabel-JYa1xk {
|
|
113
|
+
color: var(--gray-500);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.hoverCardValue-m_cylC {
|
|
117
|
+
color: var(--text);
|
|
118
|
+
text-align: right;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
}
|
|
121
|
+
|
|
19
122
|
.gridline-R_wAwP {
|
|
20
123
|
stroke: var(--gray-200);
|
|
21
124
|
stroke-width: 1px;
|
|
@@ -96,7 +199,7 @@
|
|
|
96
199
|
stroke: var(--gray-700);
|
|
97
200
|
}
|
|
98
201
|
|
|
99
|
-
:is(:is([data-theme="dark"], .dark) .axis-fBshTw, :is([data-theme="dark"], .dark) .byline-analytics-axis, :is([data-theme="dark"], .dark) .readout-L9jEpz, :is([data-theme="dark"], .dark) .byline-analytics-readout) {
|
|
202
|
+
:is(:is([data-theme="dark"], .dark) .axis-fBshTw, :is([data-theme="dark"], .dark) .byline-analytics-axis, :is([data-theme="dark"], .dark) .readout-L9jEpz, :is([data-theme="dark"], .dark) .byline-analytics-readout, :is([data-theme="dark"], .dark) .hoverCardLabel-JYa1xk) {
|
|
100
203
|
color: var(--gray-400);
|
|
101
204
|
}
|
|
102
205
|
|
|
@@ -104,7 +207,7 @@
|
|
|
104
207
|
fill: #a78bfa;
|
|
105
208
|
}
|
|
106
209
|
|
|
107
|
-
:is([data-theme="dark"], .dark) .swatchVisitors-O3BWz6 {
|
|
210
|
+
:is([data-theme="dark"], .dark) .swatchVisitors-O3BWz6, :is([data-theme="dark"], .dark) .keyVisitors-P1JVE9 {
|
|
108
211
|
background-color: #a78bfa;
|
|
109
212
|
}
|
|
110
213
|
|
|
@@ -113,9 +216,13 @@
|
|
|
113
216
|
}
|
|
114
217
|
|
|
115
218
|
@media (prefers-reduced-motion: reduce) {
|
|
116
|
-
.column-_Cw2Ua, .views-WHbm44 {
|
|
219
|
+
.column-_Cw2Ua, .views-WHbm44, .hoverCard-riiLLo, .hoverCardVisible-mYSsIo {
|
|
117
220
|
transition: none;
|
|
118
221
|
}
|
|
222
|
+
|
|
223
|
+
.hoverCard-riiLLo, .hoverCardVisible-mYSsIo {
|
|
224
|
+
translate: none;
|
|
225
|
+
}
|
|
119
226
|
}
|
|
120
227
|
|
|
121
228
|
@media (max-width: 720px) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/admin",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "6.0.
|
|
5
|
+
"version": "6.0.2",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -183,12 +183,12 @@
|
|
|
183
183
|
"react-diff-viewer-continued": "^4.4.0",
|
|
184
184
|
"uuid": "^14.0.2",
|
|
185
185
|
"zod": "^4.6.2",
|
|
186
|
-
"@byline/analytics": "6.0.
|
|
187
|
-
"@byline/
|
|
188
|
-
"@byline/
|
|
189
|
-
"@byline/core": "6.0.
|
|
190
|
-
"@byline/
|
|
191
|
-
"@byline/
|
|
186
|
+
"@byline/analytics": "6.0.2",
|
|
187
|
+
"@byline/auth": "6.0.2",
|
|
188
|
+
"@byline/analytics-agent": "6.0.2",
|
|
189
|
+
"@byline/core": "6.0.2",
|
|
190
|
+
"@byline/ui": "6.0.2",
|
|
191
|
+
"@byline/i18n": "6.0.2"
|
|
192
192
|
},
|
|
193
193
|
"peerDependencies": {
|
|
194
194
|
"react": "^19.0.0",
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Self-service like `@byline/admin/admin-account`: the actor IS the
|
|
14
14
|
* target, and there is no ability gate — authn-only. The `scope` string
|
|
15
|
-
* (e.g. `collections.docs.list`) is the
|
|
16
|
-
*
|
|
15
|
+
* (e.g. `collections.docs.list`, `analytics.dashboard`) is the
|
|
16
|
+
* generality lever: a new admin surface claims its own scope by
|
|
17
|
+
* registering a value schema for it in `schemas.ts`, which keeps each
|
|
18
|
+
* family's payload strictly validated and unregistered scopes
|
|
19
|
+
* unwritable. Storage itself is untouched — scopes share one JSON
|
|
20
|
+
* key-value table.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
export { getPreferenceCommand, setPreferenceCommand } from './commands.js'
|
|
20
24
|
export {
|
|
25
|
+
analyticsViewPreferenceValueSchema,
|
|
21
26
|
getPreferenceRequestSchema,
|
|
22
27
|
listViewPreferenceValueSchema,
|
|
23
28
|
preferenceResponseSchema,
|