@morscherlab/mld-sdk 0.7.0 → 0.7.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/composables/index.d.ts +1 -0
- package/dist/composables/index.js +2 -0
- package/dist/composables/index.js.map +1 -1
- package/dist/composables/usePluginConfig.d.ts +12 -0
- package/dist/composables/usePluginConfig.js +77 -0
- package/dist/composables/usePluginConfig.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -2
- package/package.json +1 -1
- package/src/composables/index.ts +1 -0
- package/src/composables/usePluginConfig.ts +92 -0
- package/src/index.ts +3 -0
- package/src/styles/components/number-input.css +4 -1
|
@@ -16,4 +16,5 @@ export { useSequenceUtils, type SequenceType, type SequenceStats, } from './useS
|
|
|
16
16
|
export { parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, } from './useTimeUtils';
|
|
17
17
|
export { useScheduleDrag } from './useScheduleDrag';
|
|
18
18
|
export { useFormBuilder, evaluateCondition } from './useFormBuilder';
|
|
19
|
+
export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig';
|
|
19
20
|
export { getFieldRegistryEntry, getTypeDefault, type RegistryEntry, } from './formBuilderRegistry';
|
|
@@ -16,6 +16,7 @@ import { useSequenceUtils } from "./useSequenceUtils.js";
|
|
|
16
16
|
import { addMinutes, compareTime, durationMinutes, findAvailableSlots, formatDuration, formatTime, generateTimeSlots, isTimeInRange, parseTime, rangesOverlap, snapToSlot } from "./useTimeUtils.js";
|
|
17
17
|
import { useScheduleDrag } from "./useScheduleDrag.js";
|
|
18
18
|
import { evaluateCondition, useFormBuilder } from "./useFormBuilder.js";
|
|
19
|
+
import { usePluginConfig } from "./usePluginConfig.js";
|
|
19
20
|
import { getFieldRegistryEntry, getTypeDefault } from "./formBuilderRegistry.js";
|
|
20
21
|
export {
|
|
21
22
|
ATOMIC_WEIGHTS,
|
|
@@ -44,6 +45,7 @@ export {
|
|
|
44
45
|
useFormBuilder,
|
|
45
46
|
usePasskey,
|
|
46
47
|
usePlatformContext,
|
|
48
|
+
usePluginConfig,
|
|
47
49
|
useProtocolTemplates,
|
|
48
50
|
useRackEditor,
|
|
49
51
|
useScheduleDrag,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Ref, ComputedRef } from 'vue';
|
|
2
|
+
export interface UsePluginConfigReturn {
|
|
3
|
+
config: Ref<Record<string, unknown>>;
|
|
4
|
+
isLoading: Ref<boolean>;
|
|
5
|
+
isSaving: Ref<boolean>;
|
|
6
|
+
error: Ref<string | null>;
|
|
7
|
+
isDirty: ComputedRef<boolean>;
|
|
8
|
+
load: () => Promise<void>;
|
|
9
|
+
save: () => Promise<boolean>;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function usePluginConfig(pluginName?: string): UsePluginConfigReturn;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { computed, ref, onMounted } from "vue";
|
|
2
|
+
import { useApi } from "./useApi.js";
|
|
3
|
+
import { usePlatformContext } from "./usePlatformContext.js";
|
|
4
|
+
function usePluginConfig(pluginName) {
|
|
5
|
+
const api = useApi();
|
|
6
|
+
const { plugin } = usePlatformContext();
|
|
7
|
+
const resolvedName = computed(() => {
|
|
8
|
+
var _a;
|
|
9
|
+
return pluginName ?? ((_a = plugin.value) == null ? void 0 : _a.name) ?? "";
|
|
10
|
+
});
|
|
11
|
+
const config = ref({});
|
|
12
|
+
const savedConfig = ref({});
|
|
13
|
+
const isLoading = ref(false);
|
|
14
|
+
const isSaving = ref(false);
|
|
15
|
+
const error = ref(null);
|
|
16
|
+
const isDirty = computed(() => {
|
|
17
|
+
return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value);
|
|
18
|
+
});
|
|
19
|
+
async function load() {
|
|
20
|
+
const name = resolvedName.value;
|
|
21
|
+
if (!name) return;
|
|
22
|
+
isLoading.value = true;
|
|
23
|
+
error.value = null;
|
|
24
|
+
try {
|
|
25
|
+
const response = await api.get(
|
|
26
|
+
`/api/plugins/${encodeURIComponent(name)}/config`
|
|
27
|
+
);
|
|
28
|
+
config.value = { ...response.config };
|
|
29
|
+
savedConfig.value = { ...response.config };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
error.value = e instanceof Error ? e.message : "Failed to load plugin config";
|
|
32
|
+
} finally {
|
|
33
|
+
isLoading.value = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function save() {
|
|
37
|
+
const name = resolvedName.value;
|
|
38
|
+
if (!name) return false;
|
|
39
|
+
isSaving.value = true;
|
|
40
|
+
error.value = null;
|
|
41
|
+
try {
|
|
42
|
+
const response = await api.put(
|
|
43
|
+
`/api/plugins/${encodeURIComponent(name)}/config`,
|
|
44
|
+
{ config: config.value }
|
|
45
|
+
);
|
|
46
|
+
config.value = { ...response.config };
|
|
47
|
+
savedConfig.value = { ...response.config };
|
|
48
|
+
return true;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
error.value = e instanceof Error ? e.message : "Failed to save plugin config";
|
|
51
|
+
return false;
|
|
52
|
+
} finally {
|
|
53
|
+
isSaving.value = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function reset() {
|
|
57
|
+
config.value = { ...savedConfig.value };
|
|
58
|
+
error.value = null;
|
|
59
|
+
}
|
|
60
|
+
onMounted(() => {
|
|
61
|
+
load();
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
config,
|
|
65
|
+
isLoading,
|
|
66
|
+
isSaving,
|
|
67
|
+
error,
|
|
68
|
+
isDirty,
|
|
69
|
+
load,
|
|
70
|
+
save,
|
|
71
|
+
reset
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
usePluginConfig
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=usePluginConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePluginConfig.js","sources":["../../src/composables/usePluginConfig.ts"],"sourcesContent":["import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { usePlatformContext } from './usePlatformContext'\n\nexport interface UsePluginConfigReturn {\n config: Ref<Record<string, unknown>>\n isLoading: Ref<boolean>\n isSaving: Ref<boolean>\n error: Ref<string | null>\n isDirty: ComputedRef<boolean>\n load: () => Promise<void>\n save: () => Promise<boolean>\n reset: () => void\n}\n\nexport function usePluginConfig(pluginName?: string): UsePluginConfigReturn {\n const api = useApi()\n const { plugin } = usePlatformContext()\n\n const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? '')\n\n const config = ref<Record<string, unknown>>({})\n const savedConfig = ref<Record<string, unknown>>({})\n const isLoading = ref(false)\n const isSaving = ref(false)\n const error = ref<string | null>(null)\n\n const isDirty = computed(() => {\n return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)\n })\n\n async function load(): Promise<void> {\n const name = resolvedName.value\n if (!name) return\n\n isLoading.value = true\n error.value = null\n try {\n const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(\n `/api/plugins/${encodeURIComponent(name)}/config`,\n )\n config.value = { ...response.config }\n savedConfig.value = { ...response.config }\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load plugin config'\n } finally {\n isLoading.value = false\n }\n }\n\n async function save(): Promise<boolean> {\n const name = resolvedName.value\n if (!name) return false\n\n isSaving.value = true\n error.value = null\n try {\n const response = await api.put<{ plugin_name: string; config: Record<string, unknown> }>(\n `/api/plugins/${encodeURIComponent(name)}/config`,\n { config: config.value },\n )\n config.value = { ...response.config }\n savedConfig.value = { ...response.config }\n return true\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to save plugin config'\n return false\n } finally {\n isSaving.value = false\n }\n }\n\n function reset(): void {\n config.value = { ...savedConfig.value }\n error.value = null\n }\n\n onMounted(() => {\n load()\n })\n\n return {\n config,\n isLoading,\n isSaving,\n error,\n isDirty,\n load,\n save,\n reset,\n }\n}\n"],"names":[],"mappings":";;;AAeO,SAAS,gBAAgB,YAA4C;AAC1E,QAAM,MAAM,OAAA;AACZ,QAAM,EAAE,OAAA,IAAW,mBAAA;AAEnB,QAAM,eAAe,SAAS,MAAA;;AAAM,2BAAc,YAAO,UAAP,mBAAc,SAAQ;AAAA,GAAE;AAE1E,QAAM,SAAS,IAA6B,EAAE;AAC9C,QAAM,cAAc,IAA6B,EAAE;AACnD,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,WAAW,IAAI,KAAK;AAC1B,QAAM,QAAQ,IAAmB,IAAI;AAErC,QAAM,UAAU,SAAS,MAAM;AAC7B,WAAO,KAAK,UAAU,OAAO,KAAK,MAAM,KAAK,UAAU,YAAY,KAAK;AAAA,EAC1E,CAAC;AAED,iBAAe,OAAsB;AACnC,UAAM,OAAO,aAAa;AAC1B,QAAI,CAAC,KAAM;AAEX,cAAU,QAAQ;AAClB,UAAM,QAAQ;AACd,QAAI;AACF,YAAM,WAAW,MAAM,IAAI;AAAA,QACzB,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,MAAA;AAE1C,aAAO,QAAQ,EAAE,GAAG,SAAS,OAAA;AAC7B,kBAAY,QAAQ,EAAE,GAAG,SAAS,OAAA;AAAA,IACpC,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAAA,IACjD,UAAA;AACE,gBAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA,iBAAe,OAAyB;AACtC,UAAM,OAAO,aAAa;AAC1B,QAAI,CAAC,KAAM,QAAO;AAElB,aAAS,QAAQ;AACjB,UAAM,QAAQ;AACd,QAAI;AACF,YAAM,WAAW,MAAM,IAAI;AAAA,QACzB,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,QACxC,EAAE,QAAQ,OAAO,MAAA;AAAA,MAAM;AAEzB,aAAO,QAAQ,EAAE,GAAG,SAAS,OAAA;AAC7B,kBAAY,QAAQ,EAAE,GAAG,SAAS,OAAA;AAClC,aAAO;AAAA,IACT,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAC/C,aAAO;AAAA,IACT,UAAA;AACE,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AAEA,WAAS,QAAc;AACrB,WAAO,QAAQ,EAAE,GAAG,YAAY,MAAA;AAChC,UAAM,QAAQ;AAAA,EAChB;AAEA,YAAU,MAAM;AACd,SAAA;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MLDSdk, default } from './install';
|
|
2
2
|
export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, } from './components';
|
|
3
|
-
export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, } from './composables';
|
|
3
|
+
export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, } from './composables';
|
|
4
4
|
export { useAuthStore, useSettingsStore, colorPalettes, type SettingsState, } from './stores';
|
|
5
5
|
export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, AlertType, Toast, TabItem, SelectOption, RadioOption, FormFieldProps, SidebarToolSection, CollapsibleState, TopBarVariant, TopBarPage, TopBarTab, TopBarTabOption, TopBarSettingsConfig, WellPlateFormat, WellState, WellPlateSelectionMode, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellLegendItem, Rack, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, FileUploaderMode, SegmentedOption, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, TooltipPosition, ConfirmVariant, SettingsTab, NumberNotation, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, ResourceStatus, ResourceSpec, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -156,6 +156,7 @@ import { ATOMIC_WEIGHTS, useChemicalFormula } from "./composables/useChemicalFor
|
|
|
156
156
|
import { useSequenceUtils } from "./composables/useSequenceUtils.js";
|
|
157
157
|
import { addMinutes, compareTime, durationMinutes, findAvailableSlots, formatDuration, formatTime, generateTimeSlots, isTimeInRange, parseTime, rangesOverlap, snapToSlot } from "./composables/useTimeUtils.js";
|
|
158
158
|
import { useScheduleDrag } from "./composables/useScheduleDrag.js";
|
|
159
|
+
import { usePluginConfig } from "./composables/usePluginConfig.js";
|
|
159
160
|
import { useAuthStore } from "./stores/auth.js";
|
|
160
161
|
import { colorPalettes, useSettingsStore } from "./stores/settings.js";
|
|
161
162
|
export {
|
|
@@ -253,6 +254,7 @@ export {
|
|
|
253
254
|
useDoseCalculator,
|
|
254
255
|
usePasskey,
|
|
255
256
|
usePlatformContext,
|
|
257
|
+
usePluginConfig,
|
|
256
258
|
useProtocolTemplates,
|
|
257
259
|
useRackEditor,
|
|
258
260
|
useScheduleDrag,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/styles.css
CHANGED
|
@@ -3363,6 +3363,7 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
|
|
|
3363
3363
|
/* NumberInput Component Styles */
|
|
3364
3364
|
.mld-number-input {
|
|
3365
3365
|
display: inline-flex;
|
|
3366
|
+
max-width: 100%;
|
|
3366
3367
|
border-radius: 0.5rem;
|
|
3367
3368
|
border: 1px solid var(--border-color);
|
|
3368
3369
|
overflow: hidden;
|
|
@@ -3395,6 +3396,7 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
|
|
|
3395
3396
|
display: flex;
|
|
3396
3397
|
align-items: center;
|
|
3397
3398
|
justify-content: center;
|
|
3399
|
+
flex-shrink: 0;
|
|
3398
3400
|
background-color: var(--bg-tertiary);
|
|
3399
3401
|
color: var(--text-muted);
|
|
3400
3402
|
border: none;
|
|
@@ -3429,7 +3431,8 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
|
|
|
3429
3431
|
height: 1rem;
|
|
3430
3432
|
}
|
|
3431
3433
|
.mld-number-input__input {
|
|
3432
|
-
|
|
3434
|
+
flex: 1;
|
|
3435
|
+
min-width: 0;
|
|
3433
3436
|
text-align: center;
|
|
3434
3437
|
background-color: var(--bg-secondary);
|
|
3435
3438
|
color: var(--text-primary);
|
|
@@ -14652,6 +14655,7 @@ to { transform: rotate(360deg);
|
|
|
14652
14655
|
/* NumberInput Component Styles */
|
|
14653
14656
|
.mld-number-input {
|
|
14654
14657
|
display: inline-flex;
|
|
14658
|
+
max-width: 100%;
|
|
14655
14659
|
border-radius: 0.5rem;
|
|
14656
14660
|
border: 1px solid var(--border-color);
|
|
14657
14661
|
overflow: hidden;
|
|
@@ -14684,6 +14688,7 @@ to { transform: rotate(360deg);
|
|
|
14684
14688
|
display: flex;
|
|
14685
14689
|
align-items: center;
|
|
14686
14690
|
justify-content: center;
|
|
14691
|
+
flex-shrink: 0;
|
|
14687
14692
|
background-color: var(--bg-tertiary);
|
|
14688
14693
|
color: var(--text-muted);
|
|
14689
14694
|
border: none;
|
|
@@ -14718,7 +14723,8 @@ to { transform: rotate(360deg);
|
|
|
14718
14723
|
height: 1rem;
|
|
14719
14724
|
}
|
|
14720
14725
|
.mld-number-input__input {
|
|
14721
|
-
|
|
14726
|
+
flex: 1;
|
|
14727
|
+
min-width: 0;
|
|
14722
14728
|
text-align: center;
|
|
14723
14729
|
background-color: var(--bg-secondary);
|
|
14724
14730
|
color: var(--text-primary);
|
package/package.json
CHANGED
package/src/composables/index.ts
CHANGED
|
@@ -86,6 +86,7 @@ export {
|
|
|
86
86
|
} from './useTimeUtils'
|
|
87
87
|
export { useScheduleDrag } from './useScheduleDrag'
|
|
88
88
|
export { useFormBuilder, evaluateCondition } from './useFormBuilder'
|
|
89
|
+
export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig'
|
|
89
90
|
export {
|
|
90
91
|
getFieldRegistryEntry,
|
|
91
92
|
getTypeDefault,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
|
|
2
|
+
import { useApi } from './useApi'
|
|
3
|
+
import { usePlatformContext } from './usePlatformContext'
|
|
4
|
+
|
|
5
|
+
export interface UsePluginConfigReturn {
|
|
6
|
+
config: Ref<Record<string, unknown>>
|
|
7
|
+
isLoading: Ref<boolean>
|
|
8
|
+
isSaving: Ref<boolean>
|
|
9
|
+
error: Ref<string | null>
|
|
10
|
+
isDirty: ComputedRef<boolean>
|
|
11
|
+
load: () => Promise<void>
|
|
12
|
+
save: () => Promise<boolean>
|
|
13
|
+
reset: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
17
|
+
const api = useApi()
|
|
18
|
+
const { plugin } = usePlatformContext()
|
|
19
|
+
|
|
20
|
+
const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? '')
|
|
21
|
+
|
|
22
|
+
const config = ref<Record<string, unknown>>({})
|
|
23
|
+
const savedConfig = ref<Record<string, unknown>>({})
|
|
24
|
+
const isLoading = ref(false)
|
|
25
|
+
const isSaving = ref(false)
|
|
26
|
+
const error = ref<string | null>(null)
|
|
27
|
+
|
|
28
|
+
const isDirty = computed(() => {
|
|
29
|
+
return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
async function load(): Promise<void> {
|
|
33
|
+
const name = resolvedName.value
|
|
34
|
+
if (!name) return
|
|
35
|
+
|
|
36
|
+
isLoading.value = true
|
|
37
|
+
error.value = null
|
|
38
|
+
try {
|
|
39
|
+
const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(
|
|
40
|
+
`/api/plugins/${encodeURIComponent(name)}/config`,
|
|
41
|
+
)
|
|
42
|
+
config.value = { ...response.config }
|
|
43
|
+
savedConfig.value = { ...response.config }
|
|
44
|
+
} catch (e) {
|
|
45
|
+
error.value = e instanceof Error ? e.message : 'Failed to load plugin config'
|
|
46
|
+
} finally {
|
|
47
|
+
isLoading.value = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function save(): Promise<boolean> {
|
|
52
|
+
const name = resolvedName.value
|
|
53
|
+
if (!name) return false
|
|
54
|
+
|
|
55
|
+
isSaving.value = true
|
|
56
|
+
error.value = null
|
|
57
|
+
try {
|
|
58
|
+
const response = await api.put<{ plugin_name: string; config: Record<string, unknown> }>(
|
|
59
|
+
`/api/plugins/${encodeURIComponent(name)}/config`,
|
|
60
|
+
{ config: config.value },
|
|
61
|
+
)
|
|
62
|
+
config.value = { ...response.config }
|
|
63
|
+
savedConfig.value = { ...response.config }
|
|
64
|
+
return true
|
|
65
|
+
} catch (e) {
|
|
66
|
+
error.value = e instanceof Error ? e.message : 'Failed to save plugin config'
|
|
67
|
+
return false
|
|
68
|
+
} finally {
|
|
69
|
+
isSaving.value = false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function reset(): void {
|
|
74
|
+
config.value = { ...savedConfig.value }
|
|
75
|
+
error.value = null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
load()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
config,
|
|
84
|
+
isLoading,
|
|
85
|
+
isSaving,
|
|
86
|
+
error,
|
|
87
|
+
isDirty,
|
|
88
|
+
load,
|
|
89
|
+
save,
|
|
90
|
+
reset,
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
.mld-number-input {
|
|
4
4
|
display: inline-flex;
|
|
5
|
+
max-width: 100%;
|
|
5
6
|
border-radius: 0.5rem;
|
|
6
7
|
border: 1px solid var(--border-color);
|
|
7
8
|
overflow: hidden;
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
display: flex;
|
|
43
44
|
align-items: center;
|
|
44
45
|
justify-content: center;
|
|
46
|
+
flex-shrink: 0;
|
|
45
47
|
background-color: var(--bg-tertiary);
|
|
46
48
|
color: var(--text-muted);
|
|
47
49
|
border: none;
|
|
@@ -85,7 +87,8 @@
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
.mld-number-input__input {
|
|
88
|
-
|
|
90
|
+
flex: 1;
|
|
91
|
+
min-width: 0;
|
|
89
92
|
text-align: center;
|
|
90
93
|
background-color: var(--bg-secondary);
|
|
91
94
|
color: var(--text-primary);
|