Skip to content

Commit 038e494

Browse files
committed
chore: wip
chore: wip
1 parent b61de7f commit 038e494

File tree

3 files changed

+238
-24
lines changed

3 files changed

+238
-24
lines changed

storage/framework/defaults/ide/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,4 @@ xvda
391391
yourdomain
392392
zixuanchen
393393
Zoltan
394+
zoomable

storage/framework/defaults/views/dashboard/commerce/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { ref, computed } from 'vue'
2+
import { ref } from 'vue'
33
import { useHead } from '@vueuse/head'
44
import { Line, Bar } from 'vue-chartjs'
55
import {

storage/framework/defaults/views/dashboard/models/index.vue

Lines changed: 236 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, onMounted, onUnmounted } from 'vue'
2+
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
33
import { useHead } from '@vueuse/head'
44
import * as d3 from 'd3'
55
@@ -311,6 +311,71 @@ let simulation: d3.Simulation<ModelNode, undefined> | null = null
311311
const downloadFormat = ref<'svg' | 'png'>('svg')
312312
const isDownloading = ref(false)
313313
314+
// Add filter and search functionality
315+
const searchQuery = ref('')
316+
const selectedModelType = ref('All')
317+
const modelTypes = computed(() => {
318+
const types = ['All']
319+
const uniqueColors = new Set(models.map(model => model.color))
320+
uniqueColors.forEach(color => {
321+
const modelsWithColor = models.filter(model => model.color === color)
322+
if (modelsWithColor.length > 0) {
323+
// Get the first model with this color to determine the type
324+
const modelType = getModelTypeByColor(color)
325+
if (modelType) types.push(modelType)
326+
}
327+
})
328+
return types
329+
})
330+
331+
// Filter models based on search and type
332+
const filteredModels = computed(() => {
333+
return models.filter(model => {
334+
const matchesSearch = searchQuery.value === '' ||
335+
model.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
336+
model.properties.some(prop => prop.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
337+
338+
const matchesType = selectedModelType.value === 'All' ||
339+
getModelTypeByColor(model.color) === selectedModelType.value
340+
341+
return matchesSearch && matchesType
342+
})
343+
})
344+
345+
// Get model type by color
346+
function getModelTypeByColor(color: string): string {
347+
switch(color) {
348+
case colorPalette.primary:
349+
return 'Authentication'
350+
case colorPalette.secondary:
351+
return 'Content'
352+
case colorPalette.tertiary:
353+
return 'Communication'
354+
case colorPalette.quaternary:
355+
return 'Commerce'
356+
default:
357+
return 'Other'
358+
}
359+
}
360+
361+
// Selected model for details panel
362+
const selectedModel = ref<ModelNode | null>(null)
363+
364+
// Model statistics
365+
const modelStats = computed(() => {
366+
return {
367+
totalModels: models.length,
368+
totalRelationships: relationships.length,
369+
totalProperties: models.reduce((sum, model) => sum + model.properties.length, 0),
370+
modelsByType: {
371+
Authentication: models.filter(m => m.color === colorPalette.primary).length,
372+
Content: models.filter(m => m.color === colorPalette.secondary).length,
373+
Communication: models.filter(m => m.color === colorPalette.tertiary).length,
374+
Commerce: models.filter(m => m.color === colorPalette.quaternary).length
375+
}
376+
}
377+
})
378+
314379
// Update download function to support both formats
315380
const downloadDiagram = async () => {
316381
if (!diagramContainer.value) {
@@ -487,7 +552,7 @@ const createDiagram = () => {
487552
const g = svg.append('g')
488553
489554
// Apply initial zoom to see all content
490-
const initialScale = 0.6 // Increased zoom by 10%
555+
const initialScale = 0.6
491556
svg.call(zoom.transform, d3.zoomIdentity.translate(width/2 - width*initialScale/2, 10).scale(initialScale))
492557
493558
// Set initial positions for models based on the reference image layout
@@ -498,7 +563,7 @@ const createDiagram = () => {
498563
'post': { x: width * 0.9, y: -200 },
499564
500565
// Second row - better distributed
501-
'accessToken': { x: width * 0.2, y: -200 }, // Further moved down to avoid overlapping
566+
'accessToken': { x: width * 0.2, y: -200 },
502567
'subscriber': { x: width * 0.8, y: 450 },
503568
504569
// Third row - more evenly spaced
@@ -537,7 +602,7 @@ const createDiagram = () => {
537602
// Create nodes
538603
const node = nodeGroup
539604
.selectAll('g')
540-
.data(models)
605+
.data(filteredModels.value)
541606
.join('g')
542607
.attr('transform', d => {
543608
const x = d.posX || width / 2
@@ -579,6 +644,11 @@ const createDiagram = () => {
579644
delete event.subject.dragOffsetX;
580645
delete event.subject.dragOffsetY;
581646
}))
647+
.on('click', (event, d) => {
648+
// Set the selected model when clicked
649+
event.stopPropagation() // Prevent bubbling
650+
selectedModel.value = d
651+
})
582652
583653
// Add shadow effect to nodes
584654
node.append('rect')
@@ -773,8 +843,9 @@ const createDiagram = () => {
773843
const sourceId = typeof rel.source === 'string' ? rel.source : rel.source.id
774844
const targetId = typeof rel.target === 'string' ? rel.target : rel.target.id
775845
776-
const sourceModel = models.find(m => m.id === sourceId)
777-
const targetModel = models.find(m => m.id === targetId)
846+
// Only draw links for filtered models
847+
const sourceModel = filteredModels.value.find(m => m.id === sourceId)
848+
const targetModel = filteredModels.value.find(m => m.id === targetId)
778849
779850
if (sourceModel && targetModel && sourceModel.posX && sourceModel.posY && targetModel.posX && targetModel.posY) {
780851
// Calculate control points for the curve
@@ -917,8 +988,13 @@ const createDiagram = () => {
917988
})
918989
919990
// Create force simulation with fixed positions
920-
simulation = d3.forceSimulation<ModelNode>(models)
991+
simulation = d3.forceSimulation<ModelNode>(filteredModels.value)
921992
.alphaDecay(0.02) // Slower decay for smoother animation
993+
994+
// Add click handler to clear selection when clicking on the background
995+
svg.on('click', () => {
996+
selectedModel.value = null
997+
})
922998
}
923999
9241000
// Helper function to determine arc sweep direction
@@ -962,8 +1038,9 @@ function updateLinks() {
9621038
const sourceId = typeof rel.source === 'string' ? rel.source : rel.source.id
9631039
const targetId = typeof rel.target === 'string' ? rel.target : rel.target.id
9641040
965-
const sourceModel = models.find(m => m.id === sourceId)
966-
const targetModel = models.find(m => m.id === targetId)
1041+
// Only draw links for filtered models
1042+
const sourceModel = filteredModels.value.find(m => m.id === sourceId)
1043+
const targetModel = filteredModels.value.find(m => m.id === targetId)
9671044
9681045
if (sourceModel && targetModel && sourceModel.posX && sourceModel.posY && targetModel.posX && targetModel.posY) {
9691046
// Calculate control points for the curve
@@ -997,6 +1074,16 @@ function updateLinks() {
9971074
})
9981075
}
9991076
1077+
// Watch for changes in filters and search to update diagram
1078+
watch([searchQuery, selectedModelType], () => {
1079+
// Use setTimeout to debounce the diagram update
1080+
const timer = setTimeout(() => {
1081+
createDiagram()
1082+
}, 300)
1083+
1084+
return () => clearTimeout(timer)
1085+
})
1086+
10001087
// Initialize visualization on mount
10011088
onMounted(() => {
10021089
createDiagram()
@@ -1015,20 +1102,68 @@ onUnmounted(() => {
10151102
<template>
10161103
<div class="min-h-screen py-4 dark:bg-blue-gray-800 lg:py-8">
10171104
<div class="px-4 lg:px-8 sm:px-6">
1018-
<!-- Header -->
1019-
<div class="mb-8">
1020-
<div class="flex justify-between items-center">
1021-
<div class="flex items-center gap-3">
1022-
<div class="i-hugeicons-dashboard-speed-02 w-8 h-8 text-blue-500" />
1023-
<div>
1024-
<h3 class="text-base text-gray-900 dark:text-gray-100 font-semibold leading-6">
1025-
Data Models
1026-
</h3>
1027-
<p class="mt-2 text-sm text-gray-700 dark:text-gray-400">
1028-
Visualize your application's data models and relationships.
1029-
</p>
1030-
</div>
1105+
<!-- Stats Cards -->
1106+
<dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8">
1107+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
1108+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Total Models</dt>
1109+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ modelStats.totalModels }}</dd>
1110+
<dd class="mt-2 flex items-center text-sm text-blue-600 dark:text-blue-400">
1111+
<div class="i-hugeicons-database h-4 w-4 mr-1"></div>
1112+
<span>Application entities</span>
1113+
</dd>
1114+
</div>
1115+
1116+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
1117+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Total Properties</dt>
1118+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ modelStats.totalProperties }}</dd>
1119+
<dd class="mt-2 flex items-center text-sm text-green-600 dark:text-green-400">
1120+
<div class="i-hugeicons-check-list h-4 w-4 mr-1"></div>
1121+
<span>Model attributes</span>
1122+
</dd>
1123+
</div>
1124+
1125+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
1126+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Total Relationships</dt>
1127+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ modelStats.totalRelationships }}</dd>
1128+
<dd class="mt-2 flex items-center text-sm text-purple-600 dark:text-purple-400">
1129+
<div class="i-hugeicons-link-01 h-4 w-4 mr-1"></div>
1130+
<span>Model connections</span>
1131+
</dd>
1132+
</div>
1133+
1134+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
1135+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Model Categories</dt>
1136+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ modelTypes.length - 1 }}</dd>
1137+
<dd class="mt-2 flex items-center text-sm text-orange-600 dark:text-orange-400">
1138+
<div class="i-hugeicons-tag-01 h-4 w-4 mr-1"></div>
1139+
<span>Functional groups</span>
1140+
</dd>
1141+
</div>
1142+
</dl>
1143+
1144+
<!-- Search and Filter Controls -->
1145+
<div class="mb-6 flex flex-col sm:flex-row gap-4 items-center justify-between">
1146+
<div class="relative w-full sm:w-64">
1147+
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
1148+
<div class="i-hugeicons-search-01 w-5 h-5 text-gray-400"></div>
10311149
</div>
1150+
<input
1151+
v-model="searchQuery"
1152+
type="text"
1153+
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-blue-gray-700 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm text-gray-900 dark:text-white"
1154+
placeholder="Search models or properties..."
1155+
/>
1156+
</div>
1157+
1158+
<div class="flex items-center gap-2 w-full sm:w-auto">
1159+
<label for="model-type" class="text-sm font-medium text-gray-700 dark:text-gray-300">Filter by type:</label>
1160+
<select
1161+
id="model-type"
1162+
v-model="selectedModelType"
1163+
class="block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-600 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-blue-gray-700"
1164+
>
1165+
<option v-for="type in modelTypes" :key="type" :value="type">{{ type }}</option>
1166+
</select>
10321167
</div>
10331168
</div>
10341169

@@ -1037,7 +1172,7 @@ onUnmounted(() => {
10371172
<div class="p-6">
10381173
<div class="flex items-center justify-between mb-4">
10391174
<div class="flex items-center gap-2">
1040-
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100">Entity Relationship Diagram</h4>
1175+
<h4 class="text-base font-medium text-gray-900 dark:text-white">Entity Relationship Diagram</h4>
10411176
<span class="text-sm text-gray-500 dark:text-gray-400">
10421177
(Drag nodes to rearrange)
10431178
</span>
@@ -1064,6 +1199,84 @@ onUnmounted(() => {
10641199
<div ref="diagramContainer" class="w-full h-[1200px] bg-gray-50 dark:bg-blue-gray-800 rounded-lg"></div>
10651200
</div>
10661201
</div>
1202+
1203+
<!-- Selected Model Details Panel -->
1204+
<div v-if="selectedModel" class="mb-8 bg-white dark:bg-blue-gray-700 rounded-lg shadow">
1205+
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
1206+
<div class="flex items-center justify-between">
1207+
<div class="flex items-center gap-2">
1208+
<span class="text-2xl">{{ selectedModel.emoji }}</span>
1209+
<h3 class="text-lg font-medium text-gray-900 dark:text-white">{{ selectedModel.name }} Details</h3>
1210+
</div>
1211+
<button
1212+
@click="selectedModel = null"
1213+
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
1214+
>
1215+
<div class="i-hugeicons-x-mark w-5 h-5"></div>
1216+
</button>
1217+
</div>
1218+
</div>
1219+
<div class="px-4 py-5 sm:p-6">
1220+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
1221+
<!-- Properties Section -->
1222+
<div>
1223+
<h4 class="text-base font-medium text-gray-900 dark:text-white mb-4">Properties</h4>
1224+
<div class="bg-gray-50 dark:bg-blue-gray-800 rounded-md p-4">
1225+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
1226+
<thead>
1227+
<tr>
1228+
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:text-gray-400">Name</th>
1229+
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:text-gray-400">Type</th>
1230+
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:text-gray-400">Nullable</th>
1231+
</tr>
1232+
</thead>
1233+
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
1234+
<tr v-for="prop in selectedModel.properties" :key="prop.name">
1235+
<td class="px-3 py-2 whitespace-nowrap text-sm font-medium" :class="prop.name === 'id' ? 'text-yellow-600 dark:text-yellow-400' : 'text-gray-900 dark:text-white'">{{ prop.name }}</td>
1236+
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ prop.type }}</td>
1237+
<td class="px-3 py-2 whitespace-nowrap text-sm">
1238+
<span :class="prop.nullable ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'">
1239+
{{ prop.nullable ? 'Yes' : 'No' }}
1240+
</span>
1241+
</td>
1242+
</tr>
1243+
</tbody>
1244+
</table>
1245+
</div>
1246+
</div>
1247+
1248+
<!-- Relationships Section -->
1249+
<div>
1250+
<h4 class="text-base font-medium text-gray-900 dark:text-white mb-4">Relationships</h4>
1251+
<div class="bg-gray-50 dark:bg-blue-gray-800 rounded-md p-4">
1252+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
1253+
<thead>
1254+
<tr>
1255+
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:text-gray-400">Type</th>
1256+
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:text-gray-400">Related Model</th>
1257+
</tr>
1258+
</thead>
1259+
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
1260+
<tr v-for="(rel, index) in selectedModel.relationships" :key="index">
1261+
<td class="px-3 py-2 whitespace-nowrap text-sm font-medium">
1262+
<span :class="{
1263+
'text-red-600 dark:text-red-400': rel.type === 'belongsTo',
1264+
'text-blue-600 dark:text-blue-400': rel.type === 'hasMany',
1265+
'text-green-600 dark:text-green-400': rel.type === 'hasOne',
1266+
'text-purple-600 dark:text-purple-400': rel.type === 'belongsToMany'
1267+
}">
1268+
{{ rel.type }}
1269+
</span>
1270+
</td>
1271+
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{ rel.model }}</td>
1272+
</tr>
1273+
</tbody>
1274+
</table>
1275+
</div>
1276+
</div>
1277+
</div>
1278+
</div>
1279+
</div>
10671280
</div>
10681281
</div>
10691282
</template>

0 commit comments

Comments
 (0)