@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.1
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/index.js +217 -51
- package/package.json +2 -2
- package/studio-plugin/MCPPlugin.rbxmx +332 -40
- package/studio-plugin/.Carbon.rbxm.lock +0 -0
- package/studio-plugin/Carbon.rbxm +0 -0
- package/studio-plugin/INSTALLATION.md +0 -170
- package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
- package/studio-plugin/default.project.json +0 -19
- package/studio-plugin/dev.project.json +0 -23
- package/studio-plugin/include/LibMP.lua +0 -156378
- package/studio-plugin/inspector-icon.png +0 -0
- package/studio-plugin/package-lock.json +0 -706
- package/studio-plugin/package.json +0 -19
- package/studio-plugin/plugin.json +0 -10
- package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
- package/studio-plugin/src/modules/ClientBroker.ts +0 -450
- package/studio-plugin/src/modules/Communication.ts +0 -601
- package/studio-plugin/src/modules/EvalBridges.ts +0 -255
- package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
- package/studio-plugin/src/modules/LuauExec.ts +0 -403
- package/studio-plugin/src/modules/Recording.ts +0 -28
- package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
- package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
- package/studio-plugin/src/modules/State.ts +0 -39
- package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
- package/studio-plugin/src/modules/UI.ts +0 -597
- package/studio-plugin/src/modules/Utils.ts +0 -527
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
- package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
- package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
- package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
- package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
- package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
- package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
- package/studio-plugin/src/server/index.server.ts +0 -135
- package/studio-plugin/src/types/index.d.ts +0 -57
- package/studio-plugin/tsconfig.json +0 -20
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import Utils from "../Utils";
|
|
2
|
-
import Recording from "../Recording";
|
|
3
|
-
import {
|
|
4
|
-
sanitizeLoadedAsset,
|
|
5
|
-
scanForbiddenImportedInstances,
|
|
6
|
-
type AssetSanitizationOperations,
|
|
7
|
-
type ImportedAssetInstance,
|
|
8
|
-
} from "../AssetSanitizationPolicy";
|
|
9
|
-
|
|
10
|
-
const AssetService = game.GetService("AssetService");
|
|
11
|
-
const ChangeHistoryService = game.GetService("ChangeHistoryService");
|
|
12
|
-
const Selection = game.GetService("Selection");
|
|
13
|
-
|
|
14
|
-
const { getInstancePath, getInstanceByPath } = Utils;
|
|
15
|
-
const { beginRecording, finishRecording } = Recording;
|
|
16
|
-
|
|
17
|
-
const THIRD_PARTY_ASSET_SETTING_HINT =
|
|
18
|
-
'\nTo load public Creator Store assets that you do not own, enable "Allow Loading Third Party Assets" in Game Settings > Security.';
|
|
19
|
-
|
|
20
|
-
function destroyImportedRoot(root: Instance) {
|
|
21
|
-
pcall(() => {
|
|
22
|
-
root.Destroy();
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function formatAssetLoadFailure(assetId: number, loadError: unknown): string {
|
|
27
|
-
const [settingReadable, allowInsertFreeAssets] = pcall(() => {
|
|
28
|
-
return (AssetService as unknown as { AllowInsertFreeAssets: boolean }).AllowInsertFreeAssets;
|
|
29
|
-
});
|
|
30
|
-
const settingHint = settingReadable && allowInsertFreeAssets === true
|
|
31
|
-
? ""
|
|
32
|
-
: THIRD_PARTY_ASSET_SETTING_HINT;
|
|
33
|
-
return `Failed to load asset ${assetId}: ${tostring(loadError)}${settingHint}`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const sanitizationOperations: AssetSanitizationOperations = {
|
|
37
|
-
forceUnparent: (root: ImportedAssetInstance) => {
|
|
38
|
-
const instance = root as Instance;
|
|
39
|
-
const [unparentedOk] = pcall(() => {
|
|
40
|
-
instance.Parent = undefined;
|
|
41
|
-
});
|
|
42
|
-
return unparentedOk && instance.Parent === undefined;
|
|
43
|
-
},
|
|
44
|
-
destroy: (instance: ImportedAssetInstance) => {
|
|
45
|
-
const [destroyed] = pcall(() => {
|
|
46
|
-
(instance as Instance).Destroy();
|
|
47
|
-
});
|
|
48
|
-
return destroyed;
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function insertAsset(requestData: Record<string, unknown>) {
|
|
53
|
-
const assetId = requestData.assetId as number;
|
|
54
|
-
const parentPath = (requestData.parentPath as string) ?? "game.Workspace";
|
|
55
|
-
const position = requestData.position as { x: number; y: number; z: number } | undefined;
|
|
56
|
-
|
|
57
|
-
if (!assetId) {
|
|
58
|
-
return { error: "assetId is required" };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const parentInstance = getInstanceByPath(parentPath);
|
|
62
|
-
if (!parentInstance) {
|
|
63
|
-
return { error: `Parent instance not found: ${parentPath}` };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const recordingId = beginRecording(`Insert asset ${assetId}`);
|
|
67
|
-
|
|
68
|
-
let wrapperModel: Instance | undefined;
|
|
69
|
-
const insertedInstances: Instance[] = [];
|
|
70
|
-
const [insertSuccess, insertResult] = pcall(() => {
|
|
71
|
-
const [loadSuccess, loadResult] = pcall(() => {
|
|
72
|
-
return (AssetService as unknown as { LoadAssetAsync(id: number): Instance }).LoadAssetAsync(assetId);
|
|
73
|
-
});
|
|
74
|
-
if (!loadSuccess || !loadResult) {
|
|
75
|
-
error(formatAssetLoadFailure(assetId, loadResult), 0);
|
|
76
|
-
}
|
|
77
|
-
const loadedWrapper = loadResult as Instance;
|
|
78
|
-
wrapperModel = loadedWrapper;
|
|
79
|
-
|
|
80
|
-
const sanitization = sanitizeLoadedAsset(loadedWrapper, sanitizationOperations);
|
|
81
|
-
if (!sanitization.success) {
|
|
82
|
-
error(sanitization.error ?? "Asset sanitization failed.", 0);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const children = loadedWrapper.GetChildren();
|
|
86
|
-
|
|
87
|
-
// Position while every sanitized child is still contained by the
|
|
88
|
-
// unparented wrapper. Nothing reaches the DataModel before verification.
|
|
89
|
-
if (position) {
|
|
90
|
-
const pos = new Vector3(position.x ?? 0, position.y ?? 0, position.z ?? 0);
|
|
91
|
-
for (const inst of children) {
|
|
92
|
-
if (inst.IsA("BasePart")) {
|
|
93
|
-
inst.Position = pos;
|
|
94
|
-
} else if (inst.IsA("Model")) {
|
|
95
|
-
if (inst.PrimaryPart) {
|
|
96
|
-
inst.PivotTo(new CFrame(pos));
|
|
97
|
-
} else {
|
|
98
|
-
const firstPart = inst.FindFirstChildWhichIsA("BasePart", true);
|
|
99
|
-
if (firstPart) {
|
|
100
|
-
inst.PivotTo(new CFrame(pos));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// This is the first point at which imported content is parented into
|
|
108
|
-
// Studio. Both unlimited-depth scans have already passed.
|
|
109
|
-
for (const child of children) {
|
|
110
|
-
child.Parent = parentInstance;
|
|
111
|
-
insertedInstances.push(child);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
pcall(() => {
|
|
115
|
-
Selection.Set(insertedInstances);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const resultInstances = insertedInstances.map((inst) => ({
|
|
119
|
-
name: inst.Name,
|
|
120
|
-
className: inst.ClassName,
|
|
121
|
-
path: getInstancePath(inst),
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
success: true,
|
|
126
|
-
assetId,
|
|
127
|
-
parentPath,
|
|
128
|
-
insertedCount: insertedInstances.size(),
|
|
129
|
-
instances: resultInstances,
|
|
130
|
-
sanitization: {
|
|
131
|
-
removedScriptCount: sanitization.removedScriptCount,
|
|
132
|
-
removedPackageLinkCount: sanitization.removedPackageLinkCount,
|
|
133
|
-
verifiedClean: true,
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (!insertSuccess) {
|
|
139
|
-
// Roll back any partially-parented children if a later operation failed.
|
|
140
|
-
for (const inserted of insertedInstances) {
|
|
141
|
-
pcall(() => {
|
|
142
|
-
inserted.Destroy();
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (wrapperModel) {
|
|
148
|
-
pcall(() => {
|
|
149
|
-
wrapperModel!.Destroy();
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
finishRecording(recordingId, insertSuccess);
|
|
154
|
-
|
|
155
|
-
if (!insertSuccess) {
|
|
156
|
-
return { error: `Failed to insert asset ${assetId}: ${tostring(insertResult)}` };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return insertResult;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function previewAsset(requestData: Record<string, unknown>) {
|
|
163
|
-
const assetId = requestData.assetId as number;
|
|
164
|
-
const includeProperties = (requestData.includeProperties as boolean) ?? false;
|
|
165
|
-
const maxDepth = (requestData.maxDepth as number) ?? 4;
|
|
166
|
-
|
|
167
|
-
if (!assetId) {
|
|
168
|
-
return { error: "assetId is required" };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const [loadSuccess, wrapperModel] = pcall(() => {
|
|
172
|
-
return (AssetService as unknown as { LoadAssetAsync(id: number): Instance }).LoadAssetAsync(assetId);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
if (!loadSuccess || !wrapperModel) {
|
|
176
|
-
return { error: formatAssetLoadFailure(assetId, wrapperModel) };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Previewed assets never enter the DataModel.
|
|
180
|
-
const [unparentedOk] = pcall(() => {
|
|
181
|
-
(wrapperModel as Instance).Parent = undefined;
|
|
182
|
-
});
|
|
183
|
-
if (!unparentedOk || (wrapperModel as Instance).Parent !== undefined) {
|
|
184
|
-
destroyImportedRoot(wrapperModel as Instance);
|
|
185
|
-
return { error: `Failed to keep asset ${assetId} unparented for preview` };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Security and visual-capability tracking always scan the full descendant
|
|
189
|
-
// hierarchy. maxDepth only affects the display tree below.
|
|
190
|
-
let totalInstances = 0;
|
|
191
|
-
const classCounts: Record<string, number> = {};
|
|
192
|
-
let hasAnimations = false;
|
|
193
|
-
let hasSounds = false;
|
|
194
|
-
let hasParticles = false;
|
|
195
|
-
let hasVfx = false;
|
|
196
|
-
let hasDecalsOrTextures = false;
|
|
197
|
-
let hasMeshes = false;
|
|
198
|
-
let hasLights = false;
|
|
199
|
-
let hasAttachments = false;
|
|
200
|
-
const soundReferences: Record<string, unknown>[] = [];
|
|
201
|
-
const uniqueSoundContentIds = new Set<string>();
|
|
202
|
-
|
|
203
|
-
function recordSummary(instance: Instance) {
|
|
204
|
-
totalInstances++;
|
|
205
|
-
const className = instance.ClassName;
|
|
206
|
-
classCounts[className] = (classCounts[className] ?? 0) + 1;
|
|
207
|
-
|
|
208
|
-
if (className === "Animation" || className === "AnimationController" || className === "Animator") hasAnimations = true;
|
|
209
|
-
if (instance.IsA("Sound")) {
|
|
210
|
-
hasSounds = true;
|
|
211
|
-
const sound = instance as Sound;
|
|
212
|
-
const soundId = sound.SoundId;
|
|
213
|
-
if (soundId !== "") uniqueSoundContentIds.add(soundId);
|
|
214
|
-
soundReferences.push({
|
|
215
|
-
path: sound.GetFullName(),
|
|
216
|
-
name: sound.Name,
|
|
217
|
-
className,
|
|
218
|
-
soundId,
|
|
219
|
-
volume: sound.Volume,
|
|
220
|
-
playbackSpeed: sound.PlaybackSpeed,
|
|
221
|
-
looped: sound.Looped,
|
|
222
|
-
isLoaded: sound.IsLoaded,
|
|
223
|
-
timeLength: sound.TimeLength,
|
|
224
|
-
rollOffMode: tostring(sound.RollOffMode),
|
|
225
|
-
rollOffMinDistance: sound.RollOffMinDistance,
|
|
226
|
-
rollOffMaxDistance: sound.RollOffMaxDistance,
|
|
227
|
-
});
|
|
228
|
-
} else if (instance.IsA("AudioPlayer")) {
|
|
229
|
-
hasSounds = true;
|
|
230
|
-
const audioPlayer = instance as AudioPlayer;
|
|
231
|
-
const soundId = tostring(audioPlayer.Asset);
|
|
232
|
-
if (soundId !== "") uniqueSoundContentIds.add(soundId);
|
|
233
|
-
soundReferences.push({
|
|
234
|
-
path: audioPlayer.GetFullName(),
|
|
235
|
-
name: audioPlayer.Name,
|
|
236
|
-
className,
|
|
237
|
-
soundId,
|
|
238
|
-
volume: audioPlayer.Volume,
|
|
239
|
-
playbackSpeed: audioPlayer.PlaybackSpeed,
|
|
240
|
-
looped: audioPlayer.Looping,
|
|
241
|
-
autoPlay: audioPlayer.AutoPlay,
|
|
242
|
-
isLoaded: audioPlayer.IsReady,
|
|
243
|
-
timeLength: audioPlayer.TimeLength,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
if (
|
|
247
|
-
instance.IsA("ParticleEmitter") ||
|
|
248
|
-
className === "Fire" ||
|
|
249
|
-
className === "Smoke" ||
|
|
250
|
-
className === "Sparkles"
|
|
251
|
-
) {
|
|
252
|
-
hasParticles = true;
|
|
253
|
-
hasVfx = true;
|
|
254
|
-
}
|
|
255
|
-
if (instance.IsA("Beam") || instance.IsA("Trail")) hasVfx = true;
|
|
256
|
-
if (instance.IsA("Decal") || instance.IsA("Texture")) hasDecalsOrTextures = true;
|
|
257
|
-
if (instance.IsA("MeshPart") || instance.IsA("SpecialMesh")) hasMeshes = true;
|
|
258
|
-
if (instance.IsA("Light")) hasLights = true;
|
|
259
|
-
if (instance.IsA("Attachment")) hasAttachments = true;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
recordSummary(wrapperModel as Instance);
|
|
263
|
-
for (const descendant of (wrapperModel as Instance).GetDescendants()) {
|
|
264
|
-
recordSummary(descendant);
|
|
265
|
-
}
|
|
266
|
-
const forbiddenScan = scanForbiddenImportedInstances(wrapperModel as Instance);
|
|
267
|
-
|
|
268
|
-
function buildHierarchy(instance: Instance, depth: number): Record<string, unknown> {
|
|
269
|
-
const className = instance.ClassName;
|
|
270
|
-
|
|
271
|
-
const node: Record<string, unknown> = {
|
|
272
|
-
name: instance.Name,
|
|
273
|
-
className,
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
if (includeProperties) {
|
|
277
|
-
const props: Record<string, unknown> = {};
|
|
278
|
-
|
|
279
|
-
if (instance.IsA("BasePart")) {
|
|
280
|
-
props.size = { x: instance.Size.X, y: instance.Size.Y, z: instance.Size.Z };
|
|
281
|
-
props.position = { x: instance.Position.X, y: instance.Position.Y, z: instance.Position.Z };
|
|
282
|
-
props.material = tostring(instance.Material);
|
|
283
|
-
props.color = `${instance.Color.R}, ${instance.Color.G}, ${instance.Color.B}`;
|
|
284
|
-
props.transparency = instance.Transparency;
|
|
285
|
-
props.anchored = instance.Anchored;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (instance.IsA("MeshPart")) {
|
|
289
|
-
const meshPart = instance as MeshPart;
|
|
290
|
-
props.meshId = meshPart.MeshId;
|
|
291
|
-
props.textureId = meshPart.TextureID;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (instance.IsA("Model")) {
|
|
295
|
-
const model = instance as Model;
|
|
296
|
-
if (model.PrimaryPart) {
|
|
297
|
-
props.primaryPart = model.PrimaryPart.Name;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (className === "Decal" || className === "Texture") {
|
|
302
|
-
const [ok, texId] = pcall(() => (instance as unknown as { Texture: string }).Texture);
|
|
303
|
-
if (ok) props.texture = texId;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (instance.IsA("Sound")) {
|
|
307
|
-
props.soundId = (instance as Sound).SoundId;
|
|
308
|
-
} else if (instance.IsA("AudioPlayer")) {
|
|
309
|
-
props.soundId = tostring((instance as AudioPlayer).Asset);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Only include props if there are any
|
|
313
|
-
let hasProps = false;
|
|
314
|
-
for (const _ of pairs(props)) {
|
|
315
|
-
hasProps = true;
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
if (hasProps) {
|
|
319
|
-
node.properties = props;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (depth < maxDepth) {
|
|
324
|
-
const childNodes: Record<string, unknown>[] = [];
|
|
325
|
-
for (const child of instance.GetChildren()) {
|
|
326
|
-
childNodes.push(buildHierarchy(child, depth + 1));
|
|
327
|
-
}
|
|
328
|
-
if (childNodes.size() > 0) {
|
|
329
|
-
node.children = childNodes;
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
const childCount = instance.GetChildren().size();
|
|
333
|
-
if (childCount > 0) {
|
|
334
|
-
node.childCount = childCount;
|
|
335
|
-
node.truncated = true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return node;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const [previewSuccess, previewResult] = pcall(() => {
|
|
343
|
-
const hierarchyRoots: Record<string, unknown>[] = [];
|
|
344
|
-
for (const child of (wrapperModel as Instance).GetChildren()) {
|
|
345
|
-
hierarchyRoots.push(buildHierarchy(child, 0));
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return {
|
|
349
|
-
success: true,
|
|
350
|
-
assetId,
|
|
351
|
-
hierarchy: hierarchyRoots,
|
|
352
|
-
sounds: soundReferences,
|
|
353
|
-
summary: {
|
|
354
|
-
totalInstances,
|
|
355
|
-
classCounts,
|
|
356
|
-
hasScripts: forbiddenScan.scripts.size() > 0,
|
|
357
|
-
scriptCount: forbiddenScan.scripts.size(),
|
|
358
|
-
hasPackageLinks: forbiddenScan.packageLinks.size() > 0,
|
|
359
|
-
packageLinkCount: forbiddenScan.packageLinks.size(),
|
|
360
|
-
hasAnimations,
|
|
361
|
-
hasSounds,
|
|
362
|
-
soundCount: soundReferences.size(),
|
|
363
|
-
uniqueSoundContentIdCount: uniqueSoundContentIds.size(),
|
|
364
|
-
hasParticles,
|
|
365
|
-
hasVfx,
|
|
366
|
-
hasDecalsOrTextures,
|
|
367
|
-
hasMeshes,
|
|
368
|
-
hasLights,
|
|
369
|
-
hasAttachments,
|
|
370
|
-
securityScanDepth: "unlimited",
|
|
371
|
-
scriptSourceExposed: false,
|
|
372
|
-
insertPolicy: "All LuaSourceContainer and PackageLink instances are stripped and verified before insertion.",
|
|
373
|
-
},
|
|
374
|
-
};
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
pcall(() => {
|
|
378
|
-
(wrapperModel as Instance).Destroy();
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
if (!previewSuccess) {
|
|
382
|
-
return { error: `Failed to preview asset ${assetId}: ${tostring(previewResult)}` };
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return previewResult;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export = {
|
|
389
|
-
insertAsset,
|
|
390
|
-
previewAsset,
|
|
391
|
-
};
|