Ajout du panel photo existante et ajout de la pagination

beta
anulax1225 ago%!(EXTRA string=6 months)
parent 20e1eac2f1
commit fbe7d4cf3d
  1. 74
      app/Http/Controllers/AlbumController.php
  2. 19
      app/Http/Controllers/PhotoController.php
  3. 21
      app/Http/Controllers/ProfileController.php
  4. 4
      app/Models/Album.php
  5. 2
      app/Models/Photo.php
  6. 2
      app/Models/Tag.php
  7. 31
      database/migrations/2025_01_26_015737_add_created_at.php
  8. 2
      resources/js/Pages/Album/Partials/Edit.vue
  9. 69
      resources/js/Pages/Album/Show.vue
  10. 64
      resources/js/Pages/Photo/Index.vue
  11. 78
      resources/js/Pages/Photo/Partials/Create.vue
  12. 68
      resources/js/Pages/Photo/Partials/Panel.vue
  13. 86
      resources/js/Pages/Photo/Partials/PanelCreate.vue
  14. 78
      resources/js/Pages/Photo/Partials/PanelDisplay.vue
  15. 63
      resources/js/Pages/Photo/Partials/PanelShow.vue
  16. 24
      resources/js/Pages/Photo/Partials/Show.vue
  17. 7
      resources/js/Pages/Profile/Edit.vue
  18. 108
      resources/js/Pages/Profile/Partials/DeleteUserForm.vue
  19. 8
      routes/web.php

@ -17,8 +17,12 @@ class AlbumController extends Controller
*/
public function index()
{
$total = Album::count();
$pageSize = 12;
$page = 1;
return Inertia::render('Album/Index', [
"albums" => Album::orderBy("created_at", "DESC")->get()->jsonSerialize(),
"lastPage" => ceil($total / $pageSize),
"albums" => Album::orderBy("created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize(),
]);
}
@ -28,18 +32,56 @@ public function index()
public function show(string $id)
{
$album = Album::where("uuid", $id)->first();
$total = $album->photos()->count();
$pageSize = 12;
$page = 1;
return Inertia::render('Album/Show', [
"album" => $album->jsonSerialize(),
"photos" => $album->photos->jsonSerialize()
"lastPage" => ceil($total / $pageSize),
"photos" => $album->photos()->orderBy("album_photo.created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize()
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
public function pages(Request $request)
{
//
$total = Album::count();
$pageSize = 12;
$page = $request->page ?? 2;
$page = $request->page <= ceil($total / $pageSize) ? $page : ceil($total / $pageSize);
return response()->json([
"lastPage" => ceil($total / $pageSize),
"albums" => Album::orderBy("created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize(),
]);
}
public function photoUuids(Request $request)
{
$album = Album::where("uuid", $request->id)->first();
$uuids = [];
foreach($album->photos as $photo) array_push($uuids, $photo->uuid);
return response()->json([
"uuids" => $uuids,
]);
}
public function photoPages(Request $request)
{
$album = Album::where("uuid", $request->id)->first();
$total = $album->photos()->count();
$pageSize = 12;
$page = $request->page ?? 2;
$page = $request->page <= ceil($total / $pageSize) ? $page : ceil($total / $pageSize);
return response()->json([
"lastPage" => ceil($total / $pageSize),
"photos" => $album->photos()->orderBy("album_photo.created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize(),
]);
}
public function photoRemove(Request $request)
{
$album = Album::where("uuid", $request->id)->first();
$total = $album->photos()->detach(Photo::where("uuid", $request->photoId)->first());
return redirect(route("album.show", [ "id" => $album->uuid ]))->with(["message" => "Photo supprimée avec success"]);
}
/**
@ -67,21 +109,15 @@ public function store(Request $request)
return redirect(route("album.index"))->with(["message" => "Photo ajouté avec success"]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
public function addPhoto(Request $request)
public function addPhotos(Request $request)
{
$album = Album::where("uuid", $request->id)->first();
$photo = Photo::where("uuid", $request->uuid)->first();
if(!$photo) redirect()->back()->withErrors(["uuid" => "Photo introuvable" ]);
if(!$album) redirect()->back()->withErrors(["uuid" => "Album introuvable" ]);
$album->photos()->attach($photo);
foreach($request->uuids as $uuid) {
$photo = Photo::where("uuid", $uuid)->first();
if(!$photo) redirect()->back()->withErrors(["uuid" => "Photo introuvable" ]);
$album->photos()->attach($photo);
}
return redirect()->back();
}

@ -14,13 +14,28 @@ class PhotoController extends Controller
/**
* Display a listing of the resource.
*/
public function index()
public function index(Request $request)
{
$total = Photo::count();
$pageSize = 12;
$page = 1;
return Inertia::render('Photo/Index', [
"photos" => Photo::orderBy("created_at", "DESC")->get()->jsonSerialize(),
"lastPage" => ceil($total / $pageSize),
"photos" => Photo::orderBy("created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize(),
]);
}
public function pages(Request $request)
{
$pageSize = 12;
$total = Photo::count();
$page = $request->page ?? 2;
$page = $request->page <= ceil($total / $pageSize) ? $page : ceil($total / $pageSize);
return response()->json([
"lastPage" => ceil($total / $pageSize),
"photos" => Photo::orderBy("created_at", "DESC")->offset($page * $pageSize - $pageSize)->limit($pageSize)->get()->jsonSerialize(),
]);
}
/**
* Show the form for creating a new resource.
*/

@ -46,25 +46,4 @@ public function update(ProfileUpdateRequest $request): RedirectResponse
return redirect(route('profile.edit'));
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

@ -33,11 +33,11 @@ public function user()
public function photos()
{
return $this->belongsToMany(Photo::class);
return $this->belongsToMany(Photo::class)->withTimestamps();
}
public function tags()
{
return $this->belongsToMany(Tag::class);
return $this->belongsToMany(Tag::class)->withTimestamps();
}
}

@ -33,7 +33,7 @@ public function user()
public function albums()
{
return $this->belongsToMany(Album::class);
return $this->belongsToMany(Album::class)->withTimestamps();
}
}

@ -13,6 +13,6 @@ class Tag extends Model
public function albums()
{
return $this->belongsToMany(Album::class);
return $this->belongsToMany(Album::class)->withTimestamps();
}
}

@ -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
{
//
}
};

@ -3,7 +3,7 @@ import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
const props = defineProps({
photo: {
album: {
type: Object,
required: true,
}

@ -5,6 +5,7 @@ import Create from '@/Pages/Photo/Partials/Create.vue';
import Show from'@/Pages/Photo/Partials/Show.vue';
import Modal from '@/Pages/Photo/Partials/Modal.vue';
import { reactive, ref } from 'vue';
import Panel from '../Photo/Partials/Panel.vue';
const props = defineProps({
album: {
@ -15,19 +16,23 @@ const props = defineProps({
type: Array,
default: []
},
lastPage: {
type: Number,
default: 1,
}
});
const form = useForm({ uuid: "" });
const form = useForm({ uuids: [] });
const create = reactive({ active: false });
const fullScreenState = reactive({ photo: {}, active: false });
const viewState = reactive({ square: true, list: false });
const photoState = reactive({ square: true, list: false, pageCount: 1, photos: props.photos });
const fullScreen = (photo) => {
fullScreenState.photo = photo;
fullScreenState.active = true;
viewState.square = false;
viewState.list = false;
photoState.square = false;
photoState.list = false;
}
const gridState = reactive({ columns: 3 });
@ -35,26 +40,50 @@ const gridState = reactive({ columns: 3 });
const squareView = () => {
gridState.columns = 3;
fullScreenState.active = false;
viewState.square = true;
viewState.list = false;
photoState.square = true;
photoState.list = false;
}
const listView = () => {
gridState.columns = 1;
fullScreenState.active = false;
viewState.square = false;
viewState.list = true;
photoState.square = false;
photoState.list = true;
}
const addPhoto = (uuid) => {
const addPhotos = (uuids) => {
console.log("uuid");
form.uuid = uuid;
form.uuids = uuids;
form.post("/album/" + props.album.uuid + "/add",{
headers: {
"X-CSRF-Token": document.querySelector('input[name=_token]').value,
},
onSuccess: () => window.location.reload()
});
}
const loadPhotos = async () => {
photoState.pageCount++;
const res = await axios.get(route("album.photo.page", {
id: props.album.uuid,
page: photoState.pageCount
}));
photoState.photos = photoState.photos.concat(res.data.photos)
console.log("hello", res.data.photos, photoState.photos, props.lastPage)
}
const deletePhoto = (uuid) => {
if(confirm("Voulez-vous vraiment retirer cette photo de l'album")){
form.delete(route("album.photo.remove", {id: props.album.uuid, photoId: uuid}), {
headers: {
"X-CSRF-Token": document.querySelector('input[name=_token]').value,
},
onSuccess: () => {
photoState.photos = photoState.photos.filter(photo => photo.uuid != uuid);
}
});
}
}
</script>
<template>
@ -65,15 +94,15 @@ const addPhoto = (uuid) => {
<div class="relative flex items-center">
<p class="font-semibold mr-4">Affichage</p>
<div class="flex items-center bg-white rounded-md shadow-sm shadow-gray-300 overflow-hidden">
<button @click="squareView" :class="{'bg-black/5': viewState.square}" class="flex items-center h-full border-r border-gray-400 p-1
<button @click="squareView" :class="{'bg-black/5': photoState.square}" class="flex items-center h-full border-r border-gray-400 p-1
hover:bg-black/5">
<img src="/icons/block-content.svg" class="h-7">
</button>
<button @click="listView" :class="{'bg-black/5': viewState.list}" class="flex items-center h-full border-r border-gray-400 p-1
<button @click="listView" :class="{'bg-black/5': photoState.list}" class="flex items-center h-full border-r border-gray-400 p-1
hover:bg-black/5">
<img src="/icons/list.svg" class="h-7">
</button>
<button @click="() => fullScreen(props.photos[0].uuid)" :class="{'bg-black/5': fullScreenState.active}" class="flex items-center p-1
<button @click="() => fullScreen(photoState.photos[0].uuid)" :class="{'bg-black/5': fullScreenState.active}" class="flex items-center p-1
hover:bg-black/5">
<img src="/icons/slider.svg" class="h-7">
</button>
@ -85,7 +114,8 @@ const addPhoto = (uuid) => {
<p class="font-medium mr-4">Ajouter une photo</p>
<img src="/icons/add.svg" class="h-8">
</button>
<Create :redirect="false" @data="(uuid) => addPhoto(uuid)" @close="create.active = !create.active" v-if="create.active" class="absolute -right-0 top-[110%] z-10 mt-4" />
<Panel @data="(uuids) => addPhotos(uuids)" @close="create.active = !create.active" v-if="create.active" :album="props.album"
class="absolute -right-0 top-[110%] z-10 mt-4" />
</div>
</div>
</template>
@ -101,12 +131,17 @@ const addPhoto = (uuid) => {
<div class="w-full h-full pb-20 px-1 bg-black/5">
<div v-if="!fullScreenState.active" :class="{'grid-cols-3': gridState.columns === 3}"
class="w-full grid pt-10">
<Show v-for="(photo, index) in props.photos"
:photo="photo" :index="index" :length="props.photos.length" :columns="gridState.columns"
<Show v-for="(photo, index) in photoState.photos"
:photo="photo" :index="index" :length="photoState.photos.length" :columns="gridState.columns" :noEdit="true"
@full-screen="fullScreen"
@delete-photo="deletePhoto"
/>
</div>
<Modal v-if="fullScreenState.active" :photoId="fullScreenState.photo" :photos="props.photos"
<div v-if="!fullScreenState.active && photoState.pageCount < props.lastPage" class="w-full pt-5 flex items-center justify-center">
<button @click="loadPhotos"
class="bg-gray-100 p-2 font-medium text-gray-700 rounded shadow hover:scale-[1.01]">Afficher plus de photos</button>
</div>
<Modal v-if="fullScreenState.active" :photoId="fullScreenState.photo" :photos="photoState.photos"
@close="() => { fullScreenState.photo = {}; fullScreenState.active = false; }"
/>
</div>

@ -1,5 +1,5 @@
<script setup>
import { Head, Link } from '@inertiajs/vue3';
import { Head, Link, useForm } from '@inertiajs/vue3';
import Layout from '@/Layouts/Layout.vue';
import Dropzone from '@/Components/Dropzone.vue';
import Create from './Partials/Create.vue';
@ -12,17 +12,24 @@ const props = defineProps({
type: Array,
default: []
},
lastPage: {
type: Number,
default: 1,
}
});
const create = reactive({ active: false });
const form = useForm();
const fullScreenState = reactive({ photo: {}, active: false });
const viewState = reactive({ square: true, list: false });
const photoState = reactive({ square: true, list: false, pageCount: 1, photos: props.photos });
const fullScreen = (photo) => {
fullScreenState.photo = photo;
fullScreenState.active = true;
viewState.square = false;
viewState.list = false;
photoState.square = false;
photoState.list = false;
}
const gridState = reactive({ columns: 3 });
@ -30,15 +37,35 @@ const gridState = reactive({ columns: 3 });
const squareView = () => {
gridState.columns = 3;
fullScreenState.active = false;
viewState.square = true;
viewState.list = false;
photoState.square = true;
photoState.list = false;
}
const listView = () => {
gridState.columns = 1;
fullScreenState.active = false;
viewState.square = false;
viewState.list = true;
photoState.square = false;
photoState.list = true;
}
const loadPhotos = async () => {
photoState.pageCount++;
const res = await axios.get(route("photo.page", photoState.pageCount));
photoState.photos = photoState.photos.concat(res.data.photos)
console.log("hello", res.data.photos, photoState.photos, props.lastPage)
}
const deletePhoto = (uuid) => {
if(confirm("Voulez-vous vraiment supprimé cette photo")){
form.delete("/photo/" + uuid, {
headers: {
"X-CSRF-Token": document.querySelector('input[name=_token]').value,
},
onSuccess: () => {
photoState.photos = photoState.photos.filter(photo => photo.uuid != uuid);
}
});
}
}
</script>
@ -50,15 +77,15 @@ const listView = () => {
<div class="relative flex items-center">
<p class="font-semibold mr-4">Affichage</p>
<div class="flex items-center bg-white rounded-md shadow-sm shadow-gray-300 overflow-hidden">
<button @click="squareView" :class="{'bg-black/5': viewState.square}" class="flex items-center h-full border-r border-gray-400 p-1
<button @click="squareView" :class="{'bg-black/5': photoState.square}" class="flex items-center h-full border-r border-gray-400 p-1
hover:bg-black/5">
<img src="/icons/block-content.svg" class="h-7">
</button>
<button @click="listView" :class="{'bg-black/5': viewState.list}" class="flex items-center h-full border-r border-gray-400 p-1
<button @click="listView" :class="{'bg-black/5': photoState.list}" class="flex items-center h-full border-r border-gray-400 p-1
hover:bg-black/5">
<img src="/icons/list.svg" class="h-7">
</button>
<button @click="() => fullScreen(props.photos[0].uuid)" :class="{'bg-black/5': fullScreenState.active}" class="flex items-center p-1
<button @click="() => fullScreen(photoState.photos[0].uuid)" :class="{'bg-black/5': fullScreenState.active}" class="flex items-center p-1
hover:bg-black/5">
<img src="/icons/slider.svg" class="h-7">
</button>
@ -79,14 +106,19 @@ const listView = () => {
<div class="w-full h-full pb-20 bg-black/5">
<div v-if="!fullScreenState.active" :class="{'grid-cols-3': gridState.columns === 3}"
class="w-full grid pt-10">
<Show v-for="(photo, index) in props.photos"
:photo="photo" :index="index" :length="props.photos.length" :columns="gridState.columns"
<Show v-for="(photo, index) in photoState.photos"
:photo="photo" :index="index" :length="photoState.photos.length" :columns="gridState.columns"
@full-screen="fullScreen"
@delete-photo="deletePhoto"
/>
</div>
<Modal v-if="fullScreenState.active" :photoId="fullScreenState.photo" :photos="props.photos"
@close="() => { fullScreenState.photo = {}; fullScreenState.active = false; }"
/>
<div v-if="!fullScreenState.active && photoState.pageCount < props.lastPage" class="w-full pt-5 flex items-center justify-center">
<button @click="loadPhotos"
class="bg-gray-100 p-2 font-medium text-gray-700 rounded shadow hover:scale-[1.01]">Afficher plus de photos</button>
</div>
<Modal v-if="fullScreenState.active" :photoId="fullScreenState.photo" :photos="photoState.photos"
@close="() => { fullScreenState.photo = {}; fullScreenState.active = false; }"
/>
</div>
</div>

@ -1,68 +1,21 @@
<script setup>
import { Head, Link, 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 { nextTick, onMounted, reactive, ref } from 'vue';
const props = defineProps({
redirect: {
type: Boolean,
default: true,
}
});
const dropzone = ref(null);
import { reactive } from 'vue';
import PanelCreate from './PanelCreate.vue';
const imageState = reactive({
"url": "",
})
const form = useForm({
name: "",
path: "",
redirect: props.redirect,
})
const emits = defineEmits(["close", "data"]);
const imageAdded = (file) => {
form.path = file.key;
imageState.url = file.url;
if(form.name === "") form.name = File.Basename(file.key).split("_")[1];
}
const imageRemoved = (file) => {
form.path = "";
imageState.url = "";
}
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,
},
});
}
imageState.url = "";
form.path = "";
form.name = "";
dropzone.value.removeFiles();
emits("close");
}
</script>
<template>
@ -75,26 +28,11 @@ const submit = async () => {
<div @click="() => emits('close')" class="w-full flex justify-end ">
<img src="/icons/cancel.svg" class="h-7 hover:bg-black/10 rounded-md p-1">
</div>
<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>
<PanelCreate
@file-added="imageAdded"
@file-removed="imageRemoved"
@close="emits('close')"
/>
</div>
</div>
</template>

@ -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>

@ -22,23 +22,17 @@ const props = defineProps({
type: Number,
default: 3,
},
noEdit: {
type: Boolean,
default: false
}
});
const form = useForm();
const photoState = reactive({ edit: false });
const emits = defineEmits(["full-screen"]);
const emits = defineEmits(["full-screen", "delete-photo"]);
const deletePhoto = async () => {
if(confirm("Voulez-vous vraiment supprimé cette photo")){
form.delete("/photo/" + props.photo.uuid, {
headers: {
"X-CSRF-Token": document.querySelector('input[name=_token]').value,
},
});
}
}
</script>
<template>
@ -53,14 +47,14 @@ const deletePhoto = async () => {
<div class="flex items-center">
<button @click="() => emits('full-screen', props.photo.uuid)" class="bg-black/50 p-1 rounded-md mr-2"><img src="/icons/full-screen.svg" class="h-6 invert"></button>
<div class="relative">
<button @click="photoState.edit = !photoState.edit" class="bg-black/50 p-1 rounded-md mr-2"><img src="/icons/modify.svg" class="h-6 invert"></button>
<Edit v-if="photoState.edit"
<button v-if="!props.noEdit"@click="photoState.edit = !photoState.edit" class="bg-black/50 p-1 rounded-md mr-2"><img src="/icons/modify.svg" class="h-6 invert"></button>
<Edit v-if="photoState.edit && !props.noEdit"
@close="() => photoState.edit = false"
:photo="props.photo"
:class="'absolute left-0 top-full mt-2'"
/>
</div>
<button @click="deletePhoto" class="bg-red-600 p-1 rounded-md"><img src="/icons/delete.png" class="h-6 invert"></button>
<button @click="emits('delete-photo', props.photo.uuid)" class="bg-red-600 p-1 rounded-md"><img src="/icons/delete.png" class="h-6 invert"></button>
</div>
</div>
<div class="hidden absolute left-0 right-0 bottom-0 p-2 group-hover:flex justify-between flex-wrap items-end">

@ -1,6 +1,5 @@
<script setup>
import Layout from '@/Layouts/Layout.vue';
import DeleteUserForm from './Partials/DeleteUserForm.vue';
import UpdatePasswordForm from './Partials/UpdatePasswordForm.vue';
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm.vue';
import { Head } from '@inertiajs/vue3';
@ -42,12 +41,6 @@ defineProps({
>
<UpdatePasswordForm class="pr-64" />
</div>
<div
class="bg-gray-50 p-4 shadow sm:rounded-lg sm:p-8"
>
<DeleteUserForm class="pr-64" />
</div>
</div>
</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>

@ -19,15 +19,20 @@
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/photos', [PhotoController::class, 'index'])->name('photo.index');
Route::get('/photos/page/{page}', [PhotoController::class, 'pages'])->name('photo.page');
Route::post('/photo', [PhotoController::class, 'store'])->name('photo.store');
Route::post('/photo/{id}', [PhotoController::class, 'update'])->name('photo.update');
Route::delete('/photo/{id}', [PhotoController::class, 'destroy'])->name('photo.destroy');
Route::get('/albums', [AlbumController::class, 'index'])->name('album.index');
Route::get('/albums/page/{page}', [AlbumController::class, 'pages'])->name('album.page');
Route::get('/album/{id}', [AlbumController::class, 'show'])->name('album.show');
Route::get('/album/{id}/photos', [AlbumController::class, 'photoUuids'])->name('album.photo.uuid');
Route::get('/album/{id}/page/{page}', [AlbumController::class, 'photoPages'])->name('album.photo.page');
Route::delete('/album/{id}/{photoId}', [AlbumController::class, 'photoRemove'])->name('album.photo.remove');
Route::post('/album', [AlbumController::class, 'store'])->name('album.store');
Route::post('/album/{id}', [AlbumController::class, 'update'])->name('album.update');
Route::post('/album/{id}/add', [AlbumController::class, 'addPhoto'])->name('album.add');
Route::post('/album/{id}/add', [AlbumController::class, 'addPhotos'])->name('album.add');
Route::delete('/album/{id}', [AlbumController::class, 'destroy'])->name('album.destroy');
Route::get('/admin/users', [UserController::class, 'index'])->name("admin.user.index");
@ -44,7 +49,6 @@
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';

Loading…
Cancel
Save