mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
fixed unsaved changes
This commit is contained in:
24
src/components/haex/pass/group/composables.ts
Normal file
24
src/components/haex/pass/group/composables.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
export const usePasswordGroup = () => {
|
||||
const areItemsEqual = (
|
||||
groupA: unknown | unknown[] | null,
|
||||
groupB: unknown | unknown[] | null,
|
||||
) => {
|
||||
if (groupA === null && groupB === null) return true
|
||||
|
||||
if (Array.isArray(groupA) && Array.isArray(groupB)) {
|
||||
console.log('compare object arrays', groupA, groupB)
|
||||
if (groupA.length === groupB.length) return true
|
||||
|
||||
return groupA.some((group, index) => {
|
||||
return areObjectsEqual(group, groupA[index])
|
||||
})
|
||||
}
|
||||
return areObjectsEqual(groupA, groupB)
|
||||
}
|
||||
|
||||
return {
|
||||
areItemsEqual,
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,7 @@
|
||||
class="btn-primary btn-outline flex-1-1 min-w-40"
|
||||
>
|
||||
<Icon name="mdi:plus" />
|
||||
<p class="hidden sm:inline-flex">{{ t('add') }}</p>
|
||||
<p class="hidden sm:inline-block">{{ t('add') }}</p>
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -77,7 +77,7 @@ watch(
|
||||
try {
|
||||
const foundGroup = await readGroupAsync(currentGroupId.value)
|
||||
if (foundGroup) {
|
||||
original.value = JSON.stringify(foundGroup)
|
||||
original.value = JSON.parse(JSON.stringify(foundGroup))
|
||||
group.value = foundGroup
|
||||
}
|
||||
} catch (error) {
|
||||
@ -86,22 +86,11 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
/* watch(
|
||||
currentGroup,
|
||||
(n, o) => {
|
||||
console.log('currentGroup', currentGroup.value, n, o)
|
||||
original.value = JSON.stringify(currentGroup.value)
|
||||
group.value = JSON.parse(original.value)
|
||||
ignoreChanges.value = false
|
||||
},
|
||||
{ immediate: true },
|
||||
) */
|
||||
|
||||
const read_only = ref(false)
|
||||
|
||||
const hasChanges = computed(
|
||||
() => JSON.stringify(group.value) !== original.value,
|
||||
)
|
||||
const { areItemsEqual } = usePasswordGroup()
|
||||
const hasChanges = computed(() => !!!areItemsEqual(group.value, original.value))
|
||||
|
||||
const onClose = () => {
|
||||
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
|
||||
@ -121,7 +110,7 @@ const onSaveAsync = async () => {
|
||||
|
||||
ignoreChanges.value = true
|
||||
await updateAsync(group.value)
|
||||
await syncGroupItemsAsync(group.value.id)
|
||||
await syncGroupItemsAsync()
|
||||
add({ type: 'success', text: t('change.success') })
|
||||
onClose()
|
||||
} catch (error) {
|
||||
@ -141,7 +130,7 @@ const onDeleteAsync = async () => {
|
||||
try {
|
||||
const parentId = group.value.parentId
|
||||
await deleteGroupAsync(group.value.id, inTrashGroup.value)
|
||||
await syncGroupItemsAsync(parentId)
|
||||
await syncGroupItemsAsync()
|
||||
showConfirmDeleteDialog.value = false
|
||||
ignoreChanges.value = true
|
||||
await navigateTo(
|
||||
|
||||
@ -194,7 +194,7 @@ const onPasteAsync = async () => {
|
||||
[...selectedGroupItems.value],
|
||||
currentGroupId.value,
|
||||
)
|
||||
await syncGroupItemsAsync(currentGroupId.value)
|
||||
await syncGroupItemsAsync()
|
||||
selectedGroupItems.value = []
|
||||
selectedItems.value.clear()
|
||||
} catch (error) {
|
||||
@ -235,7 +235,7 @@ const onDeleteAsync = async () => {
|
||||
}
|
||||
}
|
||||
selectedItems.value.clear()
|
||||
await syncGroupItemsAsync(currentGroupId.value)
|
||||
await syncGroupItemsAsync()
|
||||
}
|
||||
const keys = useMagicKeys()
|
||||
watch(keys.delete, async () => {
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col">
|
||||
<p>
|
||||
{{ item.originalDetails }}
|
||||
</p>
|
||||
{{ item.details }}
|
||||
</div>
|
||||
<HaexPassItem
|
||||
:history="item.history"
|
||||
:read_only
|
||||
@ -11,83 +17,8 @@
|
||||
v-model:key-values="item.keyValues"
|
||||
/>
|
||||
|
||||
<!-- <div
|
||||
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
|
||||
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
||||
>
|
||||
<div class="flex items-center justify-center flex-1">
|
||||
<UiTooltip :tooltip="t('abort')">
|
||||
<UiButton
|
||||
class="btn-accent btn-square"
|
||||
@click="onClose"
|
||||
>
|
||||
<Icon name="mdi:close" />
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
|
||||
<UiTooltip
|
||||
v-show="read_only && !hasChanges"
|
||||
:tooltip="t('edit')"
|
||||
>
|
||||
<UiButton
|
||||
class="btn-xl btn-square btn-primary"
|
||||
@click="read_only = false"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:pencil-outline"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
|
||||
<UiTooltip
|
||||
v-show="!read_only && !hasChanges"
|
||||
:tooltip="t('noEdit')"
|
||||
>
|
||||
<UiButton
|
||||
class="btn-xl btn-square btn-primary"
|
||||
@click="read_only = true"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:pencil-off-outline"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
|
||||
<UiTooltip
|
||||
:tooltip="t('save')"
|
||||
v-show="!read_only && hasChanges"
|
||||
>
|
||||
<UiButton
|
||||
class="btn-xl btn-square btn-primary motion-duration-2000"
|
||||
:class="{ 'motion-preset-pulse-sm': hasChanges }"
|
||||
@click="onUpdateAsync"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:content-save-outline"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
|
||||
<div class="flex items-center justify-center flex-1">
|
||||
<UiTooltip :tooltip="t('delete')">
|
||||
<UiButton
|
||||
class="btn-square btn-error"
|
||||
@click="showConfirmDeleteDialog = true"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:trash-outline"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<HaexPassMenuBottom
|
||||
:has-changes
|
||||
:show-edit-button="read_only && !hasChanges"
|
||||
:show-readonly-button="!read_only && !hasChanges"
|
||||
:show-save-button="!read_only && hasChanges"
|
||||
@ -119,6 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePasswordGroup } from '~/components/haex/pass/group/composables'
|
||||
import type {
|
||||
SelectHaexPasswordsItemDetails,
|
||||
SelectHaexPasswordsItemHistory,
|
||||
@ -145,8 +77,8 @@ const item = reactive<{
|
||||
keyValues: SelectHaexPasswordsItemKeyValues[]
|
||||
keyValuesAdd: SelectHaexPasswordsItemKeyValues[]
|
||||
keyValuesDelete: SelectHaexPasswordsItemKeyValues[]
|
||||
originalDetails: string | null
|
||||
originalKeyValues: string | null
|
||||
originalDetails: SelectHaexPasswordsItemDetails | null
|
||||
originalKeyValues: SelectHaexPasswordsItemKeyValues[] | null
|
||||
}>({
|
||||
details: {
|
||||
id: '',
|
||||
@ -164,7 +96,18 @@ const item = reactive<{
|
||||
history: [],
|
||||
keyValuesAdd: [],
|
||||
keyValuesDelete: [],
|
||||
originalDetails: null,
|
||||
originalDetails: {
|
||||
id: '',
|
||||
createdAt: null,
|
||||
icon: null,
|
||||
note: null,
|
||||
password: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
updateAt: null,
|
||||
url: null,
|
||||
username: null,
|
||||
},
|
||||
originalKeyValues: null,
|
||||
})
|
||||
|
||||
@ -179,8 +122,12 @@ watch(
|
||||
item.history = JSON.parse(JSON.stringify(currentItem.value?.history))
|
||||
item.keyValuesAdd = []
|
||||
item.keyValuesDelete = []
|
||||
item.originalDetails = JSON.stringify(currentItem.value?.details)
|
||||
item.originalKeyValues = JSON.stringify(currentItem.value?.keyValues)
|
||||
item.originalDetails = JSON.parse(
|
||||
JSON.stringify(currentItem.value?.details),
|
||||
)
|
||||
item.originalKeyValues = JSON.parse(
|
||||
JSON.stringify(currentItem.value?.keyValues),
|
||||
)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
@ -201,7 +148,7 @@ const onUpdateAsync = async () => {
|
||||
keyValuesDelete: item.keyValuesDelete,
|
||||
})
|
||||
if (newId) add({ type: 'success', text: t('success.update') })
|
||||
syncGroupItemsAsync(currentGroupId.value)
|
||||
syncGroupItemsAsync()
|
||||
ignoreChanges.value = true
|
||||
onClose()
|
||||
} catch (error) {
|
||||
@ -224,7 +171,7 @@ const deleteItemAsync = async () => {
|
||||
await deleteAsync(item.details.id, inTrashGroup.value)
|
||||
showConfirmDeleteDialog.value = false
|
||||
add({ type: 'success', text: t('success.delete') })
|
||||
await syncGroupItemsAsync(currentGroupId.value)
|
||||
await syncGroupItemsAsync()
|
||||
onClose()
|
||||
} catch (errro) {
|
||||
add({
|
||||
@ -234,11 +181,12 @@ const deleteItemAsync = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { areItemsEqual } = usePasswordGroup()
|
||||
const hasChanges = computed(
|
||||
() =>
|
||||
!!(
|
||||
item.originalDetails !== JSON.stringify(item.details) ||
|
||||
item.originalKeyValues !== JSON.stringify(item.keyValues) ||
|
||||
!areItemsEqual(item.originalDetails, item.details) ||
|
||||
!areItemsEqual(item.originalKeyValues, item.keyValues) ||
|
||||
item.keyValuesAdd.length ||
|
||||
item.keyValuesDelete.length
|
||||
),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ currentGroup?.id }} {{ currentGroupId }}
|
||||
<HaexPassItem
|
||||
:default-icon="currentGroup?.icon"
|
||||
:history="item.history"
|
||||
@ -10,6 +11,7 @@
|
||||
/>
|
||||
|
||||
<HaexPassMenuBottom
|
||||
:has-changes
|
||||
@close="onClose"
|
||||
@save="onCreateAsync"
|
||||
show-close-button
|
||||
@ -17,33 +19,13 @@
|
||||
>
|
||||
</HaexPassMenuBottom>
|
||||
|
||||
<!-- <div
|
||||
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end"
|
||||
:class="[isVisible ? 'left-15 ' : 'left-0']"
|
||||
>
|
||||
<div class="flex items-center justify-center flex-1">
|
||||
<UiTooltip :tooltip="t('abort')">
|
||||
<UiButton
|
||||
class="btn-error btn-square"
|
||||
@click="onClose"
|
||||
>
|
||||
<Icon name="mdi:close" />
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
<UiTooltip :tooltip="t('create')">
|
||||
<UiButton
|
||||
class="btn-xl btn-square btn-primary"
|
||||
@click="onCreateAsync"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:content-save-outline"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
<div class="flex items-center justify-center flex-1"></div>
|
||||
</div> -->
|
||||
<HaexPassDialogUnsavedChanges
|
||||
:has-changes="hasChanges"
|
||||
v-model:ignore-changes="ignoreChanges"
|
||||
@abort="showUnsavedChangesDialog = false"
|
||||
@confirm="onConfirmIgnoreChanges"
|
||||
v-model:open="showUnsavedChangesDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -64,17 +46,14 @@ defineProps({
|
||||
withCopyButton: Boolean,
|
||||
})
|
||||
|
||||
const { isVisible } = storeToRefs(useSidebarStore())
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const item = reactive<{
|
||||
details: SelectHaexPasswordsItemDetails
|
||||
history: SelectHaexPasswordsItemHistory[]
|
||||
keyValuesAdd: SelectHaexPasswordsItemKeyValues[]
|
||||
keyValuesDelete: SelectHaexPasswordsItemKeyValues[]
|
||||
originalDetails: string | null
|
||||
originalKeyValues: string | null
|
||||
originalDetails: SelectHaexPasswordsItemDetails
|
||||
originalKeyValuesAdd: []
|
||||
}>({
|
||||
details: {
|
||||
id: '',
|
||||
@ -90,13 +69,23 @@ const item = reactive<{
|
||||
},
|
||||
history: [],
|
||||
keyValuesAdd: [],
|
||||
keyValuesDelete: [],
|
||||
originalDetails: null,
|
||||
originalKeyValues: null,
|
||||
originalDetails: {
|
||||
id: '',
|
||||
createdAt: null,
|
||||
icon: null,
|
||||
note: null,
|
||||
password: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
updateAt: null,
|
||||
url: null,
|
||||
username: null,
|
||||
},
|
||||
originalKeyValuesAdd: [],
|
||||
})
|
||||
|
||||
const { add } = useSnackbar()
|
||||
const { currentGroup } = storeToRefs(usePasswordGroupStore())
|
||||
const { currentGroup, currentGroupId } = storeToRefs(usePasswordGroupStore())
|
||||
const { syncGroupItemsAsync } = usePasswordGroupStore()
|
||||
const { addAsync } = usePasswordItemStore()
|
||||
|
||||
@ -107,9 +96,11 @@ const onCreateAsync = async () => {
|
||||
item.keyValuesAdd,
|
||||
currentGroup.value,
|
||||
)
|
||||
|
||||
if (newId) {
|
||||
ignoreChanges.value = true
|
||||
add({ type: 'success', text: t('success') })
|
||||
syncGroupItemsAsync(currentGroup.value?.id)
|
||||
await syncGroupItemsAsync()
|
||||
onClose()
|
||||
}
|
||||
} catch (error) {
|
||||
@ -117,7 +108,32 @@ const onCreateAsync = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => useRouter().back()
|
||||
const ignoreChanges = ref(false)
|
||||
|
||||
const onClose = () => {
|
||||
if (showUnsavedChangesDialog.value) return
|
||||
|
||||
if (hasChanges.value && !ignoreChanges.value)
|
||||
return (showUnsavedChangesDialog.value = true)
|
||||
|
||||
useRouter().back()
|
||||
}
|
||||
|
||||
const { areItemsEqual } = usePasswordGroup()
|
||||
const hasChanges = computed(
|
||||
() =>
|
||||
!!(
|
||||
!areItemsEqual(item.originalDetails, item.details) ||
|
||||
item.keyValuesAdd.length
|
||||
),
|
||||
)
|
||||
|
||||
const showUnsavedChangesDialog = ref(false)
|
||||
const onConfirmIgnoreChanges = () => {
|
||||
showUnsavedChangesDialog.value = false
|
||||
ignoreChanges.value = true
|
||||
onClose()
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
type InsertHaexPasswordsGroups,
|
||||
type SelectHaexPasswordsGroupItems,
|
||||
type SelectHaexPasswordsGroups,
|
||||
type SelectHaexPasswordsItemDetails,
|
||||
} from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
export const trashId = 'trash'
|
||||
@ -28,14 +27,6 @@ export const usePasswordGroupStore = defineStore('passwordGroupStore', () => {
|
||||
currentGroupId.value ? readGroupAsync(currentGroupId.value) : null,
|
||||
)
|
||||
|
||||
/* const currentGroupItems = reactive<{
|
||||
items: SelectHaexPasswordsItemDetails[]
|
||||
groups: SelectHaexPasswordsGroups[]
|
||||
}>({
|
||||
items: [],
|
||||
groups: [],
|
||||
}) */
|
||||
|
||||
const selectedGroupItems = ref<IPasswordMenuItem[]>()
|
||||
|
||||
const breadCrumbs = computed(() => getParentChain(currentGroupId.value))
|
||||
@ -55,15 +46,14 @@ export const usePasswordGroupStore = defineStore('passwordGroupStore', () => {
|
||||
return chain.reverse()
|
||||
}
|
||||
|
||||
const syncGroupItemsAsync = async (currentGroupId?: string | null) => {
|
||||
const { addNotificationAsync } = useNotificationStore()
|
||||
const { readByGroupIdAsync, syncItemsAsync } = usePasswordItemStore()
|
||||
const syncGroupItemsAsync = async () => {
|
||||
const { syncItemsAsync } = usePasswordItemStore()
|
||||
|
||||
groups.value = await readGroupsAsync()
|
||||
await syncItemsAsync()
|
||||
currentGroup.value = groups.value?.find(
|
||||
/* currentGroup.value = groups.value?.find(
|
||||
(group) => group.id === currentGroupId,
|
||||
)
|
||||
) */
|
||||
|
||||
/* try {
|
||||
currentGroupItems.groups =
|
||||
@ -80,7 +70,7 @@ export const usePasswordGroupStore = defineStore('passwordGroupStore', () => {
|
||||
} */
|
||||
}
|
||||
|
||||
watch(currentGroupId, () => syncGroupItemsAsync(currentGroupId.value), {
|
||||
watch(currentGroupId, () => syncGroupItemsAsync(), {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
@ -90,6 +80,7 @@ export const usePasswordGroupStore = defineStore('passwordGroupStore', () => {
|
||||
|
||||
return {
|
||||
addGroupAsync,
|
||||
areGroupsEqual,
|
||||
breadCrumbs,
|
||||
createTrashIfNotExistsAsync,
|
||||
currentGroup,
|
||||
@ -131,13 +122,11 @@ const addGroupAsync = async (group: Partial<InsertHaexPasswordsGroups>) => {
|
||||
|
||||
const readGroupAsync = async (groupId: string) => {
|
||||
const { currentVault } = useVaultStore()
|
||||
|
||||
return (
|
||||
await currentVault.drizzle
|
||||
?.select()
|
||||
.from(haexPasswordsGroups)
|
||||
.where(eq(haexPasswordsGroups.id, groupId))
|
||||
).at(0)
|
||||
const group = await currentVault.drizzle.query.haexPasswordsGroups.findFirst({
|
||||
where: eq(haexPasswordsGroups.id, groupId),
|
||||
})
|
||||
console.log('readGroupAsync', groupId, group)
|
||||
return group
|
||||
}
|
||||
|
||||
const readGroupsAsync = async (filter?: { parentId?: string | null }) => {
|
||||
@ -274,8 +263,6 @@ const insertGroupItemsAsync = async (
|
||||
|
||||
const targetGroup = groups.find((group) => group.id === groupdId)
|
||||
|
||||
console.log('insertGroupItemsAsync', items, targetGroup)
|
||||
|
||||
for (const item of items) {
|
||||
if (item.type === 'group') {
|
||||
const updateGroup = groups.find((group) => group.id === item.id)
|
||||
@ -297,7 +284,7 @@ const insertGroupItemsAsync = async (
|
||||
.where(eq(haexPasswordsGroupItems.itemId, item.id))
|
||||
}
|
||||
}
|
||||
return syncGroupItemsAsync(targetGroup?.id)
|
||||
return syncGroupItemsAsync()
|
||||
}
|
||||
|
||||
const createTrashIfNotExistsAsync = async () => {
|
||||
@ -340,3 +327,20 @@ const deleteGroupAsync = async (groupId: string, final: boolean = false) => {
|
||||
await updateAsync({ id: groupId, parentId: trashId })
|
||||
}
|
||||
}
|
||||
|
||||
const areGroupsEqual = (
|
||||
groupA: unknown | unknown[] | null,
|
||||
groupB: unknown | unknown[] | null,
|
||||
) => {
|
||||
if (groupA === null && groupB === null) return true
|
||||
|
||||
if (Array.isArray(groupA) && Array.isArray(groupB)) {
|
||||
console.log('compare object arrays', groupA, groupB)
|
||||
if (groupA.length === groupB.length) return true
|
||||
|
||||
return groupA.some((group, index) => {
|
||||
return areObjectsEqual(group, groupA[index])
|
||||
})
|
||||
}
|
||||
return areObjectsEqual(groupA, groupB)
|
||||
}
|
||||
|
||||
@ -155,3 +155,78 @@ export const getContrastingTextColor = (
|
||||
// Ein Wert > 186 wird oft als "hell" genug für schwarzen Text angesehen.
|
||||
return luminance > 186 ? 'black' : 'white'
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine "Type Guard"-Funktion, die prüft, ob ein Wert ein Objekt (aber nicht null) ist.
|
||||
* Wenn sie `true` zurückgibt, weiß TypeScript, dass der Wert sicher als Objekt behandelt werden kann.
|
||||
* @param value Der zu prüfende Wert vom Typ `unknown`.
|
||||
* @returns {boolean} `true`, wenn der Wert ein Objekt ist.
|
||||
*/
|
||||
export const isObject = (value: unknown): value is Record<string, unknown> => {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt einen typsicheren, tiefen Vergleich (deep comparison) von zwei Werten durch.
|
||||
* Gibt `true` zurück, wenn die Werte als gleich angesehen werden.
|
||||
*
|
||||
* @param valueA Der erste Wert für den Vergleich.
|
||||
* @param valueB Der zweite Wert für den Vergleich.
|
||||
* @returns {boolean} `true`, wenn die Werte gleich sind, andernfalls `false`.
|
||||
*/
|
||||
export const areObjectsEqual = (valueA: unknown, valueB: unknown): boolean => {
|
||||
console.log('areObjectsEqual', valueA, valueB)
|
||||
// 1. Schneller Check für exakt die gleiche Referenz oder primitive Gleichheit
|
||||
if (valueA === valueB) {
|
||||
return true
|
||||
}
|
||||
|
||||
// DEINE SONDERREGEL: Behandle `null` und einen leeren String `""` als gleichwertig.
|
||||
const areNullAndEmptyString =
|
||||
(valueA === null && valueB === '') || (valueA === '' && valueB === null)
|
||||
|
||||
if (areNullAndEmptyString) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 2. Nutzen der Type Guard: Wenn beide Werte keine Objekte sind,
|
||||
// und die vorherigen Checks fehlschlugen, sind sie ungleich.
|
||||
if (!isObject(valueA) || !isObject(valueB)) {
|
||||
console.log('areObjectsEqual no objects', valueA, valueB)
|
||||
return false
|
||||
}
|
||||
|
||||
// Ab hier weiß TypeScript dank der Type Guard, dass valueA und valueB Objekte sind.
|
||||
|
||||
// 3. Holen der Schlüssel und Vergleich der Anzahl
|
||||
const keysA = Object.keys(valueA)
|
||||
const keysB = Object.keys(valueB)
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
console.log('areObjectsEqual length')
|
||||
return false
|
||||
}
|
||||
|
||||
// 4. Iteration über alle Schlüssel und rekursiver Vergleich der Werte
|
||||
for (const key of keysA) {
|
||||
// Prüfen, ob der Schlüssel auch im zweiten Objekt überhaupt existiert
|
||||
if (!keysB.includes(key)) {
|
||||
console.log('areObjectsEqual keys')
|
||||
return false
|
||||
}
|
||||
|
||||
// Die Werte der Schlüssel sind wieder `unknown`, daher nutzen wir Rekursion.
|
||||
const nestedValueA = valueA[key]
|
||||
const nestedValueB = valueB[key]
|
||||
|
||||
// Wenn der rekursive Aufruf für einen der Werte `false` zurückgibt,
|
||||
// sind die gesamten Objekte ungleich.
|
||||
if (!areObjectsEqual(nestedValueA, nestedValueB)) {
|
||||
console.log('areObjectsEqual nested')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Wenn die Schleife durchläuft, sind die Objekte gleich
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user