251 lines
13 KiB
Vue
251 lines
13 KiB
Vue
<script setup>
|
|
import { Head, Link, usePage, useForm } from '@inertiajs/vue3';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { ref, onMounted } from 'vue';
|
|
import { initFlowbite } from 'flowbite';
|
|
|
|
import blocks from '@/Utils/blocks';
|
|
import PageBuilderLayout from '@/layouts/PageBuilderLayout.vue';
|
|
import BlockGroup from '@/Components/Blocks/BlockGroup.vue';
|
|
import BlockWrapper from '@/Components/Blocks/BlockWrapper.vue';
|
|
import AppLogo from '@/Components/PageBuilderLogo.vue';
|
|
import InputError from '@/Components/InputError.vue';
|
|
|
|
const groups = blocks.groups;
|
|
const draft = ref([]);
|
|
const page = usePage();
|
|
|
|
onMounted(() => {
|
|
initFlowbite();
|
|
});
|
|
|
|
const { builtPages, images } = defineProps({
|
|
builtPages: {
|
|
type: Object
|
|
},
|
|
|
|
images: {
|
|
type: Array,
|
|
default: () => ([]),
|
|
},
|
|
});
|
|
|
|
const cloneBlock = (block) => {
|
|
block.uuid = uuidv4();
|
|
return block;
|
|
};
|
|
|
|
const deleteBlock = (index) => {
|
|
draft.value.splice(index, 1);
|
|
};
|
|
|
|
function handleDrop(event) {
|
|
const data = event.dataTransfer.getData('application/json');
|
|
if (data) {
|
|
try {
|
|
const block = JSON.parse(data);
|
|
block.uuid = uuidv4();
|
|
draft.value.push(block);
|
|
console.log('📦 Dropped block:', block);
|
|
} catch (e) {
|
|
console.error("Failed to parse dropped block", e);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Drag and drop
|
|
|
|
const draggingIndex = ref(null)
|
|
|
|
function handleDragStart(event, index) {
|
|
draggingIndex.value = index;
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
event.dataTransfer.setData('text/plain', index);
|
|
};
|
|
|
|
function handleDragOver(event) {
|
|
event.preventDefault();
|
|
event.dataTransfer.dropEffect = 'move';
|
|
};
|
|
|
|
function handleDropReorder(event, dropIndex) {
|
|
event.preventDefault();
|
|
const fromIndex = draggingIndex.value;
|
|
if (fromIndex === null || fromIndex === dropIndex) return;
|
|
|
|
const block = draft.value.splice(fromIndex, 1)[0];
|
|
draft.value.splice(dropIndex, 0, block);
|
|
draggingIndex.value = null;
|
|
};
|
|
|
|
// Save form
|
|
const pageForm = useForm({
|
|
title: '',
|
|
content: '',
|
|
publish: false,
|
|
mainpage: false,
|
|
linked: false,
|
|
linkorder: 0,
|
|
visibility: 'public'
|
|
});
|
|
|
|
const savePage = () => {
|
|
const dropdown = document.getElementById('dropdownInformation');
|
|
if (dropdown) dropdown.classList.add('hidden');
|
|
|
|
pageForm.content = JSON.stringify(draft.value);
|
|
pageForm.post(route('page-builder.landing-page.store'), {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
// onSuccess, the user is redirected to the index page for the time being, so toast handling has to be implemented there
|
|
},
|
|
onError: () => {
|
|
console.warn("Valideringsfeil ved lagring.")
|
|
}
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Head title="Ny Side" />
|
|
|
|
<PageBuilderLayout>
|
|
<template #header>
|
|
<AppLogo />
|
|
<h3 class="text-lg font-semibold">Ny side</h3>
|
|
<div>
|
|
|
|
<button id="dropdownInformationButton" data-dropdown-toggle="dropdownInformation" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" type="button">
|
|
Handlinger
|
|
<svg class="w-2.5 h-2.5 ms-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dropdown menu -->
|
|
<div id="dropdownInformation" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700 dark:divide-gray-600">
|
|
<div class="px-4 py-3 text-sm text-gray-900 dark:text-white">
|
|
<div>{{ page.props.auth.user.name }}</div>
|
|
<div class="font-medium truncate">{{ page.props.auth.user.email }}</div>
|
|
</div>
|
|
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownInformationButton">
|
|
<li>
|
|
<a
|
|
href="#"
|
|
@click.prevent="savePage"
|
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
|
>
|
|
Lagre side
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<div class="py-2">
|
|
<Link :href="route('page-builder.index')" class="block px-4 py-2 btn btn-sm btn-ghost hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
|
Lukk
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<div class="flex h-screen overflow-hidden">
|
|
<!-- Sidebar with form & block groups -->
|
|
<div class="bg-slate-100 overflow-y-auto w-1/6 p-4 space-y-4">
|
|
<div class="space-y-4 p-4 rounded border dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
|
|
<div class="mb-4">
|
|
<label
|
|
for="page_title-text"
|
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
|
>
|
|
Sidetittel
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="page_title-text"
|
|
aria-describedby="page_title-text-explanation"
|
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
placeholder="Skriv inn en tittel"
|
|
v-model="pageForm.title"
|
|
>
|
|
<InputError :message="pageForm.errors.title" class="mt-2" />
|
|
<p id="page_title-text-explanation" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
Tittelen på siden vil være synlig i fanearket i nettleseren hos besøkende, samt brukes til å generere URL (slug)
|
|
</p>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" v-model="pageForm.linked" class="sr-only peer">
|
|
<div class="relative w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 dark:peer-checked:bg-blue-600"></div>
|
|
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">Menylenke</span>
|
|
</label>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="steps-range" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Menylenke rekkefølge</label>
|
|
<input id="steps-range" :disabled="!pageForm.linked" v-model="pageForm.linkorder" type="range" min="0" max="20" step="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
|
<span class="text-xs">Prioritet: {{ pageForm.linkorder }} (høyere verdi kommer først)</span>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" v-model="pageForm.publish" class="sr-only peer">
|
|
<div class="relative w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 dark:peer-checked:bg-blue-600"></div>
|
|
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">Publisert</span>
|
|
</label>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" v-model="pageForm.mainpage" class="sr-only peer">
|
|
<div class="relative w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 dark:peer-checked:bg-blue-600"></div>
|
|
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">Sett som hovedside</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center mb-4">
|
|
<input :checked="pageForm.visibility === 'public'" id="page_state-radio-1" v-model="pageForm.visibility" type="radio" value="public" name="page_state-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
|
<label for="page_state-radio-1" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">Synlig for alle</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input :checked="pageForm.visibility === 'private'" id="page_state-radio-2" v-model="pageForm.visibility" type="radio" value="private" name="page_state-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
|
<label for="page_state-radio-2" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">Bare synlig for medlemmer</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<BlockGroup
|
|
v-for="group in groups"
|
|
:key="group.uuid"
|
|
:title="group.title"
|
|
:blocks="group.blocks"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Builder canvas -->
|
|
<div
|
|
class="bg-white dark:bg-gray-800 w-5/6 p-6 overflow-y-auto"
|
|
@dragover.prevent
|
|
@drop="handleDrop"
|
|
>
|
|
<div v-if="draft.length === 0" class="text-gray-500 text-sm italic">
|
|
Dra inn blokker fra venstre for å begynne.
|
|
</div>
|
|
|
|
<div
|
|
v-for="(block, index) in draft"
|
|
:key="block.uuid"
|
|
class="mb-4"
|
|
draggable="true"
|
|
@dragstart="handleDragStart($event, index)"
|
|
@dragover="handleDragOver"
|
|
@drop="handleDropReorder($event, index)"
|
|
>
|
|
<BlockWrapper :images="images" :block="block" @delete="() => deleteBlock(index)" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PageBuilderLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* scoped styles for the create screen */
|
|
</style>
|