Skip to content
Open
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
36 changes: 32 additions & 4 deletions StreamAwesome/src/components/MainSettings.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
<script setup lang="ts">
import IconCanvas from '@/components/IconCanvas.vue'
import IconSettings from '@/components/settings/IconSettings.vue'
import UserPresetManager from '@/components/utils/UserPresetManager.vue'
import IconBrowser from '@/components/browser/IconBrowser.vue'
import { URLManager } from '@/logic/URLManager'
import { getMatchingGenerator } from '@/logic/generator/generators'
import { useIconsStore } from '@/stores/icons'
import { useDropZone } from '@vueuse/core'
import { nextTick, ref } from 'vue'
import { getMetadata } from 'meta-png'
import { metaDataKeyword, PersistenceHandler } from '@/logic/persistence/PersistenceHandler'
import type { PersistentIcon } from "@/logic/persistence/PersistentIcon.ts";

const iconStore = useIconsStore()
URLManager.initialize()

function downloadIcon() {
const iconGenerator = getMatchingGenerator(iconStore.currentIcon)
iconGenerator.saveIcon(iconStore.currentIcon)
}

async function loadPreset(preset: PersistentIcon) {
const parsedMetadata = JSON.parse(JSON.stringify(preset)) as Record<string, unknown>;
await loadIconFromURL(parsedMetadata, 'preset')
}

const dropZoneRef = ref<HTMLDivElement>()

function onDrop(files: File[] | null) {
Expand Down Expand Up @@ -39,13 +54,17 @@ async function createIconFromMetadata(files: File[] | null) {
}

const parsedMetadata = JSON.parse(metadata) as Record<string, unknown>
await loadIconFromURL(parsedMetadata, 'dropped image')
}

async function loadIconFromURL(parsedMetadata: Record<string, unknown>, source: 'dropped image' | 'preset') {
const icon = PersistenceHandler.convertPersistentIconToIcon(parsedMetadata)
if (!icon) {
console.warn('Failed to parse icon from dropped image')
console.warn(`Failed to parse icon from ${source}.`)
return
}

console.log('Successfully parsed icon from dropped image.')
console.log(`Successfully parsed icon from ${source}.`)

// The current approach to load the icon via the URL parameters shall only be a workaround until the UI is more reactive to icon changes.
URLManager.writeURLParametersFromPersistentIcon(parsedMetadata)
Expand All @@ -57,8 +76,17 @@ async function createIconFromMetadata(files: File[] | null) {
<template>
<div class="flex flex-col md:flex-row" ref="dropZoneRef">
<div class="mr-0 grid md:mr-7">
<IconCanvas class="mt-5 mb-5 place-self-center md:place-self-auto" />
<IconSettings />
<IconCanvas
class="mt-5 mb-5 place-self-center md:place-self-auto"
@download-icon="downloadIcon"
/>
<div class="mb-3">
<UserPresetManager
:icon="iconStore.currentIcon"
@load-preset="loadPreset"
/>
</div>
<IconSettings :icon="iconStore.currentIcon" @download-icon="downloadIcon" />
</div>
<div class="flex-grow">
<IconBrowser />
Expand Down
158 changes: 158 additions & 0 deletions StreamAwesome/src/components/utils/UserPresetManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useStorage } from '@vueuse/core'
import type { CustomIcon, FontAwesomePreset } from '@/model/customIcon.ts'
import { PersistenceHandler } from "@/logic/persistence/PersistenceHandler.ts";
import type { PersistentIcon } from "@/logic/persistence/PersistentIcon.ts";

interface IconPreset {
name: string
settings: PersistentIcon
createdAt: string
}

const props = defineProps<{
icon: CustomIcon<FontAwesomePreset>
}>()

const emit = defineEmits<{
loadPreset: [preset: PersistentIcon]
}>()

const showSaveDialog = ref(false)
const showLoadDialog = ref(false)
const presetName = ref('')

const savedPresets = useStorage<IconPreset[]>('iconPresets', [])
const presetsList = computed(() => savedPresets.value)

function savePreset() {
if (!presetName.value.trim()) {
alert('Enter a preset name')
return
}

const newPreset: IconPreset = {
name: presetName.value.trim(),
settings: PersistenceHandler.convertIconToPersistentIcon(props.icon),
createdAt: new Date().toISOString()
}

savedPresets.value = [...savedPresets.value, newPreset]

showSaveDialog.value = false
presetName.value = ''
}

function loadPreset(preset: IconPreset) {
emit('loadPreset', preset.settings)
showLoadDialog.value = false
}

function deletePreset(index: number) {
if (confirm('Are you sure you want to delete this preset?')) {
savedPresets.value.splice(index, 1)
}
}

function openSaveDialog() {
showSaveDialog.value = true
presetName.value = ''
}
</script>

<template>
<div class="preset-manager flex gap-2">
<button
@click="openSaveDialog"
class="w-full rounded bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600"
>
Save Preset
</button>

<button
:disabled="presetsList.length === 0"
@click="showLoadDialog = true"
class="w-full rounded bg-green-600 px-3 py-1 text-sm text-white hover:bg-green-700 disabled:bg-gray-500 disabled:cursor-not-allowed"
>
Load Preset ({{ presetsList.length }})
</button>

<div
v-if="showSaveDialog"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 rounded-lg"
>
<div class="rounded bg-gray-800 p-6 shadow-lg">
<h3 class="mb-4 text-lg font-semibold">Save Icon Preset</h3>
<input
v-model="presetName"
type="text"
placeholder="Enter preset name..."
class="mb-4 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 px-3 py-2"
@keyup.enter="savePreset"
@keyup.esc="showSaveDialog = false"
/>
<div class="flex gap-2">
<button
@click="savePreset"
class="w-full rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
>
Save
</button>
<button
@click="showSaveDialog = false"
class="w-full rounded bg-gray-500 px-4 py-2 hover:bg-gray-600"
>
Cancel
</button>
</div>
</div>
</div>

<div
v-if="showLoadDialog"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
@keyup.esc="showLoadDialog = false"
>
<div class="max-h-[90%] min-w-96 max-w-[80%] overflow-y-auto rounded-lg bg-gray-800 p-6 shadow-lg">
<h3 class="mb-4 text-lg font-semibold">Load Icon Preset</h3>
<div class="space-y-2">
<div
v-for="(preset, index) in presetsList"
:key="index"
class="flex items-center justify-between rounded border p-3 border-gray-600"
>
<div>
<div class="font-medium">{{ preset.name }}</div>
<div class="text-sm text-gray-500">
{{ new Date(preset.createdAt).toLocaleString() }}
</div>
</div>
<div class="flex gap-1.5">
<button
@click="loadPreset(preset)"
class="rounded bg-green-600 px-2 py-1 text-sm text-white hover:bg-green-700"
>
Load
</button>
<button
@click="deletePreset(index)"
class="rounded bg-red-500 px-2 py-1 text-sm text-white hover:bg-red-600"
>
Delete
</button>
</div>
</div>
</div>
<div class="mt-4">
<button
@click="showLoadDialog = false"
class="w-full rounded bg-gray-500 px-4 py-2 hover:bg-gray-600"
>
Close
</button>
</div>
</div>
</div>
</div>
</template>