Skip to content

Commit

Permalink
Refactor media management.
Browse files Browse the repository at this point in the history
- Change tiled UI to table UI.
- Add support for search and pagination.
- Important: This breaks the `GET /api/media` API to introduce pagination
  fields. Media items are now moved into `{ data: results[] }`.
  • Loading branch information
knadh committed May 21, 2023
1 parent 3b9a0f7 commit d359ad2
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 158 deletions.
11 changes: 10 additions & 1 deletion cmd/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func handleUploadMedia(c echo.Context) error {
func handleGetMedia(c echo.Context) error {
var (
app = c.Get("app").(*App)
pg = app.paginator.NewFromURL(c.Request().URL.Query())
query = c.FormValue("query")
id, _ = strconv.Atoi(c.Param("id"))
)

Expand All @@ -143,11 +145,18 @@ func handleGetMedia(c echo.Context) error {
return c.JSON(http.StatusOK, okResp{out})
}

out, err := app.core.GetAllMedia(app.constants.MediaUpload.Provider, app.media)
res, total, err := app.core.QueryMedia(app.constants.MediaUpload.Provider, app.media, query, pg.Offset, pg.Limit)
if err != nil {
return err
}

out := models.PageResults{
Results: res,
Total: total,
Page: pg.Page,
PerPage: pg.PerPage,
}

return c.JSON(http.StatusOK, okResp{out})
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ export const deleteCampaign = async (id) => http.delete(`/api/campaigns/${id}`,
{ loading: models.campaigns });

// Media.
export const getMedia = async () => http.get('/api/media',
{ loading: models.media, store: models.media });
export const getMedia = async (params) => http.get('/api/media',
{ params, loading: models.media, store: models.media });

export const uploadMedia = (data) => http.post('/api/media', data,
{ loading: models.media });
Expand Down
97 changes: 12 additions & 85 deletions frontend/src/assets/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -766,93 +766,20 @@ section.analytics {

/* Media gallery */
.media-files {
.thumbs {
display: flex;
flex-wrap: wrap;
flex-direction: column;
flex-flow: row wrap;

.thumb {
margin: 10px;
max-height: 90px;
overflow: hidden;
position: relative;
width: 250px;
min-height: 250px;
text-align: center;

.link {
display: block;
}

.ext {
display: block;
margin-top: 60px;
font-size: 2rem;
text-transform: uppercase;
}

.filename {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: $grey;
}

img {
max-width: 250px;
}

.caption {
background-color: rgba($white, .70);
color: $grey;
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 5px 15px;

white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.actions {
background-color: rgba($white, .70);
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 2px 5px;
display: none;

a {
margin-left: 10px;
}
}

&:hover .actions {
display: block;
}
}

.box {
padding: 10px;
}
}
}

.modal .media-files {
.thumb {
min-width: 175px;
width: 175px;
min-height: 175px;
img {
max-width: 125px;
}

.gallery {
padding-left: 0;
padding-right: 0;
.thumb.box {
display: inline-block;
padding: 5px;
min-width: 140px;
max-height: 140px;
overflow: hidden;
text-align: center;
}
.ext {
text-transform: uppercase;
}
}

Expand Down
146 changes: 89 additions & 57 deletions frontend/src/views/Media.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,44 +41,81 @@
</form>
</section>

<section class="section gallery">
<div v-for="group in items" :key="group.title">
<h3 class="title is-5">{{ group.title }}</h3>

<div class="thumbs">
<div v-for="m in group.items" :key="m.id" class="box thumb">
<a @click="(e) => onMediaSelect(m, e)" :href="m.url" target="_blank" class="link">
<img v-if="m.thumbUrl" :src="m.thumbUrl" :title="m.filename" />
<template v-else>
<span class="ext" :title="m.filename">{{ m.filename.split(".").pop() }}</span><br />
</template>
<span class="caption is-size-5" :title="m.filename">
{{ m.filename }}
</span>
</a>

<div class="actions has-text-right">
<a :href="m.url" target="_blank">
<b-icon icon="arrow-top-right" size="is-small" />
</a>
<a href="#" @click.prevent="$utils.confirm(null, () => deleteMedia(m.id))">
<b-icon icon="trash-can-outline" size="is-small" />
</a>
<section class="wrap gallery mt-6">
<b-table :data="media.results" :hoverable="true" :loading="loading.media"
default-sort="createdAt" :paginated="true" backend-pagination pagination-position="both"
@page-change="onPageChange"
:current-page="media.page" :per-page="media.perPage" :total="media.total">

<template #top-left>
<div class="columns">
<div class="column is-6">
<form @submit.prevent="onQueryMedia">
<div>
<b-field>
<b-input v-model="queryParams.query" name="query" expanded
icon="magnify" ref="query" data-cy="query" />
<p class="controls">
<b-button native-type="submit" type="is-primary" icon-left="magnify"
data-cy="btn-query" />
</p>
</b-field>
</div>
</form>
</div>
</div>
</div>
<hr />
</div>
</template>

<b-table-column v-slot="props" field="name" width="40%"
:label="$t('globals.fields.name')">
<a @click="(e) => onMediaSelect(props.row, e)" :href="props.row.url"
target="_blank" class="link" :title="props.row.filename">
{{ props.row.filename }}
</a>
</b-table-column>

<b-table-column v-slot="props" field="thumb" width="30%">
<a @click="(e) => onMediaSelect(props.row, e)" :href="props.row.url"
target="_blank" class="thumb box">
<img v-if="props.row.thumbUrl" :src="props.row.thumbUrl" :title="props.row.filename" />
<span v-else class="ext">
{{ props.row.filename.split(".").pop() }}
</span>
</a>
</b-table-column>

<b-table-column v-slot="props" field="created_at" width="25%"
:label="$t('globals.fields.createdAt')" sortable>
{{ $utils.niceDate(props.row.createdAt, true) }}
</b-table-column>

<b-table-column v-slot="props" field="actions" width="5%" cell-class="has-text-right">
<a href="" @click.prevent="$utils.confirm(null, () => onDeleteMedia(props.row.id))"
data-cy="btn-delete">
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
<b-icon icon="trash-can-outline" size="is-small" />
</b-tooltip>
</a>
</b-table-column>

<template #empty v-if="!loading.media">
<empty-placeholder />
</template>
</b-table>
</section>
</section>
</template>

<script>
import Vue from 'vue';
import { mapState } from 'vuex';
import dayjs from 'dayjs';
import EmptyPlaceholder from '../components/EmptyPlaceholder.vue';
export default Vue.extend({
components: {
EmptyPlaceholder,
},
name: 'Media',
props: {
Expand All @@ -93,6 +130,11 @@ export default Vue.extend({
},
toUpload: 0,
uploaded: 0,
queryParams: {
page: 1,
query: '',
},
};
},
Expand All @@ -101,6 +143,18 @@ export default Vue.extend({
this.form.files.splice(i, 1);
},
getMedia() {
this.$api.getMedia({
page: this.queryParams.page,
query: this.queryParams.query,
});
},
onQueryMedia() {
this.queryParams.page = 1;
this.getMedia();
},
onMediaSelect(m, e) {
// If the component is open in the modal mode, close the modal and
// fire the selection event.
Expand All @@ -127,9 +181,9 @@ export default Vue.extend({
}
},
deleteMedia(id) {
onDeleteMedia(id) {
this.$api.deleteMedia(id).then(() => {
this.$api.getMedia();
this.getMedia();
});
},
Expand All @@ -140,9 +194,14 @@ export default Vue.extend({
this.uploaded = 0;
this.form.files = [];
this.$api.getMedia();
this.getMedia();
}
},
onPageChange(p) {
this.queryParams.page = p;
this.getMedia();
},
},
computed: {
Expand All @@ -154,33 +213,6 @@ export default Vue.extend({
}
return false;
},
// Filters the list of media items by months into:
// [{"title": "Jan 2020", items: [...]}, ...]
items() {
const out = [];
if (!this.media || !(this.media instanceof Array)) {
return out;
}
let lastStamp = '';
let lastIndex = 0;
this.media.forEach((m) => {
if (this.$props.type === 'image' && !m.thumbUrl) {
return;
}
const stamp = dayjs(m.createdAt).format('MMM YYYY');
if (stamp !== lastStamp) {
out.push({ title: stamp, items: [] });
lastStamp = stamp;
lastIndex = out.length;
}
out[lastIndex - 1].items.push(m);
});
return out;
},
},
mounted() {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/Subscribers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
{{ $utils.niceDate(props.row.updatedAt) }}
</b-table-column>

<b-table-column v-slot="props" label="Actions" cell-class="actions" align="right">
<b-table-column v-slot="props" cell-class="actions" align="right">
<div>
<a :href="`/api/subscribers/${props.row.id}/export`" data-cy="btn-download">
<b-tooltip :label="$t('subscribers.downloadData')" type="is-dark">
Expand Down
Loading

0 comments on commit d359ad2

Please sign in to comment.