Skip to content

Commit d1762f1

Browse files
authored
Merge pull request #61163 from nextcloud/fix/update-all
fix(appstore): bring back "update all" button
2 parents e6b8de4 + 9970d5e commit d1762f1

213 files changed

Lines changed: 381 additions & 183 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<!--
2+
SPDX-License-Identifier: AGPL-3.0-or-later
3+
SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'
8+
9+
import { mdiCheck, mdiInformationOutline, mdiUpdate, mdiWeb } from '@mdi/js'
10+
import { showError } from '@nextcloud/dialogs'
11+
import { getLanguage, t } from '@nextcloud/l10n'
12+
import { NcButton, NcDialog, NcIconSvgWrapper, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
13+
import { computed, ref } from 'vue'
14+
import MarkdownPreview from './MarkdownPreview.vue'
15+
import { useUpdatesStore } from '../store/updates.ts'
16+
import logger from '../utils/logger.ts'
17+
18+
const props = defineProps<{
19+
apps: (IAppstoreApp | IAppstoreExApp)[]
20+
}>()
21+
22+
const emit = defineEmits<{
23+
close: []
24+
}>()
25+
26+
const store = useUpdatesStore()
27+
const showDetails = ref('')
28+
const isUpdating = ref(false)
29+
30+
const changelogText = computed(() => {
31+
if (!showDetails.value) {
32+
return ''
33+
}
34+
35+
const app = props.apps.find((app) => app.id === showDetails.value)
36+
if (!app || !app.releases || app.releases.length === 0) {
37+
return ''
38+
}
39+
40+
const [release] = app.releases
41+
const localizedEntry = release.translations[getLanguage()]
42+
return localizedEntry?.changelog ?? release.translations.en?.changelog ?? ''
43+
})
44+
45+
/**
46+
* Handle update all apps
47+
*/
48+
async function onUpdate() {
49+
isUpdating.value = true
50+
for (const app of props.apps) {
51+
try {
52+
await store.updateApp(app.id)
53+
} catch (error) {
54+
logger.error(`Failed to update app ${app.id}`, { error })
55+
showError(t('appstore', 'Failed to update app {appName}', { appName: app.name }))
56+
}
57+
}
58+
isUpdating.value = false
59+
emit('close')
60+
}
61+
</script>
62+
63+
<template>
64+
<NcDialog :contentClasses="$style.updateAllDialog" size="normal" :name="t('appstore', 'Update all apps')">
65+
<p>{{ t('appstore', 'Are you sure you want to update all apps?') }}</p>
66+
<ul>
67+
<li v-for="app in apps" :key="app.id" :class="$style.updateAllDialog__listEntry">
68+
<div :class="$style.updateAllDialog__listEntryContent">
69+
<div :class="$style.updateAllDialog__listEntryHeading">
70+
<NcIconSvgWrapper
71+
:path="app.update ? mdiUpdate : mdiCheck"
72+
:name="app.update ? undefined : t('appstore', 'Update done')" />
73+
<span :class="$style.updateAllDialog__listEntryName">{{ app.name }} ({{ app.version }} → {{ app.update }})</span>
74+
</div>
75+
<div :class="$style.updateAllDialog__listEntryActions">
76+
<NcButton
77+
v-if="app.website"
78+
:aria-label="t('appstore', 'View website')"
79+
:title="t('appstore', 'View website')"
80+
:href="app.website"
81+
target="_blank"
82+
variant="tertiary">
83+
<template #icon>
84+
<NcIconSvgWrapper :path="mdiWeb" />
85+
</template>
86+
</NcButton>
87+
<NcButton
88+
v-if="app.releases"
89+
:aria-label="t('appstore', 'Show details')"
90+
:title="t('appstore', 'Show details')"
91+
:pressed="showDetails === app.id"
92+
@update:pressed="showDetails = $event ? app.id : ''">
93+
<template #icon>
94+
<NcIconSvgWrapper :path="mdiInformationOutline" />
95+
</template>
96+
</NcButton>
97+
</div>
98+
</div>
99+
</li>
100+
</ul>
101+
102+
<NcNoteCard
103+
:class="$style.updateAllDialog__listEntryDetails"
104+
:heading="t('appstore', 'Details')"
105+
type="info">
106+
<MarkdownPreview
107+
:minHeadingLevel="3"
108+
:text="changelogText" />
109+
</NcNoteCard>
110+
111+
<template #actions>
112+
<NcButton variant="tertiary" @click="emit('close')">
113+
{{ t('appstore', 'Cancel') }}
114+
</NcButton>
115+
<NcButton variant="primary" @click="onUpdate">
116+
<template v-if="isUpdating" #icon>
117+
<NcLoadingIcon />
118+
</template>
119+
{{ t('appstore', 'Update all') }}
120+
</NcButton>
121+
</template>
122+
</NcDialog>
123+
</template>
124+
125+
<style module>
126+
.updateAllDialog {
127+
min-height: 50vh !important;
128+
}
129+
130+
.updateAllDialog__list {
131+
display: flex;
132+
flex-direction: row;
133+
gap: calc(3 * var(--default-grid-baseline));
134+
}
135+
136+
.updateAllDialog__listEntry {
137+
display: flex;
138+
flex-direction: column;
139+
gap: calc(2 * var(--default-grid-baseline));
140+
padding: calc(2 * var(--default-grid-baseline));
141+
}
142+
143+
.updateAllDialog__listEntryHeading {
144+
display: flex;
145+
}
146+
147+
.updateAllDialog__listEntryName {
148+
font-weight: 500;
149+
line-height: var(--default-clickable-area);
150+
}
151+
152+
.updateAllDialog__listEntryActions {
153+
display: flex;
154+
flex-direction: row;
155+
gap: var(--default-grid-baseline);
156+
}
157+
158+
.updateAllDialog__listEntryContent {
159+
display: flex;
160+
justify-content: space-between;
161+
}
162+
163+
.updateAllDialog__listEntryDetails {
164+
margin: 0;
165+
}
166+
</style>

apps/appstore/src/store/updates.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const useUpdatesStore = defineStore('updates', () => {
4949
internalUpdateCount.value = Math.max(internalUpdateCount.value - 1, 0)
5050
}
5151

52+
app.update = undefined
5253
rebuildNavigation()
5354
} catch (error) {
5455
logger.error('Failed to update app', { appId, error })

apps/appstore/src/views/AppstoreManage.vue

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
-->
55

66
<script setup lang="ts">
7+
import { mdiUpdate } from '@mdi/js'
78
import { t } from '@nextcloud/l10n'
8-
import { computed } from 'vue'
9+
import { NcIconSvgWrapper, spawnDialog } from '@nextcloud/vue'
10+
import { computed, defineAsyncComponent } from 'vue'
911
import { useRoute } from 'vue-router'
1012
import NcButton from '@nextcloud/vue/components/NcButton'
1113
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
@@ -15,10 +17,14 @@ import AppTable from '../components/AppTable/AppTable.vue'
1517
import AppToolbar from '../components/AppToolbar.vue'
1618
import { useFilteredApps } from '../composables/useFilteredApps.ts'
1719
import { useAppsStore } from '../store/apps.ts'
20+
import { useUpdatesStore } from '../store/updates.ts'
1821
import { useUserSettingsStore } from '../store/userSettings.ts'
1922
23+
const UpdateAllDialog = defineAsyncComponent(() => import('../components/UpdateAllDialog.vue'))
24+
2025
const route = useRoute()
2126
const store = useAppsStore()
27+
const updatesStore = useUpdatesStore()
2228
const userSettings = useUserSettingsStore()
2329
2430
const currentCategory = computed(() => route.params!.category as 'enabled' | 'installed' | 'disabled' | 'updates')
@@ -30,16 +36,36 @@ const apps = computed(() => {
3036
} else if (currentCategory.value === 'disabled') {
3137
return store.apps.filter((app) => app.installed && !app.active)
3238
} else if (currentCategory.value === 'updates') {
33-
return store.apps.filter((app) => app.update)
39+
return store.apps.filter((app) => app.active && app.update)
3440
}
3541
return []
3642
})
3743
const visibleApps = useFilteredApps(apps)
44+
45+
/**
46+
* Handle update all apps
47+
*/
48+
async function onUpdateAll() {
49+
await spawnDialog(UpdateAllDialog, {
50+
apps: visibleApps.value,
51+
})
52+
}
3853
</script>
3954

4055
<template>
4156
<AppToolbar />
4257

58+
<NcButton
59+
v-if="currentCategory === 'updates' && updatesStore.updateCount > 0"
60+
:class="$style.appstoreManage__updateAllButton"
61+
variant="primary"
62+
@click="onUpdateAll">
63+
<template #icon>
64+
<NcIconSvgWrapper :path="mdiUpdate" />
65+
</template>
66+
{{ t('appstore', 'Update all applications') }}
67+
</NcButton>
68+
4369
<!-- Apps list -->
4470
<NcEmptyContent
4571
v-if="store.isLoadingApps"
@@ -69,4 +95,9 @@ const visibleApps = useFilteredApps(apps)
6995
.appstoreManage {
7096
margin-bottom: var(--body-container-margin);
7197
}
98+
99+
.appstoreManage__updateAllButton {
100+
margin-inline: var(--app-navigation-padding);
101+
margin-block: calc(3 * var(--default-grid-baseline));
102+
}
72103
</style>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
import{a as t}from"./index-DL1yHC1K-BJ_TsAjb.chunk.mjs";import{t as e}from"./translation-DoG5ZELJ-CFYnqluG.chunk.mjs";import{C as m,a}from"./CommentView-DhkE9b-A.chunk.mjs";import{l as p}from"./activity-CgsVnLJG.chunk.mjs";import{b as i,r as s,o as n,c,m as u}from"./Web-BwmPK40i.chunk.mjs";import{_ as l}from"./public-C1mLBHT3.chunk.mjs";import"./index-BW4M2n71.chunk.mjs";import"./NcModal-DUWLRm_F-CU-AeBE5.chunk.mjs";import"./logger-D3RVzcfQ-8mOgKmZ4.chunk.mjs";import"./createElementId-DhjFt1I9-B4kXTdvj.chunk.mjs";import"./index-B-dGqfIG.chunk.mjs";import"./TrashCanOutline-Cv7t-yKN.chunk.mjs";import"./mdi-Lt-19ASw.chunk.mjs";import"./pinia-CLibr4cC.chunk.mjs";import"./PencilOutline-CJ2aSuY5.chunk.mjs";/* empty css */import"./NcAvatar-M3-CbKbq-D3H79LgO.chunk.mjs";import"./index-BLNGy7h5.chunk.mjs";import"./util-Alk1iwuj.chunk.mjs";import"./ArrowRight-DKsMJImQ.chunk.mjs";import"./colors-BDeMBgfq-BQ_6MaUU.chunk.mjs";import"./NcUserStatusIcon-DsviB2Cr-CpKCZ3VO.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BJuPH7S7-B1iF5g9H.chunk.mjs";import"./NcUserBubble-CDQa0hGy-BM5qrjQl.chunk.mjs";import"./GetComments-BEnDgTs6.chunk.mjs";import"./index-BYnFfAmz.chunk.mjs";const d=i({components:{Comment:a},mixins:[m],props:{reloadCallback:{type:Function,required:!0}},methods:{onNewComment(){try{this.reloadCallback()}catch(o){t(e("comments","Could not reload comments")),p.error("Could not reload comments",{error:o})}}}});function C(o,f,y,w,D,N){const r=s("Comment");return n(),c(r,u(o.editorData,{autoComplete:o.autoComplete,resourceType:o.resourceType,editor:!0,userData:o.userData,resourceId:o.resourceId,class:"comments-action",onNew:o.onNewComment}),null,16,["autoComplete","resourceType","userData","resourceId","onNew"])}const S=l(d,[["render",C],["__scopeId","data-v-29a1e244"]]);export{S as default};
2-
//# sourceMappingURL=ActivityCommentAction-DcqrOWtA.chunk.mjs.map
1+
import{a as t}from"./index-DL1yHC1K-qIl2eChL.chunk.mjs";import{t as e}from"./translation-DoG5ZELJ-CFYnqluG.chunk.mjs";import{C as m,a}from"./CommentView-SZy04tfJ.chunk.mjs";import{l as p}from"./activity-CgsVnLJG.chunk.mjs";import{b as i,r as s,o as n,c,m as u}from"./Web-BwmPK40i.chunk.mjs";import{_ as l}from"./public-C1mLBHT3.chunk.mjs";import"./index-DnfkOsH1.chunk.mjs";import"./NcModal-DUWLRm_F-BYHFKrmn.chunk.mjs";import"./logger-D3RVzcfQ-8mOgKmZ4.chunk.mjs";import"./createElementId-DhjFt1I9-B4kXTdvj.chunk.mjs";import"./index-B-dGqfIG.chunk.mjs";import"./TrashCanOutline-Cv7t-yKN.chunk.mjs";import"./mdi-CnS18KqQ.chunk.mjs";import"./pinia-CLibr4cC.chunk.mjs";import"./PencilOutline-Bhf17C3C.chunk.mjs";/* empty css */import"./NcAvatar-M3-CbKbq-BRvAvY0H.chunk.mjs";import"./index-BLNGy7h5.chunk.mjs";import"./util-Alk1iwuj.chunk.mjs";import"./ArrowRight-DKsMJImQ.chunk.mjs";import"./colors-BDeMBgfq-BQ_6MaUU.chunk.mjs";import"./NcUserStatusIcon-DsviB2Cr-CpKCZ3VO.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BJuPH7S7-B8lchl7f.chunk.mjs";import"./NcUserBubble-CDQa0hGy-BTuZy5Jo.chunk.mjs";import"./GetComments-BEnDgTs6.chunk.mjs";import"./index-BYnFfAmz.chunk.mjs";const d=i({components:{Comment:a},mixins:[m],props:{reloadCallback:{type:Function,required:!0}},methods:{onNewComment(){try{this.reloadCallback()}catch(o){t(e("comments","Could not reload comments")),p.error("Could not reload comments",{error:o})}}}});function C(o,f,y,w,D,N){const r=s("Comment");return n(),c(r,u(o.editorData,{autoComplete:o.autoComplete,resourceType:o.resourceType,editor:!0,userData:o.userData,resourceId:o.resourceId,class:"comments-action",onNew:o.onNewComment}),null,16,["autoComplete","resourceType","userData","resourceId","onNew"])}const S=l(d,[["render",C],["__scopeId","data-v-29a1e244"]]);export{S as default};
2+
//# sourceMappingURL=ActivityCommentAction-DcJmUlXG.chunk.mjs.map
File renamed without changes.

dist/ActivityCommentAction-DcqrOWtA.chunk.mjs.map renamed to dist/ActivityCommentAction-DcJmUlXG.chunk.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/ActivityCommentAction-DcqrOWtA.chunk.mjs.map.license renamed to dist/ActivityCommentAction-DcJmUlXG.chunk.mjs.map.license

File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
import{t as s}from"./translation-DoG5ZELJ-CFYnqluG.chunk.mjs";import{C as p,a}from"./CommentView-DhkE9b-A.chunk.mjs";import{_ as i}from"./public-C1mLBHT3.chunk.mjs";import{r as n,o as c,c as u,m as l}from"./Web-BwmPK40i.chunk.mjs";import"./index-B-dGqfIG.chunk.mjs";import"./pinia-CLibr4cC.chunk.mjs";import"./PencilOutline-CJ2aSuY5.chunk.mjs";import"./logger-D3RVzcfQ-8mOgKmZ4.chunk.mjs";import"./createElementId-DhjFt1I9-B4kXTdvj.chunk.mjs";import"./NcModal-DUWLRm_F-CU-AeBE5.chunk.mjs";/* empty css */import"./NcAvatar-M3-CbKbq-D3H79LgO.chunk.mjs";import"./index-BLNGy7h5.chunk.mjs";import"./util-Alk1iwuj.chunk.mjs";import"./ArrowRight-DKsMJImQ.chunk.mjs";import"./colors-BDeMBgfq-BQ_6MaUU.chunk.mjs";import"./NcUserStatusIcon-DsviB2Cr-CpKCZ3VO.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BJuPH7S7-B1iF5g9H.chunk.mjs";import"./TrashCanOutline-Cv7t-yKN.chunk.mjs";import"./NcUserBubble-CDQa0hGy-BM5qrjQl.chunk.mjs";import"./index-DL1yHC1K-BJ_TsAjb.chunk.mjs";import"./index-BW4M2n71.chunk.mjs";import"./mdi-Lt-19ASw.chunk.mjs";import"./activity-CgsVnLJG.chunk.mjs";import"./GetComments-BEnDgTs6.chunk.mjs";import"./index-BYnFfAmz.chunk.mjs";const d={name:"ActivityCommentEntry",components:{Comment:a},mixins:[p],props:{comment:{type:Object,required:!0},reloadCallback:{type:Function,required:!0}},data(){return{commentMessage:""}},watch:{comment(){this.commentMessage=this.comment.props.message}},mounted(){this.commentMessage=this.comment.props.message},methods:{t:s}};function g(t,e,o,f,m,C){const r=n("Comment");return c(),u(r,l({ref:"comment",tag:"li"},o.comment.props,{autoComplete:t.autoComplete,resourceType:t.resourceType,message:m.commentMessage,resourceId:t.resourceId,userData:t.genMentionsData(o.comment.props.mentions),class:"comments-activity",onDelete:e[0]||(e[0]=y=>o.reloadCallback())}),null,16,["autoComplete","resourceType","message","resourceId","userData"])}const Q=i(d,[["render",g],["__scopeId","data-v-afc310f1"]]);export{Q as default};
2-
//# sourceMappingURL=ActivityCommentEntry-BCZMrASQ.chunk.mjs.map
1+
import{t as s}from"./translation-DoG5ZELJ-CFYnqluG.chunk.mjs";import{C as p,a}from"./CommentView-SZy04tfJ.chunk.mjs";import{_ as i}from"./public-C1mLBHT3.chunk.mjs";import{r as n,o as c,c as u,m as l}from"./Web-BwmPK40i.chunk.mjs";import"./index-B-dGqfIG.chunk.mjs";import"./pinia-CLibr4cC.chunk.mjs";import"./PencilOutline-Bhf17C3C.chunk.mjs";import"./logger-D3RVzcfQ-8mOgKmZ4.chunk.mjs";import"./createElementId-DhjFt1I9-B4kXTdvj.chunk.mjs";import"./NcModal-DUWLRm_F-BYHFKrmn.chunk.mjs";/* empty css */import"./NcAvatar-M3-CbKbq-BRvAvY0H.chunk.mjs";import"./index-BLNGy7h5.chunk.mjs";import"./util-Alk1iwuj.chunk.mjs";import"./ArrowRight-DKsMJImQ.chunk.mjs";import"./colors-BDeMBgfq-BQ_6MaUU.chunk.mjs";import"./NcUserStatusIcon-DsviB2Cr-CpKCZ3VO.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BJuPH7S7-B8lchl7f.chunk.mjs";import"./TrashCanOutline-Cv7t-yKN.chunk.mjs";import"./NcUserBubble-CDQa0hGy-BTuZy5Jo.chunk.mjs";import"./index-DL1yHC1K-qIl2eChL.chunk.mjs";import"./index-DnfkOsH1.chunk.mjs";import"./mdi-CnS18KqQ.chunk.mjs";import"./activity-CgsVnLJG.chunk.mjs";import"./GetComments-BEnDgTs6.chunk.mjs";import"./index-BYnFfAmz.chunk.mjs";const d={name:"ActivityCommentEntry",components:{Comment:a},mixins:[p],props:{comment:{type:Object,required:!0},reloadCallback:{type:Function,required:!0}},data(){return{commentMessage:""}},watch:{comment(){this.commentMessage=this.comment.props.message}},mounted(){this.commentMessage=this.comment.props.message},methods:{t:s}};function g(t,e,o,f,m,C){const r=n("Comment");return c(),u(r,l({ref:"comment",tag:"li"},o.comment.props,{autoComplete:t.autoComplete,resourceType:t.resourceType,message:m.commentMessage,resourceId:t.resourceId,userData:t.genMentionsData(o.comment.props.mentions),class:"comments-activity",onDelete:e[0]||(e[0]=y=>o.reloadCallback())}),null,16,["autoComplete","resourceType","message","resourceId","userData"])}const Q=i(d,[["render",g],["__scopeId","data-v-afc310f1"]]);export{Q as default};
2+
//# sourceMappingURL=ActivityCommentEntry-DJtzKQaA.chunk.mjs.map
File renamed without changes.

0 commit comments

Comments
 (0)