stash
This commit is contained in:
parent
7e8f984ee2
commit
a1fd49c5d1
11 changed files with 406 additions and 214 deletions
|
@ -6,7 +6,7 @@
|
||||||
<a class="sidebar-toggle d-flex">
|
<a class="sidebar-toggle d-flex">
|
||||||
<i class="hamburger align-self-center"></i>
|
<i class="hamburger align-self-center"></i>
|
||||||
</a>
|
</a>
|
||||||
<SearchBox/>
|
<SearchBox v-if="!hideSearch"/>
|
||||||
<div class="navbar-collapse collapse">
|
<div class="navbar-collapse collapse">
|
||||||
<ul class="navbar-nav navbar-align">
|
<ul class="navbar-nav navbar-align">
|
||||||
<Notifications :notifications="notifications"/>
|
<Notifications :notifications="notifications"/>
|
||||||
|
@ -42,7 +42,14 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['messages']),
|
...mapState(['messages']),
|
||||||
...mapGetters(['notifications'])
|
...mapGetters(['notifications']),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
hideSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="d-none d-sm-inline-block">
|
<form class="d-none d-sm-inline-block" @submit.prevent="search">
|
||||||
<div class="input-group input-group-navbar">
|
<div class="input-group input-group-navbar">
|
||||||
<input type="text" class="form-control" placeholder="Search…" aria-label="Search" v-model="query" ref="search-text" />
|
<input type="text" class="form-control" placeholder="Search…" aria-label="Search" v-model="query" ref="search-text" />
|
||||||
<button class="btn" type="button" @click.prevent="search">
|
<button class="btn" type="button" @click.prevent="search">
|
||||||
|
@ -18,6 +18,10 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
|
model: {
|
||||||
|
prop: "query",
|
||||||
|
event: "change"
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
query: ""
|
query: ""
|
||||||
|
@ -31,6 +35,12 @@ export default {
|
||||||
this.$refs["search-text"].focus();
|
this.$refs["search-text"].focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
query() {
|
||||||
|
//emit event
|
||||||
|
this.$emit("change", this.query);
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
//console.log(this.$route)
|
//console.log(this.$route)
|
||||||
//console.log(this.$route.params.query)
|
//console.log(this.$route.params.query)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<nav id="sidebar" class="sidebar">
|
<nav id="sidebar" class="sidebar">
|
||||||
<div class="sidebar-content js-simplebar">
|
<div class="sidebar-content js-simplebar">
|
||||||
<router-link to="/" class="sidebar-brand">
|
<router-link to="/" class="sidebar-brand">
|
||||||
|
<!--img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo"-->
|
||||||
<span class="align-middle">Toolshed</span>
|
<span class="align-middle">Toolshed</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<ul class="sidebar-nav">
|
<ul class="sidebar-nav">
|
||||||
|
|
|
@ -6,14 +6,50 @@ class ServerSet {
|
||||||
if (!unreachable_neighbors || typeof unreachable_neighbors.queryUnreachable !== 'function' || typeof unreachable_neighbors.unreachable !== 'function') {
|
if (!unreachable_neighbors || typeof unreachable_neighbors.queryUnreachable !== 'function' || typeof unreachable_neighbors.unreachable !== 'function') {
|
||||||
throw new Error('no unreachable_neighbors')
|
throw new Error('no unreachable_neighbors')
|
||||||
}
|
}
|
||||||
this.servers = servers;
|
this.servers = [... new Set(servers)] // deduplicate
|
||||||
this.unreachable_neighbors = unreachable_neighbors;
|
this.unreachable_neighbors = unreachable_neighbors;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(server) {
|
add(server) {
|
||||||
|
console.log('adding server', server)
|
||||||
|
if (!server || typeof server !== 'string') {
|
||||||
|
throw new Error('server must be a string')
|
||||||
|
}
|
||||||
|
if (server in this.servers) {
|
||||||
|
console.log('server already in set', server)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.servers.push(server);
|
this.servers.push(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async get(auth, target) {
|
||||||
|
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||||
|
throw new Error('no auth')
|
||||||
|
}
|
||||||
|
for (const server of this.servers) {
|
||||||
|
try {
|
||||||
|
if (this.unreachable_neighbors.queryUnreachable(server)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const url = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
...auth.buildAuthHeader(url)
|
||||||
|
},
|
||||||
|
credentials: 'omit'
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('get from server failed', server, err)
|
||||||
|
this.unreachable_neighbors.unreachable(server)
|
||||||
|
}
|
||||||
|
).then(response => response.json())
|
||||||
|
} catch (e) {
|
||||||
|
console.error('get from server failed', server, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
|
||||||
async post(auth, target, data) {
|
async post(auth, target, data) {
|
||||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||||
throw new Error('no auth')
|
throw new Error('no auth')
|
||||||
|
@ -32,7 +68,10 @@ class ServerSet {
|
||||||
},
|
},
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
}).catch(err => {
|
||||||
|
console.error('post to server failed', server, err)
|
||||||
|
this.unreachable_neighbors.unreachable(server)
|
||||||
|
}
|
||||||
).then(response => response.json())
|
).then(response => response.json())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('post to server failed', server, e)
|
console.error('post to server failed', server, e)
|
||||||
|
@ -59,7 +98,10 @@ class ServerSet {
|
||||||
},
|
},
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
}).catch(err => {
|
||||||
|
console.error('patch to server failed', server, err)
|
||||||
|
this.unreachable_neighbors.unreachable(server)
|
||||||
|
}
|
||||||
).then(response => response.json())
|
).then(response => response.json())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('patch to server failed', server, e)
|
console.error('patch to server failed', server, e)
|
||||||
|
@ -68,56 +110,6 @@ class ServerSet {
|
||||||
throw new Error('all servers failed')
|
throw new Error('all servers failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(auth, target) {
|
|
||||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
|
||||||
throw new Error('no auth')
|
|
||||||
}
|
|
||||||
for (const server of this.servers) {
|
|
||||||
try {
|
|
||||||
if (this.unreachable_neighbors.queryUnreachable(server)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const url = "http://" + server + target // TODO https
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
...auth.buildAuthHeader(url)
|
|
||||||
},
|
|
||||||
credentials: 'omit'
|
|
||||||
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
|
||||||
).then(response => response.json())
|
|
||||||
} catch (e) {
|
|
||||||
console.error('get from server failed', server, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error('all servers failed')
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(auth, target) {
|
|
||||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
|
||||||
throw new Error('no auth')
|
|
||||||
}
|
|
||||||
for (const server of this.servers) {
|
|
||||||
try {
|
|
||||||
if (this.unreachable_neighbors.queryUnreachable(server)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const url = "http://" + server + target // TODO https
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
...auth.buildAuthHeader(url)
|
|
||||||
},
|
|
||||||
credentials: 'omit'
|
|
||||||
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
|
||||||
).then(response => response.json())
|
|
||||||
} catch (e) {
|
|
||||||
console.error('delete from server failed', server, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error('all servers failed')
|
|
||||||
}
|
|
||||||
|
|
||||||
async put(auth, target, data) {
|
async put(auth, target, data) {
|
||||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||||
throw new Error('no auth')
|
throw new Error('no auth')
|
||||||
|
@ -136,7 +128,10 @@ class ServerSet {
|
||||||
},
|
},
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
}).catch(err => {
|
||||||
|
console.error('put to server failed', server, err)
|
||||||
|
this.unreachable_neighbors.unreachable(server)
|
||||||
|
}
|
||||||
).then(response => response.json())
|
).then(response => response.json())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('put to server failed', server, e)
|
console.error('put to server failed', server, e)
|
||||||
|
@ -144,8 +139,107 @@ class ServerSet {
|
||||||
}
|
}
|
||||||
throw new Error('all servers failed')
|
throw new Error('all servers failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(auth, target) {
|
||||||
|
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||||
|
throw new Error('no auth')
|
||||||
|
}
|
||||||
|
for (const server of this.servers) {
|
||||||
|
try {
|
||||||
|
if (this.unreachable_neighbors.queryUnreachable(server)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const url = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
...auth.buildAuthHeader(url)
|
||||||
|
},
|
||||||
|
credentials: 'omit'
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('delete from server failed', server, err)
|
||||||
|
this.unreachable_neighbors.unreachable(server)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('delete from server failed', server, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ServerSetUnion {
|
||||||
|
constructor(serverSets) {
|
||||||
|
if (!serverSets || !Array.isArray(serverSets)) {
|
||||||
|
throw new Error('no serverSets')
|
||||||
|
}
|
||||||
|
this.serverSets = serverSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(serverset) {
|
||||||
|
if (!serverset || !(serverset instanceof ServerSet)) {
|
||||||
|
throw new Error('no serverset')
|
||||||
|
}
|
||||||
|
if (this.serverSets.find(s => serverset.servers.every(s2 => s.servers.includes(s2)))) {
|
||||||
|
console.warn('serverset already in union', serverset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.serverSets.push(serverset)
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(auth, target, data) {
|
||||||
|
try {
|
||||||
|
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||||
|
return await serverset.post(auth, target, data)
|
||||||
|
}, Promise.resolve())
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async patch(auth, target, data) {
|
||||||
|
try {
|
||||||
|
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||||
|
return await serverset.patch(auth, target, data)
|
||||||
|
}, Promise.resolve())
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(auth, target) {
|
||||||
|
try {
|
||||||
|
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||||
|
return await serverset.get(auth, target)
|
||||||
|
}, Promise.resolve())
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(auth, target) {
|
||||||
|
try {
|
||||||
|
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||||
|
return await serverset.delete(auth, target)
|
||||||
|
}, Promise.resolve())
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(auth, target, data) {
|
||||||
|
try {
|
||||||
|
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||||
|
return await serverset.put(auth, target, data)
|
||||||
|
}, Promise.resolve())
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class authMethod {
|
class authMethod {
|
||||||
constructor(method, auth) {
|
constructor(method, auth) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
@ -167,7 +261,6 @@ function createSignAuth(username, signKey) {
|
||||||
return new authMethod(({signKey, username}, {url, data}) => {
|
return new authMethod(({signKey, username}, {url, data}) => {
|
||||||
const json = JSON.stringify(data)
|
const json = JSON.stringify(data)
|
||||||
const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url + (data ? json : "")), signKey)
|
const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url + (data ? json : "")), signKey)
|
||||||
console.log('sign', nacl.to_hex(signature), url, json)
|
|
||||||
return {'Authorization': 'Signature ' + username + ':' + nacl.to_hex(signature)}
|
return {'Authorization': 'Signature ' + username + ':' + nacl.to_hex(signature)}
|
||||||
}, context)
|
}, context)
|
||||||
}
|
}
|
||||||
|
@ -188,6 +281,6 @@ function createNullAuth() {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ServerSet, createSignAuth, createTokenAuth, createNullAuth};
|
export {ServerSet, ServerSetUnion, createSignAuth, createTokenAuth, createNullAuth};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,11 @@ const routes = [
|
||||||
{path: '/profile', component: Profile, meta: {requiresAuth: true}},
|
{path: '/profile', component: Profile, meta: {requiresAuth: true}},
|
||||||
{path: '/settings', component: Settings, meta: {requiresAuth: true}},
|
{path: '/settings', component: Settings, meta: {requiresAuth: true}},
|
||||||
{path: '/inventory', component: Inventory, meta: {requiresAuth: true}},
|
{path: '/inventory', component: Inventory, meta: {requiresAuth: true}},
|
||||||
{path: '/inventory/:id', component: InventoryDetail, meta: {requiresAuth: true}},
|
{path: '/inventory/:id', component: InventoryDetail, meta: {requiresAuth: true}, props: true},
|
||||||
{path: '/inventory/:id/edit', component: InventoryEdit, meta: {requiresAuth: true}},
|
{path: '/inventory/:id/edit', component: InventoryEdit, meta: {requiresAuth: true}, props: true},
|
||||||
{path: '/inventory/new', component: InventoryNew, meta: {requiresAuth: true}},
|
{path: '/inventory/new', component: InventoryNew, meta: {requiresAuth: true}},
|
||||||
{path: '/friends', component: Friends, meta: {requiresAuth: true}},
|
{path: '/friends', component: Friends, meta: {requiresAuth: true}},
|
||||||
{path: '/search/:query', component: Search, meta: {requiresAuth: true}},
|
{path: '/search/:query', component: Search, meta: {requiresAuth: true}, props: true},
|
||||||
{path: '/login', component: Login, meta: {requiresAuth: false}},
|
{path: '/login', component: Login, meta: {requiresAuth: false}},
|
||||||
{path: '/register', component: Register, meta: {requiresAuth: false}},
|
{path: '/register', component: Register, meta: {requiresAuth: false}},
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {createStore} from 'vuex';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import FallBackResolver from "@/dns";
|
import FallBackResolver from "@/dns";
|
||||||
import NeighborsCache from "@/neigbors";
|
import NeighborsCache from "@/neigbors";
|
||||||
import {createSignAuth, createTokenAuth, createNullAuth, ServerSet} from "@/federation";
|
import {createSignAuth, createTokenAuth, createNullAuth, ServerSet, ServerSetUnion} from "@/federation";
|
||||||
|
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
|
@ -50,26 +50,21 @@ export default createStore({
|
||||||
localStorage.setItem('remember', remember);
|
localStorage.setItem('remember', remember);
|
||||||
},
|
},
|
||||||
setInventoryItems(state, {url, items}) {
|
setInventoryItems(state, {url, items}) {
|
||||||
console.log('setInventoryItems', url, items)
|
|
||||||
state.item_map[url] = items;
|
state.item_map[url] = items;
|
||||||
},
|
},
|
||||||
setFriends(state, friends) {
|
setFriends(state, friends) {
|
||||||
state.friends = friends;
|
state.friends = friends;
|
||||||
},
|
},
|
||||||
setHomeServers(state, home_servers) {
|
setHomeServers(state, home_servers) {
|
||||||
console.log('setHomeServer', home_servers)
|
|
||||||
state.home_servers = home_servers;
|
state.home_servers = home_servers;
|
||||||
},
|
},
|
||||||
setAllFriendsServers(state, servers) {
|
setAllFriendsServers(state, servers) {
|
||||||
console.log('setAllFriendsServers', servers)
|
|
||||||
state.all_friends_servers = servers;
|
state.all_friends_servers = servers;
|
||||||
},
|
},
|
||||||
setTags(state, tags) {
|
setTags(state, tags) {
|
||||||
console.log('setTags', tags)
|
|
||||||
state.tags = tags;
|
state.tags = tags;
|
||||||
},
|
},
|
||||||
setProperties(state, properties) {
|
setProperties(state, properties) {
|
||||||
console.log('setProperties', properties)
|
|
||||||
state.properties = properties;
|
state.properties = properties;
|
||||||
},
|
},
|
||||||
logout(state) {
|
logout(state) {
|
||||||
|
@ -131,7 +126,7 @@ export default createStore({
|
||||||
if (domain === 'localhost')
|
if (domain === 'localhost')
|
||||||
return ['127.0.0.1:8000'];
|
return ['127.0.0.1:8000'];
|
||||||
if (domain === 'example.com')
|
if (domain === 'example.com')
|
||||||
return ['10.23.42.128:8000'];
|
return ['10.23.42.128:8000','10.23.42.128:8000'];
|
||||||
if (domain === 'example.jedi')
|
if (domain === 'example.jedi')
|
||||||
return ['10.23.42.128:8000'];
|
return ['10.23.42.128:8000'];
|
||||||
if (domain === 'example2.com')
|
if (domain === 'example2.com')
|
||||||
|
@ -149,13 +144,14 @@ export default createStore({
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
async getAllFriendsServers({state, dispatch, commit}) {
|
async getAllFriendsServers({state, dispatch, commit}) {
|
||||||
|
const friends = await dispatch('fetchFriends')
|
||||||
if (state.all_friends_servers)
|
if (state.all_friends_servers)
|
||||||
return state.all_friends_servers
|
return state.all_friends_servers
|
||||||
const promise = (async () => {
|
const promise = (async () => {
|
||||||
const servers = new ServerSet([], state.unreachable_neighbors)
|
const servers = new ServerSetUnion([])
|
||||||
for (const friend of state.friends) {
|
for (const friend of friends) {
|
||||||
const s = await dispatch('lookupServer', {username: friend})
|
const s = await dispatch('lookupServer', {username: friend.username})
|
||||||
servers.add(s)
|
servers.add(new ServerSet(s, state.unreachable_neighbors))
|
||||||
}
|
}
|
||||||
return servers
|
return servers
|
||||||
})()
|
})()
|
||||||
|
@ -177,31 +173,24 @@ export default createStore({
|
||||||
},
|
},
|
||||||
async createInventoryItem({state, dispatch, getters}, item) {
|
async createInventoryItem({state, dispatch, getters}, item) {
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
const data = {...item, owned_amount: 1, availability_policy: 'friends', category: 'other'}
|
const data = {availability_policy: 'friends', category: 'other', ...item}
|
||||||
return await servers.post(getters.signAuth, '/api/inventory_items/', data)
|
return await servers.post(getters.signAuth, '/api/inventory_items/', data)
|
||||||
},
|
},
|
||||||
async updateInventoryItem({state, dispatch, getters}, item) {
|
async updateInventoryItem({state, dispatch, getters}, item) {
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
return await servers.patch(getters.signAuth, '/api/inventory_items/' + item.id + '/', item)
|
const data = {availability_policy: 'friends', category: 'other', ...item}
|
||||||
|
return await servers.patch(getters.signAuth, '/api/inventory_items/' + item.id + '/', data)
|
||||||
},
|
},
|
||||||
async deleteInventoryItem({state, dispatch, getters}, item) {
|
async deleteInventoryItem({state, dispatch, getters}, item) {
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
return await servers.delete(getters.signAuth, '/api/inventory_items/' + item.id + '/')
|
const ret = await servers.delete(getters.signAuth, '/api/inventory_items/' + item.id + '/')
|
||||||
|
dispatch('fetchInventoryItems')
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
async fetchSearchResults({state, dispatch, getters}, {query}) {
|
||||||
|
const servers = await dispatch('getAllFriendsServers')
|
||||||
|
return await servers.get(getters.signAuth, '/api/search/?query=' + query)
|
||||||
},
|
},
|
||||||
/*async searchInventoryItems() {
|
|
||||||
try {
|
|
||||||
const servers = await this.fetchFriends().then(friends => friends.map(friend => this.lookupServer({username: friend.name})))
|
|
||||||
const urls = servers.map(server => server.then(s => {
|
|
||||||
return {host: s, target: "/api/inventory_items/"}
|
|
||||||
}))
|
|
||||||
urls.map(url => url.then(u => this.apiFederatedGet(u).then(items => {
|
|
||||||
this.setInventoryItems({url: u.domain, items})
|
|
||||||
}).catch(e => {
|
|
||||||
}))) // TODO: handle error
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
},*/
|
|
||||||
async fetchFriends({commit, dispatch, getters, state}) {
|
async fetchFriends({commit, dispatch, getters, state}) {
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
const data = await servers.get(getters.signAuth, '/api/friends/')
|
const data = await servers.get(getters.signAuth, '/api/friends/')
|
||||||
|
@ -252,7 +241,7 @@ export default createStore({
|
||||||
console.log('declining friend ' + args)
|
console.log('declining friend ' + args)
|
||||||
},
|
},
|
||||||
async fetchTags({state, commit, dispatch, getters}) {
|
async fetchTags({state, commit, dispatch, getters}) {
|
||||||
if(state.last_load.tags > Date.now() - 1000 * 60 * 60 * 24) {
|
if (state.last_load.tags > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
return state.tags
|
return state.tags
|
||||||
}
|
}
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
|
@ -262,7 +251,7 @@ export default createStore({
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
async fetchProperties({state, commit, dispatch, getters}) {
|
async fetchProperties({state, commit, dispatch, getters}) {
|
||||||
if(state.last_load.properties > Date.now() - 1000 * 60 * 60 * 24) {
|
if (state.last_load.properties > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
return state.properties
|
return state.properties
|
||||||
}
|
}
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
|
@ -270,6 +259,47 @@ export default createStore({
|
||||||
commit('setProperties', data)
|
commit('setProperties', data)
|
||||||
state.last_load.properties = Date.now()
|
state.last_load.properties = Date.now()
|
||||||
return data
|
return data
|
||||||
|
},
|
||||||
|
async fetchCategories({state, commit, dispatch, getters}) {
|
||||||
|
if (state.last_load.categories > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
|
return state.categories
|
||||||
|
}
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/categories/')
|
||||||
|
commit('setCategories', data)
|
||||||
|
state.last_load.categories = Date.now()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
async fetchAvailabilityPolicies({state, commit, dispatch, getters}) {
|
||||||
|
if (state.last_load.availability_policies > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
|
return state.availability_policies
|
||||||
|
}
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/availability_policies/')
|
||||||
|
commit('setAvailabilityPolicies', data)
|
||||||
|
state.last_load.availability_policies = Date.now()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
async fetchInfo({state, commit, dispatch, getters}) {
|
||||||
|
const last_load_info = Math.min(
|
||||||
|
state.last_load.tags,
|
||||||
|
state.last_load.properties,
|
||||||
|
state.last_load.categories,
|
||||||
|
state.last_load.availability_policies)
|
||||||
|
if (last_load_info > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
|
return state.info
|
||||||
|
}
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/info/')
|
||||||
|
commit('setTags', data.tags)
|
||||||
|
commit('setProperties', data.properties)
|
||||||
|
commit('setCategories', data.categories)
|
||||||
|
commit('setAvailabilityPolicies', data.availability_policies)
|
||||||
|
state.last_load.tags = Date.now()
|
||||||
|
state.last_load.properties = Date.now()
|
||||||
|
state.last_load.categories = Date.now()
|
||||||
|
state.last_load.availability_policies = Date.now()
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -287,11 +317,9 @@ export default createStore({
|
||||||
return state.user !== null && state.token !== null;
|
return state.user !== null && state.token !== null;
|
||||||
},
|
},
|
||||||
signAuth(state) {
|
signAuth(state) {
|
||||||
console.log('signAuth', state.user, state.keypair.signSk)
|
|
||||||
return createSignAuth(state.user, state.keypair.signSk)
|
return createSignAuth(state.user, state.keypair.signSk)
|
||||||
},
|
},
|
||||||
tokenAuth(state) {
|
tokenAuth(state) {
|
||||||
console.log('tokenAuth', state.token)
|
|
||||||
return createTokenAuth(state.token)
|
return createTokenAuth(state.token)
|
||||||
},
|
},
|
||||||
nullAuth(state) {
|
nullAuth(state) {
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
|
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.owner }}</td>
|
<td>{{ item.owner }}</td>
|
||||||
<td class="d-none d-md-table-cell">{{ item.owned_amount }}</td>
|
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
|
||||||
<td class="table-action">
|
<td class="table-action">
|
||||||
<router-link :to="`/inventory/${item.id}/edit`">
|
<router-link :to="`/inventory/${item.id}/edit`">
|
||||||
<b-icon-pencil-square></b-icon-pencil-square>
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a :href="`/inventory/${item.id}/delete`" @click.prevent="deleteItem(item.id)">
|
<a :href="`/inventory/${item.id}/delete`" @click.prevent="deleteInventoryItem(item)">
|
||||||
<b-icon-trash></b-icon-trash>
|
<b-icon-trash></b-icon-trash>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -61,13 +61,10 @@ export default {
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["inventory_items"]),
|
...mapGetters(["inventory_items"])
|
||||||
username() {
|
|
||||||
return this.$route.params.username
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(["fetchInventoryItems"]),
|
...mapActions(["fetchInventoryItems", "deleteInventoryItem"]),
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.fetchInventoryItems()
|
await this.fetchInventoryItems()
|
||||||
|
|
|
@ -4,49 +4,49 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Edit Item</div>
|
<div class="card-header">{{ item.name }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
{{ item.description }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tags" class="form-label">Tags</label>
|
||||||
<span class="badge bg-dark" v-for="(tag, index) in item.tags" :key="index">
|
<span class="badge bg-dark" v-for="(tag, index) in item.tags" :key="index">
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
<label for="property" class="form-label">Properties</label>
|
||||||
<span class="badge bg-dark" v-for="(property, index) in item.properties" :key="index">
|
<span class="badge bg-dark" v-for="(property, index) in item.properties" :key="index">
|
||||||
{{ property.name }}={{ property.value }}
|
{{ property.name }}={{ property.value }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name"
|
|
||||||
placeholder="Enter item name" v-model="item.name">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="description" class="form-label">Description</label>
|
|
||||||
<textarea class="form-control" id="description" name="description"
|
|
||||||
placeholder="Enter description" v-model="item.description"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="quantity" class="form-label">Quantity</label>
|
<label for="quantity" class="form-label">Quantity</label>
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
{{ item.owned_quantity }}
|
||||||
placeholder="Enter quantity" v-model="item.quantity">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<!-- TODO -->
|
||||||
<label for="price" class="form-label">Price</label>
|
<!--div class="mb-3">
|
||||||
<input type="number" class="form-control" id="price" name="price"
|
|
||||||
placeholder="Enter price" v-model="item.price">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="image" class="form-label">Image</label>
|
<label for="image" class="form-label">Image</label>
|
||||||
<input type="text" class="form-control" id="image" name="image"
|
<input type="text" class="form-control" id="image" name="image"
|
||||||
placeholder="Enter image" v-model="item.image">
|
placeholder="Enter image" v-model="item.image">
|
||||||
</div>
|
</div-->
|
||||||
</div>
|
</div>
|
||||||
<!--div class="card">
|
|
||||||
<button type="submit" class="btn btn-primary" @click="updateInventoryItem(item)">Update
|
|
||||||
</button>
|
|
||||||
</div-->
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- actions -->
|
||||||
|
<div class="card">
|
||||||
|
<a class="btn btn-primary" :href="'/inventory/' + id + '/edit'">
|
||||||
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-danger"
|
||||||
|
@click="deleteInventoryItem(item).then(() => $router.push('/inventory'))">
|
||||||
|
<b-icon-trash></b-icon-trash>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -58,27 +58,33 @@ import * as BIcons from "bootstrap-icons-vue";
|
||||||
import BaseLayout from "@/components/BaseLayout.vue";
|
import BaseLayout from "@/components/BaseLayout.vue";
|
||||||
import TagField from "@/components/TagField.vue";
|
import TagField from "@/components/TagField.vue";
|
||||||
import PropertyField from "@/components/PropertyField.vue";
|
import PropertyField from "@/components/PropertyField.vue";
|
||||||
|
import {mapActions, mapGetters} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "InventoryDetail",
|
name: "InventoryDetail",
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
item: {
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
quantity: 0,
|
|
||||||
price: 0,
|
|
||||||
image: "",
|
|
||||||
tags: [],
|
|
||||||
properties: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
PropertyField, TagField,
|
PropertyField, TagField,
|
||||||
BaseLayout,
|
BaseLayout,
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["inventory_items"]),
|
||||||
|
item() {
|
||||||
|
return this.inventory_items.find(item => item.id === parseInt(this.id)) || {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchInventoryItems", "deleteInventoryItem"])
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.fetchInventoryItems()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,16 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Edit Item</div>
|
<div class="card-header">Edit Item</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name"
|
||||||
|
placeholder="Enter item name" v-model="item.name">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description"
|
||||||
|
placeholder="Enter description" v-model="item.description"></textarea>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="tag in item.tags" :key="tag">
|
<li v-for="tag in item.tags" :key="tag">
|
||||||
|
@ -24,30 +34,10 @@
|
||||||
<label for="property" class="form-label">Property</label>
|
<label for="property" class="form-label">Property</label>
|
||||||
<property-field :value="item.properties"></property-field>
|
<property-field :value="item.properties"></property-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name"
|
|
||||||
placeholder="Enter item name" v-model="item.name">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="description" class="form-label">Description</label>
|
|
||||||
<textarea class="form-control" id="description" name="description"
|
|
||||||
placeholder="Enter description" v-model="item.description"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="quantity" class="form-label">Quantity</label>
|
<label for="quantity" class="form-label">Quantity</label>
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
<input type="number" class="form-control" id="quantity" name="quantity"
|
||||||
placeholder="Enter quantity" v-model="item.quantity">
|
placeholder="Enter quantity" v-model="item.owned_quantity">
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="price" class="form-label">Price</label>
|
|
||||||
<input type="number" class="form-control" id="price" name="price"
|
|
||||||
placeholder="Enter price" v-model="item.price">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="image" class="form-label">Image</label>
|
|
||||||
<input type="text" class="form-control" id="image" name="image"
|
|
||||||
placeholder="Enter image" v-model="item.image">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -63,35 +53,45 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as BIcons from "bootstrap-icons-vue";
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
import {mapActions} from "vuex";
|
import {mapActions, mapGetters} from "vuex";
|
||||||
import BaseLayout from "@/components/BaseLayout.vue";
|
import BaseLayout from "@/components/BaseLayout.vue";
|
||||||
import TagField from "@/components/TagField.vue";
|
import TagField from "@/components/TagField.vue";
|
||||||
import PropertyField from "@/components/PropertyField.vue";
|
import PropertyField from "@/components/PropertyField.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "InventoryEdit",
|
name: "InventoryEdit",
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
item: {
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
quantity: 0,
|
|
||||||
price: 0,
|
|
||||||
image: "",
|
|
||||||
tags: [],
|
|
||||||
properties: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
BaseLayout,
|
BaseLayout,
|
||||||
TagField,
|
TagField,
|
||||||
PropertyField,
|
PropertyField,
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
methods: {
|
props: {
|
||||||
...mapActions(["updateInventoryItem"]),
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["inventory_items"]),
|
||||||
|
item() {
|
||||||
|
return {
|
||||||
|
tags: [],
|
||||||
|
properties: [],
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
owned_quantity: 0,
|
||||||
|
image: "",
|
||||||
|
...this.inventory_items.find(item => item.id === parseInt(this.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchInventoryItems", "updateInventoryItem"])
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.fetchInventoryItems()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,16 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Create New Item</div>
|
<div class="card-header">Create New Item</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name"
|
||||||
|
placeholder="Enter item name" v-model="item.name">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description"
|
||||||
|
placeholder="Enter description" v-model="item.description"></textarea>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="tag in item.tags" :key="tag">
|
<li v-for="tag in item.tags" :key="tag">
|
||||||
|
@ -24,34 +34,21 @@
|
||||||
<label for="property" class="form-label">Property</label>
|
<label for="property" class="form-label">Property</label>
|
||||||
<property-field :value="item.properties"></property-field>
|
<property-field :value="item.properties"></property-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name"
|
|
||||||
placeholder="Enter item name" v-model="item.name">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="description" class="form-label">Description</label>
|
|
||||||
<textarea class="form-control" id="description" name="description"
|
|
||||||
placeholder="Enter description" v-model="item.description"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="quantity" class="form-label">Quantity</label>
|
<label for="quantity" class="form-label">Quantity</label>
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
<input type="number" class="form-control" id="quantity" name="quantity"
|
||||||
placeholder="Enter quantity" v-model="item.quantity">
|
placeholder="Enter quantity" v-model="item.owned_quantity">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<!-- TODO -->
|
||||||
<label for="price" class="form-label">Price</label>
|
<!--div class="mb-3">
|
||||||
<input type="number" class="form-control" id="price" name="price"
|
|
||||||
placeholder="Enter price" v-model="item.price">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="image" class="form-label">Image</label>
|
<label for="image" class="form-label">Image</label>
|
||||||
<input type="text" class="form-control" id="image" name="image"
|
<input type="text" class="form-control" id="image" name="image"
|
||||||
placeholder="Enter image" v-model="item.image">
|
placeholder="Enter image" v-model="item.image">
|
||||||
</div>
|
</div-->
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<button type="submit" class="btn btn-primary" @click="createInventoryItem(item)">Add
|
<button type="submit" class="btn btn-primary"
|
||||||
|
@click="createInventoryItem(item).then(() => $router.push('/inventory'))">Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,25 +67,24 @@ import PropertyField from "@/components/PropertyField.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "InventoryNew",
|
name: "InventoryNew",
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
item: {
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
quantity: 0,
|
|
||||||
price: 0,
|
|
||||||
image: "",
|
|
||||||
tags: [],
|
|
||||||
properties: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
BaseLayout,
|
BaseLayout,
|
||||||
TagField,
|
TagField,
|
||||||
PropertyField,
|
PropertyField,
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
item: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
owned_quantity: 0,
|
||||||
|
image: "",
|
||||||
|
tags: [],
|
||||||
|
properties: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['createInventoryItem'])
|
...mapActions(['createInventoryItem'])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,48 @@
|
||||||
<template>
|
<template>
|
||||||
<BaseLayout>
|
<BaseLayout hide-search>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<div class="container-fluid p-0">
|
<div class="container-fluid p-0">
|
||||||
<h1 class="h3 mb-3">Blank Page</h1>
|
<h1 class="h3 mb-3">Search Inventories</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card">
|
||||||
|
<SearchBox @change="filterResults" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Empty card</h5>
|
<h5 class="card-title mb-0">Results for "{{ query }}"</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="logo">
|
<table class="table table-striped">
|
||||||
<img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo">
|
<thead>
|
||||||
</div>
|
<tr>
|
||||||
|
<th style="width:40%;">Name</th>
|
||||||
|
<th style="width:25%">Owner</th>
|
||||||
|
<th class="d-none d-md-table-cell" style="width:25%">Amount</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in search_results" :key="item.id">
|
||||||
|
<td>
|
||||||
|
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.owner }}</td>
|
||||||
|
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
|
||||||
|
<td class="table-action">
|
||||||
|
<!--<router-link :to="`/inventory/${item.id}/edit`">
|
||||||
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
|
</router-link>
|
||||||
|
<a :href="`/inventory/${item.id}/delete`"
|
||||||
|
@click.prevent="deleteInventoryItem(item)">
|
||||||
|
<b-icon-trash></b-icon-trash>
|
||||||
|
</a>-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,16 +53,40 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters, mapMutations} from 'vuex';
|
import {mapActions} from 'vuex';
|
||||||
import * as BIcons from "bootstrap-icons-vue";
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
import BaseLayout from "@/components/BaseLayout.vue";
|
import BaseLayout from "@/components/BaseLayout.vue";
|
||||||
|
import SearchBox from "@/components/SearchBox.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Search',
|
name: 'Search',
|
||||||
components: {
|
components: {
|
||||||
|
SearchBox,
|
||||||
...BIcons,
|
...BIcons,
|
||||||
BaseLayout
|
BaseLayout
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
query: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
search_results: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['fetchSearchResults']),
|
||||||
|
filterResults(query) {
|
||||||
|
this.localQuery = query;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.fetchSearchResults({query: this.query}).then((results) => {
|
||||||
|
this.search_results = results;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue