Add an option to soft delete page icon
This commit is contained in:
parent
305b334d3e
commit
82636aefaf
@ -733,14 +733,39 @@
|
||||
<b-modal
|
||||
v-model="showIconModal"
|
||||
:title="$t('icon.configure')"
|
||||
:ok-title="$t('label.saveAndClose')"
|
||||
size="lg"
|
||||
label-class="text-primary"
|
||||
cancel-variant="link"
|
||||
footer-class="d-flex align-items-center"
|
||||
no-fade
|
||||
@close="closeIconModal"
|
||||
@ok="saveIconModal"
|
||||
>
|
||||
<template #modal-footer>
|
||||
<c-input-confirm
|
||||
v-if="attachments && selectedAttachmentID"
|
||||
:disabled="(attachments && !selectedAttachmentID) || processingIcon"
|
||||
size="md"
|
||||
variant="danger"
|
||||
@confirmed="deleteIcon"
|
||||
>
|
||||
{{ $t('icon.delete') }}
|
||||
</c-input-confirm>
|
||||
|
||||
<div class="ml-auto">
|
||||
<b-button
|
||||
variant="link"
|
||||
class="text-primary"
|
||||
@click="closeIconModal"
|
||||
>
|
||||
{{ $t('general:label.cancel') }}
|
||||
</b-button>
|
||||
<b-button
|
||||
variant="primary"
|
||||
class="ml-2"
|
||||
@click="saveIconModal"
|
||||
>
|
||||
{{ $t('general:label.saveAndClose') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<b-form-group
|
||||
:label="$t('icon.upload')"
|
||||
label-class="text-primary"
|
||||
@ -787,7 +812,7 @@
|
||||
label-class="text-primary"
|
||||
>
|
||||
<div
|
||||
v-if="processing"
|
||||
v-if="processingIcon"
|
||||
class="d-flex align-items-center justify-content-center h-100"
|
||||
>
|
||||
<b-spinner />
|
||||
@ -915,6 +940,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
processing: false,
|
||||
processingIcon: false,
|
||||
|
||||
page: new compose.Page(),
|
||||
initialPageState: new compose.Page(),
|
||||
@ -1243,12 +1269,16 @@ export default {
|
||||
},
|
||||
|
||||
async fetchAttachments () {
|
||||
this.processingIcon = true
|
||||
|
||||
return this.$ComposeAPI.iconList({ sort: 'id DESC' })
|
||||
.then(({ set: attachments = [] }) => {
|
||||
const baseURL = this.$ComposeAPI.baseURL
|
||||
this.attachments = []
|
||||
|
||||
if (attachments) {
|
||||
if (attachments.length === 0) {
|
||||
this.icon = {}
|
||||
} else {
|
||||
attachments.forEach(a => {
|
||||
const src = !a.url.includes(baseURL) ? this.makeAttachmentUrl(a.url) : a.url
|
||||
this.attachments.push({ ...a, src })
|
||||
@ -1256,6 +1286,9 @@ export default {
|
||||
}
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:page.iconFetchFailed')))
|
||||
.finally(() => {
|
||||
this.processingIcon = false
|
||||
})
|
||||
},
|
||||
|
||||
addLayoutAction () {
|
||||
@ -1281,7 +1314,7 @@ export default {
|
||||
|
||||
openIconModal () {
|
||||
this.linkUrl = this.icon.type === 'link' ? this.icon.src : ''
|
||||
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID
|
||||
this.setCurrentIcon()
|
||||
this.showIconModal = true
|
||||
},
|
||||
|
||||
@ -1294,11 +1327,39 @@ export default {
|
||||
}
|
||||
|
||||
this.icon = { type, src }
|
||||
|
||||
if (type === 'link' && !src) {
|
||||
this.icon = {}
|
||||
}
|
||||
|
||||
this.showIconModal = false
|
||||
},
|
||||
|
||||
deleteIcon () {
|
||||
this.processingIcon = true
|
||||
|
||||
return this.$ComposeAPI.iconDelete({ iconID: this.selectedAttachmentID }).then(() => {
|
||||
return this.fetchAttachments().then(() => {
|
||||
this.setCurrentIcon()
|
||||
this.toastSuccess(this.$t('notification:page.iconDeleteSuccess'))
|
||||
})
|
||||
}).finally(() => {
|
||||
this.processingIcon = false
|
||||
}).catch(this.toastErrorHandler(this.$t('notification:page.iconDeleteFailed')))
|
||||
},
|
||||
|
||||
closeIconModal () {
|
||||
this.linkUrl = this.icon.type === 'link' ? this.icon.src : ''
|
||||
this.setCurrentIcon()
|
||||
this.showIconModal = false
|
||||
},
|
||||
|
||||
setCurrentIcon () {
|
||||
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID
|
||||
|
||||
if (!this.selectedAttachmentID) {
|
||||
this.icon = {}
|
||||
}
|
||||
},
|
||||
|
||||
makeAttachmentUrl (src) {
|
||||
|
||||
@ -1337,6 +1337,44 @@ export default class Compose {
|
||||
return '/icon/'
|
||||
}
|
||||
|
||||
// Delete icon
|
||||
async iconDelete (a: KV, extra: AxiosRequestConfig = {}): Promise<KV> {
|
||||
const {
|
||||
iconID,
|
||||
} = (a as KV) || {}
|
||||
if (!iconID) {
|
||||
throw Error('field iconID is empty')
|
||||
}
|
||||
const cfg: AxiosRequestConfig = {
|
||||
...extra,
|
||||
method: 'delete',
|
||||
url: this.iconDeleteEndpoint({
|
||||
iconID,
|
||||
}),
|
||||
}
|
||||
|
||||
return this.api().request(cfg).then(result => stdResolve(result))
|
||||
}
|
||||
|
||||
iconDeleteCancellable (a: KV, extra: AxiosRequestConfig = {}): { response: (a: KV, extra?: AxiosRequestConfig) => Promise<KV>; cancel: () => void; } {
|
||||
const cancelTokenSource = axios.CancelToken.source();
|
||||
let options = {...extra, cancelToken: cancelTokenSource.token }
|
||||
|
||||
return {
|
||||
response: () => this.iconDelete(a, options),
|
||||
cancel: () => {
|
||||
cancelTokenSource.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iconDeleteEndpoint (a: KV): string {
|
||||
const {
|
||||
iconID,
|
||||
} = a || {}
|
||||
return `/icon/${iconID}`
|
||||
}
|
||||
|
||||
// List available page layouts
|
||||
async pageLayoutListNamespace (a: KV, extra: AxiosRequestConfig = {}): Promise<KV> {
|
||||
const {
|
||||
|
||||
@ -98,6 +98,8 @@ page:
|
||||
cloneFailed: Could not clone this page
|
||||
deleteFailed: Could not delete this page
|
||||
iconFetchFailed: Could not fetch list of icons
|
||||
iconDeleteFailed: Could not delete selected icon
|
||||
iconDeleteSuccess: Icon deleted
|
||||
loadFailed: Could not load the page tree
|
||||
noPages: No pages found
|
||||
saveFailed: Could not save this page
|
||||
|
||||
@ -79,6 +79,7 @@ icon:
|
||||
page: Page icon
|
||||
upload: Upload icon
|
||||
list: Uploaded icons
|
||||
delete: Delete selected icon
|
||||
url:
|
||||
label: Or add URL to icon
|
||||
import: 'Import page(s):'
|
||||
|
||||
@ -562,6 +562,16 @@ endpoints:
|
||||
- name: icon
|
||||
type: "*multipart.FileHeader"
|
||||
title: Icon to upload
|
||||
- name: delete
|
||||
path: "/{iconID}"
|
||||
method: DELETE
|
||||
title: Delete icon
|
||||
parameters:
|
||||
path:
|
||||
- type: uint64
|
||||
name: iconID
|
||||
required: true
|
||||
title: Icon ID
|
||||
|
||||
- title: Page Layouts
|
||||
description: Compose page layouts
|
||||
|
||||
@ -21,12 +21,14 @@ type (
|
||||
IconAPI interface {
|
||||
List(context.Context, *request.IconList) (interface{}, error)
|
||||
Upload(context.Context, *request.IconUpload) (interface{}, error)
|
||||
Delete(context.Context, *request.IconDelete) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Icon struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
@ -62,6 +64,22 @@ func NewIcon(h IconAPI) *Icon {
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconDelete()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Delete(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
@ -72,5 +90,6 @@ func (h Icon) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.H
|
||||
r.Use(middlewares...)
|
||||
r.Get("/icon/", h.List)
|
||||
r.Post("/icon/", h.Upload)
|
||||
r.Delete("/icon/{iconID}", h.Delete)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
@ -56,6 +59,9 @@ func (ctrl *Icon) List(ctx context.Context, r *request.IconList) (interface{}, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get only the undeleted icons
|
||||
f.Deleted = filter.StateExcluded
|
||||
|
||||
set, f, err = ctrl.attachment.Find(ctx, f)
|
||||
return ctrl.makeIconFilterPayload(ctx, set, f, err)
|
||||
}
|
||||
@ -105,3 +111,16 @@ func (ctrl *Icon) makeIconFilterPayload(ctx context.Context, nn types.Attachment
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ctrl *Icon) Delete(ctx context.Context, r *request.IconDelete) (interface{}, error) {
|
||||
if !auth.GetIdentityFromContext(ctx).Valid() {
|
||||
return nil, errors.Unauthorized("cannot delete icon")
|
||||
}
|
||||
|
||||
_, err := ctrl.attachment.FindByID(ctx, 0, r.IconID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.OK(), ctrl.attachment.DeleteByID(ctx, 0, r.IconID)
|
||||
}
|
||||
|
||||
@ -61,6 +61,13 @@ type (
|
||||
// Icon to upload
|
||||
Icon *multipart.FileHeader
|
||||
}
|
||||
|
||||
IconDelete struct {
|
||||
// IconID PATH parameter
|
||||
//
|
||||
// Icon ID
|
||||
IconID uint64 `json:",string"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewIconList request
|
||||
@ -191,3 +198,38 @@ func (r *IconUpload) Fill(req *http.Request) (err error) {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIconDelete request
|
||||
func NewIconDelete() *IconDelete {
|
||||
return &IconDelete{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconDelete) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iconID": r.IconID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconDelete) GetIconID() uint64 {
|
||||
return r.IconID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconDelete) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "iconID")
|
||||
r.IconID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@ -36,6 +36,8 @@ type (
|
||||
FieldName string `json:"fieldName,omitempty"`
|
||||
Filter string `json:"filter"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
|
||||
@ -120,6 +120,13 @@ func DefaultFilters() (f *extendedFilters) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add a filter expression for deleted attachments
|
||||
if f.Deleted == filter.StateExcluded {
|
||||
ee = append(ee, goqu.C("deleted_at").IsNull())
|
||||
} else if f.Deleted == filter.StateExclusive {
|
||||
ee = append(ee, goqu.C("deleted_at").IsNotNull())
|
||||
}
|
||||
|
||||
return ee, f, nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user