Skip to content

Commit a726e47

Browse files
committed
feat: Public sharing via link
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent 9f86cfc commit a726e47

14 files changed

Lines changed: 955 additions & 26 deletions

File tree

‎lib/Controller/PageController.php‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
namespace OCA\Tables\Controller;
99

10+
use InvalidArgumentException;
1011
use OCA\Tables\AppInfo\Application;
12+
use OCA\Tables\Errors\NotFoundError;
13+
use OCA\Tables\Service\ShareService;
1114
use OCA\Tables\Service\ValueObject\ShareToken;
1215
use OCA\Text\Event\LoadEditor;
1316
use OCA\Viewer\Event\LoadViewer;
@@ -20,6 +23,7 @@
2023
use OCP\AppFramework\Http\TemplateResponse;
2124
use OCP\AppFramework\Services\IInitialState;
2225
use OCP\EventDispatcher\IEventDispatcher;
26+
use OCP\IL10N;
2327
use OCP\INavigationManager;
2428
use OCP\IRequest;
2529
use OCP\Util;
@@ -31,6 +35,8 @@ public function __construct(
3135
protected IEventDispatcher $eventDispatcher,
3236
protected INavigationManager $navigationManager,
3337
protected IInitialState $initialState,
38+
protected ShareService $shareService,
39+
protected IL10N $l,
3440
) {
3541
parent::__construct(Application::APP_ID, $request);
3642
}
@@ -78,13 +84,30 @@ public function context(int $contextId): TemplateResponse {
7884
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
7985
#[FrontpageRoute(verb: 'GET', url: '/s/{token}')]
8086
public function linkShare(string $token): TemplateResponse {
87+
try {
88+
$shareToken = new ShareToken($token);
89+
$this->shareService->findByToken($shareToken);
90+
} catch (InvalidArgumentException|NotFoundError $e) {
91+
92+
return new TemplateResponse(
93+
Application::APP_ID,
94+
'error',
95+
['message' => $this->l->t('This share does not exist or is no longer available')],
96+
TemplateResponse::RENDER_AS_PUBLIC
97+
);
98+
}
99+
81100
Util::addScript(Application::APP_ID, 'tables-main');
82101
$this->loadStyles();
83102

84-
$shareToken = new ShareToken($token);
85103
$this->initialState->provideInitialState('shareToken', (string)$shareToken);
86104

87-
return new TemplateResponse(Application::APP_ID, 'main', [], TemplateResponse::RENDER_AS_GUEST);
105+
106+
if (class_exists(LoadEditor::class)) {
107+
$this->eventDispatcher->dispatchTyped(new LoadEditor());
108+
}
109+
110+
return new TemplateResponse(Application::APP_ID, 'main', [], TemplateResponse::RENDER_AS_PUBLIC);
88111
}
89112

90113
protected function loadStyles(): void {

‎src/App.vue‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
-->
55
<template>
66
<NcContent app-name="tables">
7-
<Navigation />
7+
<Navigation v-if="!isPublicShare" />
88
<NcAppContent>
99
<div v-if="isLoadingSomething" class="icon-loading" />
1010

11-
<router-view v-if="!isLoadingSomething" />
11+
<PublicTableView v-if="isPublicShare && !isLoadingSomething" :token="shareToken" />
12+
<router-view v-else-if="!isLoadingSomething" />
1213
</NcAppContent>
13-
<Sidebar />
14+
<Sidebar v-if="!isPublicShare" />
1415
</NcContent>
1516
</template>
1617

1718
<script>
1819
import { NcContent, NcAppContent } from '@nextcloud/vue'
1920
import Navigation from './modules/navigation/sections/Navigation.vue'
21+
import PublicTableView from './pages/PublicTableView.vue'
2022
import { mapState, mapActions } from 'pinia'
2123
import Sidebar from './modules/sidebar/sections/Sidebar.vue'
2224
import { useResizeObserver } from '@vueuse/core'
@@ -27,6 +29,7 @@ import { generateUrl } from '@nextcloud/router'
2729
export default {
2830
name: 'App',
2931
components: {
32+
PublicTableView,
3033
Sidebar,
3134
NcContent,
3235
NcAppContent,
@@ -46,6 +49,12 @@ export default {
4649
},
4750
computed: {
4851
...mapState(useTablesStore, ['isLoadingSomething', 'activeView', 'activeTable', 'activeContext']),
52+
isPublicShare() {
53+
return !!this.shareToken
54+
},
55+
shareToken() {
56+
return loadState('tables', 'shareToken', false)
57+
},
4958
},
5059
watch: {
5160
'$route'(to) {
@@ -54,12 +63,18 @@ export default {
5463
},
5564
async created() {
5665
const store = useTablesStore()
57-
await Promise.all([
58-
store.loadTablesFromBE(),
59-
store.getAllContexts(),
60-
store.loadViewsSharedWithMeFromBE(),
61-
store.loadTemplatesFromBE(),
62-
])
66+
if (!this.isPublicShare) {
67+
await Promise.all([
68+
store.loadTablesFromBE(),
69+
store.getAllContexts(),
70+
store.loadViewsSharedWithMeFromBE(),
71+
store.loadTemplatesFromBE(),
72+
])
73+
} else {
74+
store.setLoading({ key: 'tables', value: false })
75+
store.setLoading({ key: 'viewsShared', value: false })
76+
store.setLoading({ key: 'contexts', value: false })
77+
}
6378
this.routing(this.$router.currentRoute)
6479
this.observeAppContent()
6580
},

‎src/modules/main/sections/Dashboard.vue‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ import { NcActionButton, NcActions, NcAvatar, NcButton, NcLoadingIcon } from '@n
129129
import PlaylistEditIcon from 'vue-material-design-icons/PlaylistEdit.vue'
130130
import { emit } from '@nextcloud/event-bus'
131131
import { showError, showSuccess } from '@nextcloud/dialogs'
132+
import { isPublicLinkShare } from '../../../shared/utils/shareUtils.js'
132133
133134
export default {
134135
components: {
@@ -263,18 +264,19 @@ export default {
263264
async loadShares() {
264265
// load shares for table
265266
this.loadingTableShares = true
266-
this.tableShares = await this.getSharesForTableFromBE(this.table.id)
267+
const allTableShares = await this.getSharesForTableFromBE(this.table.id)
268+
this.tableShares = allTableShares.filter(s => !isPublicLinkShare(s))
267269
this.loadingTableShares = false
268270
269271
// load shares for all views
270272
this.loadingViewShares = true
271273
for (const index in this.table.views) {
272274
const view = this.table.views[index]
273275
if (view.hasShares) {
274-
this.viewShares[view.id] = await this.getSharesForViewFromBE(view.id)
276+
const allViewShares = await this.getSharesForViewFromBE(view.id)
277+
this.viewShares[view.id] = allViewShares.filter(s => !isPublicLinkShare(s))
275278
}
276279
}
277-
this.tableShares = await this.getSharesForTableFromBE(this.table.id)
278280
this.loadingViewShares = false
279281
},
280282
},
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<div class="public-table-wrapper">
7+
<div v-if="loading" class="icon-loading" />
8+
9+
<div v-else>
10+
<PublicView
11+
:view="mockView"
12+
:columns="columns"
13+
:rows="rows"
14+
@download-csv="downloadCSV" />
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script>
20+
import { mapActions, storeToRefs } from 'pinia'
21+
import PublicView from './PublicView.vue'
22+
import exportTableMixin from '../../../shared/components/ncTable/mixins/exportTableMixin.js'
23+
import { useDataStore } from '../../../store/data.js'
24+
import { computed } from 'vue'
25+
26+
export default {
27+
name: 'PublicMainWrapper',
28+
29+
components: {
30+
PublicView,
31+
},
32+
33+
mixins: [exportTableMixin],
34+
35+
props: {
36+
token: {
37+
type: String,
38+
required: true,
39+
},
40+
},
41+
42+
setup(props) {
43+
const store = useDataStore()
44+
const { getColumns, getRows } = storeToRefs(store)
45+
46+
const stateKey = 'public-' + props.token
47+
const rows = computed(() => getRows.value(false, stateKey))
48+
const columns = computed(() => getColumns.value(false, stateKey))
49+
50+
return { rows, columns }
51+
},
52+
53+
data() {
54+
return {
55+
loading: false,
56+
mockView: {
57+
id: 'public',
58+
title: 'Public Share',
59+
description: '',
60+
isShared: true,
61+
onSharePermissions: {
62+
read: true,
63+
manage: false,
64+
share: false,
65+
update: false,
66+
create: false,
67+
delete: false,
68+
},
69+
permissionDelete: false,
70+
permissionManage: false,
71+
permissionShare: false,
72+
permissionUpdate: false,
73+
permissionCreate: false,
74+
},
75+
}
76+
},
77+
78+
beforeMount() {
79+
this.loadData()
80+
},
81+
82+
methods: {
83+
...mapActions(useDataStore, ['loadPublicColumnsFromBE', 'loadPublicRowsFromBE']),
84+
85+
async loadData() {
86+
this.loading = true
87+
try {
88+
await Promise.all([
89+
this.loadPublicColumnsFromBE({ token: this.token }),
90+
this.loadPublicRowsFromBE({ token: this.token }),
91+
])
92+
} catch (e) {
93+
console.error('Error loading public data', e)
94+
} finally {
95+
this.loading = false
96+
}
97+
},
98+
99+
downloadCSV() {
100+
this.downloadCsv(this.rows, this.columns, 'public-export')
101+
},
102+
},
103+
}
104+
</script>
105+
106+
<style scoped lang="scss">
107+
.public-table-wrapper {
108+
width: 100%;
109+
height: 100%;
110+
111+
:deep(.tables-list__table) {
112+
margin-top: 60px;
113+
}
114+
}
115+
</style>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<div class="table-wrapper">
7+
<EmptyView v-if="columns.length === 0" :view="view" />
8+
<TableView v-else
9+
:rows="rows"
10+
:columns="columns"
11+
:element="view"
12+
:can-read-rows="true"
13+
:can-create-rows="false"
14+
:can-edit-rows="false"
15+
:can-delete-rows="false"
16+
:can-create-columns="false"
17+
:can-edit-columns="false"
18+
:can-delete-columns="false"
19+
:can-delete-table="false">
20+
<template #actions>
21+
<NcActions :force-menu="true" type="tertiary">
22+
<NcActionButton :close-after-click="true"
23+
icon="icon-download"
24+
@click="$emit('download-csv')">
25+
{{ t('tables', 'Export as CSV') }}
26+
</NcActionButton>
27+
</NcActions>
28+
</template>
29+
</TableView>
30+
</div>
31+
</template>
32+
33+
<script>
34+
import TableView from '../partials/TableView.vue'
35+
import EmptyView from './EmptyView.vue'
36+
import { NcActions, NcActionButton } from '@nextcloud/vue'
37+
38+
export default {
39+
name: 'PublicView',
40+
components: {
41+
EmptyView,
42+
TableView,
43+
NcActions,
44+
NcActionButton,
45+
},
46+
47+
props: {
48+
view: {
49+
type: Object,
50+
default: null,
51+
},
52+
columns: {
53+
type: Array,
54+
default: () => [],
55+
},
56+
rows: {
57+
type: Array,
58+
default: () => [],
59+
},
60+
},
61+
}
62+
</script>

0 commit comments

Comments
 (0)