Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@
"<node_internals>/**",
"**/node_modules/**"
]
}
},
"typescript.tsdk": "structures-frontend-next/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"volar.useWorkspaceTsdk": true
}
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
{
"label": "Frontend: Install Dependencies",
"type": "shell",
"command": "pnpm install",
"command": "./pnpmw install --reporter=append-only",
"options": {
"cwd": "${workspaceFolder}/structures-frontend-next"
},
Expand All @@ -180,7 +180,7 @@
{
"label": "Frontend: Dev Server",
"type": "shell",
"command": "pnpm dev",
"command": "./pnpmw dev",
"options": {
"cwd": "${workspaceFolder}/structures-frontend-next"
},
Expand Down
3 changes: 1 addition & 2 deletions structures-frontend-next/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ declare module 'vue' {
StructureItemModal: typeof import('./src/components/modals/StructureItemModal.vue')['default']
StructureNode: typeof import('./src/components/structures/flow-components/StructureNode.vue')['default']
StructureSettings: typeof import('./src/components/structures/sidebar-dashboard/StructureSettings.vue')['default']
StructureSidebarDashboard: typeof import('./src/components/structures/StructureSidebarDashboard.vue')['default']
StructureSidebarDashboard: typeof import('./src/components/structures/sidebar-dashboard/StructureSidebarDashboard.vue')['default']
StructuresList: typeof import('./src/components/StructuresList.vue')['default']
Tab: typeof import('primevue/tab')['default']
TabList: typeof import('primevue/tablist')['default']
Expand All @@ -65,7 +65,6 @@ declare module 'vue' {
Tag: typeof import('primevue/tag')['default']
Textarea: typeof import('primevue/textarea')['default']
Toast: typeof import('primevue/toast')['default']
ToggleButton: typeof import('primevue/togglebutton')['default']
Toolbar: typeof import('primevue/toolbar')['default']
UnionNode: typeof import('./src/components/nodes/UnionNode.vue')['default']
}
Expand Down
7 changes: 5 additions & 2 deletions structures-frontend-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
"dev": "vite",
"build": "vue-tsc -b && vite build",
"build:kind": "vue-tsc -b && vite build --mode kind",
"preview": "vite preview"
"preview": "vite preview",
"deps:install": "./pnpmw install --reporter=append-only",
"pnpm:build": "./pnpmw run build",
"pnpm:dev": "./pnpmw run dev"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.2.0",
"@mdi/js": "^7.4.47",
"@mindignited/continuum-client": "^2.14.0",
"@mindignited/continuum-idl": "^2.0.3",
"@mindignited/structures-api": "^3.5.0-beta.17",
"@mdi/js": "^7.4.47",
"@primeuix/themes": "^1.2.1",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.2",
Expand Down
157 changes: 77 additions & 80 deletions structures-frontend-next/src/components/ApplicationSidebar.vue
Original file line number Diff line number Diff line change
@@ -1,106 +1,103 @@
<script lang="ts">
import { Component, Vue, Prop } from 'vue-facing-decorator'
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { Structures, type Application } from '@mindignited/structures-api'
import InputText from 'primevue/inputtext';
import Textarea from 'primevue/textarea';
import Button from 'primevue/button';
import Select from 'primevue/select';
import ToggleButton from 'primevue/togglebutton';
import InputText from 'primevue/inputtext'
import Textarea from 'primevue/textarea'
import Button from 'primevue/button'
import ToggleButton from 'primevue/togglebutton'
import { useToast } from 'primevue/usetoast'

interface ApplicationForm {
name: string
description: string
graphql: boolean
openapi: boolean
}

@Component({
components: {
InputText,
Textarea,
Button,
Select,
ToggleButton
}
const props = defineProps<{ visible: boolean }>()

const emit = defineEmits<{
(e: 'submit', createdApplication: Application): void
(e: 'close'): void
}>()

const toast = useToast()

const form = reactive<ApplicationForm>({
name: '',
description: '',
graphql: true,
openapi: false
})
export default class ApplicationSidebar extends Vue {
@Prop({ required: true }) readonly visible!: boolean

form: ApplicationForm = {
name: '',
description: '',
graphql: true,
openapi: false
}

loading = false
get isSubmitDisabled(): boolean {
return this.loading || this.form.name.trim() === '';
}
const loading = ref(false)

private sanitizeId(name: string): string {
let sanitized = name.trim()
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9._-]/g, '')
const isSubmitDisabled = computed(() => loading.value || form.name.trim() === '')

if (!/^[a-zA-Z]/.test(sanitized)) {
sanitized = 'app-' + sanitized
}
return sanitized.toLowerCase()
}
function sanitizeId(name: string): string {
let sanitized = name
.trim()
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9._-]/g, '')

async handleSubmit(): Promise<void> {
this.loading = true
try {
const applicationData: Application = {
id: this.sanitizeId(this.form.name),
description: this.form.description,
enableGraphQL: this.form.graphql,
enableOpenAPI: this.form.openapi,
updated: null
}
const createdApplication: Application = await Structures.getApplicationService().create(applicationData)

this.$toast.add({
severity: 'success',
summary: 'Success',
detail: 'Application successfully added',
life: 3000
})

this.resetForm()
this.$emit('submit', createdApplication)
} catch (error) {
console.error('[ApplicationSidebar] Failed to create application:', error)
this.$toast.add({
severity: 'error',
summary: 'Error',
detail: 'Failed to create application. Please check name validity.',
life: 3000
})
} finally {
this.loading = false
}
if (!/^[a-zA-Z]/.test(sanitized)) {
sanitized = 'app-' + sanitized
}

handleClose(): void {
this.resetForm()
this.$emit('close')
}
return sanitized.toLowerCase()
}

private resetForm(): void {
this.form = {
name: '',
description: '',
graphql: true,
openapi: false
function resetForm(): void {
form.name = ''
form.description = ''
form.graphql = true
form.openapi = false
}

async function handleSubmit(): Promise<void> {
loading.value = true
try {
const applicationData: Application = {
id: sanitizeId(form.name),
description: form.description,
enableGraphQL: form.graphql,
enableOpenAPI: form.openapi,
updated: null
}

const createdApplication = await Structures.getApplicationService().create(applicationData)

toast.add({
severity: 'success',
summary: 'Success',
detail: 'Application successfully added',
life: 3000
})

resetForm()
emit('submit', createdApplication)
} catch (error) {
console.error('[ApplicationSidebar] Failed to create application:', error)
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Failed to create application. Please check name validity.',
life: 3000
})
} finally {
loading.value = false
}
}

function handleClose(): void {
resetForm()
emit('close')
}
</script>

<template>
<transition name="slide">
<div v-if="visible" class="fixed inset-y-0 right-0 w-[400px] h-screen bg-white shadow-xl z-50 overflow-y-auto">
<div v-if="props.visible" class="fixed inset-y-0 right-0 w-[400px] h-screen bg-white shadow-xl z-50 overflow-y-auto">
<div class="flex justify-between items-center mb-4 border-b border-b-[#E6E7EB] p-4">
<div class="flex items-center gap-3">
<img src="@/assets/action-plus-icon.svg" />
Expand Down
20 changes: 17 additions & 3 deletions structures-frontend-next/src/pages/EntityList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import InputText from 'primevue/inputtext'
import { Pageable, type Page, Order, Direction, type Identifiable } from '@mindignited/continuum-client'
import { Structure, type IStructureService, Structures, type IEntitiesService } from '@mindignited/structures-api'

type EntityItem = Identifiable<string> & { id: string } & Record<string, any>

interface HeaderDef {
header: string
field: string
sortable: boolean
width: number
isCollapsable?: boolean
expandedWidth?: number | null
[key: string]: any
}

import DatetimeUtil from '@/util/DatetimeUtil'
import { StructureUtil } from '@/util/StructureUtil'
import { rowColors } from '@/util/rowColors'
Expand All @@ -28,12 +40,12 @@ class EntityList extends Vue {

loading = false
finishedInitialLoad = false
items: Array<Identifiable<string>> = []
items: EntityItem[] = []
totalItems = 0
searchText: string | null = null

keys: string[] = []
headers: any[] = []
headers: HeaderDef[] = []
structureProperties: any = {}
structure!: Structure

Expand Down Expand Up @@ -768,9 +780,11 @@ if (!id) {
}

displayAlert(text: string) {
console.error('[EntityList Alert]:', text)
window.alert(text)
}

startColumnResize(event: MouseEvent, header: any) {
startColumnResize(event: MouseEvent, header: HeaderDef) {
this.resizingColumn = header
this.startX = event.pageX
this.wasExpanded = this.isColumnExpanded(header.field)
Expand Down
12 changes: 7 additions & 5 deletions structures-frontend-next/src/shims-vue.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

declare module 'prismjs/components/prism-core'
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

declare module 'prismjs/components/prism-core'


Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ function renderValue(value: any, indent: number = 0): string {
return String(value)
}
if (typeof value === 'string') {
return `"${value.replace(/"/g, '\\"')}"`
// JSON.stringify produces a valid JS/TS string literal with correct escaping
// (handles backslashes, quotes, newlines, tabs, etc.)
return JSON.stringify(value)
}
if (typeof value === 'number') {
return String(value)
Expand Down
Loading