diff --git a/src/components/gameMaster/mapEditor/partials/ListPanel.vue b/src/components/gameMaster/mapEditor/partials/ListPanel.vue
deleted file mode 100644
index 9b2a99a..0000000
--- a/src/components/gameMaster/mapEditor/partials/ListPanel.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-  <div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" v-if="isOpen">
-    <div class="flex flex-col gap-2.5 p-2.5">
-      <div class="relative flex">
-        <img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
-        <label class="mb-1.5 font-titles hidden" for="search">Search</label>
-        <input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
-      </div>
-      <div class="flex">
-        <select class="input-field w-full" name="lists" v-model="lists">
-          <option value="tile">Tiles</option>
-          <option value="map_object">Objects</option>
-        </select>
-      </div>
-    </div>
-    <div class="h-full overflow-auto relative border-0 border-t border-solid border-gray-500 p-2.5">
-      <TileList v-if="mapEditor.drawMode.value === 'tile'" />
-      <ObjectList v-if="mapEditor.drawMode.value === 'map_object'" />
-    </div>
-    <div class="flex flex-col h-40 gap-2.5 p-3.5 border-t border-0 border-solid border-gray-500">
-      <span>Tags:</span>
-      <div class="flex grow items-center flex-wrap gap-1.5 overflow-auto">
-        <span class="m-auto">No tags selected</span>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import type { Tile } from '@/application/types'
-import ObjectList from '@/components/gameMaster/mapEditor/partials/lists/MapObjectList.vue'
-import TileList from '@/components/gameMaster/mapEditor/partials/lists/TileList.vue'
-import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { TileStorage } from '@/storage/storages'
-import { liveQuery } from 'dexie'
-import { onMounted, onUnmounted, ref, watch } from 'vue'
-
-const mapEditor = useMapEditorComposable()
-
-const isOpen = ref(false)
-const tileStorage = new TileStorage()
-const searchQuery = ref('')
-const tiles = ref<Tile[]>([])
-const lists = ref<string>(mapEditor.drawMode.value)
-
-defineExpose({
-  open: () => (isOpen.value = true),
-  close: () => (isOpen.value = false),
-  toggle: () => (isOpen.value = !isOpen.value)
-})
-
-watch(lists, (list) => {
-  mapEditor.setDrawMode(list)
-})
-
-watch(mapEditor.drawMode, (list) => {
-  lists.value = list
-})
-
-let subscription: any = null
-
-onMounted(() => {
-  subscription = liveQuery(() => tileStorage.liveQuery()).subscribe({
-    next: (result) => {
-      tiles.value = result
-    },
-    error: (error) => {
-      console.error('Failed to fetch tiles:', error)
-    }
-  })
-})
-
-onUnmounted(() => {
-  if (subscription) {
-    subscription.unsubscribe()
-  }
-})
-</script>
diff --git a/src/components/gameMaster/mapEditor/partials/MapObjectList.vue b/src/components/gameMaster/mapEditor/partials/MapObjectList.vue
new file mode 100644
index 0000000..619ca5c
--- /dev/null
+++ b/src/components/gameMaster/mapEditor/partials/MapObjectList.vue
@@ -0,0 +1,107 @@
+<template>
+  <div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" v-if="isOpen">
+    <div class="flex flex-col gap-2.5 p-2.5">
+      <div class="relative flex">
+        <img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
+        <label class="mb-1.5 font-titles hidden" for="search">Search</label>
+        <input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
+      </div>
+      <div class="flex">
+        <select class="input-field w-full" name="lists">
+          <option value="tile">Tiles</option>
+          <option value="map_object">Objects</option>
+        </select>
+      </div>
+    </div>
+    <div class="h-full overflow-auto relative border-0 border-t border-solid border-gray-500 p-2.5">
+      <div class="h-full overflow-auto">
+        <div class="flex justify-between flex-wrap gap-2.5 items-center">
+          <div v-for="(mapObject, index) in filteredMapObjects" :key="index" class="max-w-1/4 inline-block">
+            <img
+              class="border-2 border-solid rounded max-w-full"
+              :src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`"
+              alt="Object"
+              @click="mapEditor.setSelectedMapObject(mapObject)"
+              :class="{
+                'cursor-pointer transition-all duration-300': true,
+                'border-cyan shadow-lg': mapEditor.selectedMapObject.value?.id === mapObject.id,
+                'border-transparent hover:border-gray-300': mapEditor.selectedMapObject.value?.id !== mapObject.id
+              }"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="flex flex-col h-40 gap-2.5 p-3.5 border-t border-0 border-solid border-gray-500">
+      <span>Tags:</span>
+      <div class="flex grow items-center flex-wrap gap-1.5 overflow-auto">
+        <span class="m-auto">No tags selected</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import config from '@/application/config'
+import type { MapObject } from '@/application/types'
+import Modal from '@/components/utilities/Modal.vue'
+import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
+import { MapObjectStorage } from '@/storage/storages'
+import { liveQuery } from 'dexie'
+import { computed, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
+
+const isOpen = ref(false)
+const mapObjectStorage = new MapObjectStorage()
+const isModalOpen = ref(false)
+const mapEditor = useMapEditorComposable()
+const searchQuery = ref('')
+const selectedTags = ref<string[]>([])
+const mapObjectList = ref<MapObject[]>([])
+
+defineExpose({
+  open: () => (isOpen.value = true),
+  close: () => (isOpen.value = false),
+  toggle: () => (isOpen.value = !isOpen.value)
+})
+
+const uniqueTags = computed(() => {
+  const allTags = mapObjectList.value.flatMap((obj) => obj.tags || [])
+  return Array.from(new Set(allTags))
+})
+
+const filteredMapObjects = computed(() => {
+  return mapObjectList.value.filter((object) => {
+    const matchesSearch = !searchQuery.value || object.name.toLowerCase().includes(searchQuery.value.toLowerCase())
+    const matchesTags = selectedTags.value.length === 0 || (object.tags && selectedTags.value.some((tag) => object.tags.includes(tag)))
+    return matchesSearch && matchesTags
+  })
+})
+
+const toggleTag = (tag: string) => {
+  if (selectedTags.value.includes(tag)) {
+    selectedTags.value = selectedTags.value.filter((t) => t !== tag)
+  } else {
+    selectedTags.value.push(tag)
+  }
+}
+
+let subscription: any = null
+
+onMounted(() => {
+  isModalOpen.value = true
+  subscription = liveQuery(() => mapObjectStorage.liveQuery()).subscribe({
+    next: (result) => {
+      mapObjectList.value = result
+    },
+    error: (error) => {
+      console.error('Failed to fetch tiles:', error)
+    }
+  })
+})
+
+onUnmounted(() => {
+  if (subscription) {
+    subscription.unsubscribe()
+  }
+})
+</script>
diff --git a/src/components/gameMaster/mapEditor/partials/TileList.vue b/src/components/gameMaster/mapEditor/partials/TileList.vue
new file mode 100644
index 0000000..322a318
--- /dev/null
+++ b/src/components/gameMaster/mapEditor/partials/TileList.vue
@@ -0,0 +1,173 @@
+<template>
+  <div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800">
+    <div class="flex flex-col gap-2.5 p-2.5">
+      <div class="relative flex">
+        <img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
+        <label class="mb-1.5 font-titles hidden" for="search">Search</label>
+        <input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
+      </div>
+      <div class="flex">
+        <select class="input-field w-full" name="lists">
+          <option value="tile">Tiles</option>
+          <option value="map_object">Objects</option>
+        </select>
+      </div>
+    </div>
+    <div class="h-full overflow-auto relative border-0 border-t border-solid border-gray-500 p-2.5">
+      <div class="h-full" v-if="!selectedGroup">
+        <div class="grid grid-cols-4 gap-2 justify-items-center">
+          <div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
+            <img
+              class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
+              :src="`${config.server_endpoint}/textures/tiles/${group.parent.id}.png`"
+              :alt="group.parent.name"
+              @click="openGroup(group)"
+              @load="() => tileProcessor.processTile(group.parent)"
+              :class="{
+                'border-cyan shadow-lg': isActiveTile(group.parent),
+                'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
+              }"
+            />
+            <span class="text-xs mt-1">{{ getTileCategory(group.parent) }}</span>
+            <span v-if="group.children.length > 0" class="absolute top-0 right-0 bg-cyan text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">
+              {{ group.children.length + 1 }}
+            </span>
+          </div>
+        </div>
+      </div>
+      <div v-else class="h-full overflow-auto">
+        <div class="p-4">
+          <button @click="closeGroup" class="btn-cyan mb-4">Back to All Tiles</button>
+          <h4 class="text-lg mb-4">{{ selectedGroup.parent.name }} Group</h4>
+          <div class="grid grid-cols-4 gap-2 justify-items-center">
+            <div class="flex flex-col items-center justify-center">
+              <img
+                class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
+                :src="`${config.server_endpoint}/textures/tiles/${selectedGroup.parent.id}.png`"
+                :alt="selectedGroup.parent.name"
+                @click="selectTile(selectedGroup.parent.id)"
+                :class="{
+                  'border-cyan shadow-lg': isActiveTile(selectedGroup.parent),
+                  'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
+                }"
+              />
+              <span class="text-xs mt-1">{{ getTileCategory(selectedGroup.parent) }}</span>
+            </div>
+            <div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
+              <img
+                class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
+                :src="`${config.server_endpoint}/textures/tiles/${childTile.id}.png`"
+                :alt="childTile.name"
+                @click="selectTile(childTile.id)"
+                :class="{
+                  'border-cyan shadow-lg': isActiveTile(childTile),
+                  'border-transparent hover:border-gray-300': !isActiveTile(childTile)
+                }"
+              />
+              <span class="text-xs mt-1">{{ getTileCategory(childTile) }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="flex flex-col h-40 gap-2.5 p-3.5 border-t border-0 border-solid border-gray-500">
+      <span>Tags:</span>
+      <div class="flex grow items-center flex-wrap gap-1.5 overflow-auto">
+        <span class="m-auto">No tags selected</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import config from '@/application/config'
+import type { Tile } from '@/application/types'
+import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
+import { useTileProcessingComposable } from '@/composables/useTileProcessingComposable'
+import { TileStorage } from '@/storage/storages'
+import { computed, onMounted, onUnmounted, ref } from 'vue'
+
+const tileStorage = new TileStorage()
+const mapEditor = useMapEditorComposable()
+const tileProcessor = useTileProcessingComposable()
+const searchQuery = ref('')
+const selectedTags = ref<string[]>([])
+const tileCategories = ref<Map<string, string>>(new Map())
+const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null)
+const tiles = ref<Tile[]>([])
+
+defineExpose({
+  open: () => (isOpen.value = true),
+  close: () => (isOpen.value = false),
+  toggle: () => (isOpen.value = !isOpen.value)
+})
+
+const uniqueTags = computed(() => {
+  const allTags = tiles.value.flatMap((tile) => tile.tags || [])
+  return Array.from(new Set(allTags))
+})
+
+const groupedTiles = computed(() => {
+  const groups: { parent: Tile; children: Tile[] }[] = []
+  const filteredTiles = tiles.value.filter((tile) => {
+    const matchesSearch = !searchQuery.value || tile.name.toLowerCase().includes(searchQuery.value.toLowerCase())
+    const matchesTags = selectedTags.value.length === 0 || (tile.tags && selectedTags.value.some((tag) => tile.tags.includes(tag)))
+    return matchesSearch && matchesTags
+  })
+
+  filteredTiles.forEach((tile) => {
+    const parentGroup = groups.find((group) => tileProcessor.areTilesRelated(group.parent, tile))
+    if (parentGroup && parentGroup.parent.id !== tile.id) {
+      parentGroup.children.push(tile)
+    } else {
+      groups.push({ parent: tile, children: [] })
+    }
+  })
+
+  return groups
+})
+
+const toggleTag = (tag: string) => {
+  if (selectedTags.value.includes(tag)) {
+    selectedTags.value = selectedTags.value.filter((t) => t !== tag)
+  } else {
+    selectedTags.value.push(tag)
+  }
+}
+
+function getTileCategory(tile: Tile): string {
+  return tileCategories.value.get(tile.id) || ''
+}
+
+function openGroup(group: { parent: Tile; children: Tile[] }) {
+  selectedGroup.value = group
+}
+
+function closeGroup() {
+  selectedGroup.value = null
+}
+
+function selectTile(tile: string) {
+  mapEditor.setSelectedTile(tile)
+}
+
+function isActiveTile(tile: Tile): boolean {
+  return mapEditor.selectedTile.value === tile.id
+}
+
+onMounted(async () => {
+  tiles.value = await tileStorage.getAll()
+  const initialBatchSize = 20
+  const initialTiles = tiles.value.slice(0, initialBatchSize)
+  initialTiles.forEach((tile) => tileProcessor.processTile(tile))
+
+  // Process remaining tiles in background
+  setTimeout(() => {
+    tiles.value.slice(initialBatchSize).forEach((tile) => tileProcessor.processTile(tile))
+  }, 1000)
+})
+
+onUnmounted(() => {
+  tileProcessor.cleanup()
+})
+</script>
diff --git a/src/components/gameMaster/mapEditor/partials/Toolbar.vue b/src/components/gameMaster/mapEditor/partials/Toolbar.vue
index 35587ef..e1661b2 100644
--- a/src/components/gameMaster/mapEditor/partials/Toolbar.vue
+++ b/src/components/gameMaster/mapEditor/partials/Toolbar.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="flex justify-between p-5 w-[calc(100%_-_40px)] fixed bottom-0 left-0 z-20" :class="{ 'list-open' : listOpen }">
+  <div class="flex justify-between p-5 w-[calc(100%_-_40px)] fixed bottom-0 left-0 z-20" :class="{ 'list-open': listOpen }">
     <div class="toolbar rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10">
       <div ref="toolbar" class="tools flex gap-2.5" v-if="mapEditor.currentMap.value">
         <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'move' }" @click="handleClick('move')">
@@ -104,7 +104,7 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'
 
 const mapEditor = useMapEditorComposable()
 
-const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor', 'open-lists', 'close-lists'])
+const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor'])
 
 // States
 const toolbar = ref(null)
@@ -116,15 +116,6 @@ const listOpen = ref(false)
 
 // drawMode
 function setDrawMode(value: string) {
-  if (mapEditor.tool.value === 'paint' || mapEditor.tool.value === 'pencil' || mapEditor.tool.value === 'eraser') {
-    listOpen.value = false
-    emit('close-lists')
-    if (value === 'tile' || value === 'map_object') {
-      listOpen.value = true
-      emit('open-lists')
-    }
-  }
-
   mapEditor.setDrawMode(value)
   selectPencilOpen.value = false
   selectEraserOpen.value = false
@@ -154,15 +145,10 @@ function handleClick(tool: string) {
   if (tool === 'mapEditorSettings') {
     isMapEditorSettingsModalOpen.value = true
     listOpen.value = false
-    emit('close-lists')
-  }
-  if (tool === 'settings') {
+  } else if (tool === 'settings') {
     listOpen.value = false
-    emit('open-settings')
-    emit('close-lists')
   } else if (tool === 'move') {
     listOpen.value = false
-    emit('close-lists')
     mapEditor.setTool(tool)
   } else {
     mapEditor.setTool(tool)
diff --git a/src/components/gameMaster/mapEditor/partials/lists/MapObjectList.vue b/src/components/gameMaster/mapEditor/partials/lists/MapObjectList.vue
deleted file mode 100644
index 0b785aa..0000000
--- a/src/components/gameMaster/mapEditor/partials/lists/MapObjectList.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<template>
-  <div class="h-full overflow-auto">
-    <div class="flex justify-between flex-wrap gap-2.5 items-center">
-      <div v-for="(mapObject, index) in filteredMapObjects" :key="index" class="max-w-1/4 inline-block">
-        <img
-          class="border-2 border-solid rounded max-w-full"
-          :src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`"
-          alt="Object"
-          @click="mapEditor.setSelectedMapObject(mapObject)"
-          :class="{
-            'cursor-pointer transition-all duration-300': true,
-            'border-cyan shadow-lg': mapEditor.selectedMapObject.value?.id === mapObject.id,
-            'border-transparent hover:border-gray-300': mapEditor.selectedMapObject.value?.id !== mapObject.id
-          }"
-        />
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import config from '@/application/config'
-import type { MapObject } from '@/application/types'
-import Modal from '@/components/utilities/Modal.vue'
-import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { MapObjectStorage } from '@/storage/storages'
-import { liveQuery } from 'dexie'
-import { computed, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
-
-const isOpen = ref(false)
-const mapObjectStorage = new MapObjectStorage()
-const isModalOpen = ref(false)
-const mapEditor = useMapEditorComposable()
-const searchQuery = ref('')
-const selectedTags = ref<string[]>([])
-const mapObjectList = ref<MapObject[]>([])
-
-defineExpose({
-  open: () => (isOpen.value = true),
-  close: () => (isOpen.value = false),
-  toggle: () => (isOpen.value = !isOpen.value)
-})
-
-const uniqueTags = computed(() => {
-  const allTags = mapObjectList.value.flatMap((obj) => obj.tags || [])
-  return Array.from(new Set(allTags))
-})
-
-const filteredMapObjects = computed(() => {
-  return mapObjectList.value.filter((object) => {
-    const matchesSearch = !searchQuery.value || object.name.toLowerCase().includes(searchQuery.value.toLowerCase())
-    const matchesTags = selectedTags.value.length === 0 || (object.tags && selectedTags.value.some((tag) => object.tags.includes(tag)))
-    return matchesSearch && matchesTags
-  })
-})
-
-const toggleTag = (tag: string) => {
-  if (selectedTags.value.includes(tag)) {
-    selectedTags.value = selectedTags.value.filter((t) => t !== tag)
-  } else {
-    selectedTags.value.push(tag)
-  }
-}
-
-let subscription: any = null
-
-onMounted(() => {
-  isModalOpen.value = true
-  subscription = liveQuery(() => mapObjectStorage.liveQuery()).subscribe({
-    next: (result) => {
-      mapObjectList.value = result
-    },
-    error: (error) => {
-      console.error('Failed to fetch tiles:', error)
-    }
-  })
-})
-
-onUnmounted(() => {
-  if (subscription) {
-    subscription.unsubscribe()
-  }
-})
-</script>
diff --git a/src/components/gameMaster/mapEditor/partials/lists/TileList.vue b/src/components/gameMaster/mapEditor/partials/lists/TileList.vue
deleted file mode 100644
index f7f24b1..0000000
--- a/src/components/gameMaster/mapEditor/partials/lists/TileList.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<template>
-  <div class="h-full" v-if="!selectedGroup">
-    <div class="grid grid-cols-4 gap-2 justify-items-center">
-      <div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
-        <img
-          class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
-          :src="`${config.server_endpoint}/textures/tiles/${group.parent.id}.png`"
-          :alt="group.parent.name"
-          @click="openGroup(group)"
-          @load="() => tileProcessor.processTile(group.parent)"
-          :class="{
-            'border-cyan shadow-lg': isActiveTile(group.parent),
-            'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
-          }"
-        />
-        <span class="text-xs mt-1">{{ getTileCategory(group.parent) }}</span>
-        <span v-if="group.children.length > 0" class="absolute top-0 right-0 bg-cyan text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">
-          {{ group.children.length + 1 }}
-        </span>
-      </div>
-    </div>
-  </div>
-  <div v-else class="h-full overflow-auto">
-    <div class="p-4">
-      <button @click="closeGroup" class="btn-cyan mb-4">Back to All Tiles</button>
-      <h4 class="text-lg mb-4">{{ selectedGroup.parent.name }} Group</h4>
-      <div class="grid grid-cols-4 gap-2 justify-items-center">
-        <div class="flex flex-col items-center justify-center">
-          <img
-            class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
-            :src="`${config.server_endpoint}/textures/tiles/${selectedGroup.parent.id}.png`"
-            :alt="selectedGroup.parent.name"
-            @click="selectTile(selectedGroup.parent.id)"
-            :class="{
-              'border-cyan shadow-lg': isActiveTile(selectedGroup.parent),
-              'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
-            }"
-          />
-          <span class="text-xs mt-1">{{ getTileCategory(selectedGroup.parent) }}</span>
-        </div>
-        <div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
-          <img
-            class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
-            :src="`${config.server_endpoint}/textures/tiles/${childTile.id}.png`"
-            :alt="childTile.name"
-            @click="selectTile(childTile.id)"
-            :class="{
-              'border-cyan shadow-lg': isActiveTile(childTile),
-              'border-transparent hover:border-gray-300': !isActiveTile(childTile)
-            }"
-          />
-          <span class="text-xs mt-1">{{ getTileCategory(childTile) }}</span>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import config from '@/application/config'
-import type { Tile } from '@/application/types'
-import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { useTileProcessingComposable } from '@/composables/useTileProcessingComposable'
-import { TileStorage } from '@/storage/storages'
-import { computed, onMounted, onUnmounted, ref } from 'vue'
-
-const isOpen = ref(false)
-const tileStorage = new TileStorage()
-const mapEditor = useMapEditorComposable()
-const tileProcessor = useTileProcessingComposable()
-const searchQuery = ref('')
-const selectedTags = ref<string[]>([])
-const tileCategories = ref<Map<string, string>>(new Map())
-const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null)
-const tiles = ref<Tile[]>([])
-
-defineExpose({
-  open: () => (isOpen.value = true),
-  close: () => (isOpen.value = false),
-  toggle: () => (isOpen.value = !isOpen.value)
-})
-
-const uniqueTags = computed(() => {
-  const allTags = tiles.value.flatMap((tile) => tile.tags || [])
-  return Array.from(new Set(allTags))
-})
-
-const groupedTiles = computed(() => {
-  const groups: { parent: Tile; children: Tile[] }[] = []
-  const filteredTiles = tiles.value.filter((tile) => {
-    const matchesSearch = !searchQuery.value || tile.name.toLowerCase().includes(searchQuery.value.toLowerCase())
-    const matchesTags = selectedTags.value.length === 0 || (tile.tags && selectedTags.value.some((tag) => tile.tags.includes(tag)))
-    return matchesSearch && matchesTags
-  })
-
-  filteredTiles.forEach((tile) => {
-    const parentGroup = groups.find((group) => tileProcessor.areTilesRelated(group.parent, tile))
-    if (parentGroup && parentGroup.parent.id !== tile.id) {
-      parentGroup.children.push(tile)
-    } else {
-      groups.push({ parent: tile, children: [] })
-    }
-  })
-
-  return groups
-})
-
-const toggleTag = (tag: string) => {
-  if (selectedTags.value.includes(tag)) {
-    selectedTags.value = selectedTags.value.filter((t) => t !== tag)
-  } else {
-    selectedTags.value.push(tag)
-  }
-}
-
-function getTileCategory(tile: Tile): string {
-  return tileCategories.value.get(tile.id) || ''
-}
-
-function openGroup(group: { parent: Tile; children: Tile[] }) {
-  selectedGroup.value = group
-}
-
-function closeGroup() {
-  selectedGroup.value = null
-}
-
-function selectTile(tile: string) {
-  mapEditor.setSelectedTile(tile)
-}
-
-function isActiveTile(tile: Tile): boolean {
-  return mapEditor.selectedTile.value === tile.id
-}
-
-onMounted(async () => {
-  tiles.value = await tileStorage.getAll()
-  const initialBatchSize = 20
-  const initialTiles = tiles.value.slice(0, initialBatchSize)
-  initialTiles.forEach((tile) => tileProcessor.processTile(tile))
-
-  // Process remaining tiles in background
-  setTimeout(() => {
-    tiles.value.slice(initialBatchSize).forEach((tile) => tileProcessor.processTile(tile))
-  }, 1000)
-})
-
-onUnmounted(() => {
-  tileProcessor.cleanup()
-})
-</script>
diff --git a/src/components/screens/MapEditor.vue b/src/components/screens/MapEditor.vue
index c72202f..c559fee 100644
--- a/src/components/screens/MapEditor.vue
+++ b/src/components/screens/MapEditor.vue
@@ -5,9 +5,10 @@
         <div v-if="!isLoaded" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-3xl font-ui">Loading...</div>
         <div v-else>
           <Map v-if="mapEditor.currentMap.value" :key="mapEditor.currentMap.value?.id" />
-          <Toolbar ref="toolbar" @save="save" @clear="clear" @open-maps="mapModal?.open" @open-settings="mapSettingsModal?.open" @close-editor="mapEditor.toggleActive" @close-lists="list?.close" @open-lists="list?.open" />
+          <Toolbar ref="toolbar" @save="save" @clear="clear" @open-maps="mapModal?.open" @open-settings="mapSettingsModal?.open" @close-editor="mapEditor.toggleActive" />
           <MapList ref="mapModal" @open-create-map="mapSettingsModal?.open" />
-          <ListPanel ref="list" />
+          <TileList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'tile'" />
+          <MapObjectList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'map_object'" />
           <MapSettings ref="mapSettingsModal" />
           <TeleportModal ref="teleportModal" />
         </div>
@@ -21,10 +22,11 @@ import config from '@/application/config'
 import 'phaser'
 import type { Map as MapT } from '@/application/types'
 import Map from '@/components/gameMaster/mapEditor/Map.vue'
-import ListPanel from '@/components/gameMaster/mapEditor/partials/ListPanel.vue'
 import MapList from '@/components/gameMaster/mapEditor/partials/MapList.vue'
+import MapObjectList from '@/components/gameMaster/mapEditor/partials/MapObjectList.vue'
 import MapSettings from '@/components/gameMaster/mapEditor/partials/MapSettings.vue'
 import TeleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
+import TileList from '@/components/gameMaster/mapEditor/partials/TileList.vue'
 import Toolbar from '@/components/gameMaster/mapEditor/partials/Toolbar.vue'
 import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
 import { loadAllTileTextures } from '@/services/mapService'
@@ -38,7 +40,6 @@ const mapEditor = useMapEditorComposable()
 const gameStore = useGameStore()
 
 const mapModal = useTemplateRef('mapModal')
-const list = useTemplateRef('list')
 const mapSettingsModal = useTemplateRef('mapSettingsModal')
 
 const isLoaded = ref(false)
@@ -93,7 +94,7 @@ function save() {
     pvp: currentMap.pvp,
     mapEffects: currentMap.mapEffects,
     mapEventTiles: currentMap.mapEventTiles,
-    placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, depth, isRotated, positionX, positionY }) => ({ id, mapObject, depth, isRotated, positionX, positionY })) ?? []
+    placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, isRotated, positionX, positionY }) => ({ id, mapObject, isRotated, positionX, positionY })) ?? []
   }
 
   gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
diff --git a/src/composables/useTileProcessingComposable.ts b/src/composables/useTileProcessingComposable.ts
index 3b3d0b0..d67dd5f 100644
--- a/src/composables/useTileProcessingComposable.ts
+++ b/src/composables/useTileProcessingComposable.ts
@@ -4,26 +4,32 @@ import type { TileAnalysisResult, TileWorkerMessage } from '@/types/tileTypes'
 import { ref } from 'vue'
 
 // Constants for image processing
-const DOWNSCALE_WIDTH = 32
-const DOWNSCALE_HEIGHT = 16
+const DOWNSCALE_WIDTH = 16
+const DOWNSCALE_HEIGHT = 8
 const COLOR_SIMILARITY_THRESHOLD = 30
 const EDGE_SIMILARITY_THRESHOLD = 20
-const BATCH_SIZE = 4
+const BATCH_SIZE = 8
 
 export function useTileProcessingComposable() {
   const tileAnalysisCache = ref<Map<string, { color: { r: number; g: number; b: number }; edge: number; namePrefix: string }>>(new Map())
   const processingQueue = ref<Tile[]>([])
   let isProcessing = false
-  const worker = new Worker(new URL('@/workers/tileAnalyzerWorker.ts', import.meta.url), { type: 'module' })
 
-  worker.onmessage = (e: MessageEvent<TileAnalysisResult>) => {
-    const { tileId, color, edge, namePrefix } = e.data
-    tileAnalysisCache.value.set(tileId, { color, edge, namePrefix })
-    isProcessing = false
-    processBatch()
-  }
+  const NUM_WORKERS = 4
+  const workers = Array.from({ length: NUM_WORKERS }, () => new Worker(new URL('@/workers/tileAnalyzerWorker.ts', import.meta.url), { type: 'module' }))
+  let currentWorker = 0
 
-  async function processTileAsync(tile: Tile): Promise<void> {
+  // Modify worker message handling
+  workers.forEach((worker) => {
+    worker.onmessage = (e: MessageEvent<TileAnalysisResult>) => {
+      const { tileId, color, edge, namePrefix } = e.data
+      tileAnalysisCache.value.set(tileId, { color, edge, namePrefix })
+      isProcessing = false
+      processBatch()
+    }
+  })
+
+  async function processTileAsync(tile: Tile, worker: Worker): Promise<void> {
     if (tileAnalysisCache.value.has(tile.id)) return
 
     return new Promise((resolve) => {
@@ -60,7 +66,12 @@ export function useTileProcessingComposable() {
     isProcessing = true
 
     const batch = processingQueue.value.splice(0, BATCH_SIZE)
-    Promise.all(batch.map((tile) => processTileAsync(tile))).then(() => {
+    Promise.all(
+      batch.map((tile) => {
+        currentWorker = (currentWorker + 1) % NUM_WORKERS
+        return processTileAsync(tile, workers[currentWorker])
+      })
+    ).then(() => {
       isProcessing = false
       if (processingQueue.value.length > 0) {
         setTimeout(processBatch, 0)
@@ -87,7 +98,7 @@ export function useTileProcessingComposable() {
   }
 
   function cleanup() {
-    worker.terminate()
+    workers.forEach((worker) => worker.terminate())
   }
 
   return {
diff --git a/src/workers/tileAnalyzerWorker.ts b/src/workers/tileAnalyzerWorker.ts
index 752e7a4..4ead142 100644
--- a/src/workers/tileAnalyzerWorker.ts
+++ b/src/workers/tileAnalyzerWorker.ts
@@ -12,7 +12,6 @@ function analyzeTile(imageData: ImageData, tileId: string, tileName: string): Ti
   const { r, g, b } = getDominantColorFast(imageData)
   const edge = getEdgeComplexityFast(imageData)
   const namePrefix = tileName.split('_')[0]
-
   return {
     tileId,
     color: { r, g, b },
@@ -53,16 +52,14 @@ function getEdgeComplexityFast(imageData: ImageData) {
   const height = imageData.height
   let edgePixels = 0
 
-  for (let y = 0; y < height; y += PIXEL_SAMPLE_RATE) {
-    for (let x = 0; x < width; x += PIXEL_SAMPLE_RATE) {
+  // Only check every other row/column
+  for (let y = 0; y < height; y += PIXEL_SAMPLE_RATE * 2) {
+    for (let x = 0; x < width; x += PIXEL_SAMPLE_RATE * 2) {
       const i = (y * width + x) * 4
-      if (
-        data[i + 3] > 0 &&
-        (x === 0 || y === 0 || x >= width - PIXEL_SAMPLE_RATE || y >= height - PIXEL_SAMPLE_RATE || data[i - 4 * PIXEL_SAMPLE_RATE + 3] === 0 || data[i + 4 * PIXEL_SAMPLE_RATE + 3] === 0 || data[i - width * 4 * PIXEL_SAMPLE_RATE + 3] === 0 || data[i + width * 4 * PIXEL_SAMPLE_RATE + 3] === 0)
-      ) {
+      if (data[i + 3] > 0 && (x === 0 || y === 0 || x >= width - PIXEL_SAMPLE_RATE || y >= height - PIXEL_SAMPLE_RATE || data[i - 4 * PIXEL_SAMPLE_RATE + 3] === 0)) {
         edgePixels++
       }
     }
   }
-  return edgePixels * PIXEL_SAMPLE_RATE
+  return edgePixels * PIXEL_SAMPLE_RATE * 2
 }