Manual order: you can now change the order by dragging and dropping
This commit is contained in:
parent
45c14f6a12
commit
42839a5886
@ -934,6 +934,8 @@ class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionMod
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='shared_with')
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
filter = models.ForeignKey('cookbook.CustomFilter', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
order = models.IntegerField(default=0)
|
||||
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
@ -950,7 +950,7 @@ class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer)
|
||||
|
||||
class Meta:
|
||||
model = RecipeBook
|
||||
fields = ('id', 'name', 'description', 'shared', 'created_by', 'filter')
|
||||
fields = ('id', 'name', 'description', 'shared', 'created_by', 'filter', 'order')
|
||||
read_only_fields = ('created_by',)
|
||||
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
<b-dropdown-item @click = "orderBy('id','asc')" :disabled= "isActiveSort('id','asc')">oldest to newest</b-dropdown-item>
|
||||
<b-dropdown-item @click = "orderBy('id','desc')" :disabled= "isActiveSort('id','desc')">newest to oldest</b-dropdown-item>
|
||||
<b-dropdown-item @click = "orderBy('name','asc')" :disabled= "isActiveSort('name','asc')">alphabetical order</b-dropdown-item>
|
||||
<b-dropdown-item @click = " enableSortManually" :disabled= "isActiveSort('name','asc')" >manually</b-dropdown-item>
|
||||
<b-dropdown-item @click = "orderBy('order','asc')" :disabled= "isActiveSort('order','asc')" >manually</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</b-input-group-append>
|
||||
<b-button class= "ml-2" variant="primary" v-show="!showMan" @click="submitManualChanging">
|
||||
<b-button class= "ml-2" variant="primary" v-if="isActiveSort('order','asc')" @click="handleEditButton" >
|
||||
{{submitText}}
|
||||
</b-button>
|
||||
</b-input-group>
|
||||
@ -27,54 +27,74 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-bottom: 55px">
|
||||
<div class="mb-3" v-for="(book, index) in filteredBooks" :key="book.id">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<b-card class="d-flex flex-column" v-hover >
|
||||
<b-row no-gutters style="height: inherit" class="d-flex align-items-center">
|
||||
<b-col no-gutters :md="md" style="height: inherit">
|
||||
<b-card-body class="m-0 py-0" style="height: inherit">
|
||||
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<b-button v-on:click="openBook(book.id)" style="color: #000; background-color: white" variant="primary">
|
||||
<h5 class="m-0 mt-1 text-truncate" >
|
||||
{{ book.name }} <span class="float-right"><i class="fa fa-book"></i></span>
|
||||
</h5></b-button>
|
||||
<div class="m-0 text-truncate">{{ book.description }}</div>
|
||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end"></div>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button-group vertical md = "1" >
|
||||
<b-button v-if="!showMan && index != 0 && submitManual " variant="primary" style="border-radius:28px!important;" @click= "swapUpBooks(index)">↑</b-button>
|
||||
<b-button v-if="!showMan && index != cookbooks.length-1 && submitManual" variant="primary" style="border-radius:28px!important;" @click= "swapDownBooks(index)">↓</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group vertical md = "1" class="ml-2">
|
||||
<input v-model.lazy="inputValue" v-if="!showMan && submitManual" placeholder="enter swap position">
|
||||
<b-button v-if="!showMan && submitManual" variant="primary" style="border-radius:28px!important;" @click= "swapWithPos(index)">swap</b-button>
|
||||
</b-button-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-card>
|
||||
<div v-if="!isActiveSort('order','asc') || !manSubmitted">
|
||||
<div style="padding-bottom: 55px">
|
||||
<div class="mb-3" v-for="(book) in filteredBooks" :key="book.id">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<b-card class="d-flex flex-column" v-hover >
|
||||
<b-row no-gutters style="height: inherit" class="d-flex align-items-center">
|
||||
<b-col no-gutters style="height: inherit">
|
||||
<b-card-body class="m-0 py-0" style="height: inherit">
|
||||
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<b-button v-on:click="openBook(book.id)" style="color: #000; background-color: white" variant="primary">
|
||||
<h5 class="m-0 mt-1 text-truncate" >
|
||||
{{ book.name }} <span class="float-right"><i class="fa fa-book"></i></span>
|
||||
</h5></b-button>
|
||||
<div class="m-0 text-truncate">{{ book.description }}</div>
|
||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end"></div>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<loading-spinner v-if="current_book === book.id && loading"></loading-spinner>
|
||||
<transition name="slide-fade">
|
||||
<cookbook-slider
|
||||
:recipes="recipes"
|
||||
:book="book"
|
||||
:key="`slider_${book.id}`"
|
||||
v-if="current_book === book.id && !loading"
|
||||
v-on:refresh="refreshData"
|
||||
@reload="openBook(current_book, true)"
|
||||
></cookbook-slider>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<loading-spinner v-if="current_book === book.id && loading"></loading-spinner>
|
||||
<transition name="slide-fade">
|
||||
<cookbook-slider
|
||||
:recipes="recipes"
|
||||
:book="book"
|
||||
:key="`slider_${book.id}`"
|
||||
v-if="current_book === book.id && !loading"
|
||||
v-on:refresh="refreshData"
|
||||
@reload="openBook(current_book, true)"
|
||||
></cookbook-slider>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<draggable
|
||||
@change="updateManualSorting"
|
||||
:list="cookbooks" ghost-class="ghost">
|
||||
<b-card no-body class="mt-1 list-group-item p-2"
|
||||
style="cursor: move"
|
||||
v-for=" (book,index) in filteredBooks"
|
||||
v-hover
|
||||
:key="book.id">
|
||||
<b-card-header class="p-2 border-0">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<button type="button"
|
||||
class="btn btn-lg shadow-none"><i
|
||||
class="fas fa-arrows-alt-v"></i></button>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<h5 class="mt-1 mb-1">
|
||||
<b-badge class="float-left text-white mr-2">
|
||||
#{{ index + 1 }}
|
||||
</b-badge>
|
||||
{{ book.name }}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</b-card-header>
|
||||
</b-card>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<bottom-navigation-bar active-view="view_books">
|
||||
<template #custom_create_functions>
|
||||
@ -92,7 +112,7 @@
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
|
||||
import draggable from "vuedraggable"
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||
import CookbookSlider from "@/components/CookbookSlider"
|
||||
@ -105,7 +125,7 @@ Vue.use(BootstrapVue)
|
||||
export default {
|
||||
name: "CookbookView",
|
||||
mixins: [ApiMixin],
|
||||
components: { LoadingSpinner, CookbookSlider, BottomNavigationBar },
|
||||
components: { LoadingSpinner, CookbookSlider, BottomNavigationBar, draggable },
|
||||
data() {
|
||||
return {
|
||||
cookbooks: [],
|
||||
@ -115,11 +135,9 @@ export default {
|
||||
loading: false,
|
||||
search: "",
|
||||
activeSortField : 'id',
|
||||
activeSortDirection: 'asc',
|
||||
showMan: true,
|
||||
md: 12,
|
||||
activeSortDirection: 'desc',
|
||||
inputValue: "",
|
||||
submitManual: false,
|
||||
manSubmitted : false,
|
||||
submitText: "Edit"
|
||||
}
|
||||
},
|
||||
@ -211,74 +229,27 @@ export default {
|
||||
// Check if the current item is the active sorting option
|
||||
return this.activeSortField === field && this.activeSortDirection === direction;
|
||||
},
|
||||
enableSortManually: function(){
|
||||
this.synchroniseLocalToDatabase();
|
||||
if (localStorage.getItem('cookbooks') ){
|
||||
this.cookbooks = JSON.parse(localStorage.getItem('cookbooks'))
|
||||
}
|
||||
this.showOtN= true
|
||||
this.showAlp= true
|
||||
this.showNtO = true
|
||||
this.showMan = false
|
||||
this.dropdown_text = "Sort by: manually"
|
||||
|
||||
},
|
||||
swapUpBooks: function(index){
|
||||
const tempArray = this.cookbooks
|
||||
const temp = tempArray[index - 1]
|
||||
tempArray[index-1] = tempArray[index]
|
||||
tempArray[index] = temp
|
||||
this.cookbooks = []
|
||||
this.cookbooks = tempArray
|
||||
},
|
||||
swapDownBooks: function(index){
|
||||
const tempArray = this.cookbooks
|
||||
const temp = tempArray[index + 1]
|
||||
tempArray[index+1] = tempArray[index]
|
||||
tempArray[index] = temp
|
||||
this.cookbooks = []
|
||||
this.cookbooks = tempArray
|
||||
},
|
||||
swapWithPos: function(index){
|
||||
const position = parseInt(this.inputValue)
|
||||
this.inputValue = ""
|
||||
if (!(/^\d+$/.test(position)) || position >= this.cookbooks.length || position < 0){
|
||||
this.inputValue = ""
|
||||
handleEditButton: function(){
|
||||
if (!this.manSubmitted){
|
||||
this.submitText = "Back"
|
||||
this.manSubmitted = true
|
||||
} else {
|
||||
const tempArray = this.cookbooks
|
||||
const temp = tempArray[position]
|
||||
tempArray[position] = tempArray[index]
|
||||
tempArray[index] = temp
|
||||
this.cookbooks = []
|
||||
this.cookbooks = tempArray
|
||||
}
|
||||
|
||||
}, submitManualChanging: function(){
|
||||
if (!this.submitManual){
|
||||
this.submitText = "Submit"
|
||||
this.submitManual = true
|
||||
this.md = 8
|
||||
} else {
|
||||
localStorage.setItem('cookbooks',JSON.stringify(this.cookbooks))
|
||||
this.submitText = "Edit"
|
||||
this.submitManual = false
|
||||
this.md = 12
|
||||
this.manSubmitted = false
|
||||
}
|
||||
}, synchroniseLocalToDatabase: function(){
|
||||
const localStorageData = localStorage.getItem('cookbooks');
|
||||
const localStorageArray = JSON.parse(localStorageData) || [];
|
||||
const updatedLocalStorageArray = localStorageArray.filter(localStorageElement => {
|
||||
// Assuming there's a unique identifier in your objects, replace 'id' with the actual property
|
||||
const isElementInTargetArray = this.cookbooks.some(targetElement => targetElement.id === localStorageElement.id);
|
||||
return isElementInTargetArray;
|
||||
});
|
||||
this.cookbooks.forEach(targetElement => {
|
||||
const isNewElement = !updatedLocalStorageArray.some(localStorageElement => localStorageElement.id === targetElement.id);
|
||||
if (isNewElement) {
|
||||
updatedLocalStorageArray.push(targetElement);
|
||||
}
|
||||
});
|
||||
localStorage.setItem('cookbooks', JSON.stringify(updatedLocalStorageArray));
|
||||
},
|
||||
updateManualSorting: function(){
|
||||
let old_order = Object.assign({}, this.cookbooks);
|
||||
let promises = []
|
||||
this.cookbooks.forEach((element, index) => {
|
||||
let apiClient = new ApiApiFactory()
|
||||
promises.push(apiClient.partialUpdateManualOrderBooks(element.id, {order: index}))
|
||||
})
|
||||
return Promise.all(promises).then(() => {
|
||||
}).catch((err) => {
|
||||
this.cookbooks = old_order
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
|
@ -10032,6 +10032,40 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying the book.
|
||||
* @param {RecipeBook} [recipeBook]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
partialUpdateManualOrderBooks: async (id: string, recipeBook?: RecipeBook , options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/recipe-book/{id}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(recipeBook , localVarRequestOptions, configuration)
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this access token.
|
||||
@ -16172,6 +16206,17 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.moveKeyword(id, parent, keyword, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {RecipeBook} [recipeBook]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async partialUpdateManualOrderBooks(id: string, recipeBook?: RecipeBook, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SupermarketCategoryRelation>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.partialUpdateManualOrderBooks(id, recipeBook, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this access token.
|
||||
@ -18838,6 +18883,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
moveKeyword(id: string, parent: string, keyword?: Keyword, options?: any): AxiosPromise<Keyword> {
|
||||
return localVarFp.moveKeyword(id, parent, keyword, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {RecipeBook} [recipeBook]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
partialUpdateManualOrderBooks(id: string, recipeBook?: RecipeBook, options?: any): AxiosPromise<SupermarketCategoryRelation> {
|
||||
return localVarFp.partialUpdateManualOrderBooks(id, recipeBook, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this access token.
|
||||
@ -20413,7 +20468,6 @@ export class ApiApi extends BaseAPI {
|
||||
public createRecipeBookEntry(recipeBookEntry?: RecipeBookEntry, options?: any) {
|
||||
return ApiApiFp(this.configuration).createRecipeBookEntry(recipeBookEntry, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ShoppingList} [shoppingList]
|
||||
@ -21639,7 +21693,16 @@ export class ApiApi extends BaseAPI {
|
||||
public moveKeyword(id: string, parent: string, keyword?: Keyword, options?: any) {
|
||||
return ApiApiFp(this.configuration).moveKeyword(id, parent, keyword, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {RecipeBook} [recipeBook]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public partialUpdateManualOrderBooks(id: string, recipeBook?: RecipeBook, options?: any) {
|
||||
return ApiApiFp(this.configuration).partialUpdateManualOrderBooks(id, recipeBook, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this access token.
|
||||
|
Loading…
Reference in New Issue
Block a user