@platforma-open/milaboratories.repertoire-diversity-2.ui 1.1.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.
@@ -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:">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <script type="module" crossorigin src="./assets/index-hTANCj-q.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-vWPzwwIZ.css">
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,4 @@
1
+ import { ui } from '@platforma-sdk/eslint-config';
2
+
3
+ /** @type {import('eslint').Linter.Config[]} */
4
+ export default [...ui];
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:">
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,27 @@
1
+ {
2
+ "name": "@platforma-open/milaboratories.repertoire-diversity-2.ui",
3
+ "version": "1.1.2",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@milaboratories/graph-maker": "^1.1.71",
7
+ "@platforma-sdk/ui-vue": "^1.29.2",
8
+ "@platforma-sdk/model": "^1.29.2",
9
+ "vue": "^3.5.13",
10
+ "@platforma-open/milaboratories.repertoire-diversity-2.model": "1.1.2"
11
+ },
12
+ "devDependencies": {
13
+ "@vitejs/plugin-vue": "^5.2.1",
14
+ "typescript": "~5.5.4",
15
+ "vite": "^6.2.2",
16
+ "vue-tsc": "^2.2.8",
17
+ "@platforma-sdk/eslint-config": "^1.0.3",
18
+ "sass-embedded": "^1.77.8"
19
+ },
20
+ "scripts": {
21
+ "dev": "vite",
22
+ "watch": "vue-tsc && vite build --watch",
23
+ "build": "vue-tsc -b && vite build",
24
+ "lint": "eslint .",
25
+ "preview": "vite preview"
26
+ }
27
+ }
package/src/app.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { model } from '@platforma-open/milaboratories.repertoire-diversity-2.model';
2
+ import { defineApp } from '@platforma-sdk/ui-vue';
3
+ import DiversityGraph from './pages/DiversityGraph.vue';
4
+ import MainPage from './pages/MainPage.vue';
5
+
6
+ export const sdkPlugin = defineApp(model, () => {
7
+ return {
8
+ routes: {
9
+ '/': () => MainPage,
10
+ '/diversityGraph': () => DiversityGraph,
11
+ },
12
+ };
13
+ });
14
+
15
+ export const useApp = sdkPlugin.useApp;
package/src/main.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { BlockLayout } from '@platforma-sdk/ui-vue';
2
+ import '@platforma-sdk/ui-vue/styles';
3
+ import { createApp } from 'vue';
4
+ import { sdkPlugin } from './app';
5
+
6
+ createApp(BlockLayout).use(sdkPlugin).mount('#app');
@@ -0,0 +1,76 @@
1
+ <script setup lang="ts">
2
+ import type { Metric } from '@platforma-open/milaboratories.repertoire-diversity-2.model';
3
+ import type { ListOption } from '@platforma-sdk/ui-vue';
4
+ import { PlBtnGroup, PlDropdown, PlNumberField } from '@platforma-sdk/ui-vue';
5
+ import './metrics-manager.scss';
6
+ import { metricTypeOptions } from './util';
7
+
8
+ const downsamplingOptions: ListOption<string | undefined>[] = [
9
+ { label: 'None', value: 'none' },
10
+ { label: 'Top N', value: 'top' },
11
+ { label: 'Cumulative Top', value: 'cumtop' },
12
+ { label: 'Random Sampling', value: 'hypergeometric' },
13
+ ];
14
+
15
+ const props = defineModel<Metric>({ default: {
16
+ type: undefined,
17
+ downsampling: {
18
+ type: 'none',
19
+ },
20
+ } });
21
+
22
+ </script>
23
+
24
+ <template>
25
+ <div class="d-flex flex-column gap-24">
26
+ <PlDropdown
27
+ v-model="props.type" :options="metricTypeOptions"
28
+ label="Type"
29
+ required
30
+ />
31
+
32
+ <PlDropdown
33
+ v-model="props.downsampling.type" :options="downsamplingOptions"
34
+ label="Downsampling"
35
+ required
36
+ />
37
+
38
+ <PlNumberField
39
+ v-if="props.downsampling.type === 'cumtop'"
40
+ v-model="props.downsampling.n"
41
+ label="Select % of the repertoire to include"
42
+ :minValue="0"
43
+ :maxValue="100"
44
+ :step="1"
45
+ required
46
+ />
47
+
48
+ <PlNumberField
49
+ v-if="props.downsampling.type === 'top'"
50
+ v-model="props.downsampling.n"
51
+ label="Select Top N"
52
+ :minValue="0"
53
+ required
54
+ />
55
+
56
+ <PlBtnGroup
57
+ v-if="props.downsampling.type === 'hypergeometric'"
58
+ v-model="props.downsampling.valueChooser"
59
+ :options="[
60
+ { value: 'fixed', label: 'Fixed' },
61
+ { value: 'min', label: 'Min', },
62
+ { value: 'auto', label: 'Auto', },
63
+ ]"
64
+ />
65
+
66
+ <PlNumberField
67
+ v-if="props.downsampling.valueChooser === 'fixed'"
68
+ v-model="props.downsampling.n"
69
+ label="Select N"
70
+ :minValue="0"
71
+ required
72
+ />
73
+ </div>
74
+ </template>
75
+
76
+ <!-- @click="removeMetric(index)" -->
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import type { GraphMakerProps } from '@milaboratories/graph-maker';
3
+ import { GraphMaker } from '@milaboratories/graph-maker';
4
+ import '@milaboratories/graph-maker/styles';
5
+ import { computed } from 'vue';
6
+ import { useApp } from '../app';
7
+
8
+ const app = useApp();
9
+
10
+ const defaultOptions = computed((): GraphMakerProps['defaultOptions'] => {
11
+ return [
12
+ {
13
+ inputName: 'y',
14
+ selectedSource: {
15
+ kind: 'PColumn',
16
+ valueType: 'Double',
17
+ name: 'pl7.app/diversity',
18
+ axesSpec: [],
19
+ },
20
+ },
21
+ {
22
+ inputName: 'primaryGrouping',
23
+ selectedSource: {
24
+ type: 'String',
25
+ name: 'pl7.app/sampleId',
26
+ },
27
+ }
28
+ ];
29
+ });
30
+
31
+ </script>
32
+
33
+ <template>
34
+ <GraphMaker
35
+ v-model="app.model.ui.graphState"
36
+ chart-type="discrete"
37
+ :p-frame="app.model.outputs.pf"
38
+ :default-options="defaultOptions"
39
+ />
40
+ </template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import type { PlRef } from '@platforma-sdk/model';
3
+ import { plRefsEqual } from '@platforma-sdk/model';
4
+ import type { PlDataTableSettings } from '@platforma-sdk/ui-vue';
5
+ import { PlAgDataTable, PlAgDataTableToolsPanel, PlBlockPage, PlBtnGhost, PlEditableTitle, PlMaskIcon24 } from '@platforma-sdk/ui-vue';
6
+ import { computed, ref } from 'vue';
7
+ import { useApp } from '../app';
8
+ import SettingsModal from './SettingsModal.vue';
9
+
10
+ const app = useApp();
11
+
12
+ const tableSettings = computed<PlDataTableSettings>(() => ({
13
+ sourceType: 'ptable',
14
+ pTable: app.model.outputs.pt,
15
+ }));
16
+
17
+ const settingsAreShown = ref(app.model.outputs.pt === undefined);
18
+ const showSettings = () => {
19
+ settingsAreShown.value = true;
20
+ };
21
+
22
+ </script>
23
+
24
+ <template>
25
+ <PlBlockPage>
26
+ <template #title>
27
+ <PlEditableTitle v-model="app.model.ui.blockTitle" max-width="600px" :max-length="40" />
28
+ </template>
29
+ <template #append>
30
+ <PlAgDataTableToolsPanel />
31
+
32
+ <PlBtnGhost @click.stop="showSettings">
33
+ Settings
34
+ <template #append>
35
+ <PlMaskIcon24 name="settings" />
36
+ </template>
37
+ </PlBtnGhost>
38
+ </template>
39
+ <PlAgDataTable v-model="app.model.ui.tableState" :settings="tableSettings" show-columns-panel show-export-button />
40
+ </PlBlockPage>
41
+
42
+ <SettingsModal v-model="settingsAreShown" />
43
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import type { PlRef } from '@platforma-sdk/model';
3
+ import { plRefsEqual } from '@platforma-sdk/model';
4
+ import { PlDropdownRef, PlMaskIcon16, PlMaskIcon24, PlSlideModal } from '@platforma-sdk/ui-vue';
5
+ import { reactive } from 'vue';
6
+ import { useApp } from '../app';
7
+ import DiversityCard from './DiversityCard.vue';
8
+ import './metrics-manager.scss';
9
+ import { getMetricLabel } from './util';
10
+
11
+ const app = useApp();
12
+
13
+ function setAbundanceRef(abundanceRef?: PlRef) {
14
+ app.model.args.abundanceRef = abundanceRef;
15
+ let label = '';
16
+ if (abundanceRef) {
17
+ label = app.model.outputs.abundanceOptions?.find((o) => plRefsEqual(o.ref, abundanceRef))?.label ?? '';
18
+ }
19
+ app.model.ui.blockTitle = 'Repertoire Diversity – ' + label;
20
+ }
21
+
22
+ const settingsAreShown = defineModel<boolean>({ required: true });
23
+
24
+ const openState = reactive<Record<number, boolean>>({});
25
+
26
+ const toggleExpandMetric = (index: number) => {
27
+ if (!openState[index]) openState[index] = true;
28
+ else delete openState[index];
29
+ };
30
+
31
+ const deleteMetric = (index: number) => {
32
+ const len = app.model.args.metrics.length;
33
+ app.model.args.metrics.splice(index, 1);
34
+ delete openState[index];
35
+ for (let i = index; i < len - 1; i++) {
36
+ openState[i] = openState[i + 1];
37
+ }
38
+ };
39
+
40
+ const addMetric = () => {
41
+ app.updateArgs((args) => {
42
+ const index = args.metrics.length;
43
+ args.metrics.push({
44
+ type: undefined,
45
+ downsampling: {
46
+ type: 'none',
47
+ valueChooser: 'auto',
48
+ },
49
+ });
50
+ openState[index] = true;
51
+ });
52
+ };
53
+ </script>
54
+
55
+ <template>
56
+ <PlSlideModal v-model="settingsAreShown">
57
+ <template #title>Settings</template>
58
+ <PlDropdownRef
59
+ v-model="app.model.args.abundanceRef" :options="app.model.outputs.abundanceOptions ?? []"
60
+ label="Abundance"
61
+ required
62
+ @update:model-value="setAbundanceRef"
63
+ />
64
+
65
+ <div class="metrics-manager d-flex flex-column gap-6">
66
+ <div
67
+ v-for="(entry, index) in app.model.args.metrics"
68
+ :key="index"
69
+ :class="{ open: openState[index] ?? false }"
70
+ class="metrics-manager__metric"
71
+ >
72
+ <div
73
+ class="metrics-manager__header d-flex align-center gap-8"
74
+ @click="toggleExpandMetric(index)"
75
+ >
76
+ <div class="metrics-manager__expand-icon">
77
+ <PlMaskIcon16 name="chevron-right" />
78
+ </div>
79
+
80
+ <div class="metrics-manager__title flex-grow-1 text-s-btn">
81
+ {{ entry.type ? getMetricLabel(entry.type) : 'New Metric' }}
82
+ </div>
83
+
84
+ <div class="metrics-manager__actions">
85
+ <div class="metrics-manager__delete ms-auto" @click.stop="deleteMetric(index)">
86
+ <PlMaskIcon24 name="close" />
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="metrics-manager__content d-flex gap-24 p-24 flex-column">
92
+ <DiversityCard
93
+ v-model="app.model.args.metrics[index]"
94
+ />
95
+ </div>
96
+ </div>
97
+
98
+ <div :class="{ 'pt-24': true }" class="metrics-manager__add-action-wrapper">
99
+ <div
100
+ class="metrics-manager__add-btn"
101
+ @click="addMetric"
102
+ >
103
+ <div class="metrics-manager__add-btn-icon">
104
+ <PlMaskIcon16 name="add" />
105
+ </div>
106
+ <div class="metrics-manager__add-btn-title text-s-btn">Add Metric</div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </PlSlideModal>
111
+ </template>
@@ -0,0 +1,154 @@
1
+ .metrics-manager {
2
+ $this: &;
3
+
4
+ .text-s {
5
+ font-weight: 600;
6
+ }
7
+
8
+ &__add-action-wrapper {
9
+ position: sticky;
10
+ bottom: -16px;
11
+ background-color: var(--bg-elevated-01);
12
+ transition: all .15s ease-in-out;
13
+ }
14
+
15
+ &__add-btn {
16
+ height: 40px;
17
+ background-color: var(--bg-elevated-01);
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 8px;
21
+ padding-left: 12px;
22
+ padding-right: 12px;
23
+ border-radius: 6px;
24
+ border: 1px dashed var(--border-color-div-grey);
25
+ line-height: 0;
26
+ cursor: pointer;
27
+ }
28
+
29
+ &__add-btn:hover {
30
+ border-radius: 6px;
31
+ border: 1px dashed var(--border-color-focus, #49CC49);
32
+ background: rgba(99, 224, 36, 0.12);
33
+ }
34
+
35
+ &__add-btn-title {
36
+ flex-grow: 1;
37
+ }
38
+
39
+ &__header {
40
+ height: 40px;
41
+ padding-left: 12px;
42
+ padding-right: 12px;
43
+ cursor: pointer;
44
+ }
45
+
46
+ &__content {
47
+ max-height: 0;
48
+ overflow: hidden;
49
+ transition: all .2s ease-in-out;
50
+ padding-top: 0;
51
+ padding-bottom: 0;
52
+ }
53
+
54
+ &__actions {
55
+ display: flex;
56
+ justify-content: flex-end;
57
+ align-items: center;
58
+ }
59
+
60
+ &__expand-icon {
61
+ .mask-16 {
62
+ transition: all .15s ease-in-out;
63
+ }
64
+ }
65
+
66
+ &__toggle,
67
+ &__expand-icon,
68
+ &__delete {
69
+ line-height: 0;
70
+ cursor: pointer;
71
+ }
72
+
73
+ &__toggle,
74
+ &__delete {
75
+ display: none;
76
+
77
+ .mask-24 {
78
+ background-color: var(--ic-02);
79
+ }
80
+ }
81
+
82
+ &__toggle:hover {
83
+ .mask-24 {
84
+ background-color: var(--ic-01);
85
+ }
86
+ }
87
+
88
+ &__delete:hover {
89
+ .mask-24 {
90
+ background-color: var(--ic-01);
91
+ }
92
+ }
93
+
94
+ &__metric:hover &__toggle,
95
+ &__metric:hover &__delete {
96
+ display: block;
97
+ }
98
+
99
+ &__metric {
100
+ border-radius: 6px;
101
+ border: 1px solid var(--border-color-div-grey);
102
+ background-color: var(--bg-base-light);
103
+ transition: background-color .15s ease-in-out;
104
+ overflow: auto;
105
+ }
106
+
107
+ &__metric.disabled {
108
+
109
+ #{$this}__expand-icon,
110
+ #{$this}__title {
111
+ opacity: 0.3;
112
+ }
113
+ }
114
+
115
+ &__metric:hover {
116
+ background-color: var(--bg-elevated-01);
117
+ }
118
+
119
+ &__metric.open {
120
+ background-color: var(--bg-elevated-01);
121
+
122
+ #{$this}__content {
123
+ max-height: 1600px;
124
+ // overflow: auto;
125
+ padding: 24px;
126
+ transition: all .2s ease-in-out;
127
+ }
128
+
129
+ #{$this}__header {
130
+ background: linear-gradient(180deg, #EBFFEB 0%, #FFF 100%);
131
+ }
132
+
133
+ #{$this}__expand-icon {
134
+ .mask-16 {
135
+ transform: rotate(90deg);
136
+ }
137
+ }
138
+ }
139
+
140
+ &__revert-btn {
141
+ padding: 8px 14px;
142
+ border-radius: 6px;
143
+ cursor: pointer;
144
+ }
145
+
146
+ &__revert-btn:hover {
147
+ background-color: var(--btn-sec-hover-grey);
148
+ }
149
+
150
+ &__revert-btn.disabled {
151
+ opacity: 0.3;
152
+ pointer-events: none;
153
+ }
154
+ }
@@ -0,0 +1,23 @@
1
+ export const metricTypeOptions = [
2
+ { value: 'chao1', label: 'Chao1 Estimate' },
3
+ { value: 'd50', label: 'D50 Diversity' },
4
+ { value: 'efronThisted', label: 'Efron-Thisted Estimate' },
5
+ { value: 'observed', label: 'Observed Diversity' },
6
+ { value: 'shannonWienerIndex', label: 'Shannon-Wiener Index' },
7
+ { value: 'shannonWiener', label: 'Shannon-Wiener Diversity' },
8
+ { value: 'normalizedShannonWiener', label: 'Normalized Shannon-Wiener Index' },
9
+ { value: 'inverseSimpson', label: 'Inverse Simpson Index' },
10
+ { value: 'gini', label: 'Gini Index' },
11
+ ];
12
+
13
+ const labelsMap = (() => {
14
+ const map: Map<string, string> = new Map();
15
+
16
+ for (const option of metricTypeOptions) {
17
+ map.set(option.value, option.label);
18
+ }
19
+
20
+ return map;
21
+ })();
22
+
23
+ export const getMetricLabel = (value: string) => labelsMap.get(value);
@@ -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"]
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.app.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.node.json"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": false,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "noEmit": true
10
+ },
11
+ "include": ["vite.config.ts"]
12
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ base: './',
8
+ build: {
9
+ sourcemap: true,
10
+ },
11
+ });