Skip to content

Commit

Permalink
WIP, update Florian
Browse files Browse the repository at this point in the history
- Dashboard
     edit table name and emoji
     make edit view and delete view usable
     remove hover-effects
- add delete table modal
- add delete view modal (was inline before)
- cleanup modals structure
- rename some "dashboard" to "Table" names
- add routes and methods to update tables (title && emoji)
- no routing after update view settings by default
- fix typos

Signed-off-by: Florian Steffens <florian.steffens@nextcloud.com>
  • Loading branch information
Florian Steffens committed Aug 4, 2023
1 parent 548624d commit 28556f7
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 63 deletions.
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
['name' => 'table#index', 'url' => '/table', 'verb' => 'GET'],
['name' => 'table#show', 'url' => '/table/{id}', 'verb' => 'GET'],
['name' => 'table#create', 'url' => '/table', 'verb' => 'POST'],
['name' => 'table#update', 'url' => '/table/{id}', 'verb' => 'PUT'],
['name' => 'table#destroy', 'url' => '/table/{id}', 'verb' => 'DELETE'],

// view
Expand Down
9 changes: 9 additions & 0 deletions lib/Controller/TableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,13 @@ public function destroy(int $id): DataResponse {
return $this->service->delete($id);
});
}

/**
* @NoAdminRequired
*/
public function update(int $id, string $title = null, string $emoji = null): DataResponse {
return $this->handleError(function () use ($id, $title, $emoji) {
return $this->service->update($id, $title, $emoji, $this->userId);
});
}
}
6 changes: 2 additions & 4 deletions lib/Db/ColumnTypes/SuperColumnQB.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ private function buildSQLString(string $operator, string $formattedCellValue, st
}
}

private function getFormattedDataCellValue(string $columnPlaceHolder): string
{
private function getFormattedDataCellValue(string $columnPlaceHolder): string {
$cellValue = 'JSON_EXTRACT(data, CONCAT( JSON_UNQUOTE(JSON_SEARCH(JSON_EXTRACT(data, \'$[*].columnId\'), \'one\', :'.$columnPlaceHolder.')), \'.value\'))';
return $this->formatCellValue($cellValue);
}
Expand All @@ -87,8 +86,7 @@ private function getFormattedDataCellValue(string $columnPlaceHolder): string
* @return string
* @throws InternalError
*/
private function getFormattedMetaDataCellValue(int $metaId): string
{
private function getFormattedMetaDataCellValue(int $metaId): string {
switch($metaId) {
case -1: return 'id';
case -2: return 'created_by';
Expand Down
19 changes: 19 additions & 0 deletions lib/Service/PermissionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ public function preCheckUserId(string $userId = null, bool $canBeEmpty = true):

// ***** TABLES permissions *****

/**
* @param Table $table
* @param string|null $userId
* @return bool
*/
public function canUpdateTable(Table $table, ?string $userId = null): bool {
try {
$userId = $this->preCheckUserId($userId);
} catch (InternalError $e) {
return false;
}

if ($userId === '') {
return true;
}

return $this->canManageTable($table, $userId);
}

public function canAccessView($view, ?string $userId = null): bool {
if($this->basisCheck($view, 'view', $userId)) {
return true;
Expand Down
38 changes: 38 additions & 0 deletions lib/Service/TableService.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,44 @@ public function delete(int $id, ?string $userId = null): Table {
}
}

/**
* @noinspection PhpUndefinedMethodInspection
*
* @param int $id $userId
* @param string|null $title
* @param string|null $emoji
* @param string|null $userId
* @return Table
* @throws InternalError
*/
public function update(int $id, ?string $title, ?string $emoji, ?string $userId = null): Table {
$userId = $this->permissionsService->preCheckUserId($userId);

try {
$table = $this->mapper->find($id);

// security
if (!$this->permissionsService->canUpdateTable($table, $userId)) {
throw new PermissionError('PermissionError: can not update table with id '.$id);
}

$time = new DateTime();
if ($title !== null) {
$table->setTitle($title);
}
if ($emoji !== null) {
$table->setEmoji($emoji);
}
$table->setLastEditBy($userId);
$table->setLastEditAt($time->format('Y-m-d H:i:s'));
$table = $this->mapper->update($table);
$this->enhanceTable($table, $userId);
return $table;
} catch (Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError($e->getMessage());
}
}

// PRIVATE FUNCTIONS ---------------------------------------------------------------

Expand Down
94 changes: 76 additions & 18 deletions src/modules/main/sections/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,37 @@
<tbody>
<tr>
<td>{{ t('tables', 'Title') }}</td>
<td>{{ table.title }}</td>
</tr>
<tr>
<td>{{ t('tables', 'Emoji') }}</td>
<td>{{ table.emoji }}</td>
<td>
<div v-if="tableTitle === null" class="inline">
<NcEmojiPicker :close-on-select="true" @select="emoji => updateTableEmoji(emoji)">
<NcButton type="tertiary"
:aria-label="t('tables', 'Select emoji for table')"
:title="t('tables', 'Select emoji')"
@click.prevent>
{{ table.emoji ? table.emoji : '...' }}
</NcButton>
</NcEmojiPicker>
{{ table.title }}&nbsp;&nbsp;
<NcButton type="tertiary" :aria-label="t('tables', 'Edit table title')" @click="startEditingTableTitle">
<template #icon>
<IconRename :size="20" />
</template>
</NcButton>
</div>
<div v-else class="inline">
<input ref="tableTitle" v-model="tableTitle" maxlength="200">&nbsp;&nbsp;
<NcButton type="primary" :aria-label="t('tables', 'Save')" @click="updateTableTitle">
<template #icon>
<IconCheck :size="20" />
</template>
</NcButton>&nbsp;&nbsp;
<NcButton icon="icon-close" type="tertiary" :aria-label="t('tables', 'Close')" @click="tableTitle = null">
<template #icon>
<IconClose :size="20" />
</template>
</NcButton>
</div>
</td>
</tr>
<tr>
<td>{{ t('tables', 'Created at') }}</td>
Expand All @@ -72,10 +98,6 @@
<td>{{ t('tables', 'Table ID') }}</td>
<td>{{ table.id }}</td>
</tr>
<tr>
<td>{{ t('tables', 'Is shared with you') }}</td>
<td>{{ table.isShared }}</td>
</tr>
<tr>
<td>{{ t('tables', 'Shares') }}</td>
<td>
Expand Down Expand Up @@ -150,15 +172,17 @@
<NcButton v-if="canManageElement(table)"
type="secondary"
:aria-label="t('tables', 'Edit view')"
:close-after-click="true">
:close-after-click="true"
@click="emit('tables:view:edit', { view })">
<template #icon>
<PlaylistEditIcon :size="20" />
</template>
</NcButton>
<NcButton v-if="canManageElement(table)"
type="error"
:aria-label="t('tables', 'Delete view')"
:close-after-click="true">
:close-after-click="true"
@click="emit('tables:view:delete', view)">
<template #icon>
<Delete :size="20" />
</template>
Expand Down Expand Up @@ -188,16 +212,23 @@ import Delete from 'vue-material-design-icons/Delete.vue'
import Moment from '@nextcloud/moment'
import IconImport from 'vue-material-design-icons/Import.vue'
import Creation from 'vue-material-design-icons/Creation.vue'
import IconRename from 'vue-material-design-icons/Rename.vue'
import IconClose from 'vue-material-design-icons/Close.vue'
import IconCheck from 'vue-material-design-icons/Check.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import displayError from '../../../shared/utils/displayError.js'
import { NcActionButton, NcActions, NcAvatar, NcButton, NcLoadingIcon } from '@nextcloud/vue'
import { NcActionButton, NcActions, NcAvatar, NcButton, NcLoadingIcon, NcEmojiPicker } from '@nextcloud/vue'
import PlaylistEditIcon from 'vue-material-design-icons/PlaylistEdit.vue'
import LinkIcon from 'vue-material-design-icons/Link.vue'
import { emit } from '@nextcloud/event-bus'
import { showError, showSuccess } from '@nextcloud/dialogs'
export default {
components: {
IconCheck,
IconClose,
IconRename,
NcLoadingIcon,
NcActionButton,
Creation,
Expand All @@ -210,6 +241,7 @@ export default {
PlaylistEditIcon,
LinkIcon,
Delete,
NcEmojiPicker,
},
filters: {
Expand All @@ -233,6 +265,8 @@ export default {
loadingViewShares: true,
viewShares: {},
tableShares: [],
tableTitle: null,
tableEmoji: null,
}
},
computed: {
Expand All @@ -255,8 +289,35 @@ export default {
},
methods: {
deleteTable() {
emit('tables:table:delete', this.table)
startEditingTableTitle() {
this.tableTitle = this.table.title
this.$nextTick(() => {
this.$refs.tableTitle.focus()
})
},
async updateTableEmoji(emoji) {
const res = await this.$store.dispatch('updateTable', { id: this.table.id, data: { title: this.table.title, emoji } })
if (res) {
showSuccess(t('tables', 'Updated table "{emoji}{table}".', { emoji: emoji ? emoji + ' ' : '', table: this.table.title }))
}
},
async updateTableTitle() {
if (this.tableTitle === '' || this.tableTitle === null) {
showError(t('tables', 'Cannot update table. Title is missing.'))
} else {
const res = await this.$store.dispatch('updateTable', { id: this.table.id, data: { title: this.tableTitle, emoji: this.table.emoji } })
if (res) {
showSuccess(t('tables', 'Updated table "{emoji}{table}".', { emoji: this.icon ? this.icon + ' ' : '', table: this.tableTitle }))
this.tableTitle = null
}
}
},
deleteTable(table) {
emit('tables:table:delete', table)
},
async getSharesForViewFromBE(viewId) {
try {
Expand Down Expand Up @@ -305,6 +366,7 @@ export default {
.table td .inline {
display: inline-flex;
align-items: center;
}
.table th,
Expand All @@ -327,10 +389,6 @@ export default {
box-shadow: inset 0 -1px 0 var(--color-border);
}
.table tr:hover, .table tr:hover td {
background-color: var(--color-background-dark) !important;
}
.dashboard-content {
padding: calc(var(--default-grid-baseline) * 4);
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/modals/DeleteTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default {
computed: {
...mapGetters(['activeElement', 'isView']),
getTranslatedDescription() {
return t('tables', 'Do you really want to delete the table "{table}"?', { table: this.table?.title })
return t('tables', 'Do you really want to delete the table "{table}"? This will also delete all data, views and shares that are connected to this table.', { table: this.table?.title })
},
},
methods: {
Expand Down
58 changes: 58 additions & 0 deletions src/modules/modals/DeleteView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div>
<DialogConfirmation :description="getTranslatedDescription"
:title="t('tables', 'Confirm view deletion')"
:cancel-title="t('tables', 'Cancel')"
:confirm-title="t('tables', 'Delete')"
confirm-class="error"
:show-modal="showModal"
@confirm="deleteMe"
@cancel="$emit('cancel')" />
</div>
</template>

<script>
import DialogConfirmation from '../../shared/modals/DialogConfirmation.vue'
import { showSuccess } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/dist/index.css'
import { mapGetters } from 'vuex'
export default {
components: {
DialogConfirmation,
},
props: {
view: {
type: Object,
default: null,
},
showModal: {
type: Boolean,
default: false,
},
},
computed: {
...mapGetters(['activeView', 'isView']),
getTranslatedDescription() {
return t('tables', 'Do you really want to delete the view "{view}"?', { view: this.view?.title })
},
},
methods: {
async deleteMe() {
const viewId = this.view.id
const activeViewId = this.activeView?.id
const res = await this.$store.dispatch('removeView', { viewId: this.view.id })
if (res) {
showSuccess(t('tables', 'View "{emoji}{view}" removed.', { emoji: this.view.emoji ? this.view.emoji + ' ' : '', view: this.view.title }))
// if the actual view was deleted, go to startpage
if (viewId === activeViewId) {
await this.$router.push('/').catch(err => err)
}
this.$emit('cancel')
}
},
},
}
</script>
Loading

0 comments on commit 28556f7

Please sign in to comment.