@platforma-open/milaboratories.immune-assay-data.ui 1.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/.turbo/turbo-build.log +19 -0
- package/CHANGELOG.md +9 -0
- package/dist/assets/index-BsJvu2Wd.css +1 -0
- package/dist/assets/index-hSG_lBZY.js +13330 -0
- package/dist/assets/index-hSG_lBZY.js.map +1 -0
- package/dist/index.html +13 -0
- package/eslint.config.mjs +4 -0
- package/index.html +12 -0
- package/package.json +31 -0
- package/src/app.ts +13 -0
- package/src/importFile.ts +184 -0
- package/src/main.ts +6 -0
- package/src/pages/MainPage.vue +191 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +12 -0
- package/vite.config.ts +11 -0
- package/vitest.config.mts +7 -0
package/dist/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob: https: 'unsafe-eval';">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-hSG_lBZY.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BsJvu2Wd.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="app"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob: https: 'unsafe-eval';">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@platforma-open/milaboratories.immune-assay-data.ui",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@platforma-sdk/ui-vue": "^1.31.16",
|
|
7
|
+
"@platforma-sdk/model": "^1.31.16",
|
|
8
|
+
"@milaboratories/graph-maker": "^1.1.101",
|
|
9
|
+
"vue": "^3.5.13",
|
|
10
|
+
"@biowasm/aioli": "~3.2.1",
|
|
11
|
+
"sass-embedded": "^1.77.8",
|
|
12
|
+
"xlsx": "../vendor/xlsx-0.20.3.tgz",
|
|
13
|
+
"@platforma-open/milaboratories.immune-assay-data.model": "1.0.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
17
|
+
"typescript": "~5.5.4",
|
|
18
|
+
"vite": "^6.2.2",
|
|
19
|
+
"vitest": "^2.1.8",
|
|
20
|
+
"vue-tsc": "^2.2.8",
|
|
21
|
+
"@platforma-sdk/eslint-config": "^1.0.3"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "vite",
|
|
25
|
+
"watch": "vue-tsc && vite build --watch",
|
|
26
|
+
"build": "vue-tsc -b && vite build",
|
|
27
|
+
"lint": "eslint .",
|
|
28
|
+
"preview": "vite preview",
|
|
29
|
+
"test": "vitest"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { model } from '@platforma-open/milaboratories.immune-assay-data.model';
|
|
2
|
+
import { defineApp } from '@platforma-sdk/ui-vue';
|
|
3
|
+
import MainPage from './pages/MainPage.vue';
|
|
4
|
+
|
|
5
|
+
export const sdkPlugin = defineApp(model, () => {
|
|
6
|
+
return {
|
|
7
|
+
routes: {
|
|
8
|
+
'/': () => MainPage,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const useApp = sdkPlugin.useApp;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getFileNameFromHandle, getRawPlatformaInstance, type LocalImportFileHandle } from '@platforma-sdk/model';
|
|
2
|
+
|
|
3
|
+
import { useApp } from './app';
|
|
4
|
+
|
|
5
|
+
import type { ImportColumnInfo } from '@platforma-open/milaboratories.immune-assay-data.model';
|
|
6
|
+
import * as XLSX from 'xlsx';
|
|
7
|
+
|
|
8
|
+
// Define a more specific type for raw Excel data
|
|
9
|
+
type TableRow = string[];
|
|
10
|
+
type TableData = TableRow[];
|
|
11
|
+
|
|
12
|
+
// Helper function to infer data type from a value
|
|
13
|
+
function inferValueType(value: unknown): 'Int' | 'Double' | 'String' {
|
|
14
|
+
if (value === null || value === undefined || value === '') {
|
|
15
|
+
return 'String'; // Default to String for empty values
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const stringValue = String(value).trim();
|
|
19
|
+
|
|
20
|
+
// Try to parse as integer
|
|
21
|
+
const intValue = parseInt(stringValue, 10);
|
|
22
|
+
if (!isNaN(intValue) && String(intValue) === stringValue) {
|
|
23
|
+
return 'Int';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Try to parse as double/float
|
|
27
|
+
const floatValue = parseFloat(stringValue);
|
|
28
|
+
if (!isNaN(floatValue) && isFinite(floatValue) && /^-?\d*\.?\d+([eE][+-]?\d+)?$/.test(stringValue)) {
|
|
29
|
+
return 'Double';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return 'String';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Helper function to infer column type from array of values
|
|
36
|
+
function inferColumnType(values: unknown[]): 'Int' | 'Double' | 'String' {
|
|
37
|
+
const typeCounts = { Int: 0, Double: 0, String: 0 };
|
|
38
|
+
let nonEmptyCount = 0;
|
|
39
|
+
|
|
40
|
+
for (const value of values) {
|
|
41
|
+
if (value !== null && value !== undefined && String(value).trim() !== '') {
|
|
42
|
+
nonEmptyCount++;
|
|
43
|
+
const type = inferValueType(value);
|
|
44
|
+
typeCounts[type]++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If no non-empty values, default to String
|
|
49
|
+
if (nonEmptyCount === 0) {
|
|
50
|
+
return 'String';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If any value is String, the whole column is String
|
|
54
|
+
if (typeCounts.String > 0) {
|
|
55
|
+
return 'String';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If any value is Double, the whole column is Double
|
|
59
|
+
if (typeCounts.Double > 0) {
|
|
60
|
+
return 'Double';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Otherwise, it's Int
|
|
64
|
+
return 'Int';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Helper function to infer sequence type from array of values
|
|
68
|
+
function inferSequenceType(values: unknown[]): 'nucleotide' | 'aminoacid' | undefined {
|
|
69
|
+
const validSequences = values
|
|
70
|
+
.filter((v) => v !== null && v !== undefined && String(v).trim() !== '')
|
|
71
|
+
.map((v) => String(v).trim())
|
|
72
|
+
.filter((v) => v.length >= 3) // Only consider sequences of meaningful length
|
|
73
|
+
.slice(0, 1000); // Check only first 1000 sequences
|
|
74
|
+
|
|
75
|
+
if (validSequences.length === 0) return undefined;
|
|
76
|
+
|
|
77
|
+
// Count characters across all sequences
|
|
78
|
+
let nucleotideCharCount = 0;
|
|
79
|
+
let aminoAcidCharCount = 0;
|
|
80
|
+
let totalCharCount = 0;
|
|
81
|
+
|
|
82
|
+
// Basic nucleotide characters
|
|
83
|
+
const nucleotideChars = new Set(['A', 'T', 'G', 'C', 'N']);
|
|
84
|
+
// Standard 20 amino acids plus X and * (excluding nucleotide overlap)
|
|
85
|
+
const aminoAcidOnlyChars = new Set(['R', 'D', 'E', 'Q', 'H', 'I', 'L', 'K', 'M', 'F', 'P', 'S', 'W', 'Y', 'V', 'X', '*']);
|
|
86
|
+
|
|
87
|
+
for (const sequence of validSequences) {
|
|
88
|
+
const upperSeq = sequence.toUpperCase();
|
|
89
|
+
for (const char of upperSeq) {
|
|
90
|
+
if (char === '-') {
|
|
91
|
+
// Skip gap characters
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (nucleotideChars.has(char)) {
|
|
96
|
+
nucleotideCharCount++;
|
|
97
|
+
} else if (aminoAcidOnlyChars.has(char)) {
|
|
98
|
+
aminoAcidCharCount++;
|
|
99
|
+
}
|
|
100
|
+
totalCharCount++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const totalValidCharCount = nucleotideCharCount + aminoAcidCharCount;
|
|
105
|
+
|
|
106
|
+
// Need at least some valid characters to make a decision
|
|
107
|
+
if (totalValidCharCount < 10 || totalValidCharCount / totalCharCount < 0.9) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Calculate percentages
|
|
112
|
+
const nucleotidePercentage = nucleotideCharCount / totalValidCharCount;
|
|
113
|
+
|
|
114
|
+
// If nucleotide characters dominate (>90%), it's nucleotide
|
|
115
|
+
if (nucleotidePercentage > 0.9) {
|
|
116
|
+
return 'nucleotide';
|
|
117
|
+
} else {
|
|
118
|
+
return 'aminoacid';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function importFile(file: LocalImportFileHandle) {
|
|
123
|
+
const app = useApp();
|
|
124
|
+
|
|
125
|
+
app.model.args.fileHandle = file;
|
|
126
|
+
|
|
127
|
+
// clear state
|
|
128
|
+
app.model.args.importColumns = undefined;
|
|
129
|
+
app.model.ui.fileImportError = undefined;
|
|
130
|
+
|
|
131
|
+
const fileName = getFileNameFromHandle(file);
|
|
132
|
+
const extension = fileName.split('.').pop();
|
|
133
|
+
app.model.args.fileExtension = extension;
|
|
134
|
+
|
|
135
|
+
if (extension === 'xlsx' || extension === 'xls') {
|
|
136
|
+
app.model.ui.fileImportError = 'XLS import is not yet available; use CSV instead';
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const data = await getRawPlatformaInstance().lsDriver.getLocalFileContent(file);
|
|
141
|
+
const wb = XLSX.read(data);
|
|
142
|
+
|
|
143
|
+
// @TODO: allow user to select worksheet
|
|
144
|
+
const worksheet = wb.Sheets[wb.SheetNames[0]];
|
|
145
|
+
|
|
146
|
+
const rawData = XLSX.utils.sheet_to_json(worksheet, {
|
|
147
|
+
header: 1,
|
|
148
|
+
raw: true,
|
|
149
|
+
blankrows: false,
|
|
150
|
+
}) as TableData;
|
|
151
|
+
|
|
152
|
+
const header = rawData[0];
|
|
153
|
+
if (!header) {
|
|
154
|
+
app.model.ui.fileImportError = 'File does not contain any data';
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (new Set(header).size !== header.length) {
|
|
159
|
+
app.model.ui.fileImportError = 'Headers in the input file must be unique';
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const importColumns: ImportColumnInfo[] = [];
|
|
164
|
+
|
|
165
|
+
// Process each column to infer its type
|
|
166
|
+
for (let colIndex = 0; colIndex < header.length; colIndex++) {
|
|
167
|
+
const columnHeader = header[colIndex];
|
|
168
|
+
const columnValues = rawData.slice(1).map((row) => row[colIndex]);
|
|
169
|
+
const inferredType = inferColumnType(columnValues);
|
|
170
|
+
const sequenceType = inferredType === 'String' ? inferSequenceType(columnValues) : undefined;
|
|
171
|
+
|
|
172
|
+
importColumns.push({
|
|
173
|
+
header: columnHeader,
|
|
174
|
+
type: inferredType,
|
|
175
|
+
sequenceType,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
app.model.args.importColumns = importColumns;
|
|
180
|
+
if (!importColumns.some((c) => c.sequenceType !== undefined)) {
|
|
181
|
+
app.model.ui.fileImportError = 'No sequence columns found';
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
ImportFileHandle,
|
|
4
|
+
LocalImportFileHandle,
|
|
5
|
+
PlRef,
|
|
6
|
+
} from '@platforma-sdk/model';
|
|
7
|
+
import {
|
|
8
|
+
plRefsEqual,
|
|
9
|
+
} from '@platforma-sdk/model';
|
|
10
|
+
import type {
|
|
11
|
+
PlAgDataTableSettings,
|
|
12
|
+
} from '@platforma-sdk/ui-vue';
|
|
13
|
+
import {
|
|
14
|
+
PlAgDataTableToolsPanel,
|
|
15
|
+
PlAgDataTableV2,
|
|
16
|
+
PlBlockPage,
|
|
17
|
+
PlBtnGhost,
|
|
18
|
+
PlDropdown,
|
|
19
|
+
PlDropdownRef,
|
|
20
|
+
PlFileInput,
|
|
21
|
+
PlMaskIcon24,
|
|
22
|
+
PlNumberField,
|
|
23
|
+
PlSectionSeparator,
|
|
24
|
+
PlSlideModal,
|
|
25
|
+
} from '@platforma-sdk/ui-vue';
|
|
26
|
+
import {
|
|
27
|
+
computed,
|
|
28
|
+
ref,
|
|
29
|
+
} from 'vue';
|
|
30
|
+
import {
|
|
31
|
+
useApp,
|
|
32
|
+
} from '../app';
|
|
33
|
+
|
|
34
|
+
import { importFile } from '../importFile';
|
|
35
|
+
|
|
36
|
+
const app = useApp();
|
|
37
|
+
|
|
38
|
+
function setDataset(ref: PlRef | undefined) {
|
|
39
|
+
app.model.args.datasetRef = ref;
|
|
40
|
+
app.model.ui.title = 'Immune Assay Data - ' + (ref
|
|
41
|
+
? app.model.outputs.datasetOptions?.find((o) =>
|
|
42
|
+
plRefsEqual(o.ref, ref),
|
|
43
|
+
)?.label
|
|
44
|
+
: '');
|
|
45
|
+
}
|
|
46
|
+
const settingsOpen = ref(app.model.args.datasetRef === undefined);
|
|
47
|
+
|
|
48
|
+
const tableSettings = computed<PlAgDataTableSettings>(() => {
|
|
49
|
+
const pTable = app.model.outputs.table;
|
|
50
|
+
|
|
51
|
+
if (pTable === undefined && !app.model.outputs.isRunning) {
|
|
52
|
+
// special case: when block is not yet started at all (no table calculated)
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
sourceType: 'ptable',
|
|
58
|
+
model: pTable,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const setFile = async (file: ImportFileHandle | undefined) => {
|
|
63
|
+
if (!file) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
importFile(file as LocalImportFileHandle);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const sequenceColumnOptions = computed(() => {
|
|
70
|
+
return app.model.args.importColumns
|
|
71
|
+
?.filter((c) => c.sequenceType !== undefined)
|
|
72
|
+
?.map((c) => ({
|
|
73
|
+
label: c.header,
|
|
74
|
+
value: c.header,
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const similarityTypeOptions = [
|
|
79
|
+
{ label: 'Alignment Score', value: 'alignment-score' },
|
|
80
|
+
{ label: 'Sequence Identity', value: 'sequence-identity' },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const coverageModeOptions = [
|
|
84
|
+
{ label: 'Coverage of clone and assay sequences', value: 0 },
|
|
85
|
+
{ label: 'Coverage of assay sequence', value: 1 },
|
|
86
|
+
{ label: 'Coverage of clone sequence', value: 2 },
|
|
87
|
+
{ label: 'Target length ≥ x% of clone length', value: 3 },
|
|
88
|
+
{ label: 'Query length ≥ x% of assay sequence length', value: 4 },
|
|
89
|
+
{ label: 'Shorter sequence ≥ x% of longer', value: 5 },
|
|
90
|
+
];
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<PlBlockPage>
|
|
95
|
+
<template #title>
|
|
96
|
+
{{ app.model.ui.title }}
|
|
97
|
+
</template>
|
|
98
|
+
<template #append>
|
|
99
|
+
<PlAgDataTableToolsPanel>
|
|
100
|
+
<!-- <PlMultiSequenceAlignment
|
|
101
|
+
v-model="app.model.ui.alignmentModel"
|
|
102
|
+
:label-column-option-predicate="isLabelColumnOption"
|
|
103
|
+
:sequence-column-predicate="isSequenceColumn"
|
|
104
|
+
:linker-column-predicate="isLinkerColumn"
|
|
105
|
+
:p-frame="app.model.outputs.pf"
|
|
106
|
+
:selection="selection"
|
|
107
|
+
/> -->
|
|
108
|
+
</PlAgDataTableToolsPanel>
|
|
109
|
+
<PlBtnGhost @click.stop="() => (settingsOpen = true)">
|
|
110
|
+
Settings
|
|
111
|
+
<template #append>
|
|
112
|
+
<PlMaskIcon24 name="settings" />
|
|
113
|
+
</template>
|
|
114
|
+
</PlBtnGhost>
|
|
115
|
+
</template>
|
|
116
|
+
<PlAgDataTableV2
|
|
117
|
+
v-model="app.model.ui.tableState"
|
|
118
|
+
:settings="tableSettings"
|
|
119
|
+
show-columns-panel
|
|
120
|
+
show-export-button
|
|
121
|
+
/>
|
|
122
|
+
<PlSlideModal v-model="settingsOpen" :close-on-outside-click="false">
|
|
123
|
+
<template #title>Settings</template>
|
|
124
|
+
<PlDropdownRef
|
|
125
|
+
:model-value="app.model.args.datasetRef"
|
|
126
|
+
:options="app.model.outputs.datasetOptions"
|
|
127
|
+
label="Dataset"
|
|
128
|
+
clearable
|
|
129
|
+
required
|
|
130
|
+
@update:model-value="setDataset"
|
|
131
|
+
/>
|
|
132
|
+
<PlDropdown
|
|
133
|
+
v-model="app.model.args.targetRef"
|
|
134
|
+
:options="app.model.outputs.targetOptions"
|
|
135
|
+
label="Clonotype sequence to match"
|
|
136
|
+
clearable
|
|
137
|
+
required
|
|
138
|
+
>
|
|
139
|
+
<template #tooltip>
|
|
140
|
+
Select the sequence column used to match the assay data sequence with. If you select amino acid sequence and
|
|
141
|
+
the assay data sequence is nucleotide, the assay data sequence will be converted to amino acid sequence automatically.
|
|
142
|
+
</template>
|
|
143
|
+
</PlDropdown>
|
|
144
|
+
<PlFileInput
|
|
145
|
+
v-model="app.model.args.fileHandle"
|
|
146
|
+
label="Assay data to import"
|
|
147
|
+
placeholder="Assay data table"
|
|
148
|
+
:extensions="['.csv', '.xlsx', '.xls', '.tsv']"
|
|
149
|
+
:error="app.model.ui.fileImportError"
|
|
150
|
+
required
|
|
151
|
+
@update:model-value="setFile"
|
|
152
|
+
/>
|
|
153
|
+
<!-- @TODO: delete this after bug with not working error message in PlFileInput is fixed -->
|
|
154
|
+
<span v-if="app.model.ui.fileImportError" style="color: red;">
|
|
155
|
+
{{ app.model.ui.fileImportError }}
|
|
156
|
+
</span>
|
|
157
|
+
|
|
158
|
+
<PlDropdown
|
|
159
|
+
v-model="app.model.args.sequenceColumnHeader"
|
|
160
|
+
:options="sequenceColumnOptions"
|
|
161
|
+
label="Assay sequence column"
|
|
162
|
+
placeholder="Sequence column"
|
|
163
|
+
clearable
|
|
164
|
+
required
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<PlSectionSeparator>Matching parameters</PlSectionSeparator>
|
|
168
|
+
<PlDropdown
|
|
169
|
+
v-model="app.model.args.settings.coverageMode"
|
|
170
|
+
:options="coverageModeOptions"
|
|
171
|
+
label="Coverage Mode"
|
|
172
|
+
>
|
|
173
|
+
<template #tooltip>
|
|
174
|
+
How to calculate the coverage between sequences for the coverage threshold.
|
|
175
|
+
</template>
|
|
176
|
+
</PlDropdown>
|
|
177
|
+
|
|
178
|
+
<PlNumberField
|
|
179
|
+
v-model="app.model.args.settings.coverageThreshold"
|
|
180
|
+
label="Coverage Threshold"
|
|
181
|
+
:minValue="0.1"
|
|
182
|
+
:step="0.1"
|
|
183
|
+
:maxValue="1.0"
|
|
184
|
+
>
|
|
185
|
+
<template #tooltip>
|
|
186
|
+
Select min fraction of aligned (covered) residues of clonotypes in the cluster.
|
|
187
|
+
</template>
|
|
188
|
+
</PlNumberField>
|
|
189
|
+
</PlSlideModal>
|
|
190
|
+
</PlBlockPage>
|
|
191
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": false,
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"moduleDetection": "force",
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "preserve",
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
// "noUnusedLocals": true,
|
|
22
|
+
// "noUnusedParameters": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
},
|
|
25
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/shims.d.ts"]
|
|
26
|
+
}
|
package/tsconfig.json
ADDED
package/vite.config.ts
ADDED