@lowdefy/blocks-diff 0.0.0-experimental-20260420110710
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/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/blocks/DiffGit/DiffGit.js +39 -0
- package/dist/blocks/DiffGit/e2e.js +25 -0
- package/dist/blocks/DiffGit/meta.js +95 -0
- package/dist/blocks/DiffList/DiffList.js +63 -0
- package/dist/blocks/DiffList/e2e.js +25 -0
- package/dist/blocks/DiffList/meta.js +225 -0
- package/dist/blocks/DiffSideBySide/DiffSideBySide.js +65 -0
- package/dist/blocks/DiffSideBySide/e2e.js +25 -0
- package/dist/blocks/DiffSideBySide/meta.js +225 -0
- package/dist/blocks/DiffTimeline/DiffTimeline.js +62 -0
- package/dist/blocks/DiffTimeline/e2e.js +25 -0
- package/dist/blocks/DiffTimeline/meta.js +211 -0
- package/dist/blocks.js +18 -0
- package/dist/e2e.js +18 -0
- package/dist/metas.js +18 -0
- package/dist/shared/ChangeTypeTag.js +44 -0
- package/dist/shared/DiffShell.js +50 -0
- package/dist/shared/ValueCell.js +126 -0
- package/dist/shared/breadcrumbLabel.js +44 -0
- package/dist/shared/buildDiffModel.js +231 -0
- package/dist/shared/constants.js +36 -0
- package/dist/shared/formatValue.js +111 -0
- package/dist/shared/pathUtils.js +99 -0
- package/dist/shared/renderers/GitDiffRenderer.js +87 -0
- package/dist/shared/renderers/ListRenderer.js +314 -0
- package/dist/shared/renderers/SideBySideRenderer.js +193 -0
- package/dist/shared/renderers/TimelineRenderer.js +57 -0
- package/dist/shared/serializeYaml.js +74 -0
- package/dist/shared/withTheme.js +40 -0
- package/dist/types.js +17 -0
- package/package.json +74 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import diff from 'microdiff';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import breadcrumbLabel from './breadcrumbLabel.js';
|
|
18
|
+
import { CHANGE_TYPES, GROUP_ROOT } from './constants.js';
|
|
19
|
+
import { getValueAtPath, matchesPath, pathLabel, pathToString, resolveFormatter } from './pathUtils.js';
|
|
20
|
+
const MICRODIFF_TYPE_MAP = {
|
|
21
|
+
CREATE: CHANGE_TYPES.CREATE,
|
|
22
|
+
REMOVE: CHANGE_TYPES.REMOVE,
|
|
23
|
+
CHANGE: CHANGE_TYPES.CHANGE
|
|
24
|
+
};
|
|
25
|
+
// microdiff only accepts objects or arrays. Anything else (null, primitive)
|
|
26
|
+
// becomes an empty object so "before was nothing" still produces a clean diff.
|
|
27
|
+
function toInput(value) {
|
|
28
|
+
if (type.isArray(value)) return value;
|
|
29
|
+
if (type.isObject(value)) return value;
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
function enrichChange(entry, { labels, format }) {
|
|
33
|
+
const pathStr = pathToString(entry.path);
|
|
34
|
+
const mappedType = MICRODIFF_TYPE_MAP[entry.type] ?? CHANGE_TYPES.CHANGE;
|
|
35
|
+
let formatter = resolveFormatter(pathStr, format);
|
|
36
|
+
if (type.isNone(formatter) && mappedType === CHANGE_TYPES.CHANGE && (type.isArray(entry.value) || type.isObject(entry.value))) {
|
|
37
|
+
formatter = {
|
|
38
|
+
type: 'json'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
type: mappedType,
|
|
43
|
+
path: entry.path.slice(),
|
|
44
|
+
pathStr,
|
|
45
|
+
displayPath: pathToString(entry.path, {
|
|
46
|
+
display: true
|
|
47
|
+
}),
|
|
48
|
+
label: pathLabel(entry.path, labels),
|
|
49
|
+
oldValue: entry.type === 'CREATE' ? undefined : entry.oldValue,
|
|
50
|
+
newValue: entry.type === 'REMOVE' ? undefined : entry.value,
|
|
51
|
+
formatter,
|
|
52
|
+
depth: entry.path.length,
|
|
53
|
+
breadcrumb: breadcrumbLabel(entry.path, labels)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function collectLeafPaths(value, basePath, collected, visited) {
|
|
57
|
+
if (type.isArray(value) || type.isObject(value)) {
|
|
58
|
+
if (visited.has(value)) return;
|
|
59
|
+
visited.add(value);
|
|
60
|
+
const keys = type.isArray(value) ? value.map((_, i)=>i) : Object.keys(value);
|
|
61
|
+
if (keys.length === 0) {
|
|
62
|
+
collected.push(basePath.slice());
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
keys.forEach((key)=>{
|
|
66
|
+
collectLeafPaths(value[key], basePath.concat(key), collected, visited);
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
collected.push(basePath.slice());
|
|
71
|
+
}
|
|
72
|
+
function synthesiseUnchanged({ before, after, existingPathSet, labels, format }) {
|
|
73
|
+
const leafPaths = [];
|
|
74
|
+
collectLeafPaths(after, [], leafPaths, new WeakSet());
|
|
75
|
+
collectLeafPaths(before, [], leafPaths, new WeakSet());
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
const results = [];
|
|
78
|
+
leafPaths.forEach((path)=>{
|
|
79
|
+
const pathStr = pathToString(path);
|
|
80
|
+
if (!pathStr || seen.has(pathStr) || existingPathSet.has(pathStr)) return;
|
|
81
|
+
seen.add(pathStr);
|
|
82
|
+
const beforeVal = getValueAtPath(before, path);
|
|
83
|
+
const afterVal = getValueAtPath(after, path);
|
|
84
|
+
if (beforeVal === afterVal || JSON.stringify(beforeVal) === JSON.stringify(afterVal)) {
|
|
85
|
+
results.push({
|
|
86
|
+
type: CHANGE_TYPES.UNCHANGED,
|
|
87
|
+
path: path.slice(),
|
|
88
|
+
pathStr,
|
|
89
|
+
displayPath: pathToString(path, {
|
|
90
|
+
display: true
|
|
91
|
+
}),
|
|
92
|
+
label: pathLabel(path, labels),
|
|
93
|
+
oldValue: beforeVal,
|
|
94
|
+
newValue: afterVal,
|
|
95
|
+
formatter: resolveFormatter(pathStr, format),
|
|
96
|
+
depth: path.length,
|
|
97
|
+
breadcrumb: breadcrumbLabel(path, labels)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
function applyFilters(changes, { hide, show }) {
|
|
104
|
+
let filtered = changes;
|
|
105
|
+
if (type.isArray(show) && show.length > 0) {
|
|
106
|
+
filtered = filtered.filter((change)=>matchesPath(change.pathStr, show));
|
|
107
|
+
}
|
|
108
|
+
if (type.isArray(hide) && hide.length > 0) {
|
|
109
|
+
filtered = filtered.filter((change)=>!matchesPath(change.pathStr, hide));
|
|
110
|
+
}
|
|
111
|
+
return filtered;
|
|
112
|
+
}
|
|
113
|
+
function summariseGroup(changes) {
|
|
114
|
+
const summary = {
|
|
115
|
+
added: 0,
|
|
116
|
+
removed: 0,
|
|
117
|
+
changed: 0,
|
|
118
|
+
unchanged: 0
|
|
119
|
+
};
|
|
120
|
+
let hasArrayIndices = false;
|
|
121
|
+
changes.forEach((change)=>{
|
|
122
|
+
if (change.type === CHANGE_TYPES.CREATE) summary.added += 1;
|
|
123
|
+
else if (change.type === CHANGE_TYPES.REMOVE) summary.removed += 1;
|
|
124
|
+
else if (change.type === CHANGE_TYPES.CHANGE) summary.changed += 1;
|
|
125
|
+
else if (change.type === CHANGE_TYPES.UNCHANGED) summary.unchanged += 1;
|
|
126
|
+
if (change.path.length > 1 && /^\d+$/.test(String(change.path[1]))) {
|
|
127
|
+
hasArrayIndices = true;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
...summary,
|
|
132
|
+
hasArrayIndices
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function groupChanges(changes, { groupByRoot }) {
|
|
136
|
+
if (!groupByRoot) {
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
key: GROUP_ROOT,
|
|
140
|
+
label: '',
|
|
141
|
+
changes,
|
|
142
|
+
summary: summariseGroup(changes)
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
const byKey = new Map();
|
|
147
|
+
changes.forEach((change)=>{
|
|
148
|
+
const key = change.path.length <= 1 ? GROUP_ROOT : String(change.path[0]);
|
|
149
|
+
if (!byKey.has(key)) byKey.set(key, []);
|
|
150
|
+
byKey.get(key).push(change);
|
|
151
|
+
});
|
|
152
|
+
return Array.from(byKey.entries()).map(([key, groupChangesList])=>({
|
|
153
|
+
key,
|
|
154
|
+
label: key === GROUP_ROOT ? '' : key,
|
|
155
|
+
changes: groupChangesList,
|
|
156
|
+
summary: summariseGroup(groupChangesList)
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
function collapseDeep(raw, { maxDepth, before, after }) {
|
|
160
|
+
if (!type.isInt(maxDepth) || maxDepth < 1) return raw;
|
|
161
|
+
const passthrough = [];
|
|
162
|
+
const bucketOrder = [];
|
|
163
|
+
const buckets = new Map();
|
|
164
|
+
raw.forEach((entry)=>{
|
|
165
|
+
if (!type.isArray(entry.path) || entry.path.length <= maxDepth) {
|
|
166
|
+
passthrough.push(entry);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const parent = entry.path.slice(0, maxDepth);
|
|
170
|
+
const key = parent.join('.');
|
|
171
|
+
if (!buckets.has(key)) {
|
|
172
|
+
buckets.set(key, parent);
|
|
173
|
+
bucketOrder.push(key);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
const collapsed = [];
|
|
177
|
+
bucketOrder.forEach((key)=>{
|
|
178
|
+
const parent = buckets.get(key);
|
|
179
|
+
const oldValue = getValueAtPath(before, parent);
|
|
180
|
+
const newValue = getValueAtPath(after, parent);
|
|
181
|
+
if (JSON.stringify(oldValue) === JSON.stringify(newValue)) return;
|
|
182
|
+
collapsed.push({
|
|
183
|
+
type: 'CHANGE',
|
|
184
|
+
path: parent,
|
|
185
|
+
oldValue,
|
|
186
|
+
value: newValue
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
return passthrough.concat(collapsed);
|
|
190
|
+
}
|
|
191
|
+
function buildDiffModel({ before, after, options = {} }) {
|
|
192
|
+
const { labels, hide, show, format, showUnchanged = false, groupByRoot = true, maxDepth = 4 } = options;
|
|
193
|
+
const safeBefore = toInput(before);
|
|
194
|
+
const safeAfter = toInput(after);
|
|
195
|
+
const rawChanges = diff(safeBefore, safeAfter);
|
|
196
|
+
const collapsedRaw = collapseDeep(rawChanges, {
|
|
197
|
+
maxDepth,
|
|
198
|
+
before: safeBefore,
|
|
199
|
+
after: safeAfter
|
|
200
|
+
});
|
|
201
|
+
const enriched = collapsedRaw.map((entry)=>enrichChange(entry, {
|
|
202
|
+
labels,
|
|
203
|
+
format
|
|
204
|
+
}));
|
|
205
|
+
const changePathSet = new Set(enriched.map((change)=>change.pathStr));
|
|
206
|
+
let allChanges = enriched;
|
|
207
|
+
if (showUnchanged) {
|
|
208
|
+
const unchanged = synthesiseUnchanged({
|
|
209
|
+
before: safeBefore,
|
|
210
|
+
after: safeAfter,
|
|
211
|
+
existingPathSet: changePathSet,
|
|
212
|
+
labels,
|
|
213
|
+
format
|
|
214
|
+
});
|
|
215
|
+
allChanges = allChanges.concat(unchanged);
|
|
216
|
+
}
|
|
217
|
+
const filtered = applyFilters(allChanges, {
|
|
218
|
+
hide,
|
|
219
|
+
show
|
|
220
|
+
});
|
|
221
|
+
const groups = groupChanges(filtered, {
|
|
222
|
+
groupByRoot
|
|
223
|
+
});
|
|
224
|
+
const hasMeaningfulChanges = filtered.some((change)=>change.type !== CHANGE_TYPES.UNCHANGED);
|
|
225
|
+
return {
|
|
226
|
+
groups,
|
|
227
|
+
empty: !hasMeaningfulChanges && filtered.length === 0,
|
|
228
|
+
total: filtered.length
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export default buildDiffModel;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ export const CHANGE_TYPES = {
|
|
16
|
+
CREATE: 'CREATE',
|
|
17
|
+
REMOVE: 'REMOVE',
|
|
18
|
+
CHANGE: 'CHANGE',
|
|
19
|
+
UNCHANGED: 'UNCHANGED'
|
|
20
|
+
};
|
|
21
|
+
// Sentinel for the synthetic group that holds top-level primitive changes when
|
|
22
|
+
// groupByRoot is enabled, and every change when groupByRoot is false. Leading
|
|
23
|
+
// underscores keep it from colliding with user keys in typical datasets.
|
|
24
|
+
export const GROUP_ROOT = '__root__';
|
|
25
|
+
export const DEFAULT_CHANGE_TYPE_LABELS = {
|
|
26
|
+
added: 'Added',
|
|
27
|
+
removed: 'Removed',
|
|
28
|
+
changed: 'Changed',
|
|
29
|
+
unchanged: 'Unchanged'
|
|
30
|
+
};
|
|
31
|
+
export const CHANGE_TYPE_TAG_COLORS = {
|
|
32
|
+
[CHANGE_TYPES.CREATE]: 'success',
|
|
33
|
+
[CHANGE_TYPES.REMOVE]: 'error',
|
|
34
|
+
[CHANGE_TYPES.CHANGE]: 'warning',
|
|
35
|
+
[CHANGE_TYPES.UNCHANGED]: 'default'
|
|
36
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import React from 'react';
|
|
16
|
+
import { Tag, Typography } from 'antd';
|
|
17
|
+
import dayjs from 'dayjs';
|
|
18
|
+
import { type } from '@lowdefy/helpers';
|
|
19
|
+
const { Text } = Typography;
|
|
20
|
+
function renderEmpty() {
|
|
21
|
+
return /*#__PURE__*/ React.createElement(Text, {
|
|
22
|
+
type: "secondary"
|
|
23
|
+
}, "—");
|
|
24
|
+
}
|
|
25
|
+
function renderDate(value, { pattern = 'YYYY-MM-DD' } = {}) {
|
|
26
|
+
const parsed = dayjs(value);
|
|
27
|
+
if (!parsed.isValid()) return /*#__PURE__*/ React.createElement(Text, null, String(value));
|
|
28
|
+
return /*#__PURE__*/ React.createElement(Text, null, parsed.format(pattern));
|
|
29
|
+
}
|
|
30
|
+
function renderDateTime(value, { pattern = 'YYYY-MM-DD HH:mm' } = {}) {
|
|
31
|
+
const parsed = dayjs(value);
|
|
32
|
+
if (!parsed.isValid()) return /*#__PURE__*/ React.createElement(Text, null, String(value));
|
|
33
|
+
return /*#__PURE__*/ React.createElement(Text, null, parsed.format(pattern));
|
|
34
|
+
}
|
|
35
|
+
function renderBoolean(value, { yes = 'Yes', no = 'No' } = {}) {
|
|
36
|
+
const truthy = Boolean(value);
|
|
37
|
+
return /*#__PURE__*/ React.createElement(Tag, {
|
|
38
|
+
color: truthy ? 'success' : 'default'
|
|
39
|
+
}, truthy ? yes : no);
|
|
40
|
+
}
|
|
41
|
+
function renderCurrency(value, { locale, currency = 'USD' } = {}) {
|
|
42
|
+
const number = Number(value);
|
|
43
|
+
if (!Number.isFinite(number)) return /*#__PURE__*/ React.createElement(Text, null, String(value));
|
|
44
|
+
try {
|
|
45
|
+
const formatted = new Intl.NumberFormat(locale, {
|
|
46
|
+
style: 'currency',
|
|
47
|
+
currency
|
|
48
|
+
}).format(number);
|
|
49
|
+
return /*#__PURE__*/ React.createElement(Text, null, formatted);
|
|
50
|
+
} catch (_err) {
|
|
51
|
+
return /*#__PURE__*/ React.createElement(Text, null, String(value));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function renderJson(value) {
|
|
55
|
+
return /*#__PURE__*/ React.createElement("pre", {
|
|
56
|
+
style: {
|
|
57
|
+
margin: 0,
|
|
58
|
+
padding: '8px 10px',
|
|
59
|
+
fontSize: '0.85em',
|
|
60
|
+
background: 'var(--ant-color-fill-tertiary, rgba(0,0,0,0.04))',
|
|
61
|
+
border: '1px solid var(--ant-color-border-secondary, rgba(0,0,0,0.06))',
|
|
62
|
+
borderRadius: 'var(--ant-border-radius-sm, 4px)',
|
|
63
|
+
whiteSpace: 'pre-wrap',
|
|
64
|
+
wordBreak: 'break-word'
|
|
65
|
+
}
|
|
66
|
+
}, JSON.stringify(value, null, 2));
|
|
67
|
+
}
|
|
68
|
+
function renderCode(value) {
|
|
69
|
+
return /*#__PURE__*/ React.createElement(Typography.Text, {
|
|
70
|
+
code: true
|
|
71
|
+
}, String(value));
|
|
72
|
+
}
|
|
73
|
+
function renderEnum(value, { map = {} } = {}) {
|
|
74
|
+
const key = String(value);
|
|
75
|
+
const entry = map[key];
|
|
76
|
+
if (type.isNone(entry)) return /*#__PURE__*/ React.createElement(Tag, null, key);
|
|
77
|
+
if (type.isString(entry)) return /*#__PURE__*/ React.createElement(Tag, null, entry);
|
|
78
|
+
if (type.isObject(entry)) {
|
|
79
|
+
return /*#__PURE__*/ React.createElement(Tag, {
|
|
80
|
+
color: entry.color
|
|
81
|
+
}, entry.label ?? key);
|
|
82
|
+
}
|
|
83
|
+
return /*#__PURE__*/ React.createElement(Tag, null, key);
|
|
84
|
+
}
|
|
85
|
+
function renderFallback(value) {
|
|
86
|
+
if (type.isPrimitive(value)) return /*#__PURE__*/ React.createElement(Text, null, String(value));
|
|
87
|
+
return renderJson(value);
|
|
88
|
+
}
|
|
89
|
+
function formatValue(value, formatter) {
|
|
90
|
+
if (type.isNone(value)) return renderEmpty();
|
|
91
|
+
const formatType = type.isObject(formatter) ? formatter.type : undefined;
|
|
92
|
+
switch(formatType){
|
|
93
|
+
case 'date':
|
|
94
|
+
return renderDate(value, formatter);
|
|
95
|
+
case 'datetime':
|
|
96
|
+
return renderDateTime(value, formatter);
|
|
97
|
+
case 'boolean':
|
|
98
|
+
return renderBoolean(value, formatter);
|
|
99
|
+
case 'currency':
|
|
100
|
+
return renderCurrency(value, formatter);
|
|
101
|
+
case 'json':
|
|
102
|
+
return renderJson(value);
|
|
103
|
+
case 'code':
|
|
104
|
+
return renderCode(value);
|
|
105
|
+
case 'enum':
|
|
106
|
+
return renderEnum(value, formatter);
|
|
107
|
+
default:
|
|
108
|
+
return renderFallback(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export default formatValue;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import pluralize from 'pluralize';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
export function isIndex(segment) {
|
|
18
|
+
return type.isInt(segment) || type.isString(segment) && /^\d+$/.test(segment);
|
|
19
|
+
}
|
|
20
|
+
function joinInternal(path) {
|
|
21
|
+
return path.map((segment)=>String(segment)).join('.');
|
|
22
|
+
}
|
|
23
|
+
function joinDisplay(path) {
|
|
24
|
+
return path.reduce((acc, segment, index)=>{
|
|
25
|
+
if (isIndex(segment)) return `${acc}[${segment}]`;
|
|
26
|
+
return index === 0 ? String(segment) : `${acc}.${segment}`;
|
|
27
|
+
}, '');
|
|
28
|
+
}
|
|
29
|
+
export function pathToString(path, { display = false } = {}) {
|
|
30
|
+
if (!type.isArray(path)) return '';
|
|
31
|
+
return display ? joinDisplay(path) : joinInternal(path);
|
|
32
|
+
}
|
|
33
|
+
export function matchesPath(pathStr, patterns) {
|
|
34
|
+
if (!type.isArray(patterns) || patterns.length === 0) return false;
|
|
35
|
+
return patterns.some((pattern)=>{
|
|
36
|
+
if (!type.isString(pattern) || pattern === '') return false;
|
|
37
|
+
if (pattern === pathStr) return true;
|
|
38
|
+
if (pattern.endsWith('.*')) {
|
|
39
|
+
const prefix = pattern.slice(0, -2);
|
|
40
|
+
return pathStr === prefix || pathStr.startsWith(`${prefix}.`);
|
|
41
|
+
}
|
|
42
|
+
if (pattern.startsWith('*.')) {
|
|
43
|
+
const suffix = pattern.slice(2);
|
|
44
|
+
return pathStr === suffix || pathStr.endsWith(`.${suffix}`);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function humaniseSegment(segment) {
|
|
50
|
+
const str = String(segment);
|
|
51
|
+
const spaced = str.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').trim();
|
|
52
|
+
if (spaced.length === 0) return str;
|
|
53
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
54
|
+
}
|
|
55
|
+
export function pathLabel(path, labelsMap) {
|
|
56
|
+
if (!type.isArray(path) || path.length === 0) return '';
|
|
57
|
+
const pathStr = pathToString(path);
|
|
58
|
+
if (type.isObject(labelsMap) && type.isString(labelsMap[pathStr])) {
|
|
59
|
+
return labelsMap[pathStr];
|
|
60
|
+
}
|
|
61
|
+
const leaf = path[path.length - 1];
|
|
62
|
+
if (isIndex(leaf)) {
|
|
63
|
+
if (path.length > 1) {
|
|
64
|
+
const parent = pathToString(path.slice(0, -1));
|
|
65
|
+
const parentLabel = type.isObject(labelsMap) && type.isString(labelsMap[parent]) ? labelsMap[parent] : humaniseSegment(path[path.length - 2]);
|
|
66
|
+
return `${parentLabel} [${leaf}]`;
|
|
67
|
+
}
|
|
68
|
+
return `[${leaf}]`;
|
|
69
|
+
}
|
|
70
|
+
return humaniseSegment(leaf);
|
|
71
|
+
}
|
|
72
|
+
export function resolveFormatter(pathStr, formatMap) {
|
|
73
|
+
if (!type.isObject(formatMap)) return undefined;
|
|
74
|
+
if (type.isObject(formatMap[pathStr])) return formatMap[pathStr];
|
|
75
|
+
const keys = Object.keys(formatMap);
|
|
76
|
+
for (const key of keys){
|
|
77
|
+
if (key.endsWith('.*')) {
|
|
78
|
+
const prefix = key.slice(0, -2);
|
|
79
|
+
if (pathStr === prefix || pathStr.startsWith(`${prefix}.`)) return formatMap[key];
|
|
80
|
+
} else if (key.startsWith('*.')) {
|
|
81
|
+
const suffix = key.slice(2);
|
|
82
|
+
if (pathStr === suffix || pathStr.endsWith(`.${suffix}`)) return formatMap[key];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
export function getValueAtPath(obj, path) {
|
|
88
|
+
let current = obj;
|
|
89
|
+
for (const segment of path){
|
|
90
|
+
if (type.isNone(current)) return undefined;
|
|
91
|
+
current = current[segment];
|
|
92
|
+
}
|
|
93
|
+
return current;
|
|
94
|
+
}
|
|
95
|
+
export function singularise(label) {
|
|
96
|
+
if (!type.isString(label) || label.length === 0) return label;
|
|
97
|
+
return pluralize.singular(label);
|
|
98
|
+
}
|
|
99
|
+
export { default as breadcrumbLabel } from './breadcrumbLabel.js';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import React from 'react';
|
|
16
|
+
import { diffLines } from 'diff';
|
|
17
|
+
import serializeYaml from '../serializeYaml.js';
|
|
18
|
+
const ADDED_STYLE = {
|
|
19
|
+
background: 'var(--ant-color-success-bg, rgba(82,196,26,0.1))',
|
|
20
|
+
color: 'var(--ant-color-success-text, var(--ant-color-text))',
|
|
21
|
+
display: 'block'
|
|
22
|
+
};
|
|
23
|
+
const REMOVED_STYLE = {
|
|
24
|
+
background: 'var(--ant-color-error-bg, rgba(255,77,79,0.1))',
|
|
25
|
+
color: 'var(--ant-color-error-text, var(--ant-color-text))',
|
|
26
|
+
display: 'block'
|
|
27
|
+
};
|
|
28
|
+
const CONTEXT_STYLE = {
|
|
29
|
+
color: 'var(--ant-color-text-secondary, rgba(0,0,0,0.65))',
|
|
30
|
+
display: 'block'
|
|
31
|
+
};
|
|
32
|
+
const PRE_STYLE = {
|
|
33
|
+
fontFamily: 'var(--ant-font-family-code, monospace)',
|
|
34
|
+
fontSize: 'var(--ant-font-size-sm, 12px)',
|
|
35
|
+
padding: '12px',
|
|
36
|
+
background: 'var(--ant-color-fill-tertiary, rgba(0,0,0,0.04))',
|
|
37
|
+
borderRadius: 'var(--ant-border-radius-sm, 4px)',
|
|
38
|
+
whiteSpace: 'pre',
|
|
39
|
+
overflowX: 'auto',
|
|
40
|
+
margin: 0
|
|
41
|
+
};
|
|
42
|
+
function splitHunkLines(value) {
|
|
43
|
+
const lines = value.split('\n');
|
|
44
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
function GitDiffRenderer({ before, after, hide, show, classNames = {}, styles = {} }) {
|
|
48
|
+
const beforeYaml = serializeYaml(before, {
|
|
49
|
+
hide,
|
|
50
|
+
show
|
|
51
|
+
});
|
|
52
|
+
const afterYaml = serializeYaml(after, {
|
|
53
|
+
hide,
|
|
54
|
+
show
|
|
55
|
+
});
|
|
56
|
+
const hunks = diffLines(beforeYaml, afterYaml);
|
|
57
|
+
const nodes = [];
|
|
58
|
+
hunks.forEach((hunk, hunkIndex)=>{
|
|
59
|
+
const lines = splitHunkLines(hunk.value);
|
|
60
|
+
let prefix;
|
|
61
|
+
let style;
|
|
62
|
+
if (hunk.added) {
|
|
63
|
+
prefix = '+ ';
|
|
64
|
+
style = ADDED_STYLE;
|
|
65
|
+
} else if (hunk.removed) {
|
|
66
|
+
prefix = '- ';
|
|
67
|
+
style = REMOVED_STYLE;
|
|
68
|
+
} else {
|
|
69
|
+
prefix = ' ';
|
|
70
|
+
style = CONTEXT_STYLE;
|
|
71
|
+
}
|
|
72
|
+
lines.forEach((line, lineIndex)=>{
|
|
73
|
+
nodes.push(/*#__PURE__*/ React.createElement("span", {
|
|
74
|
+
key: `${hunkIndex}-${lineIndex}`,
|
|
75
|
+
style: style
|
|
76
|
+
}, `${prefix}${line}`));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
return /*#__PURE__*/ React.createElement("pre", {
|
|
80
|
+
className: classNames.group,
|
|
81
|
+
style: {
|
|
82
|
+
...PRE_STYLE,
|
|
83
|
+
...styles.group
|
|
84
|
+
}
|
|
85
|
+
}, nodes);
|
|
86
|
+
}
|
|
87
|
+
export default GitDiffRenderer;
|