@axium/storage 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +1 -1
- package/dist/server.js +16 -15
- package/lib/List.svelte +7 -42
- package/lib/Usage.svelte +1 -3
- package/package.json +3 -2
- package/routes/files/+layout.svelte +31 -20
- package/routes/files/+layout.ts +8 -2
- package/routes/files/+page.svelte +4 -0
- package/routes/files/[id]/+page.svelte +5 -1
- package/routes/files/shared/+page.svelte +11 -41
- package/routes/files/trash/+page.svelte +11 -41
package/dist/plugin.d.ts
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -56,7 +56,7 @@ async function clean(opt) {
|
|
|
56
56
|
const nDaysAgo = new Date(Date.now() - 86400000 * config.storage.trash_duration);
|
|
57
57
|
await database
|
|
58
58
|
.deleteFrom('storage')
|
|
59
|
-
.where('trashedAt', '
|
|
59
|
+
.where('trashedAt', 'is not', null)
|
|
60
60
|
.where('trashedAt', '<', nDaysAgo)
|
|
61
61
|
.executeTakeFirstOrThrow()
|
|
62
62
|
.then(done);
|
package/dist/server.js
CHANGED
|
@@ -157,7 +157,7 @@ addRoute({
|
|
|
157
157
|
const items = await database
|
|
158
158
|
.selectFrom('storage')
|
|
159
159
|
.where('parentId', '=', itemId)
|
|
160
|
-
.where('trashedAt', '
|
|
160
|
+
.where('trashedAt', 'is not', null)
|
|
161
161
|
.selectAll()
|
|
162
162
|
.execute();
|
|
163
163
|
return items.map(parseItem);
|
|
@@ -326,7 +326,7 @@ addRoute({
|
|
|
326
326
|
const userId = event.params.id;
|
|
327
327
|
await checkAuthForUser(event, userId);
|
|
328
328
|
const [items, usage, limits] = await Promise.all([
|
|
329
|
-
database.selectFrom('storage').where('userId', '=', userId).where('trashedAt', '
|
|
329
|
+
database.selectFrom('storage').where('userId', '=', userId).where('trashedAt', 'is not', null).selectAll().execute(),
|
|
330
330
|
currentUsage(userId),
|
|
331
331
|
getLimits(userId),
|
|
332
332
|
]).catch(withError('Could not fetch data'));
|
|
@@ -344,7 +344,7 @@ addRoute({
|
|
|
344
344
|
const items = await database
|
|
345
345
|
.selectFrom('storage')
|
|
346
346
|
.where('userId', '=', userId)
|
|
347
|
-
.where('trashedAt', '
|
|
347
|
+
.where('trashedAt', 'is not', null)
|
|
348
348
|
.where('parentId', '=', null)
|
|
349
349
|
.selectAll()
|
|
350
350
|
.execute()
|
|
@@ -352,6 +352,13 @@ addRoute({
|
|
|
352
352
|
return items.map(parseItem);
|
|
353
353
|
},
|
|
354
354
|
});
|
|
355
|
+
function existsInACL(column, userId) {
|
|
356
|
+
return (eb) => eb.exists(eb
|
|
357
|
+
.selectFrom('acl.storage')
|
|
358
|
+
.whereRef('itemId', '=', `item.${column}`)
|
|
359
|
+
.where('userId', '=', userId)
|
|
360
|
+
.where('permission', '!=', Permission.None));
|
|
361
|
+
}
|
|
355
362
|
addRoute({
|
|
356
363
|
path: '/api/users/:id/storage/shared',
|
|
357
364
|
params: { id: z.uuid() },
|
|
@@ -361,17 +368,11 @@ addRoute({
|
|
|
361
368
|
const userId = event.params.id;
|
|
362
369
|
await checkAuthForUser(event, userId);
|
|
363
370
|
const items = await database
|
|
364
|
-
.selectFrom('storage')
|
|
365
|
-
.
|
|
366
|
-
.where(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.where('userId', '=', userId)
|
|
370
|
-
.where('permission', '!=', Permission.None));
|
|
371
|
-
// Exclude items that are in a directory shared with the user
|
|
372
|
-
return and([existsInAcl('id'), not(existsInAcl('parentId'))]);
|
|
373
|
-
})
|
|
374
|
-
.selectAll()
|
|
371
|
+
.selectFrom('storage as item')
|
|
372
|
+
.selectAll('item')
|
|
373
|
+
.where('trashedAt', 'is not', null)
|
|
374
|
+
.where(existsInACL('id', userId))
|
|
375
|
+
.where(eb => eb.not(existsInACL('parentId', userId)))
|
|
375
376
|
.execute()
|
|
376
377
|
.catch(withError('Could not get storage items'));
|
|
377
378
|
return items.map(parseItem);
|
|
@@ -388,7 +389,7 @@ addRoute({
|
|
|
388
389
|
const items = await database
|
|
389
390
|
.selectFrom('storage')
|
|
390
391
|
.where('userId', '=', userId)
|
|
391
|
-
.where('trashedAt', '
|
|
392
|
+
.where('trashedAt', 'is not', null)
|
|
392
393
|
.selectAll()
|
|
393
394
|
.execute()
|
|
394
395
|
.catch(withError('Could not get trash'));
|
package/lib/List.svelte
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { FormDialog, Icon } from '@axium/server/components';
|
|
5
5
|
import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
6
6
|
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
7
|
+
import '../styles/list.css';
|
|
7
8
|
|
|
8
9
|
let { items = $bindable([]), appMode }: { appMode?: boolean; items: StorageItemMetadata[] } = $props();
|
|
9
10
|
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
{/snippet}
|
|
36
37
|
|
|
37
38
|
{#snippet _item(item: StorageItemMetadata, i: number)}
|
|
38
|
-
<div class="
|
|
39
|
+
<div class="list-item">
|
|
39
40
|
<dfn title={item.type}><Icon i={iconForMime(item.type)} /></dfn>
|
|
40
41
|
<span class="name">{item.name}</span>
|
|
41
42
|
<span>{item.modifiedAt.toLocaleString()}</span>
|
|
@@ -46,8 +47,8 @@
|
|
|
46
47
|
</div>
|
|
47
48
|
{/snippet}
|
|
48
49
|
|
|
49
|
-
<div class="
|
|
50
|
-
<div class="
|
|
50
|
+
<div class="list">
|
|
51
|
+
<div class="list-item list-header">
|
|
51
52
|
<span></span>
|
|
52
53
|
<span>Name</span>
|
|
53
54
|
<span>Last Modified</span>
|
|
@@ -55,12 +56,12 @@
|
|
|
55
56
|
</div>
|
|
56
57
|
{#each items as item, i (item.id)}
|
|
57
58
|
{#if item.type == 'inode/directory' && appMode}
|
|
58
|
-
<a class="
|
|
59
|
+
<a class="list-item-container" href="/files/{item.id}">{@render _item(item, i)}</a>
|
|
59
60
|
{:else}
|
|
60
61
|
{@render _item(item, i)}
|
|
61
62
|
{/if}
|
|
62
63
|
{:else}
|
|
63
|
-
<
|
|
64
|
+
<p class="list-empty">Folder is empty.</p>
|
|
64
65
|
{/each}
|
|
65
66
|
</div>
|
|
66
67
|
|
|
@@ -105,47 +106,11 @@
|
|
|
105
106
|
</FormDialog>
|
|
106
107
|
|
|
107
108
|
<style>
|
|
108
|
-
.
|
|
109
|
-
display: flex;
|
|
110
|
-
flex-direction: column;
|
|
111
|
-
padding: 0.5em;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.StorageListItem.SL_header {
|
|
115
|
-
font-weight: bold;
|
|
116
|
-
border-bottom: 1px solid #bbc;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.StorageListItem-container {
|
|
120
|
-
text-decoration: none;
|
|
121
|
-
color: inherit;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.StorageListItem {
|
|
109
|
+
.list-item {
|
|
125
110
|
display: grid;
|
|
126
111
|
grid-template-columns: 1em 4fr 15em 5em repeat(3, 1em);
|
|
127
112
|
align-items: center;
|
|
128
113
|
gap: 0.5em;
|
|
129
114
|
padding: 0.5em 0;
|
|
130
115
|
}
|
|
131
|
-
|
|
132
|
-
.StorageListItem:not(:last-child) {
|
|
133
|
-
border-bottom: 1px solid #bbc;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.StorageListItem:not(.SL_header):hover {
|
|
137
|
-
background-color: #7777;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.action {
|
|
141
|
-
visibility: hidden;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.StorageListItem:hover .action {
|
|
145
|
-
visibility: visible;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.action:hover {
|
|
149
|
-
cursor: pointer;
|
|
150
|
-
}
|
|
151
116
|
</style>
|
package/lib/Usage.svelte
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
const { userId, info }: { userId: string; info?: UserStorageInfo } = $props();
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
|
-
{#await info || getUserStorageInfo(userId)}
|
|
11
|
-
<p>Loading...</p>
|
|
12
|
-
{:then info}
|
|
10
|
+
{#await info || getUserStorageInfo(userId) then info}
|
|
13
11
|
<p>
|
|
14
12
|
<NumberBar
|
|
15
13
|
max={info.limits.user_size * 1_000_000}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/storage",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
|
|
5
5
|
"description": "User file storage for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"./*": "./dist/*.js",
|
|
25
25
|
"./sidebar": "./lib/sidebar.svelte.js",
|
|
26
26
|
"./components": "./lib/index.js",
|
|
27
|
-
"./components/*": "./lib/*.svelte"
|
|
27
|
+
"./components/*": "./lib/*.svelte",
|
|
28
|
+
"./styles/*": "./styles/*.css"
|
|
28
29
|
},
|
|
29
30
|
"routes": "routes",
|
|
30
31
|
"files": [
|
|
@@ -1,58 +1,69 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Icon } from '@axium/server/components';
|
|
3
|
-
import { capitalize } from 'utilium';
|
|
4
3
|
import { StorageUsage } from '@axium/storage/components';
|
|
5
|
-
import
|
|
6
|
-
import type { LayoutProps
|
|
7
|
-
|
|
8
|
-
type SidebarTab = 'files' | 'trash' | 'shared' | 'usage';
|
|
4
|
+
import { capitalize } from 'utilium';
|
|
5
|
+
import type { LayoutProps } from './$types';
|
|
9
6
|
|
|
10
7
|
let { children, data }: LayoutProps = $props();
|
|
11
8
|
</script>
|
|
12
9
|
|
|
13
|
-
{#snippet tab(text: string, i: string, href: LayoutRouteId, isDefault: boolean = false)}
|
|
14
|
-
<a class={['item', data.route.id == href || (data.route.id == '/files/[id]' && isDefault)]} {href}><Icon {i} />{capitalize(text)}</a>
|
|
15
|
-
{/snippet}
|
|
16
|
-
|
|
17
10
|
<div class="app">
|
|
18
11
|
<div class="sidebar">
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
{
|
|
12
|
+
{#each data.tabs as { href, name, icon: i, active }}
|
|
13
|
+
<a {href} class={['item', active && 'active']}><Icon {i} /> {capitalize(name)}</a>
|
|
14
|
+
{/each}
|
|
22
15
|
|
|
23
16
|
<div class="usage">
|
|
24
17
|
<StorageUsage userId={data.session.userId} />
|
|
25
18
|
</div>
|
|
26
19
|
</div>
|
|
27
20
|
|
|
28
|
-
<div class="content">
|
|
21
|
+
<div class="files-content">
|
|
29
22
|
{@render children()}
|
|
30
23
|
</div>
|
|
31
24
|
</div>
|
|
32
25
|
|
|
33
26
|
<style>
|
|
27
|
+
.app {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-template-columns: 15em 1fr;
|
|
30
|
+
height: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
34
33
|
.sidebar {
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
grid-column: 1;
|
|
35
|
+
width: 100%;
|
|
36
|
+
display: inline-flex;
|
|
37
37
|
flex-direction: column;
|
|
38
38
|
gap: 0.5em;
|
|
39
|
+
padding-left: 1em;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
.item {
|
|
42
|
-
padding: 0.5em;
|
|
43
|
-
border-radius: 0.25em;
|
|
43
|
+
padding: 0.3em 0.5em;
|
|
44
|
+
border-radius: 0.25em 1em 1em 0.25em;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 1em;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
.item:hover {
|
|
47
|
-
background-color: #
|
|
51
|
+
background-color: #333;
|
|
48
52
|
cursor: pointer;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
.item.active {
|
|
52
|
-
background-color: #
|
|
56
|
+
background-color: #334;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
.usage {
|
|
56
|
-
|
|
60
|
+
margin-top: auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.files-content {
|
|
64
|
+
grid-column: 2;
|
|
65
|
+
padding: 1em;
|
|
66
|
+
overflow-x: hidden;
|
|
67
|
+
overflow-y: scroll;
|
|
57
68
|
}
|
|
58
69
|
</style>
|
package/routes/files/+layout.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getCurrentSession } from '@axium/client/user';
|
|
2
2
|
import { redirect } from '@sveltejs/kit';
|
|
3
|
-
import type { LayoutLoadEvent } from './$types';
|
|
3
|
+
import type { LayoutLoadEvent, LayoutRouteId } from './$types';
|
|
4
4
|
|
|
5
5
|
export const ssr = false;
|
|
6
6
|
|
|
@@ -9,8 +9,14 @@ export async function load({ url, route }: LayoutLoadEvent) {
|
|
|
9
9
|
|
|
10
10
|
if (!session) redirect(307, '/login?after=' + url.pathname);
|
|
11
11
|
|
|
12
|
+
const tabs = [
|
|
13
|
+
{ name: 'files', href: '/files', icon: 'folders', active: route.id.endsWith('/files/[id]') || route.id.endsWith('/files') },
|
|
14
|
+
{ name: 'trash', href: '/files/trash', icon: 'trash', active: route.id.endsWith('/files/trash') },
|
|
15
|
+
{ name: 'shared', href: '/files/shared', icon: 'user-group', active: route.id.endsWith('/files/shared') },
|
|
16
|
+
] satisfies { name: string; href: LayoutRouteId; icon: string; active: boolean }[];
|
|
17
|
+
|
|
12
18
|
return {
|
|
13
19
|
session: await getCurrentSession(),
|
|
14
|
-
|
|
20
|
+
tabs,
|
|
15
21
|
};
|
|
16
22
|
}
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
const item = $state(data.item);
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
+
<svelte:head>
|
|
13
|
+
<title>Files - Preview - {item.name}</title>
|
|
14
|
+
</svelte:head>
|
|
15
|
+
|
|
12
16
|
{#if item.trashedAt}
|
|
13
17
|
<p>This item is trashed</p>
|
|
14
18
|
<button
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
<Icon i="trash-can-undo" /> Restore
|
|
21
25
|
</button>
|
|
22
26
|
{:else if item.type == 'inode/directory'}
|
|
23
|
-
<StorageList items={data.items!} />
|
|
27
|
+
<StorageList appMode bind:items={data.items!} />
|
|
24
28
|
{:else}
|
|
25
29
|
<p>No preview available.</p>
|
|
26
30
|
{/if}
|
|
@@ -2,67 +2,37 @@
|
|
|
2
2
|
import { formatBytes } from '@axium/core/format';
|
|
3
3
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
4
4
|
import { Icon } from '@axium/server/components';
|
|
5
|
+
import '@axium/storage/styles/list';
|
|
5
6
|
import type { PageProps } from './$types';
|
|
6
7
|
|
|
7
8
|
const { data }: PageProps = $props();
|
|
8
9
|
</script>
|
|
9
10
|
|
|
10
|
-
<
|
|
11
|
-
<
|
|
11
|
+
<svelte:head>
|
|
12
|
+
<title>Files - Shared With You</title>
|
|
13
|
+
</svelte:head>
|
|
14
|
+
|
|
15
|
+
<div class="list">
|
|
16
|
+
<div class="list-item list-header">
|
|
12
17
|
<span></span>
|
|
13
18
|
<span>Name</span>
|
|
14
19
|
<span>Last Modified</span>
|
|
15
20
|
<span>Size</span>
|
|
16
21
|
</div>
|
|
17
22
|
{#each data.items as item, i (item.id)}
|
|
18
|
-
<div class="
|
|
23
|
+
<div class="list-item">
|
|
19
24
|
<dfn title={item.type}><Icon i={iconForMime(item.type)} /></dfn>
|
|
20
25
|
<span class="name">{item.name}</span>
|
|
21
26
|
<span>{item.modifiedAt.toLocaleString()}</span>
|
|
22
27
|
<span>{formatBytes(item.size)}</span>
|
|
23
28
|
</div>
|
|
24
29
|
{:else}
|
|
25
|
-
<
|
|
30
|
+
<p class="list-empty">No items have been shared with you.</p>
|
|
26
31
|
{/each}
|
|
27
32
|
</div>
|
|
28
33
|
|
|
29
34
|
<style>
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
flex-direction: column;
|
|
33
|
-
padding: 0.5em;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.SharedItem.list-header {
|
|
37
|
-
font-weight: bold;
|
|
38
|
-
border-bottom: 1px solid #bbc;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.SharedItem {
|
|
42
|
-
display: grid;
|
|
43
|
-
grid-template-columns: 1em 4fr 15em 5em;
|
|
44
|
-
align-items: center;
|
|
45
|
-
gap: 0.5em;
|
|
46
|
-
padding: 0.5em 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.SharedItem:not(:last-child) {
|
|
50
|
-
border-bottom: 1px solid #bbc;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.SharedItem:not(.list-header):hover {
|
|
54
|
-
background-color: #7777;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.action {
|
|
58
|
-
visibility: hidden;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.SharedItem:hover .action {
|
|
62
|
-
visibility: visible;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.action:hover {
|
|
66
|
-
cursor: pointer;
|
|
35
|
+
.list-item {
|
|
36
|
+
grid-template-columns: 1em 4fr 15em 5em !important;
|
|
67
37
|
}
|
|
68
38
|
</style>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
4
4
|
import { FormDialog, Icon } from '@axium/server/components';
|
|
5
5
|
import { deleteItem } from '@axium/storage/client';
|
|
6
|
+
import '@axium/storage/styles/list';
|
|
6
7
|
import type { PageProps } from './$types';
|
|
7
8
|
|
|
8
9
|
const { data }: PageProps = $props();
|
|
@@ -13,15 +14,19 @@
|
|
|
13
14
|
const activeItem = $derived(items[activeIndex]);
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
|
-
<
|
|
17
|
-
<
|
|
17
|
+
<svelte:head>
|
|
18
|
+
<title>Files - Trash</title>
|
|
19
|
+
</svelte:head>
|
|
20
|
+
|
|
21
|
+
<div class="list">
|
|
22
|
+
<div class="list-item list-header">
|
|
18
23
|
<span></span>
|
|
19
24
|
<span>Name</span>
|
|
20
25
|
<span>Last Modified</span>
|
|
21
26
|
<span>Size</span>
|
|
22
27
|
</div>
|
|
23
28
|
{#each items as item, i (item.id)}
|
|
24
|
-
<div class="
|
|
29
|
+
<div class="list-item">
|
|
25
30
|
<dfn title={item.type}><Icon i={iconForMime(item.type)} /></dfn>
|
|
26
31
|
<span class="name">{item.name}</span>
|
|
27
32
|
<span>{item.modifiedAt.toLocaleString()}</span>
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
</span>
|
|
40
45
|
</div>
|
|
41
46
|
{:else}
|
|
42
|
-
<
|
|
47
|
+
<p class="list-empty">Trash is empty.</p>
|
|
43
48
|
{/each}
|
|
44
49
|
</div>
|
|
45
50
|
|
|
@@ -61,42 +66,7 @@
|
|
|
61
66
|
</FormDialog>
|
|
62
67
|
|
|
63
68
|
<style>
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
flex-direction: column;
|
|
67
|
-
padding: 0.5em;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.TrashItem.list-header {
|
|
71
|
-
font-weight: bold;
|
|
72
|
-
border-bottom: 1px solid #bbc;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.TrashItem {
|
|
76
|
-
display: grid;
|
|
77
|
-
grid-template-columns: 1em 4fr 15em 5em 1em;
|
|
78
|
-
align-items: center;
|
|
79
|
-
gap: 0.5em;
|
|
80
|
-
padding: 0.5em 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.TrashItem:not(:last-child) {
|
|
84
|
-
border-bottom: 1px solid #bbc;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.TrashItem:not(.list-header):hover {
|
|
88
|
-
background-color: #7777;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.action {
|
|
92
|
-
visibility: hidden;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.TrashItem:hover .action {
|
|
96
|
-
visibility: visible;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.action:hover {
|
|
100
|
-
cursor: pointer;
|
|
69
|
+
.list-item {
|
|
70
|
+
grid-template-columns: 1em 4fr 15em 5em 1em !important;
|
|
101
71
|
}
|
|
102
72
|
</style>
|