Skip to content

Commit 49672df

Browse files
authored
[6.x] Listings (#11868)
1 parent 28ea204 commit 49672df

60 files changed

Lines changed: 3184 additions & 2526 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

resources/css/elements/tables.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
@apply whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-300 text-start;
2323
@apply px-4 py-2;
2424

25-
&:last-child {
26-
@apply text-end;
27-
}
28-
2925
.contained & {
3026
@apply py-2.5 border-b;
3127
}

resources/js/components/AddonDetails.vue

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<template>
22
<div>
3-
<div class="mb-6 flex items-center">
4-
<h1 class="flex-1" v-text="addon.name" />
5-
<a :href="addon.url" target="_blank" class="btn">
6-
<svg-icon name="light/external-link" class="h-3 w-3 shrink-0 ltr:mr-2 rtl:ml-2" />
7-
{{ __('View on Marketplace') }}
8-
</a>
9-
</div>
3+
<ui-header :title="addon.name" icon="addons">
4+
<ui-button :text="__('Back')" icon="arrow-left" @click="$emit('close')" />
5+
<ui-button
6+
variant="primary"
7+
:href="addon.url"
8+
target="_blank"
9+
icon="external-link"
10+
:text="__('View on Marketplace')"
11+
/>
12+
</ui-header>
1013
<div class="flex flex-col-reverse gap-6 space-y-6 xl:grid xl:grid-cols-3 xl:space-y-0">
1114
<div class="lg:col-span-2">
1215
<div class="card prose max-w-full p-6" v-html="description" />
@@ -60,6 +63,8 @@ export default {
6063
AddonEditions,
6164
},
6265
66+
emits: ['close'],
67+
6368
props: ['addon'],
6469
6570
data() {
Lines changed: 87 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,85 @@
1-
<template>
2-
<div>
3-
<div class="breadcrumb flex" v-if="showingAddon">
4-
<button
5-
@click="showingAddon = false"
6-
class="-m-2 flex flex-initial items-center p-2 text-xs text-gray-700 hover:text-gray-900 dark:text-dark-175 dark:hover:text-dark-100"
7-
>
8-
<svg-icon name="micro/chevron-right" class="h-6 w-4 rotate-180" />
9-
<span v-text="__('Addons')" />
10-
</button>
11-
</div>
12-
13-
<div class="mb-6 flex" v-if="!showingAddon">
14-
<h1 class="flex-1" v-text="__('Addons')" />
15-
</div>
16-
17-
<div v-if="error" class="card text-sm">
18-
{{ __('messages.addon_list_loading_error') }}
19-
</div>
1+
<script setup>
2+
import { ref, computed } from 'vue';
3+
import {
4+
Header,
5+
Badge,
6+
Card,
7+
Panel,
8+
PanelFooter,
9+
Tabs,
10+
TabList,
11+
TabTrigger,
12+
Listing,
13+
ListingSearch as Search,
14+
ListingPagination as Pagination,
15+
} from '@statamic/ui';
16+
17+
const props = defineProps(['domain', 'endpoints', 'installCount']);
18+
19+
const requestUrl = cp_url('/api/addons');
20+
const filter = ref('all');
21+
const showingAddon = ref(false);
22+
const unlisted = ref([]);
23+
24+
const additionalParameters = computed(() => ({
25+
installed: filter.value === 'installed' ? 1 : 0,
26+
}));
27+
28+
function getCover(addon) {
29+
return addon.assets.length
30+
? addon.assets[0].url
31+
: 'https://statamic.com/images/img/marketplace/placeholder-addon.png';
32+
}
2033
21-
<div v-if="initializing" class="card p-6 text-center">
22-
<loading-graphic />
23-
</div>
34+
function getPriceRange(addon) {
35+
let [low, high] = addon.price_range;
36+
low = low ? `$${low}` : 'Free';
37+
high = high ? `$${high}` : 'Free';
38+
return low == high ? low : `${low} - ${high}`;
39+
}
2440
25-
<data-list :rows="rows" v-if="!initializing && !showingAddon" v-slot="{ rows: addons }">
26-
<div class="">
27-
<div class="card p-0">
28-
<div class="border-b px-4 text-sm dark:border-dark-900">
29-
<button
30-
class="data-list-filter-link"
31-
:class="{ active: filter === 'all' }"
32-
@click="filter = 'all'"
33-
v-text="__('All')"
34-
/>
35-
<button
36-
class="data-list-filter-link"
37-
:class="{ active: filter === 'installed' }"
38-
@click="filter = 'installed'"
39-
>
40-
{{ __('Installed') }}
41-
<span class="badge" v-if="installCount">{{ installCount }}</span>
42-
</button>
43-
</div>
41+
function showAddon(addon) {
42+
showingAddon.value = addon;
43+
window.scrollTo(0, 0);
44+
}
45+
</script>
4446

45-
<div class="p-2">
46-
<data-list-search ref="search" v-model="searchQuery" />
47-
</div>
48-
</div>
47+
<template>
48+
<div>
49+
<Header v-if="!showingAddon" :title="__('Addons')" icon="addons" />
50+
<Listing
51+
v-if="!showingAddon"
52+
:url="requestUrl"
53+
:additional-parameters="additionalParameters"
54+
v-slot="{ items: addons, loading }"
55+
>
56+
<Tabs v-model="filter">
57+
<TabList>
58+
<TabTrigger :text="__('All')" name="all" />
59+
<TabTrigger name="installed">
60+
{{ __('Installed') }}
61+
<Badge size="sm" v-if="installCount" :text="installCount" />
62+
</TabTrigger>
63+
</TabList>
64+
</Tabs>
65+
66+
<div class="py-3">
67+
<Search />
68+
</div>
4969

50-
<div class="addon-grid my-8" :class="{ 'opacity-50': loading }">
51-
<div
52-
class="addon-card relative h-full cursor-pointer rounded-sm bg-white text-gray-800 shadow dark:bg-dark-600 dark:text-dark-150 dark:shadow-lg"
70+
<Panel>
71+
<div class="grid grid-cols-3 gap-2" :class="{ 'opacity-50': loading }">
72+
<Card
73+
class="relative cursor-pointer"
5374
v-for="addon in addons"
5475
:key="addon.id"
5576
@click="showAddon(addon)"
5677
>
57-
<span
58-
class="badge absolute top-0 mt-2 ltr:left-0 ltr:ml-2 rtl:right-0 rtl:mr-2"
78+
<Badge
5979
v-if="addon.installed"
60-
>Installed</span
61-
>
80+
:text="__('Installed')"
81+
class="absolute top-0 mt-2 ltr:left-0 ltr:ml-2 rtl:right-0 rtl:mr-2"
82+
/>
6283
<div
6384
class="h-48 rounded-t bg-cover bg-center"
6485
:style="'background-image: url(\'' + getCover(addon) + '\')'"
@@ -68,20 +89,20 @@
6889
<img
6990
:src="addon.seller.avatar"
7091
:alt="addon.seller.name"
71-
class="relative -mt-8 inline h-14 w-14 rounded-full border-2 border-white bg-white dark:border-dark-600 dark:bg-dark-600"
92+
class="dark:border-dark-600 dark:bg-dark-600 relative -mt-8 inline h-14 w-14 rounded-full border-2 border-white bg-white"
7293
/>
7394
</a>
7495
<div class="addon-card-title mb-2 text-center text-lg font-bold">{{ addon.name }}</div>
75-
<p class="mb-4 text-gray dark:text-dark-175" v-text="getPriceRange(addon)" />
96+
<p class="text-gray dark:text-dark-175 mb-4" v-text="getPriceRange(addon)" />
7697
<p v-text="addon.summary" class="text-sm"></p>
7798
</div>
78-
</div>
99+
</Card>
79100
</div>
80-
81-
<data-list-pagination :resource-meta="meta" @page-selected="setPage"></data-list-pagination>
82-
</div>
83-
</data-list>
84-
101+
<PanelFooter>
102+
<Pagination />
103+
</PanelFooter>
104+
</Panel>
105+
</Listing>
85106
<template v-if="unlisted.length && !showingAddon">
86107
<h6 class="mt-8">{{ __('Unlisted Addons') }}</h6>
87108
<div class="card mt-2 p-0">
@@ -95,133 +116,11 @@
95116
</table>
96117
</div>
97118
</template>
98-
99-
<addon-details v-if="showingAddon" :addon="showingAddon" :cover="getCover(showingAddon)" />
119+
<addon-details
120+
v-if="showingAddon"
121+
:addon="showingAddon"
122+
:cover="getCover(showingAddon)"
123+
@close="showingAddon = false"
124+
/>
100125
</div>
101126
</template>
102-
103-
<style>
104-
.addon-grid {
105-
display: grid;
106-
grid-gap: 32px;
107-
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
108-
grid-auto-flow: dense;
109-
}
110-
</style>
111-
112-
<script>
113-
export default {
114-
props: ['domain', 'endpoints', 'installCount'],
115-
116-
data() {
117-
return {
118-
initializing: true,
119-
loading: true,
120-
rows: [],
121-
meta: {},
122-
searchQuery: '',
123-
filter: 'all',
124-
page: 1,
125-
showingAddon: false,
126-
error: false,
127-
unlisted: [],
128-
};
129-
},
130-
131-
computed: {
132-
params() {
133-
return {
134-
page: this.page,
135-
q: this.searchQuery,
136-
installed: this.filter === 'installed' ? 1 : 0,
137-
};
138-
},
139-
140-
loaded() {
141-
return !this.loading && !this.error;
142-
},
143-
},
144-
145-
watch: {
146-
page() {
147-
this.getAddons();
148-
},
149-
150-
searchQuery() {
151-
this.page = 1;
152-
this.getAddons();
153-
},
154-
155-
filter() {
156-
this.page = 1;
157-
this.getAddons();
158-
},
159-
160-
loading: {
161-
immediate: true,
162-
handler(loading) {
163-
this.$progress.loading('addon-list', loading);
164-
},
165-
},
166-
},
167-
168-
created() {
169-
this.rows = this.getAddons();
170-
171-
this.$events.$on('composer-finished', this.getAddons);
172-
},
173-
174-
methods: {
175-
getAddons() {
176-
this.loading = true;
177-
178-
this.$axios
179-
.get(cp_url('/api/addons'), { params: this.params })
180-
.then((response) => {
181-
this.loading = false;
182-
this.initializing = false;
183-
this.rows = response.data.data;
184-
this.meta = response.data.meta;
185-
this.unlisted = response.data.unlisted ?? [];
186-
187-
if (this.showingAddon) {
188-
this.refreshShowingAddon();
189-
}
190-
})
191-
.catch((e) => {
192-
this.loading = false;
193-
this.error = true;
194-
this.$toast.error(__('Something went wrong'));
195-
});
196-
},
197-
198-
setPage(page) {
199-
this.page = page;
200-
},
201-
202-
refreshShowingAddon() {
203-
this.showingAddon = this.rows.find((row) => row.id === this.showingAddon.id);
204-
205-
this.$events.$emit('addon-refreshed');
206-
},
207-
208-
getCover(addon) {
209-
return addon.assets.length
210-
? addon.assets[0].url
211-
: 'https://statamic.com/images/img/marketplace/placeholder-addon.png';
212-
},
213-
214-
getPriceRange(addon) {
215-
let [low, high] = addon.price_range;
216-
low = low ? `$${low}` : 'Free';
217-
high = high ? `$${high}` : 'Free';
218-
return low == high ? low : `${low} - ${high}`;
219-
},
220-
221-
showAddon(addon) {
222-
this.showingAddon = addon;
223-
window.scrollTo(0, 0);
224-
},
225-
},
226-
};
227-
</script>

resources/js/components/actions/ConfirmableAction.vue

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ let runButtonText = computed(() => {
4242
4343
function onDone() {
4444
running.value = false;
45+
reset();
4546
}
4647
4748
function confirm() {
@@ -67,18 +68,8 @@ function runAction() {
6768
function reset() {
6869
confirming.value = false;
6970
values.value = clone(props.action.values);
70-
71-
// TODO: `reset-action-modals` listeners are over-registering still
72-
// You can see it with this:
73-
// console.log('resetting!');
7471
}
7572
76-
Statamic.$events.$on('reset-action-modals', reset);
77-
78-
onUnmounted(() => {
79-
Statamic.$events.$off('reset-action-modals', reset);
80-
});
81-
8273
defineExpose({
8374
handle: props.action.handle,
8475
confirm,

0 commit comments

Comments
 (0)