updates
This commit is contained in:
@@ -591,6 +591,7 @@ class Conductor
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$requestIncludes = [];
|
||||
$modelFields = $conductor->fields(new $conductor->class());
|
||||
|
||||
// Limit fields
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use App\Models\MediaJob;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -26,7 +27,7 @@ class MediaConductor extends Conductor
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $includes = ['user'];
|
||||
protected $includes = ['user', 'jobs'];
|
||||
|
||||
/**
|
||||
* The default filters to use in a request.
|
||||
@@ -156,4 +157,9 @@ class MediaConductor extends Conductor
|
||||
|
||||
return UserConductor::includeModel(request(), 'user', $user);
|
||||
}
|
||||
|
||||
public function includeJobs(Model $model) {
|
||||
$jobs = $model->jobs()->select(['id','created_at','updated_at','user_id','status','status_text','progress'])->orderBy('created_at', 'desc')->get();
|
||||
return $jobs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,19 +165,15 @@ class MediaController extends ApiController
|
||||
if ($request->has('transform') === true) {
|
||||
$transform = [];
|
||||
|
||||
foreach ($request->get('transform') as $key => $value) {
|
||||
foreach (explode(',', $request->get('transform', '')) as $value) {
|
||||
if (is_string($value) === true) {
|
||||
if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) {
|
||||
unset($transform[$key]);
|
||||
$transform['rotate'] = $matches[1];
|
||||
} elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) {
|
||||
unset($transform[$key]);
|
||||
$transform['flip'] = $matches[1];
|
||||
} elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||
unset($transform[$key]);
|
||||
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]];
|
||||
} elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||
unset($transform[$key]);
|
||||
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2], 'x' => $matches[3], 'y' => $matches[4]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -794,6 +795,7 @@ class Media extends Model
|
||||
|
||||
if (strpos($this->mime_type, 'image/') === 0) {
|
||||
$image = Image::make($filePath);
|
||||
$image->orientate();
|
||||
$image->resize($thumbnailWidth, $thumbnailHeight, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
});
|
||||
@@ -1034,4 +1036,8 @@ class Media extends Model
|
||||
// $this->status = "Info: " . $status;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function jobs(): HasMany {
|
||||
return $this->hasMany(MediaJob::class, 'media_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,24 +140,22 @@
|
||||
'bg-center',
|
||||
'bg-no-repeat',
|
||||
'relative',
|
||||
{ 'mb-6': showMediaName(item) },
|
||||
'mb-6',
|
||||
]"
|
||||
:style="{
|
||||
backgroundImage: `url('${mediaGetThumbnail(
|
||||
item,
|
||||
)}')`,
|
||||
backgroundColor: 'initial',
|
||||
}">
|
||||
<div
|
||||
v-if="showMediaName(item)"
|
||||
class="absolute -bottom-6 small w-full text-ellipsis overflow-hidden whitespace-nowrap">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<SMLoading
|
||||
v-if="false"
|
||||
v-if="item.status"
|
||||
small
|
||||
class="bg-white bg-op-90 w-full h-full"
|
||||
>NONE</SMLoading
|
||||
>{{ item.status }}</SMLoading
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
@@ -257,17 +255,13 @@
|
||||
<p
|
||||
v-if="lastSelected.status != 'OK'"
|
||||
class="m-0 italic">
|
||||
{{
|
||||
lastSelected.status.split(":")
|
||||
.length > 1
|
||||
? lastSelected.status
|
||||
.split(":")[1]
|
||||
.trim()
|
||||
: lastSelected.status
|
||||
}}
|
||||
{{ lastSelected.status }}
|
||||
</p>
|
||||
<p
|
||||
v-if="allowEditSelected"
|
||||
v-if="
|
||||
allowEditSelected &&
|
||||
!mediaIsBusy(lastSelected)
|
||||
"
|
||||
class="flex gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -399,7 +393,7 @@
|
||||
)}')`,
|
||||
}">
|
||||
<SMLoading
|
||||
v-if="false"
|
||||
v-if="true"
|
||||
small
|
||||
class="bg-white bg-op-90 w-full h-full" />
|
||||
<div
|
||||
@@ -483,7 +477,12 @@ import {
|
||||
MediaResponse,
|
||||
} from "../../helpers/api.types";
|
||||
import { useApplicationStore } from "../../store/ApplicationStore";
|
||||
import { mediaGetThumbnail, mimeMatches } from "../../helpers/media";
|
||||
import {
|
||||
mediaGetThumbnail,
|
||||
mimeMatches,
|
||||
mediaIsBusy,
|
||||
mediaStatus,
|
||||
} from "../../helpers/media";
|
||||
import SMInput from "../SMInput.vue";
|
||||
import SMLoading from "../SMLoading.vue";
|
||||
import SMTabGroup from "../SMTabGroup.vue";
|
||||
@@ -499,6 +498,11 @@ import { useUserStore } from "../../store/UserStore";
|
||||
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
|
||||
import { openDialog } from "../../components/SMDialog";
|
||||
|
||||
/* Extended Media Interface */
|
||||
interface ExtendedMedia extends Media {
|
||||
status: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
mime: {
|
||||
type: String,
|
||||
@@ -581,13 +585,13 @@ const totalItems = ref(0);
|
||||
/**
|
||||
* List of current media items.
|
||||
*/
|
||||
const mediaItems: Ref<Media[]> = ref([]);
|
||||
const mediaItems: Ref<ExtendedMedia[]> = ref([]);
|
||||
|
||||
/**
|
||||
* Selected media item id.
|
||||
*/
|
||||
const selected: Ref<Media[]> = ref([]);
|
||||
let lastSelected: Ref<Media | null> = ref(null);
|
||||
const selected: Ref<ExtendedMedia[]> = ref([]);
|
||||
let lastSelected: Ref<ExtendedMedia | null> = ref(null);
|
||||
|
||||
/**
|
||||
* How many media items are we showing per page.
|
||||
@@ -621,10 +625,10 @@ const computedAccepts = computed(() => {
|
||||
/**
|
||||
* Get the media item by id.
|
||||
* @param {string} item_id The media item id.
|
||||
* @returns {Media | null} The media object or null.
|
||||
* @returns {ExtendedMedia | null} The media object or null.
|
||||
*/
|
||||
const getMediaItem = (item_id: string): Media | null => {
|
||||
let found: Media | null = null;
|
||||
const getMediaItem = (item_id: string): ExtendedMedia | null => {
|
||||
let found: ExtendedMedia | null = null;
|
||||
|
||||
mediaItems.value.every((item) => {
|
||||
if (item.id == item_id) {
|
||||
@@ -638,14 +642,6 @@ const getMediaItem = (item_id: string): Media | null => {
|
||||
return found;
|
||||
};
|
||||
|
||||
const showMediaName = (media: Media): boolean => {
|
||||
return true;
|
||||
// return !(
|
||||
// media.mime_type.startsWith("image/") ||
|
||||
// media.mime_type.startsWith("video/")
|
||||
// );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle user clicking the cancel/close button.
|
||||
*/
|
||||
@@ -758,7 +754,7 @@ const handleClickItem = (item_id: string): void => {
|
||||
|
||||
/**
|
||||
* Handle user double clicking a media item.
|
||||
* @param item_id The media id.
|
||||
* @param {string} item_id The media id.
|
||||
*/
|
||||
const handleDblClickItem = (item_id: string): void => {
|
||||
if (!props.multiple) {
|
||||
@@ -839,6 +835,7 @@ const handleFilesUpload = (files: FileList) => {
|
||||
variants: {},
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
jobs: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -868,7 +865,7 @@ const startFilesUpload = async () => {
|
||||
);
|
||||
submitFormData.append("description", "");
|
||||
try {
|
||||
let result = await api.post({
|
||||
let result = await api.chunk({
|
||||
url: "/media",
|
||||
body: submitFormData,
|
||||
headers: {
|
||||
@@ -895,16 +892,20 @@ const startFilesUpload = async () => {
|
||||
});
|
||||
if (result.data) {
|
||||
const data = result.data as MediaResponse;
|
||||
const extendedMedium: ExtendedMedia = {
|
||||
...data.medium,
|
||||
status: "",
|
||||
};
|
||||
|
||||
const currentUploadFileNumStr =
|
||||
currentUploadFileNum.value.toString();
|
||||
mediaItems.value.every((item, index) => {
|
||||
if (item.id == currentUploadFileNumStr) {
|
||||
mediaItems.value[index] = data.medium;
|
||||
mediaItems.value[index] = extendedMedium;
|
||||
if (!selected.value) {
|
||||
selected.value.push(data.medium);
|
||||
selected.value.push(extendedMedium);
|
||||
} else if (props.multiple) {
|
||||
selected.value.push(data.medium);
|
||||
selected.value.push(extendedMedium);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -955,72 +956,47 @@ const updateFiles = async () => {
|
||||
if (updateFilesNonce.value == null) {
|
||||
let remaining = false;
|
||||
|
||||
mediaItems.value.forEach((item, index) => {
|
||||
for (const [index, item] of mediaItems.value.entries()) {
|
||||
if (isUUID(item.id)) {
|
||||
remaining = true;
|
||||
|
||||
api.get({
|
||||
let updateResult = await api.get({
|
||||
url: "/media/{id}",
|
||||
params: {
|
||||
id: item.id,
|
||||
},
|
||||
})
|
||||
.then((updateResult) => {
|
||||
});
|
||||
|
||||
if (updateResult.data) {
|
||||
const updateData =
|
||||
updateResult.data as MediaResponse;
|
||||
mediaItems.value[index].status =
|
||||
updateData.medium.status;
|
||||
if (updateData.medium.status == "OK") {
|
||||
mediaItems.value[index] = updateData.medium;
|
||||
forceRefresh.push(updateData.medium.id);
|
||||
if (
|
||||
lastSelected.value &&
|
||||
lastSelected.value.id ==
|
||||
updateData.medium.id
|
||||
) {
|
||||
lastSelected.value = updateData.medium;
|
||||
}
|
||||
} else if (
|
||||
updateData.medium.status.startsWith("Error") ===
|
||||
true
|
||||
) {
|
||||
mediaItems.value = mediaItems.value.filter(
|
||||
(mediaItem) =>
|
||||
mediaItem.id !== updateData.medium.id,
|
||||
);
|
||||
lastSelected.value = null;
|
||||
totalItems.value--;
|
||||
const updateData = updateResult.data as MediaResponse;
|
||||
const statusData = mediaStatus(updateData.medium);
|
||||
console.log(item.id, statusData);
|
||||
|
||||
useToastStore().addToast({
|
||||
title: "Upload failed",
|
||||
type: "danger",
|
||||
content: updateData.medium.status,
|
||||
// content: `${item.name} failed to be processed by the server.`,
|
||||
});
|
||||
if (updateData.medium.thumbnail != item.thumbnail) {
|
||||
mediaItems.value[index] = {
|
||||
...updateData.medium,
|
||||
status: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (statusData.busy == false) {
|
||||
mediaItems.value[index].status = "";
|
||||
} else {
|
||||
throw "error";
|
||||
remaining = true;
|
||||
mediaItems.value[index].status =
|
||||
statusData.status +
|
||||
" " +
|
||||
statusData.status_text +
|
||||
" " +
|
||||
statusData.progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* error retreiving data */
|
||||
mediaItems.value = mediaItems.value.filter(
|
||||
(mediaItem) => mediaItem.id !== item.id,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// mediaItems.value = mediaItems.value.filter(
|
||||
// (item) => item.status.startsWith("Error") === false,
|
||||
// );
|
||||
|
||||
if (remaining) {
|
||||
updateFilesNonce.value = setTimeout(() => {
|
||||
updateFilesNonce.value = null;
|
||||
updateFiles();
|
||||
}, 1000);
|
||||
}, 500);
|
||||
} else {
|
||||
updateFilesNonce.value = null;
|
||||
}
|
||||
@@ -1084,15 +1060,18 @@ const handleLoad = async () => {
|
||||
if (result.data) {
|
||||
const data = result.data as MediaCollection;
|
||||
|
||||
const mediaIds = new Set(
|
||||
mediaItems.value.map((item) => item.id),
|
||||
);
|
||||
const filteredItems = data.media.filter(
|
||||
(item) => !mediaIds.has(item.id),
|
||||
);
|
||||
// const mediaIds = new Set(
|
||||
// mediaItems.value.map((item) => item.id),
|
||||
// );
|
||||
// const filteredItems = data.media.filter(
|
||||
// (item) => !mediaIds.has(item.id),
|
||||
// );
|
||||
|
||||
totalItems.value = data.total;
|
||||
mediaItems.value.push(...filteredItems);
|
||||
mediaItems.value = data.media.map((item) => ({
|
||||
...item,
|
||||
status: mediaStatus(item).status_text,
|
||||
}));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -1220,7 +1199,8 @@ const formatDate = (date) => {
|
||||
const allowEditSelected = computed(() => {
|
||||
return (
|
||||
lastSelected.value != null &&
|
||||
lastSelected.value.status === "OK" &&
|
||||
lastSelected.value.jobs.length > 0 &&
|
||||
lastSelected.value.jobs[0].status == "complete" &&
|
||||
userStore.id &&
|
||||
(userHasPermission("admin/media") ||
|
||||
lastSelected.value.user_id == userStore.id)
|
||||
@@ -1259,13 +1239,13 @@ const handleRotateLeft = async (item: Media) => {
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
const data = result.data as MediaResponse;
|
||||
const index = mediaItems.value.findIndex(
|
||||
(mediaItem) => mediaItem.id === item.id,
|
||||
);
|
||||
// const index = mediaItems.value.findIndex(
|
||||
// (mediaItem) => mediaItem.id === item.id,
|
||||
// );
|
||||
|
||||
if (index !== -1) {
|
||||
mediaItems.value[index] = data.medium;
|
||||
}
|
||||
// if (index !== -1) {
|
||||
// mediaItems.value[index] = data.medium;
|
||||
// }
|
||||
|
||||
updateFiles();
|
||||
}
|
||||
@@ -1288,13 +1268,13 @@ const handleRotateRight = async (item: Media) => {
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
const data = result.data as MediaResponse;
|
||||
const index = mediaItems.value.findIndex(
|
||||
(mediaItem) => mediaItem.id === item.id,
|
||||
);
|
||||
// const index = mediaItems.value.findIndex(
|
||||
// (mediaItem) => mediaItem.id === item.id,
|
||||
// );
|
||||
|
||||
if (index !== -1) {
|
||||
mediaItems.value[index] = data.medium;
|
||||
}
|
||||
// if (index !== -1) {
|
||||
// mediaItems.value[index] = data.medium;
|
||||
// }
|
||||
|
||||
updateFiles();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ export interface Media {
|
||||
mime_type: string;
|
||||
permission: string;
|
||||
size: number;
|
||||
status: string;
|
||||
storage: string;
|
||||
url: string;
|
||||
thumbnail: string;
|
||||
@@ -77,6 +76,7 @@ export interface Media {
|
||||
variants: { [key: string]: string };
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
jobs: Array<MediaJob>;
|
||||
}
|
||||
|
||||
export interface MediaResponse {
|
||||
|
||||
@@ -24,6 +24,12 @@ export const mediaGetVariantUrl = (
|
||||
: media.url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a mime matches.
|
||||
* @param {string} mimeExpected The mime expected.
|
||||
* @param {string} mimeToCheck The mime to check.
|
||||
* @returns {boolean} The mimeToCheck matches mimeExpected.
|
||||
*/
|
||||
export const mimeMatches = (
|
||||
mimeExpected: string,
|
||||
mimeToCheck: string,
|
||||
@@ -38,10 +44,22 @@ export const mimeMatches = (
|
||||
return regex.test(mimeToCheck);
|
||||
};
|
||||
|
||||
/**
|
||||
* MediaGetThumbnailCallback Type
|
||||
*/
|
||||
export type mediaGetThumbnailCallback = (url: string) => void;
|
||||
|
||||
/**
|
||||
* Get Media/File Thumbnail.
|
||||
* @param {Media|File} media The Media/File object.
|
||||
* @param {string|null} useVariant The variable to use.
|
||||
* @param {mediaGetThumbnailCallback|null} callback Callback with the thumbnail. Required when passing File.
|
||||
* @returns {string} The thumbnail url.
|
||||
*/
|
||||
export const mediaGetThumbnail = (
|
||||
media: Media | File,
|
||||
useVariant: string | null = "",
|
||||
callback = null,
|
||||
callback: mediaGetThumbnailCallback | null = null,
|
||||
): string => {
|
||||
let url: string = "";
|
||||
|
||||
@@ -91,3 +109,65 @@ export const mediaGetThumbnail = (
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the media is currently busy.
|
||||
* @param {Media} media The media item to check.
|
||||
* @returns {boolean} If the media is busy.
|
||||
*/
|
||||
export const mediaIsBusy = (media: Media): boolean => {
|
||||
let busy = false;
|
||||
|
||||
if (media.jobs) {
|
||||
media.jobs.forEach((item) => {
|
||||
if (
|
||||
item.status != "invalid" &&
|
||||
item.status != "complete" &&
|
||||
item.status != "failed"
|
||||
) {
|
||||
busy = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return busy;
|
||||
};
|
||||
|
||||
interface MediaStatus {
|
||||
busy: boolean;
|
||||
status: string;
|
||||
status_text: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Media status
|
||||
* @param {Media} media The media item to check.
|
||||
* @returns {MediaStatus} The media status.
|
||||
*/
|
||||
export const mediaStatus = (media: Media): MediaStatus => {
|
||||
const status = {
|
||||
busy: false,
|
||||
status: "",
|
||||
status_text: "",
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
if (media.jobs) {
|
||||
for (const item of media.jobs) {
|
||||
if (
|
||||
item.status != "invalid" &&
|
||||
item.status != "complete" &&
|
||||
item.status != "failed"
|
||||
) {
|
||||
status.busy = true;
|
||||
status.status = item.status;
|
||||
status.status_text = item.status_text;
|
||||
status.progress = item.progress;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
@@ -370,6 +370,8 @@ const handleDeleteSelected = async () => {
|
||||
});
|
||||
|
||||
if (result == true) {
|
||||
itemsLoading.value = true;
|
||||
|
||||
let errorCount = 0;
|
||||
let successCount = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user