Skip to content

Commit

Permalink
Support for more layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria committed Nov 15, 2024
1 parent 15c8908 commit eb18874
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 108 deletions.
3 changes: 3 additions & 0 deletions app/Http/Resources/Search/InitResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Resources\Search;

use App\Enum\PhotoLayoutType;
use App\Models\Configs;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
Expand All @@ -13,9 +14,11 @@
class InitResource extends Data
{
public int $search_minimum_length = 3;
public PhotoLayoutType $photo_layout;

public function __construct()
{
$this->search_minimum_length = Configs::getValueAsInt('search_minimum_length_required');
$this->photo_layout = Configs::getValueAsEnum('search_photos_layout', PhotoLayoutType::class);
}
}
4 changes: 2 additions & 2 deletions resources/js/components/forms/album/AlbumProperties.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ function load(editable: App.Http.Resources.Editable.EditableBaseAlbumResource, p
albumSortingColumn.value = SelectBuilders.buildAlbumSorting(editable.album_sorting?.column);
albumSortingOrder.value = SelectBuilders.buildSortingOrder(editable.album_sorting?.order);
photoLayout.value = SelectBuilders.buildPhotoLayout(editable.photo_layout ?? undefined);
license.value = SelectBuilders.buildLicense(editable.license);
aspectRatio.value = SelectBuilders.buildAspectRatio(editable.aspect_ratio);
license.value = SelectBuilders.buildLicense(editable.license ?? undefined);
aspectRatio.value = SelectBuilders.buildAspectRatio(editable.aspect_ratio ?? undefined);
header_id.value = buildHeaderId(editable.header_id, photos);
tags.value = editable.tags;
}
Expand Down
39 changes: 14 additions & 25 deletions resources/js/components/gallery/AlbumThumbPanel.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
<template>
<Panel :header="$t(props.header)" :toggleable="!isAlone" :pt:header:class="headerClass" class="border-0 w-full">
<div class="flex flex-wrap flex-row flex-shrink w-full justify-start gap-1 sm:gap-2 md:gap-4 pt-4">
<template v-for="(album, idx) in props.albums">
<AlbumThumb
@click="maySelect(idx + props.idxShift, $event)"
@contextmenu.prevent="menuOpen(idx + props.idxShift, $event)"
:album="album"
:cover_id="null"
:config="props.config"
v-if="!album.is_nsfw || are_nsfw_visible"
:is-selected="props.selectedAlbums.includes(album.id)"
/>
</template>
<AlbumThumbPanelList
:albums="props.albums"
:album="props.album"
:config="props.config"
:idx-shift="props.idxShift"
:iter="0"
:selected-albums="props.selectedAlbums"
@clicked="propagateClicked"
@contexted="propagateMenuOpen"
/>
</div>
</Panel>
</template>
<script setup lang="ts">
import Panel from "primevue/panel";
import AlbumThumb, { AlbumThumbConfig } from "@/components/gallery/thumbs/AlbumThumb.vue";
import { AlbumThumbConfig } from "@/components/gallery/thumbs/AlbumThumb.vue";
import { computed } from "vue";
import { useLycheeStateStore } from "@/stores/LycheeState";
import { storeToRefs } from "pinia";
const lycheeStore = useLycheeStateStore();
const { are_nsfw_visible } = storeToRefs(lycheeStore);
import AlbumThumbPanelList from "./AlbumThumbPanelList.vue";
const props = defineProps<{
header: string;
Expand All @@ -41,17 +36,11 @@ const emits = defineEmits<{
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => {
if (props.idxShift < 0) {
return;
}
const propagateClicked = (idx: number, e: MouseEvent) => {
emits("clicked", idx, e);
};
const menuOpen = (idx: number, e: MouseEvent) => {
if (props.idxShift < 0) {
return;
}
const propagateMenuOpen = (idx: number, e: MouseEvent) => {
emits("contexted", idx, e);
};
Expand Down
50 changes: 50 additions & 0 deletions resources/js/components/gallery/AlbumThumbPanelList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>
<template v-for="(album, idx) in props.albums">
<AlbumThumb
@click="maySelect(idx + props.iter + props.idxShift, $event)"
@contextmenu.prevent="menuOpen(idx + props.iter + props.idxShift, $event)"
:album="album"
:cover_id="null"
:config="props.config"
v-if="!album.is_nsfw || are_nsfw_visible"
:is-selected="props.selectedAlbums.includes(album.id)"
/>
</template>
</template>
<script setup lang="ts">
import AlbumThumb, { AlbumThumbConfig } from "@/components/gallery/thumbs/AlbumThumb.vue";
import { useLycheeStateStore } from "@/stores/LycheeState";
import { storeToRefs } from "pinia";
const lycheeStore = useLycheeStateStore();
const { are_nsfw_visible } = storeToRefs(lycheeStore);
const props = defineProps<{
album: App.Http.Resources.Models.AlbumResource | undefined | null;
albums: { [key: number]: App.Http.Resources.Models.ThumbAlbumResource };
config: AlbumThumbConfig;
iter: number;
idxShift: number;
selectedAlbums: string[];
}>();
// bubble up.
const emits = defineEmits<{
clicked: [idx: number, event: MouseEvent];
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => {
if (props.idxShift < 0) {
return;
}
emits("clicked", idx, e);
};
const menuOpen = (idx: number, e: MouseEvent) => {
if (props.idxShift < 0) {
return;
}
emits("contexted", idx, e);
};
</script>
57 changes: 23 additions & 34 deletions resources/js/components/gallery/PhotoThumbPanel.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,25 @@
<template>
<Panel id="lychee_view_content" :header="$t(props.header)" class="w-full border-0">
<template #icons>
<a class="px-1 cursor-pointer group" @click="(layout = 'square') && activateLayout()" :title="$t('lychee.LAYOUT_SQUARES')">
<MiniIcon icon="squares" fill="fill-transparent" :class="squareClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'justified') && activateLayout()" :title="$t('lychee.LAYOUT_JUSTIFIED')">
<MiniIcon icon="justified" fill="" :class="justifiedClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'masonry') && activateLayout()" :title="$t('lychee.LAYOUT_MASONRY')">
<MiniIcon icon="masonry" fill="fill-transparent" :class="masonryClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'grid') && activateLayout()" :title="$t('lychee.LAYOUT_GRID')">
<MiniIcon icon="grid" fill="fill-transparent" :class="gridClass" />
</a>
<PhotoThumbPanelControl v-model:layout="layout" />
</template>
<div class="relative flex flex-wrap flex-row flex-shrink w-full justify-start align-top" id="photoListing">
<template v-for="(photo, idx) in props.photos">
<PhotoThumb
@click="maySelect(idx, $event)"
@contextmenu.prevent="menuOpen(idx, $event)"
:is-selected="props.selectedPhotos.includes(photo.id)"
:photo="photo"
:album="props.album"
:is-lazy="idx > 10"
/>
</template>
</div>
<PhotoThumbPanelList
:photos="props.photos"
:layout="layout"
:album="props.album"
:galleryConfig="props.galleryConfig"
:selectedPhotos="props.selectedPhotos"
:iter="0"
@clicked="propagateClicked"
@contexted="propagateMenuOpen"
/>
</Panel>
</template>
<script setup lang="ts">
import { onMounted, onUpdated } from "vue";
import { ref } from "vue";
import Panel from "primevue/panel";
import PhotoThumb from "@/components/gallery/thumbs/PhotoThumb.vue";
import MiniIcon from "@/components/icons/MiniIcon.vue";
import { useLayouts } from "@/layouts/PhotoLayout";
import PhotoThumbPanelList from "./PhotoThumbPanelList.vue";
import PhotoThumbPanelControl from "./PhotoThumbPanelControl.vue";
const props = defineProps<{
header: string;
Expand All @@ -48,16 +34,19 @@ const props = defineProps<{
selectedPhotos: string[];
}>();
const layout = ref(props.photoLayout);
// bubble up.
const emits = defineEmits<{
clicked: [idx: number, event: MouseEvent];
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => emits("clicked", idx, e);
const menuOpen = (idx: number, e: MouseEvent) => emits("contexted", idx, e);
// Layouts stuff
const { activateLayout, layout, squareClass, justifiedClass, masonryClass, gridClass } = useLayouts(props.galleryConfig, props.photoLayout);
onMounted(() => activateLayout());
onUpdated(() => activateLayout());
const propagateClicked = (idx: number, e: MouseEvent) => {
emits("clicked", idx, e);
};
const propagateMenuOpen = (idx: number, e: MouseEvent) => {
emits("contexted", idx, e);
};
</script>
24 changes: 24 additions & 0 deletions resources/js/components/gallery/PhotoThumbPanelControl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<a class="px-1 cursor-pointer group inline-block h-8" @click="layout = 'square'" :title="$t('lychee.LAYOUT_SQUARES')">
<MiniIcon icon="squares" fill="fill-transparent" :class="squareClass" />
</a>
<a class="px-1 cursor-pointer group inline-block h-8" @click="layout = 'justified'" :title="$t('lychee.LAYOUT_JUSTIFIED')">
<MiniIcon icon="justified" fill="" :class="justifiedClass" />
</a>
<a class="px-1 cursor-pointer group inline-block h-8" @click="layout = 'masonry'" :title="$t('lychee.LAYOUT_MASONRY')">
<MiniIcon icon="masonry" fill="fill-transparent" :class="masonryClass" />
</a>
<a class="px-1 cursor-pointer group inline-block h-8" @click="layout = 'grid'" :title="$t('lychee.LAYOUT_GRID')">
<MiniIcon icon="grid" fill="fill-transparent" :class="gridClass" />
</a>
</template>
<script setup lang="ts">
import { Ref } from "vue";
import MiniIcon from "@/components/icons/MiniIcon.vue";
import { useLayoutClass } from "@/layouts/PhotoLayout";
const layout = defineModel("layout") as Ref<App.Enum.PhotoLayoutType>;
// Layouts stuff
const { squareClass, justifiedClass, masonryClass, gridClass } = useLayoutClass(layout);
</script>
46 changes: 46 additions & 0 deletions resources/js/components/gallery/PhotoThumbPanelList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<div class="relative flex flex-wrap flex-row flex-shrink w-full justify-start align-top" :id="'photoListing' + props.iter">
<template v-for="(photo, idx) in props.photos">
<PhotoThumb
@click="maySelect(idx + iter, $event)"
@contextmenu.prevent="menuOpen(idx + iter, $event)"
:is-selected="props.selectedPhotos.includes(photo.id)"
:photo="photo"
:album="props.album"
:is-lazy="idx + iter > 10"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { useLayouts } from "@/layouts/PhotoLayout";
import { onMounted, onUpdated, Ref } from "vue";
import PhotoThumb from "./thumbs/PhotoThumb.vue";
const props = defineProps<{
photos: { [key: number]: App.Http.Resources.Models.PhotoResource };
album:
| App.Http.Resources.Models.AlbumResource
| App.Http.Resources.Models.TagAlbumResource
| App.Http.Resources.Models.SmartAlbumResource
| undefined;
galleryConfig: App.Http.Resources.GalleryConfigs.PhotoLayoutConfig;
selectedPhotos: string[];
iter: number;
}>();
const layout = defineModel("layout") as Ref<App.Enum.PhotoLayoutType>;
const isTimeline = defineModel("isTimeline") as Ref<boolean>;
const emits = defineEmits<{
clicked: [idx: number, event: MouseEvent];
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => emits("clicked", idx, e);
const menuOpen = (idx: number, e: MouseEvent) => emits("contexted", idx, e);
// Layouts stuff
const { activateLayout } = useLayouts(props.galleryConfig, layout, "photoListing" + props.iter);
onMounted(() => activateLayout());
onUpdated(() => activateLayout());
</script>
3 changes: 1 addition & 2 deletions resources/js/components/settings/AllSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@
@filled="update"
@reset="reset"
/>

<SelectOptionsField
v-else-if="config.key === 'layout'"
v-else-if="config.type === 'square|justified|masonry|grid'"
:config="config"
:options="photoLayoutOptions"
:mapper="SelectBuilders.buildPhotoLayout"
Expand Down
9 changes: 0 additions & 9 deletions resources/js/composables/album/albumRefresher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export function useAlbumRefresher(albumId: Ref<string>, auth: AuthStore, isLogin
const tagAlbum = ref(undefined as undefined | App.Http.Resources.Models.TagAlbumResource);
const smartAlbum = ref(undefined as undefined | App.Http.Resources.Models.SmartAlbumResource);
const album = computed(() => modelAlbum.value || tagAlbum.value || smartAlbum.value);
const layout = ref(null) as Ref<null | App.Http.Resources.GalleryConfigs.PhotoLayoutConfig>;
const isAlbumConsented = ref(false);

const photos = ref([]) as Ref<App.Http.Resources.Models.PhotoResource[]>;
Expand Down Expand Up @@ -54,12 +53,6 @@ export function useAlbumRefresher(albumId: Ref<string>, auth: AuthStore, isLogin
});
}

function loadLayout() {
AlbumService.getLayout().then((data) => {
layout.value = data.data;
});
}

function refresh() {
loadUser();
}
Expand All @@ -74,12 +67,10 @@ export function useAlbumRefresher(albumId: Ref<string>, auth: AuthStore, isLogin
smartAlbum,
album,
rights,
layout,
photos,
config,
loadUser,
loadAlbum,
loadLayout,
refresh,
};
}
3 changes: 3 additions & 0 deletions resources/js/composables/album/searchRefresher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function useSearch(albumid: Ref<string>, lycheeStore: LycheeStateStore, s
const from = ref(0);
const per_page = ref(0);
const total = ref(0);
const layout = ref("square" as App.Enum.PhotoLayoutType);

const photoHeader = computed(() => {
return trans("lychee.PHOTOS") + " (" + total.value + ")";
Expand All @@ -24,6 +25,7 @@ export function useSearch(albumid: Ref<string>, lycheeStore: LycheeStateStore, s
function searchInit() {
SearchService.init(albumid.value).then((response) => {
searchMinimumLengh.value = response.data.search_minimum_length;
layout.value = response.data.photo_layout;
});
}

Expand Down Expand Up @@ -68,6 +70,7 @@ export function useSearch(albumid: Ref<string>, lycheeStore: LycheeStateStore, s
}

return {
layout,
albums,
photos,
noData,
Expand Down
Loading

0 comments on commit eb18874

Please sign in to comment.