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">
 | 
			
		||||
                    <i class="hamburger align-self-center"></i>
 | 
			
		||||
                </a>
 | 
			
		||||
                <SearchBox/>
 | 
			
		||||
                <SearchBox v-if="!hideSearch"/>
 | 
			
		||||
                <div class="navbar-collapse collapse">
 | 
			
		||||
                    <ul class="navbar-nav navbar-align">
 | 
			
		||||
                        <Notifications :notifications="notifications"/>
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,14 @@ export default {
 | 
			
		|||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapState(['messages']),
 | 
			
		||||
        ...mapGetters(['notifications'])
 | 
			
		||||
        ...mapGetters(['notifications']),
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
        hideSearch: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: false,
 | 
			
		||||
            default: false
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    async mounted() {
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<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">
 | 
			
		||||
            <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">
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,10 @@ export default {
 | 
			
		|||
    components: {
 | 
			
		||||
        ...BIcons
 | 
			
		||||
    },
 | 
			
		||||
    model: {
 | 
			
		||||
        prop: "query",
 | 
			
		||||
        event: "change"
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            query: ""
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +35,12 @@ export default {
 | 
			
		|||
                this.$refs["search-text"].focus();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        query() {
 | 
			
		||||
            //emit event
 | 
			
		||||
            this.$emit("change", this.query);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        //console.log(this.$route)
 | 
			
		||||
        //console.log(this.$route.params.query)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
    <nav id="sidebar" class="sidebar">
 | 
			
		||||
        <div class="sidebar-content js-simplebar">
 | 
			
		||||
            <router-link to="/" class="sidebar-brand">
 | 
			
		||||
                <!--img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo"-->
 | 
			
		||||
                <span class="align-middle">Toolshed</span>
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <ul class="sidebar-nav">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,14 +6,50 @@ class ServerSet {
 | 
			
		|||
        if (!unreachable_neighbors || typeof unreachable_neighbors.queryUnreachable !== 'function' || typeof unreachable_neighbors.unreachable !== 'function') {
 | 
			
		||||
            throw new Error('no unreachable_neighbors')
 | 
			
		||||
        }
 | 
			
		||||
        this.servers = servers;
 | 
			
		||||
        this.servers = [... new Set(servers)] // deduplicate
 | 
			
		||||
        this.unreachable_neighbors = unreachable_neighbors;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        if (!auth || typeof auth.buildAuthHeader !== 'function') {
 | 
			
		||||
            throw new Error('no auth')
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +68,10 @@ class ServerSet {
 | 
			
		|||
                    },
 | 
			
		||||
                    credentials: 'omit',
 | 
			
		||||
                    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())
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error('post to server failed', server, e)
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +98,10 @@ class ServerSet {
 | 
			
		|||
                    },
 | 
			
		||||
                    credentials: 'omit',
 | 
			
		||||
                    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())
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error('patch to server failed', server, e)
 | 
			
		||||
| 
						 | 
				
			
			@ -68,56 +110,6 @@ class ServerSet {
 | 
			
		|||
        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) {
 | 
			
		||||
        if (!auth || typeof auth.buildAuthHeader !== 'function') {
 | 
			
		||||
            throw new Error('no auth')
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +128,10 @@ class ServerSet {
 | 
			
		|||
                    },
 | 
			
		||||
                    credentials: 'omit',
 | 
			
		||||
                    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())
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error('put to server failed', server, e)
 | 
			
		||||
| 
						 | 
				
			
			@ -144,8 +139,107 @@ class ServerSet {
 | 
			
		|||
        }
 | 
			
		||||
        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 {
 | 
			
		||||
    constructor(method, auth) {
 | 
			
		||||
        this.method = method;
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +261,6 @@ function createSignAuth(username, signKey) {
 | 
			
		|||
    return new authMethod(({signKey, username}, {url, data}) => {
 | 
			
		||||
        const json = JSON.stringify(data)
 | 
			
		||||
        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)}
 | 
			
		||||
    }, 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: '/settings', component: Settings, meta: {requiresAuth: true}},
 | 
			
		||||
    {path: '/inventory', component: Inventory, meta: {requiresAuth: true}},
 | 
			
		||||
    {path: '/inventory/:id', component: InventoryDetail, meta: {requiresAuth: true}},
 | 
			
		||||
    {path: '/inventory/:id/edit', component: InventoryEdit, meta: {requiresAuth: true}},
 | 
			
		||||
    {path: '/inventory/:id', component: InventoryDetail, meta: {requiresAuth: true}, props: true},
 | 
			
		||||
    {path: '/inventory/:id/edit', component: InventoryEdit, meta: {requiresAuth: true}, props: true},
 | 
			
		||||
    {path: '/inventory/new', component: InventoryNew, 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: '/register', component: Register, meta: {requiresAuth: false}},
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import {createStore} from 'vuex';
 | 
			
		|||
import router from '@/router';
 | 
			
		||||
import FallBackResolver from "@/dns";
 | 
			
		||||
import NeighborsCache from "@/neigbors";
 | 
			
		||||
import {createSignAuth, createTokenAuth, createNullAuth, ServerSet} from "@/federation";
 | 
			
		||||
import {createSignAuth, createTokenAuth, createNullAuth, ServerSet, ServerSetUnion} from "@/federation";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default createStore({
 | 
			
		||||
| 
						 | 
				
			
			@ -50,26 +50,21 @@ export default createStore({
 | 
			
		|||
            localStorage.setItem('remember', remember);
 | 
			
		||||
        },
 | 
			
		||||
        setInventoryItems(state, {url, items}) {
 | 
			
		||||
            console.log('setInventoryItems', url, items)
 | 
			
		||||
            state.item_map[url] = items;
 | 
			
		||||
        },
 | 
			
		||||
        setFriends(state, friends) {
 | 
			
		||||
            state.friends = friends;
 | 
			
		||||
        },
 | 
			
		||||
        setHomeServers(state, home_servers) {
 | 
			
		||||
            console.log('setHomeServer', home_servers)
 | 
			
		||||
            state.home_servers = home_servers;
 | 
			
		||||
        },
 | 
			
		||||
        setAllFriendsServers(state, servers) {
 | 
			
		||||
            console.log('setAllFriendsServers', servers)
 | 
			
		||||
            state.all_friends_servers = servers;
 | 
			
		||||
        },
 | 
			
		||||
        setTags(state, tags) {
 | 
			
		||||
            console.log('setTags', tags)
 | 
			
		||||
            state.tags = tags;
 | 
			
		||||
        },
 | 
			
		||||
        setProperties(state, properties) {
 | 
			
		||||
            console.log('setProperties', properties)
 | 
			
		||||
            state.properties = properties;
 | 
			
		||||
        },
 | 
			
		||||
        logout(state) {
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +126,7 @@ export default createStore({
 | 
			
		|||
            if (domain === 'localhost')
 | 
			
		||||
                return ['127.0.0.1:8000'];
 | 
			
		||||
            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')
 | 
			
		||||
                return ['10.23.42.128:8000'];
 | 
			
		||||
            if (domain === 'example2.com')
 | 
			
		||||
| 
						 | 
				
			
			@ -149,13 +144,14 @@ export default createStore({
 | 
			
		|||
            return promise
 | 
			
		||||
        },
 | 
			
		||||
        async getAllFriendsServers({state, dispatch, commit}) {
 | 
			
		||||
            const friends = await dispatch('fetchFriends')
 | 
			
		||||
            if (state.all_friends_servers)
 | 
			
		||||
                return state.all_friends_servers
 | 
			
		||||
            const promise = (async () => {
 | 
			
		||||
                const servers = new ServerSet([], state.unreachable_neighbors)
 | 
			
		||||
                for (const friend of state.friends) {
 | 
			
		||||
                    const s = await dispatch('lookupServer', {username: friend})
 | 
			
		||||
                    servers.add(s)
 | 
			
		||||
                const servers = new ServerSetUnion([])
 | 
			
		||||
                for (const friend of friends) {
 | 
			
		||||
                    const s = await dispatch('lookupServer', {username: friend.username})
 | 
			
		||||
                    servers.add(new ServerSet(s, state.unreachable_neighbors))
 | 
			
		||||
                }
 | 
			
		||||
                return servers
 | 
			
		||||
            })()
 | 
			
		||||
| 
						 | 
				
			
			@ -177,31 +173,24 @@ export default createStore({
 | 
			
		|||
        },
 | 
			
		||||
        async createInventoryItem({state, dispatch, getters}, item) {
 | 
			
		||||
            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)
 | 
			
		||||
        },
 | 
			
		||||
        async updateInventoryItem({state, dispatch, getters}, item) {
 | 
			
		||||
            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) {
 | 
			
		||||
            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}) {
 | 
			
		||||
            const servers = await dispatch('getHomeServers')
 | 
			
		||||
            const data = await servers.get(getters.signAuth, '/api/friends/')
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +241,7 @@ export default createStore({
 | 
			
		|||
            console.log('declining friend ' + args)
 | 
			
		||||
        },
 | 
			
		||||
        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
 | 
			
		||||
            }
 | 
			
		||||
            const servers = await dispatch('getHomeServers')
 | 
			
		||||
| 
						 | 
				
			
			@ -262,7 +251,7 @@ export default createStore({
 | 
			
		|||
            return data
 | 
			
		||||
        },
 | 
			
		||||
        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
 | 
			
		||||
            }
 | 
			
		||||
            const servers = await dispatch('getHomeServers')
 | 
			
		||||
| 
						 | 
				
			
			@ -270,6 +259,47 @@ export default createStore({
 | 
			
		|||
            commit('setProperties', data)
 | 
			
		||||
            state.last_load.properties = Date.now()
 | 
			
		||||
            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: {
 | 
			
		||||
| 
						 | 
				
			
			@ -287,11 +317,9 @@ export default createStore({
 | 
			
		|||
            return state.user !== null && state.token !== null;
 | 
			
		||||
        },
 | 
			
		||||
        signAuth(state) {
 | 
			
		||||
            console.log('signAuth', state.user, state.keypair.signSk)
 | 
			
		||||
            return createSignAuth(state.user, state.keypair.signSk)
 | 
			
		||||
        },
 | 
			
		||||
        tokenAuth(state) {
 | 
			
		||||
            console.log('tokenAuth', state.token)
 | 
			
		||||
            return createTokenAuth(state.token)
 | 
			
		||||
        },
 | 
			
		||||
        nullAuth(state) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,12 +25,12 @@
 | 
			
		|||
                                        <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_amount }}</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="deleteItem(item.id)">
 | 
			
		||||
                                        <a :href="`/inventory/${item.id}/delete`" @click.prevent="deleteInventoryItem(item)">
 | 
			
		||||
                                            <b-icon-trash></b-icon-trash>
 | 
			
		||||
                                        </a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -61,13 +61,10 @@ export default {
 | 
			
		|||
        ...BIcons
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapGetters(["inventory_items"]),
 | 
			
		||||
        username() {
 | 
			
		||||
            return this.$route.params.username
 | 
			
		||||
        }
 | 
			
		||||
        ...mapGetters(["inventory_items"])
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        ...mapActions(["fetchInventoryItems"]),
 | 
			
		||||
        ...mapActions(["fetchInventoryItems", "deleteInventoryItem"]),
 | 
			
		||||
    },
 | 
			
		||||
    async mounted() {
 | 
			
		||||
        await this.fetchInventoryItems()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,49 +4,49 @@
 | 
			
		|||
            <div class="row">
 | 
			
		||||
                <div class="col">
 | 
			
		||||
                    <div class="card">
 | 
			
		||||
                        <div class="card-header">Edit Item</div>
 | 
			
		||||
                        <div class="card-header">{{ item.name }}</div>
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <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">
 | 
			
		||||
                                    {{ tag }}
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <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">
 | 
			
		||||
                                    {{ property.name }}={{ property.value }}
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </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">
 | 
			
		||||
                                <label for="quantity" class="form-label">Quantity</label>
 | 
			
		||||
                                <input type="number" class="form-control" id="quantity" name="quantity"
 | 
			
		||||
                                       placeholder="Enter quantity" v-model="item.quantity">
 | 
			
		||||
                                {{ 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">
 | 
			
		||||
                            <!-- TODO -->
 | 
			
		||||
                            <!--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 class="card">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary" @click="updateInventoryItem(item)">Update
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </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>
 | 
			
		||||
        </main>
 | 
			
		||||
| 
						 | 
				
			
			@ -58,27 +58,33 @@ import * as BIcons from "bootstrap-icons-vue";
 | 
			
		|||
import BaseLayout from "@/components/BaseLayout.vue";
 | 
			
		||||
import TagField from "@/components/TagField.vue";
 | 
			
		||||
import PropertyField from "@/components/PropertyField.vue";
 | 
			
		||||
import {mapActions, mapGetters} from "vuex";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "InventoryDetail",
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            item: {
 | 
			
		||||
                name: "",
 | 
			
		||||
                description: "",
 | 
			
		||||
                quantity: 0,
 | 
			
		||||
                price: 0,
 | 
			
		||||
                image: "",
 | 
			
		||||
                tags: [],
 | 
			
		||||
                properties: []
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        PropertyField, TagField,
 | 
			
		||||
        BaseLayout,
 | 
			
		||||
        ...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>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,16 @@
 | 
			
		|||
                    <div class="card">
 | 
			
		||||
                        <div class="card-header">Edit Item</div>
 | 
			
		||||
                        <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">
 | 
			
		||||
                                <ul>
 | 
			
		||||
                                    <li v-for="tag in item.tags" :key="tag">
 | 
			
		||||
| 
						 | 
				
			
			@ -24,30 +34,10 @@
 | 
			
		|||
                                <label for="property" class="form-label">Property</label>
 | 
			
		||||
                                <property-field :value="item.properties"></property-field>
 | 
			
		||||
                            </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">
 | 
			
		||||
                                <label for="quantity" class="form-label">Quantity</label>
 | 
			
		||||
                                <input type="number" class="form-control" id="quantity" name="quantity"
 | 
			
		||||
                                       placeholder="Enter quantity" v-model="item.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">
 | 
			
		||||
                                       placeholder="Enter quantity" v-model="item.owned_quantity">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="card">
 | 
			
		||||
| 
						 | 
				
			
			@ -63,35 +53,45 @@
 | 
			
		|||
 | 
			
		||||
<script>
 | 
			
		||||
import * as BIcons from "bootstrap-icons-vue";
 | 
			
		||||
import {mapActions} from "vuex";
 | 
			
		||||
import {mapActions, mapGetters} from "vuex";
 | 
			
		||||
import BaseLayout from "@/components/BaseLayout.vue";
 | 
			
		||||
import TagField from "@/components/TagField.vue";
 | 
			
		||||
import PropertyField from "@/components/PropertyField.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "InventoryEdit",
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            item: {
 | 
			
		||||
                name: "",
 | 
			
		||||
                description: "",
 | 
			
		||||
                quantity: 0,
 | 
			
		||||
                price: 0,
 | 
			
		||||
                image: "",
 | 
			
		||||
                tags: [],
 | 
			
		||||
                properties: []
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        BaseLayout,
 | 
			
		||||
        TagField,
 | 
			
		||||
        PropertyField,
 | 
			
		||||
        ...BIcons
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        ...mapActions(["updateInventoryItem"]),
 | 
			
		||||
    props: {
 | 
			
		||||
        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>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,16 @@
 | 
			
		|||
                    <div class="card">
 | 
			
		||||
                        <div class="card-header">Create New Item</div>
 | 
			
		||||
                        <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">
 | 
			
		||||
                                <ul>
 | 
			
		||||
                                    <li v-for="tag in item.tags" :key="tag">
 | 
			
		||||
| 
						 | 
				
			
			@ -24,34 +34,21 @@
 | 
			
		|||
                                <label for="property" class="form-label">Property</label>
 | 
			
		||||
                                <property-field :value="item.properties"></property-field>
 | 
			
		||||
                            </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">
 | 
			
		||||
                                <label for="quantity" class="form-label">Quantity</label>
 | 
			
		||||
                                <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">
 | 
			
		||||
                            <!-- TODO -->
 | 
			
		||||
                            <!--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 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>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -70,25 +67,24 @@ import PropertyField from "@/components/PropertyField.vue";
 | 
			
		|||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "InventoryNew",
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            item: {
 | 
			
		||||
                name: "",
 | 
			
		||||
                description: "",
 | 
			
		||||
                quantity: 0,
 | 
			
		||||
                price: 0,
 | 
			
		||||
                image: "",
 | 
			
		||||
                tags: [],
 | 
			
		||||
                properties: []
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        BaseLayout,
 | 
			
		||||
        TagField,
 | 
			
		||||
        PropertyField,
 | 
			
		||||
        ...BIcons
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            item: {
 | 
			
		||||
                name: "",
 | 
			
		||||
                description: "",
 | 
			
		||||
                owned_quantity: 0,
 | 
			
		||||
                image: "",
 | 
			
		||||
                tags: [],
 | 
			
		||||
                properties: []
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        ...mapActions(['createInventoryItem'])
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +1,48 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <BaseLayout>
 | 
			
		||||
    <BaseLayout hide-search>
 | 
			
		||||
        <main class="content">
 | 
			
		||||
            <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="col-md-3">
 | 
			
		||||
                        <div class="card">
 | 
			
		||||
                            <SearchBox @change="filterResults" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-12">
 | 
			
		||||
                        <div class="card">
 | 
			
		||||
                            <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 class="card-body">
 | 
			
		||||
                                <div class="logo">
 | 
			
		||||
                                    <img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo">
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <table class="table table-striped">
 | 
			
		||||
                                    <thead>
 | 
			
		||||
                                    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,16 +53,40 @@
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import {mapGetters, mapMutations} from 'vuex';
 | 
			
		||||
import {mapActions} from 'vuex';
 | 
			
		||||
import * as BIcons from "bootstrap-icons-vue";
 | 
			
		||||
import BaseLayout from "@/components/BaseLayout.vue";
 | 
			
		||||
import SearchBox from "@/components/SearchBox.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'Search',
 | 
			
		||||
    components: {
 | 
			
		||||
        SearchBox,
 | 
			
		||||
        ...BIcons,
 | 
			
		||||
        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>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue