parent
20e1eac2f1
commit
fbe7d4cf3d
19 changed files with 526 additions and 282 deletions
@ -0,0 +1,31 @@ |
||||
<?php |
||||
|
||||
use Illuminate\Database\Migrations\Migration; |
||||
use Illuminate\Database\Schema\Blueprint; |
||||
use Illuminate\Support\Facades\DB; |
||||
use Illuminate\Support\Facades\Schema; |
||||
|
||||
return new class extends Migration |
||||
{ |
||||
/** |
||||
* Run the migrations. |
||||
*/ |
||||
public function up(): void |
||||
{ |
||||
$aps = DB::table("album_photo")->get(); |
||||
foreach($aps as $ap) { |
||||
$photo = DB::table("photos")->where("id", $ap->photo_id)->first(); |
||||
DB::table("album_photo")->where("id", $ap->id)->update([ |
||||
"created_at" => $photo->created_at |
||||
]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Reverse the migrations. |
||||
*/ |
||||
public function down(): void |
||||
{ |
||||
// |
||||
} |
||||
}; |
@ -0,0 +1,68 @@ |
||||
<script setup> |
||||
import { reactive } from 'vue'; |
||||
import PanelCreate from './PanelCreate.vue'; |
||||
import PanelDisplay from './PanelDisplay.vue'; |
||||
import axios from 'axios'; |
||||
|
||||
const props = defineProps({ |
||||
album: { |
||||
type: Object, |
||||
required: true, |
||||
} |
||||
}); |
||||
|
||||
const uuids = async () => { |
||||
let res = await axios.get(route("album.photo.uuid", props.album.uuid)); |
||||
console.log(res.data.uuids); |
||||
panelState.uuids = res.data.uuids; |
||||
} |
||||
|
||||
const panelState = reactive({ toggle: true, uuids: [] }); |
||||
|
||||
const imageState = reactive({ |
||||
"url": "", |
||||
}) |
||||
|
||||
const emits = defineEmits(["close", "data"]); |
||||
|
||||
const imageAdded = (file) => { |
||||
imageState.url = file.url; |
||||
} |
||||
|
||||
const imageRemoved = (file) => { |
||||
imageState.url = ""; |
||||
} |
||||
|
||||
uuids(); |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="text-left text-black shadow-2xl rounded-lg bg-gray-50 |
||||
border border-gray-300" :class="{ 'w-[30rem]': !panelState.toggle, 'w-[60rem] h-[40rem]': panelState.toggle, }"> |
||||
<div v-if="imageState.url" class="max-h-72 overflow-hidden flex items-center"> |
||||
<img :src="imageState.url"> |
||||
</div> |
||||
<div class="px-10 pb-5 pt-5"> |
||||
<div class="w-full flex justify-between mb-5 items-center"> |
||||
<select @change="panelState.toggle = !panelState.toggle" class="bg-gray-200 border-none rounded shadow"> |
||||
<option>Photo Existante</option> |
||||
<option>Nouvelle photo</option> |
||||
</select> |
||||
<img @click="() => emits('close')" src="/icons/cancel.svg" class="h-7 hover:bg-black/10 rounded-md p-1"> |
||||
</div> |
||||
<PanelDisplay |
||||
v-show="panelState.toggle" |
||||
@data="(uuids) => emits('data', uuids)" |
||||
:hidden-photos="panelState.uuids" |
||||
/> |
||||
<PanelCreate |
||||
v-show="!panelState.toggle" |
||||
@file-added="imageAdded" |
||||
@file-removed="imageRemoved" |
||||
@data="(uuid) => emits('data', [uuid])" |
||||
@close="emits('close')" |
||||
:redirect="false" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,86 @@ |
||||
<script setup> |
||||
import { useForm } from '@inertiajs/vue3'; |
||||
import File from '@/file'; |
||||
import Layout from '@/Layouts/Layout.vue'; |
||||
import Dropzone from '@/Components/Dropzone.vue'; |
||||
import TextInput from '@/Components/TextInput.vue'; |
||||
import InputError from '@/Components/InputError.vue'; |
||||
import { ref } from 'vue'; |
||||
|
||||
|
||||
const props = defineProps({ |
||||
redirect: { |
||||
type: Boolean, |
||||
default: true, |
||||
} |
||||
}); |
||||
|
||||
const dropzone = ref(null); |
||||
|
||||
const form = useForm({ |
||||
name: "", |
||||
path: "", |
||||
redirect: props.redirect, |
||||
}) |
||||
|
||||
const emits = defineEmits(["data", "close", "file-added", "file-removed"]); |
||||
|
||||
const imageAdded = (file) => { |
||||
form.path = file.key; |
||||
if(form.name === "") form.name = File.Basename(file.key).split("_")[1]; |
||||
emits('file-added', file) |
||||
} |
||||
|
||||
const imageRemoved = (file) => { |
||||
form.path = ""; |
||||
emits('file-removed', file) |
||||
} |
||||
|
||||
|
||||
const submit = async () => { |
||||
if(!props.redirect) { |
||||
let res = await axios.post("/photo", { |
||||
name: form.name, |
||||
path: form.path, |
||||
redirect: false, |
||||
}) |
||||
const uuid = res.data.uuid; |
||||
console.log(uuid, res); |
||||
emits("data", uuid); |
||||
} else { |
||||
form.post("/photo", { |
||||
headers: { |
||||
"Content-Type": "application/json", |
||||
"X-CSRF-Token": document.querySelector('input[name=_token]').value, |
||||
}, |
||||
}); |
||||
} |
||||
form.path = ""; |
||||
form.name = ""; |
||||
dropzone.value.removeFiles(); |
||||
emits("close"); |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<form @submit.prevent="submit"> |
||||
<p>Photo</p> |
||||
<Dropzone |
||||
ref="dropzone" |
||||
@file-added="imageAdded" |
||||
@file-removed="imageRemoved" |
||||
:name="photos" |
||||
:empty="'Téléversé une image'" |
||||
:multiple="false" |
||||
:accept="'image/*'" |
||||
:class="'mb-1'" |
||||
/> |
||||
<InputError :message="form.errors.path"/> |
||||
<p class="mt-3">Nom</p> |
||||
<TextInput :class="'mb-1 w-full'" v-model="form.name" required :placeholder="'Nom de la photo'"/> |
||||
<InputError :message="form.errors.name"/> |
||||
<div class="w-full flex justify-end mt-3"> |
||||
<button type="submit" class="text-white font-semibold p-1 px-2 bg-primary rounded-md">Ajouter</button> |
||||
</div> |
||||
</form> |
||||
</template> |
@ -0,0 +1,78 @@ |
||||
<script setup> |
||||
import axios from 'axios'; |
||||
import { reactive } from 'vue'; |
||||
import Show from './Show.vue'; |
||||
import PanelShow from './PanelShow.vue'; |
||||
const props = defineProps({ |
||||
hiddenPhotos: { |
||||
type: Array, |
||||
default: [] |
||||
} |
||||
}) |
||||
const emits = defineEmits(["data"]); |
||||
|
||||
const albumState = reactive({albums: [], focusAlbum: "", photos: [], pageCount: 1, lastPage: 1}); |
||||
|
||||
let photos = []; |
||||
|
||||
|
||||
const loadPhotos = async (album, page) => { |
||||
if(!album || album === "") { |
||||
let res = await axios.get(route("photo.page", page)); |
||||
albumState.photos = albumState.photos.concat(res.data.photos); |
||||
albumState.lastPage = res.data.lastPage; |
||||
} else { |
||||
let res = await axios.get("/album/" + album + "/page/" + page); |
||||
albumState.photos = albumState.photos.concat(res.data.photos); |
||||
albumState.lastPage = res.data.lastPage; |
||||
} |
||||
} |
||||
const loadAlbum = (album) => { |
||||
albumState.photos = []; |
||||
albumState.focusAlbum = album; |
||||
albumState.pageCount = 1; |
||||
loadPhotos(albumState.focusAlbum, albumState.pageCount); |
||||
} |
||||
|
||||
const loadAlbums = async (page) => { |
||||
let res = await axios.get(route("album.page", page)); |
||||
albumState.albums = res.data.albums; |
||||
} |
||||
const initLoad = async () => { |
||||
await loadAlbums(1); |
||||
loadAlbum(""); |
||||
} |
||||
initLoad(); |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex pb-10 h-[35rem]"> |
||||
<div class="flex flex-col items-start justify-between w-32 border-r border-gray-200 pr-2"> |
||||
<div class="w-full"> |
||||
<p class="my-1 py-1 px-2 text-gray-700 rounded shadow" |
||||
@click="loadAlbum('')" |
||||
:class="{ 'bg-gray-200': albumState.focusAlbum === '', 'bg-gray-100': albumState.focusAlbum !== ''}">Photothèque</p> |
||||
<p v-for="album in albumState.albums" |
||||
@click="loadAlbum(album.uuid)" |
||||
class="my-1 py-1 px-2 bg-gray-100 text-gray-700 |
||||
overflow-hidden text-nowrap rounded shadow" |
||||
:class="{ 'bg-gray-200': albumState.focusAlbum === album.uuid, 'bg-gray-100': albumState.focusAlbum !== album.uuid}">{{ album.name }}</p> |
||||
<p class="my-1 py-1 px-2 bg-gray-100 text-gray-700 rounded shadow">plus d'album</p> |
||||
</div> |
||||
<button @click="emits('data', photos)" class="text-white font-semibold p-1 px-2 bg-primary rounded-md">Ajouter</button> |
||||
</div> |
||||
<div class="w-[calc(100%-8rem)] h-full overflow-y-scroll py-5 ml-2"> |
||||
<div class="grid grid-cols-3"> |
||||
<PanelShow v-for="(photo, index) in albumState.photos" v-show="!props.hiddenPhotos.includes(photo.uuid)" :photo="photo" |
||||
:index="index" :length="albumState.photos.length" :columns="3" :active="photos.includes(photo.uuid)" |
||||
@photo-added="(uuid) => { if(!photos.includes(uuid)) photos.push(uuid) }" |
||||
@photo-removed="(uuid) => { if(photos.includes(uuid)) photos = photos.filter(uu => uu != uuid) }" |
||||
/> |
||||
</div> |
||||
<div v-if="albumState.pageCount < albumState.lastPage" class="py-2 w-full flex justify-center"> |
||||
<button @click="loadPhotos(albumState.focusAlbum, ++albumState.pageCount)" class="bg-gray-100 p-2 font-medium text-gray-700 |
||||
rounded shadow hover:scale-[1.01]">Afficher plus de photos</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,63 @@ |
||||
<script setup> |
||||
import { Link } from '@inertiajs/vue3'; |
||||
import { useForm } from '@inertiajs/vue3'; |
||||
import { reactive } from 'vue'; |
||||
import Edit from './Edit.vue'; |
||||
import axios from 'axios'; |
||||
|
||||
const props = defineProps({ |
||||
photo: { |
||||
type: Object, |
||||
required: true, |
||||
}, |
||||
active: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
index: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
length: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
columns: { |
||||
type: Number, |
||||
default: 3, |
||||
}, |
||||
}); |
||||
|
||||
const emits = defineEmits([ "photo-added", "photo-removed" ]); |
||||
|
||||
let flag = false; |
||||
|
||||
const imageChange = () => { |
||||
flag = !flag; |
||||
if (flag) emits("photo-added", props.photo.uuid); |
||||
else emits("photo-removed", props.photo.uuid); |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="{ |
||||
'border-r': (props.index + 1) % props.columns != 0, |
||||
'border-b': props.index < props.length - props.columns, |
||||
'h-56': props.columns === 3, |
||||
}" |
||||
class="group relative w-full overflow-hidden border-white hover:scale-[1.003] flex items-center bg-black/90"> |
||||
<div class="absolute left-0 right-0 top-0 p-2 flex justify-between"> |
||||
<div class="flex items-center"> |
||||
<input @click="imageChange" type="checkbox" class="bg-black/50 p-1 rounded-md mr-2" :checked="props.active"> |
||||
</div> |
||||
</div> |
||||
<div class="hidden absolute left-0 right-0 bottom-0 p-2 group-hover:flex justify-between flex-wrap items-end"> |
||||
<div class="text-right bg-black/30 p-1 px-3 rounded-md mt-1"> |
||||
<p class="text-sm text-white">{{ props.photo.name }}</p> |
||||
<p class="text-sm text-white">publier par {{ props.photo.user.name }}</p> |
||||
</div> |
||||
</div> |
||||
<img :src="props.photo.path" class="w-full bg-white"> |
||||
</div> |
||||
</template> |
@ -1,108 +0,0 @@ |
||||
<script setup> |
||||
import DangerButton from '@/Components/DangerButton.vue'; |
||||
import InputError from '@/Components/InputError.vue'; |
||||
import InputLabel from '@/Components/InputLabel.vue'; |
||||
import Modal from '@/Components/Modal.vue'; |
||||
import SecondaryButton from '@/Components/SecondaryButton.vue'; |
||||
import TextInput from '@/Components/TextInput.vue'; |
||||
import { useForm } from '@inertiajs/vue3'; |
||||
import { nextTick, ref } from 'vue'; |
||||
|
||||
const confirmingUserDeletion = ref(false); |
||||
const passwordInput = ref(null); |
||||
|
||||
const form = useForm({ |
||||
password: '', |
||||
}); |
||||
|
||||
const confirmUserDeletion = () => { |
||||
confirmingUserDeletion.value = true; |
||||
|
||||
nextTick(() => passwordInput.value.focus()); |
||||
}; |
||||
|
||||
const deleteUser = () => { |
||||
form.delete(route('profile.destroy'), { |
||||
preserveScroll: true, |
||||
onSuccess: () => closeModal(), |
||||
onError: () => passwordInput.value.focus(), |
||||
onFinish: () => form.reset(), |
||||
}); |
||||
}; |
||||
|
||||
const closeModal = () => { |
||||
confirmingUserDeletion.value = false; |
||||
|
||||
form.clearErrors(); |
||||
form.reset(); |
||||
}; |
||||
</script> |
||||
|
||||
<template> |
||||
<section class="space-y-6"> |
||||
<header> |
||||
<h2 class="text-lg font-medium text-gray-900"> |
||||
Delete Account |
||||
</h2> |
||||
|
||||
<p class="mt-1 text-sm text-gray-600"> |
||||
Once your account is deleted, all of its resources and data will |
||||
be permanently deleted. Before deleting your account, please |
||||
download any data or information that you wish to retain. |
||||
</p> |
||||
</header> |
||||
|
||||
<DangerButton @click="confirmUserDeletion">Delete Account</DangerButton> |
||||
|
||||
<Modal :show="confirmingUserDeletion" @close="closeModal"> |
||||
<div class="p-6"> |
||||
<h2 |
||||
class="text-lg font-medium text-gray-900" |
||||
> |
||||
Are you sure you want to delete your account? |
||||
</h2> |
||||
|
||||
<p class="mt-1 text-sm text-gray-600"> |
||||
Once your account is deleted, all of its resources and data |
||||
will be permanently deleted. Please enter your password to |
||||
confirm you would like to permanently delete your account. |
||||
</p> |
||||
|
||||
<div class="mt-6"> |
||||
<InputLabel |
||||
for="password" |
||||
value="Password" |
||||
class="sr-only" |
||||
/> |
||||
|
||||
<TextInput |
||||
id="password" |
||||
ref="passwordInput" |
||||
v-model="form.password" |
||||
type="password" |
||||
class="mt-1 block w-3/4" |
||||
placeholder="Password" |
||||
@keyup.enter="deleteUser" |
||||
/> |
||||
|
||||
<InputError :message="form.errors.password" class="mt-2" /> |
||||
</div> |
||||
|
||||
<div class="mt-6 flex justify-end"> |
||||
<SecondaryButton @click="closeModal"> |
||||
Cancel |
||||
</SecondaryButton> |
||||
|
||||
<DangerButton |
||||
class="ms-3" |
||||
:class="{ 'opacity-25': form.processing }" |
||||
:disabled="form.processing" |
||||
@click="deleteUser" |
||||
> |
||||
Delete Account |
||||
</DangerButton> |
||||
</div> |
||||
</div> |
||||
</Modal> |
||||
</section> |
||||
</template> |
Loading…
Reference in New Issue