Skip to content

Commit

Permalink
refactor: use composables
Browse files Browse the repository at this point in the history
  • Loading branch information
heristop authored Jul 5, 2024
1 parent 7cf52e6 commit 49ab5d8
Show file tree
Hide file tree
Showing 22 changed files with 933 additions and 246 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/vitest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Vitest

on: [push]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: test
run: |
pnpm test:coverage
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ logs
/playwright-report/
/blob-report/
/playwright/.cache/
/coverage
/app/coverage
6 changes: 0 additions & 6 deletions app/components/ConfigDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,4 @@ onMounted(() => {
display: flex;
justify-content: flex-end;
}
.status-item {
cursor: grab;
}
.status-item:active {
cursor: grabbing;
}
</style>
31 changes: 10 additions & 21 deletions app/components/StatusManager.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import { useStore, pastelColors } from '~/composables/store'
import { useStatusManagement } from '~/composables/status'
const { addStatus, removeStatus } = useStatusManagement()
const store = useStore()
const statuses = computed(() => store.statuses)
const inputFocused = ref(false)
const newStatus = reactive({
name: '',
color: getNextColor() || '#FFFFFF', // Ensure it's always a string
color: getNextColor() || '#FFFFFF',
})
const addNewStatus = () => {
if (newStatus.name && newStatus.color) {
store.addStatus({ name: newStatus.name, color: newStatus.color })
addStatus({ name: newStatus.name, color: newStatus.color })
newStatus.name = ''
newStatus.color = getNextColor() || '#FFFFFF' // Ensure it's always a string
newStatus.color = getNextColor() || '#FFFFFF'
}
}
const removeStatus = (index: number) => {
store.removeStatus(index)
}
const disableStatusDrag = () => {
inputFocused.value = true
}
Expand Down Expand Up @@ -67,13 +65,13 @@ function getNextColor() {
<div
v-for="(status, index) in statuses"
:key="index"
class="flex items-center space-x-2 space-y-2 align-middle status-item text-stone-800"
class="flex items-center space-x-2 space-y-2 align-middle cursor-grab active:cursor-grabbing text-stone-800"
:draggable="!inputFocused"
@dragstart="startStatusDrag($event, index)"
@dragover.prevent
@drop="dropStatus($event, index)"
>
<div class="text-stone-500 dark:text-stone-300 w-20 text-xs mt-2">
<div class="text-stone-600 dark:text-stone-300 w-20 text-xs mt-2">
{{ index + 1 }}.
</div>

Expand All @@ -94,15 +92,15 @@ function getNextColor() {
>
<button
:disabled="statuses.length <= 1"
class="text-stone-500 dark:text-stone-300 hover:text-stone-400 dark:hover:text-stone-100 font-semibold w-[200px] disabled:text-stone-500 text-xs"
class="text-stone-600 dark:text-stone-300 hover:text-stone-400 dark:hover:text-stone-100 font-semibold w-[200px] disabled:text-stone-500 text-xs"
@click="removeStatus(index)"
>
remove
</button>
</div>

<div class="flex items-center space-x-2 space-y-2 align-middle status-item text-stone-600 mb-6">
<div class="text-stone-500 dark:text-stone-300 w-20 text-xs mt-2">
<div class="text-stone-600 dark:text-stone-300 w-20 text-xs mt-2">
{{ statuses.length + 1 }}.
</div>
<input
Expand All @@ -122,19 +120,10 @@ function getNextColor() {
>
<button
:disabled="!newStatus.name || !newStatus.color"
class="text-stone-500 dark:text-stone-300 hover:text-stone-400 dark:hover:text-stone-100 font-semibold w-[200px] text-xs disabled:cursor-not-allowed disabled:opacity-50"
class="text-stone-600 dark:text-stone-300 hover:text-stone-400 dark:hover:text-stone-100 font-semibold w-[200px] text-xs disabled:cursor-not-allowed disabled:opacity-50"
@click="addNewStatus"
>
add
</button>
</div>
</template>

<style scoped>
.status-item {
cursor: grab;
}
.status-item:active {
cursor: grabbing;
}
</style>
131 changes: 28 additions & 103 deletions app/components/TreeNode.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<script setup lang="ts">
import { ref, computed, onMounted, type StyleValue } from 'vue'
import { ref, computed, onMounted, watchEffect } from 'vue'
import { useStore } from '~/composables/store'
import type { Section } from '~~/types'
import { useSectionManagement } from '~/composables/section'
import { useNodeOperations } from '~/composables/node'
import { useNodeStyles } from '~/composables/style'
import { useStatusManagement } from '~/composables/status'
const props = defineProps<{ node: Section, depth: number }>()
const emit = defineEmits(['status-updated'])
Expand All @@ -10,104 +14,49 @@ const isSuccessNode = ref(false)
const nodeStatus = ref(props.node.status || store.statuses[0]?.name)
const isDragging = ref(false)
const { updateSectionKey, updateSectionName, deleteSection } = useSectionManagement()
const { addSection, addSiblingSection, addRootSection } = useNodeOperations()
const { getNodeStyles } = useNodeStyles()
const { updateSectionStatus } = useStatusManagement()
const localKey = ref(props.node.key)
const localName = ref(props.node.name)
const updateNodeKey = (newKey: string) => {
store.updateSectionKey(props.node.key, newKey)
}
const updateNodeName = (newName: string) => {
store.updateSectionName(props.node.key, newName)
}
const displayContent = computed({
get: () => store.displayLabel === 'key' ? localKey.value : localName.value,
set: (value) => {
if (store.displayLabel === 'key') {
localKey.value = value
updateNodeKey(value)
updateSectionKey(props.node.key, value)
}
else {
localName.value = value
updateNodeName(value)
updateSectionName(props.node.key, value)
}
},
})
const minWidth = computed(() => store.minWidth)
const minHeight = computed(() => store.minHeight)
const duplicateKeys = computed(() => store.duplicateProjects.keys)
const getNodeStyles = (node: Section, depth: number) => {
const statusObj = store.statuses.find((s: { name: string | undefined }) => s.name === node.status)
let backgroundColor = statusObj ? statusObj.color : '#D44D8'
backgroundColor = darkenColor(backgroundColor, depth * 6)
const isDuplicate = duplicateKeys.value.includes(node.key)
return {
backgroundColor: backgroundColor,
borderColor: isDuplicate ? '#FF7B7B' : backgroundColor,
minWidth: `${minWidth.value}px`,
minHeight: `${minHeight.value}px`,
margin: '4px',
padding: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
boxSizing: 'border-box',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
transition: 'transform 0.2s, box-shadow 0.2s, background-color 0.3s',
borderStyle: isDuplicate ? 'dashed' : 'solid',
} as StyleValue
}
const checkIfSuccessNode = (node: Section) => {
if (store.statuses?.length > 0 && node.status === store.statuses[store.statuses.length - 1]?.name) {
isSuccessNode.value = true
return
}
isSuccessNode.value = false
}
const updateParentStatus = () => {
emit('status-updated')
}
const darkenColor = (color: string, percent: number) => {
if (!/^#[0-9A-F]{6}$/i.test(color)) {
return color
}
const num = parseInt(color.replace('#', ''), 16),
amt = Math.round(2.55 * percent),
R = Math.max((num >> 16) - amt, 0),
G = Math.max((num >> 8 & 0x00FF) - amt, 0),
B = Math.max((num & 0x0000FF) - amt, 0)
return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1).toUpperCase()
}
const toggleCollapse = (event: MouseEvent) => {
event.stopPropagation()
if (document.startViewTransition) {
document.startViewTransition(() => {
store.updateSectionCollapse(props.node.key)
})
}
else {
store.updateSectionCollapse(props.node.key)
}
store.updateSectionCollapse(props.node.key)
}
const getUniqueKeyName = (base: string, type: 'key' | 'name') => {
let index = 1
let newName = `${base}${index}`
const allSections = []
const allSections: Section[] = []
const gatherSections = (sections: Section[]) => {
sections.forEach((section) => {
Expand Down Expand Up @@ -139,15 +88,7 @@ const addNode = (event: MouseEvent) => {
isCollapsed: false,
}
if (document.startViewTransition) {
document.startViewTransition(() => {
store.addSection(props.node.key, newNode)
})
return
}
store.addSection(props.node.key, newNode)
addSection(props.node.key, newNode)
}
const addSiblingNode = (event: MouseEvent) => {
Expand All @@ -166,43 +107,22 @@ const addSiblingNode = (event: MouseEvent) => {
const parentKey = store.parentMap[props.node.key]
if (parentKey) {
if (document.startViewTransition) {
document.startViewTransition(() => {
store.addSiblingSection(parentKey, props.node.key, newNode)
})
}
else {
store.addSiblingSection(parentKey, props.node.key, newNode)
}
addSiblingSection(parentKey, props.node.key, newNode)
}
else {
if (document.startViewTransition) {
document.startViewTransition(() => {
store.addRootSection(newNode)
})
}
else {
store.addRootSection(newNode)
}
addRootSection(newNode)
}
}
const deleteNode = (event: MouseEvent) => {
event.stopPropagation()
if (document.startViewTransition) {
document.startViewTransition(() => {
store.deleteSection(props.node.key)
})
return
}
store.deleteSection(props.node.key)
deleteSection(props.node.key)
}
const handleDragStart = (event: DragEvent) => {
if (isDragging.value) return
if (isDragging.value) {
return
}
event.stopPropagation()
event.dataTransfer?.setData('text/plain', props.node.key)
Expand All @@ -212,6 +132,7 @@ const handleDrop = (event: DragEvent) => {
event.preventDefault()
event.stopPropagation()
const draggedKey = event.dataTransfer?.getData('text/plain')
if (draggedKey && draggedKey !== props.node.key) {
store.swapSections(props.node.key, draggedKey)
}
Expand Down Expand Up @@ -246,6 +167,7 @@ const handleClick = (event: MouseEvent) => {
if (!props.node.children || !props.node.children.length) {
updateStatus()
return
}
}
Expand All @@ -256,7 +178,7 @@ const updateStatus = () => {
const nextStatusIndex = (store.statuses.indexOf(newStatus!) + 1) % store.statuses.length
const nextStatus = store.statuses[nextStatusIndex]?.name
nodeStatus.value = nextStatus
store.updateSectionStatus(props.node.key || '', nextStatus || '')
updateSectionStatus(props.node.key || '', nextStatus || '')
if (store.statuses.length > 0 && nextStatus === store.statuses[store.statuses.length - 1]?.name) {
applySuccessAnimation(props.node)
Expand Down Expand Up @@ -291,7 +213,7 @@ const applySuccessAnimation = (node: Section) => {
:class="{ 'success-animation': isSuccessNode }"
:style="nodeStyle"
:data-node-key="node.key"
draggable="true"
:draggable="!store.isEditingMode"
@dragstart="handleDragStart"
@drop="handleDrop"
@dragover="handleDragOver"
Expand Down Expand Up @@ -346,7 +268,9 @@ const applySuccessAnimation = (node: Section) => {
@focus="isDragging = true"
@blur="isDragging = false"
>

<span v-else>{{ displayContent }}</span>

<div
v-if="store.isEditingMode"
class="flex ml-4 space-x-2"
Expand Down Expand Up @@ -506,6 +430,7 @@ const applySuccessAnimation = (node: Section) => {
background-color: rgba(0, 0, 0, 0.25);
pointer-events: auto;
padding: 8px;
margin: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
Expand Down
4 changes: 2 additions & 2 deletions app/components/ViewportSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const captureTreeMap = () => {

<div class="flex items-center space-x-4 mb-4 px-2">
<div class="flex flex-col items-start space-y-2">
<span class="text-xs text-stone-500 dark:text-stone-300">Width</span>
<span class="text-xs text-stone-600 dark:text-stone-300">Width</span>
<input
v-model="minWidth"
type="range"
Expand All @@ -112,7 +112,7 @@ const captureTreeMap = () => {
</div>

<div class="flex flex-col items-start space-y-2">
<span class="text-xs text-stone-500 dark:text-stone-300">Height</span>
<span class="text-xs text-stone-600 dark:text-stone-300">Height</span>
<input
v-model="minHeight"
type="range"
Expand Down
2 changes: 1 addition & 1 deletion app/composables/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function useConfig() {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `tree-pulse-${new Date().toISOString()}.json`
link.download = `treemap-pulse-${new Date().toISOString()}.json`
link.click()
URL.revokeObjectURL(url)
}
Expand Down
Loading

0 comments on commit 49ab5d8

Please sign in to comment.