fix(front): make (`lint:tsc`) completely happy
This commit is contained in:
parent
d8a7c033eb
commit
1972f6c8a0
|
@ -18,7 +18,7 @@
|
||||||
"test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node",
|
"test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node",
|
||||||
"lint": "yarn lint:es && yarn lint:tsc",
|
"lint": "yarn lint:es && yarn lint:tsc",
|
||||||
"lint:es": "eslint --max-warnings 0 --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html,.cjs . cypress public/embed.html src test ui-docs",
|
"lint:es": "eslint --max-warnings 0 --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html,.cjs . cypress public/embed.html src test ui-docs",
|
||||||
"lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental -p cypress src test ui-docs",
|
"lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental --project tsconfig.json",
|
||||||
"generate-types-from-local-schema": "yarn run openapi-typescript ../api/funkwhale_api/common/schema.yml -o src/generated/types.ts",
|
"generate-types-from-local-schema": "yarn run openapi-typescript ../api/funkwhale_api/common/schema.yml -o src/generated/types.ts",
|
||||||
"generate-types-from-remote-schema": "yarn run openapi-typescript https://docs.funkwhale.audio/develop/swagger/schema.yml -o src/generated/types.ts",
|
"generate-types-from-remote-schema": "yarn run openapi-typescript https://docs.funkwhale.audio/develop/swagger/schema.yml -o src/generated/types.ts",
|
||||||
"fmt:es": "yarn lint:es --fix",
|
"fmt:es": "yarn lint:es --fix",
|
||||||
|
|
|
@ -108,7 +108,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="albums && count > props.limit"
|
v-if="page && albums && count > props.limit"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / props.limit)"
|
:pages="Math.ceil((count || 0) / props.limit)"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
|
|
|
@ -108,7 +108,7 @@ watch(
|
||||||
:artist="artist"
|
:artist="artist"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="artists && count > limit"
|
v-if="page && artists && count > limit"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
:pages="Math.ceil((count || 0) / limit)"
|
:pages="Math.ceil((count || 0) / limit)"
|
||||||
|
|
|
@ -90,7 +90,7 @@ watch([() => props.filters, page],
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && count > limit && limit > 16"
|
v-if="page && result && count > limit && limit > 16"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / limit)"
|
:pages="Math.ceil((count || 0) / limit)"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
|
@ -101,7 +101,7 @@ watch([() => props.filters, page],
|
||||||
:object="channel"
|
:object="channel"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && count > limit"
|
v-if="page && result && count > limit"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / limit)"
|
:pages="Math.ceil((count || 0) / limit)"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
|
|
|
@ -216,7 +216,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="count > props.limit"
|
v-if="page && count > props.limit"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / props.limit)"
|
:pages="Math.ceil((count || 0) / props.limit)"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
|
|
|
@ -215,7 +215,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="results && count > paginateBy"
|
v-if="page && results && count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / paginateBy)"
|
:pages="Math.ceil((count || 0) / paginateBy)"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
|
|
|
@ -229,7 +229,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
</Layout>
|
</Layout>
|
||||||
<Loader v-if="isLoading" />
|
<Loader v-if="isLoading" />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((result.count || 0)/paginateBy)"
|
:pages="Math.ceil((result.count || 0)/paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
@ -271,7 +271,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
</Layout>
|
</Layout>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((result.count || 0)/paginateBy)"
|
:pages="Math.ceil((result.count || 0)/paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -237,7 +237,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
</Layout>
|
</Layout>
|
||||||
<Loader v-if="isLoading" />
|
<Loader v-if="isLoading" />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
@ -279,7 +279,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
</Layout>
|
</Layout>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -309,7 +309,7 @@ const { to: upload } = useModal('upload')
|
||||||
</Layout>
|
</Layout>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
:page="page"
|
:page="page"
|
||||||
:pages="Math.ceil((result?.results.length || 0)/paginateBy)"
|
:pages="Math.ceil((result?.results.length || 0)/paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -277,7 +277,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
||||||
flex
|
flex
|
||||||
>
|
>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
@ -288,7 +288,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
||||||
:custom-radio="radio"
|
:custom-radio="radio"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -250,13 +250,13 @@ const labels = computed(() => ({
|
||||||
</action-table>
|
</action-table>
|
||||||
<div>
|
<div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.ChannelsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.ChannelsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -253,12 +253,12 @@ const labels = computed(() => ({
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && page !== undefined && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.AlbumsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.AlbumsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -249,12 +249,12 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.ArtistsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.ArtistsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -266,12 +266,12 @@ const getCurrentState = (target?: StateTarget): ReviewState => {
|
||||||
@refresh="fetchData()"
|
@refresh="fetchData()"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.EditsCardList.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.EditsCardList.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -273,12 +273,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.LibrariesTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.LibrariesTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -215,12 +215,12 @@ const showUploadDetailModal = ref(false)
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.TagsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.TagsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -254,13 +254,13 @@ const labels = computed(() => ({
|
||||||
</action-table>
|
</action-table>
|
||||||
<div>
|
<div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.TracksTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.TracksTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -365,12 +365,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.UploadsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -231,14 +231,14 @@ const labels = computed(() => ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:paginate-by="paginateBy"
|
:paginate-by="paginateBy"
|
||||||
:pages="result.count"
|
:pages="result.count"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.moderation.AccountsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.moderation.AccountsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -262,16 +262,16 @@ const labels = computed(() => ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<pagination
|
<pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
v-model:pages="result.count"
|
:pages="result.count"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
:paginate-by="paginateBy"
|
:paginate-by="paginateBy"
|
||||||
:total="result.count"
|
:total="result.count"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.moderation.DomainsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.moderation.DomainsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -230,13 +230,13 @@ const labels = computed(() => ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
v-model:pages="result.count"
|
v-model:pages="result.count"
|
||||||
:paginate-by="paginateBy"
|
:paginate-by="paginateBy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.users.InvitationsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }, result.results.length) }}
|
{{ t('components.manage.users.InvitationsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }, result.results.length) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -275,7 +275,7 @@ const labels = computed(() => ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="result && result.results.length > 0">
|
||||||
{{ t('components.manage.users.UsersTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}, result.results.length) }}
|
{{ t('components.manage.users.UsersTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }, result.results.length) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Notification, LibraryFollow } from '~/types'
|
import type { Notification, LibraryFollow } from '~/types'
|
||||||
import type { components } from '~/types/generated'
|
import type { components } from '~/generated/types'
|
||||||
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
|
||||||
import { computed, ref, watchEffect, watch } from 'vue'
|
import { computed, ref, watchEffect, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
@ -72,7 +73,10 @@ const notificationData = computed(() => {
|
||||||
if (activity.related_object?.approved === null) {
|
if (activity.related_object?.approved === null) {
|
||||||
return {
|
return {
|
||||||
detailUrl,
|
detailUrl,
|
||||||
message: t('components.notifications.NotificationRow.message.userPendingFollow', { username: username.value, user: activity.object.target?.full_username }),
|
message: t('components.notifications.NotificationRow.message.userPendingFollow', { username: username.value,
|
||||||
|
// TODO: This is just wrong. Start with fixing the types upstream.
|
||||||
|
// @ts-expect-error `activity.object needs to have a type. Where is it declared?
|
||||||
|
user: activity.object.target?.full_username }),
|
||||||
acceptFollow: {
|
acceptFollow: {
|
||||||
buttonClass: 'success',
|
buttonClass: 'success',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
|
@ -134,24 +138,37 @@ const handleAction = (handler?: () => void) => {
|
||||||
|
|
||||||
const approveLibraryFollow = async (follow: LibraryFollow) => {
|
const approveLibraryFollow = async (follow: LibraryFollow) => {
|
||||||
await axios.post(`federation/follows/library/${follow.uuid}/accept/`)
|
await axios.post(`federation/follows/library/${follow.uuid}/accept/`)
|
||||||
|
// TODO: This is not how Axios works. You have to send a request with
|
||||||
|
// the correct type as a parameter.
|
||||||
|
// @ts-expect-error Post this with the axios payload: { ...follow, approved: true}
|
||||||
follow.approved = true
|
follow.approved = true
|
||||||
item.value.is_read = true
|
item.value.is_read = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rejectLibraryFollow = async (follow: LibraryFollow) => {
|
const rejectLibraryFollow = async (follow: LibraryFollow) => {
|
||||||
await axios.post(`federation/follows/library/${follow.uuid}/reject/`)
|
await axios.post(`federation/follows/library/${follow.uuid}/reject/`)
|
||||||
|
// TODO: This is not how Axios works. You have to send a request with
|
||||||
|
// the correct type as a parameter.
|
||||||
|
// @ts-expect-error Post this with the axios payload: { ...follow, approved: false}
|
||||||
follow.approved = false
|
follow.approved = false
|
||||||
item.value.is_read = true
|
item.value.is_read = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const approveUserFollow = async (follow: UserFollow) => {
|
const approveUserFollow = async (follow: components["schemas"]["Follow"]) => {
|
||||||
await axios.post(`federation/follows/user/${follow.uuid}/accept/`)
|
await axios.post(`federation/follows/user/${follow.uuid}/accept/`)
|
||||||
|
// TODO: This is not how Axios works. You have to send a request with
|
||||||
|
// the correct type as a parameter.
|
||||||
|
// @ts-expect-error Post this with the axios payload: { ...follow, approved: true}
|
||||||
follow.approved = true
|
follow.approved = true
|
||||||
item.value.is_read = true
|
item.value.is_read = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rejectUserFollow = async (follow: UserFollow) => {
|
const rejectUserFollow = async (follow: components["schemas"]["Follow"]) => {
|
||||||
await axios.post(`federation/follows/user/${follow.uuid}/reject/`)
|
await axios.post(`federation/follows/user/${follow.uuid}/reject/`)
|
||||||
|
|
||||||
|
// TODO: This is not how Axios works. You have to send a request with
|
||||||
|
// the correct type as a parameter.
|
||||||
|
// @ts-expect-error Post this with the axios payload: { ...follow, approved: false}
|
||||||
follow.approved = false
|
follow.approved = false
|
||||||
item.value.is_read = true
|
item.value.is_read = true
|
||||||
}
|
}
|
||||||
|
@ -166,11 +183,13 @@ const rejectUserFollow = async (follow: UserFollow) => {
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<!-- TODO: Make sure `notificationData.detailUrl` has a type that satisfies `RouteLocationRaw` -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<router-link
|
<router-link
|
||||||
v-if="notificationData.detailUrl"
|
v-if="notificationData.detailUrl"
|
||||||
v-slot="{ navigate }"
|
v-slot="{ navigate }"
|
||||||
custom
|
custom
|
||||||
:to="notificationData.detailUrl"
|
:to="notificationData.detailUrl as RouteLocationRaw"
|
||||||
>
|
>
|
||||||
<sanitized-html
|
<sanitized-html
|
||||||
tag="span"
|
tag="span"
|
||||||
|
|
|
@ -6,7 +6,6 @@ import defaultCover from '~/assets/audio/default-cover.png'
|
||||||
import { momentFormat } from '~/utils/filters'
|
import { momentFormat } from '~/utils/filters'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
@ -21,7 +20,6 @@ interface Props {
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const images = computed(() => {
|
const images = computed(() => {
|
||||||
|
|
|
@ -46,7 +46,7 @@ interface ModifiedPlaylistTrack extends PlaylistTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracks = computed({
|
const tracks = computed({
|
||||||
get: () => playlistTracks.value.map((playlistTrack, index) => ({ ...playlistTrack, _id: `${index}-${playlistTrack.track.id}` } as ModifiedPlaylistTrack)),
|
get: () => playlistTracks.value.map((playlistTrack, index) => ({ ...playlistTrack, _id: `${ index }-${ playlistTrack.track }` } as ModifiedPlaylistTrack)),
|
||||||
set: (playlist) => {
|
set: (playlist) => {
|
||||||
playlistTracks.value = playlist.map((modifiedPlaylistTrack, index) => {
|
playlistTracks.value = playlist.map((modifiedPlaylistTrack, index) => {
|
||||||
const res = { ...modifiedPlaylistTrack, index } as ModifiedPlaylistTrack
|
const res = { ...modifiedPlaylistTrack, index } as ModifiedPlaylistTrack
|
||||||
|
|
|
@ -105,7 +105,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="objects && count > (props.filters.limit as number)"
|
v-if="page && objects && count > (props.filters.limit as number)"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil((count || 0) / (props.filters.limit as number))"
|
:pages="Math.ceil((count || 0) / (props.filters.limit as number))"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
import { useAttrs } from 'vue'
|
|
||||||
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
@ -60,6 +59,9 @@ const props = defineProps<{
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<slot name="topleft" />
|
<slot name="topleft" />
|
||||||
|
<!-- The inferred type of props occasionally overloads the typescript compiler. -->
|
||||||
|
<!-- TODO: Remove @vue-ignore once tsc is re-implemented in Go (and 10x faster) -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Heading
|
<Heading
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
style="
|
style="
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useElementSize } from '@vueuse/core'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { isMobileView } from '~/composables/screen'
|
import { isMobileView } from '~/composables/screen'
|
||||||
import { preventNonNumeric } from '~/utils/event-validators'
|
|
||||||
|
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
|
|
|
@ -109,13 +109,23 @@ onMounted(() => {
|
||||||
:key="key"
|
:key="key"
|
||||||
:class="$style.description"
|
:class="$style.description"
|
||||||
:style="`margin-right: -20%; --current-step: 0; color: magenta;`"
|
:style="`margin-right: -20%; --current-step: 0; color: magenta;`"
|
||||||
><Markdown :md="options[key]" /></span>
|
>
|
||||||
|
<!-- For some reason, the linter complains that (Record<T, string>)[T] is not string... -->
|
||||||
|
<!-- TODO: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437 -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
|
<Markdown :md="options[model]" />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="model !== undefined"
|
v-if="model !== undefined"
|
||||||
style="position: absolute;"
|
style="position: absolute;"
|
||||||
:class="$style.description"
|
:class="$style.description"
|
||||||
><Markdown :md="options[model]" /></span>
|
>
|
||||||
|
<!-- For some reason, the linter complains that (Record<T, string>)[T] is not string... -->
|
||||||
|
<!-- TODO: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437 -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
|
<Markdown :md="options[model]" />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type RouterLinkProps } from 'vue-router'
|
import { type TabProps, TABS_INJECTION_KEY } from '~/injection-keys'
|
||||||
import { TABS_INJECTION_KEY } from '~/injection-keys'
|
|
||||||
import { whenever } from '@vueuse/core'
|
import { whenever } from '@vueuse/core'
|
||||||
import { inject, ref } from 'vue'
|
import { inject, ref } from 'vue'
|
||||||
|
|
||||||
export type Props = {
|
const props = defineProps<TabProps>()
|
||||||
title: string,
|
|
||||||
to?: RouterLinkProps['to']
|
|
||||||
icon?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
const { currentTitle, tabs } = inject(TABS_INJECTION_KEY, {
|
const { currentTitle, tabs } = inject(TABS_INJECTION_KEY, {
|
||||||
currentTitle: ref(props.title),
|
currentTitle: ref(props.title),
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TABS_INJECTION_KEY } from '~/injection-keys'
|
import { type TabProps, TABS_INJECTION_KEY } from '~/injection-keys'
|
||||||
import { computed, provide, reactive, ref, watch } from 'vue'
|
import { computed, provide, reactive, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { type Props as TabProps } from '~/components/ui/Tab.vue'
|
|
||||||
|
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Link from '~/components/ui/Link.vue'
|
import Link from '~/components/ui/Link.vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
|
@ -39,16 +39,12 @@ const styles = {
|
||||||
}[a!])
|
}[a!])
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>) =>
|
const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>):string =>
|
||||||
(
|
(
|
||||||
typeof styles[key] === 'function'
|
typeof styles[key] === 'string'
|
||||||
? (styles[key](
|
? styles[key]
|
||||||
// @ts-expect-error We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
|
// @ts-expect-error We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
|
||||||
(key in props && props[key])
|
: (styles[key]((key in props && props[key]) ? props[((props[key]), (key))] : value))
|
||||||
? props[((props[key]), (key))]
|
|
||||||
: value
|
|
||||||
))
|
|
||||||
: styles[key]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const merge = (rules: string[]) => (attributes: HTMLAttributes = {}) =>
|
const merge = (rules: string[]) => (attributes: HTMLAttributes = {}) =>
|
||||||
|
|
|
@ -111,6 +111,9 @@ export const useQueue = createGlobalState(() => {
|
||||||
const { uploads } = await axios.get(`tracks/${track.id}/`)
|
const { uploads } = await axios.get(`tracks/${track.id}/`)
|
||||||
.then(response => response.data as Track, () => ({ uploads: [] as Upload[] }))
|
.then(response => response.data as Track, () => ({ uploads: [] as Upload[] }))
|
||||||
|
|
||||||
|
// TODO: Either make `track` a writable ref or implement the client/cache model
|
||||||
|
// See Issue: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437
|
||||||
|
// @ts-expect-error `track` is read-only
|
||||||
track.uploads = uploads
|
track.uploads = uploads
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/
|
||||||
import type { components } from '~/generated/types'
|
import type { components } from '~/generated/types'
|
||||||
import type { ContentFilter } from '~/store/moderation'
|
import type { ContentFilter } from '~/store/moderation'
|
||||||
|
|
||||||
import { useCurrentElement } from '@vueuse/core'
|
|
||||||
import { computed, markRaw, ref } from 'vue'
|
import { computed, markRaw, ref } from 'vue'
|
||||||
import { i18n } from '~/init/locale'
|
import { i18n } from '~/init/locale'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
@ -154,7 +153,7 @@ export default (props: PlayOptionsProps) => {
|
||||||
return tracks.filter(track => track.uploads?.length).map(markRaw)
|
return tracks.filter(track => track.uploads?.length).map(markRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = useCurrentElement()
|
// const el = useCurrentElement()
|
||||||
|
|
||||||
const enqueue = async () => {
|
const enqueue = async () => {
|
||||||
const tracks = await getPlayableTracks()
|
const tracks = await getPlayableTracks()
|
||||||
|
|
|
@ -4,10 +4,12 @@ import { ref } from 'vue'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const pageQuery = useRouteQuery<string>('page', '1')
|
const pageQuery = useRouteQuery<string>('page', '1')
|
||||||
const page = ref()
|
const page = ref<number>()
|
||||||
syncRef(pageQuery, page, {
|
syncRef(pageQuery, page, {
|
||||||
transform: {
|
transform: {
|
||||||
ltr: (left) => +left,
|
ltr: (left) => +left,
|
||||||
|
// TODO: Why toString?
|
||||||
|
// @ts-expect-error string vs. number
|
||||||
rtl: (right) => right.toString()
|
rtl: (right) => right.toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,13 +42,13 @@ const styles = {
|
||||||
...widths, ...sizes
|
...widths, ...sizes
|
||||||
} as const satisfies Record<Key, string | ((w: string) => string)>
|
} as const satisfies Record<Key, string | ((w: string) => string)>
|
||||||
|
|
||||||
|
// The `lint:tsc` script more errors here than the language server is happy.
|
||||||
|
// TODO: Fix this Issue: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437
|
||||||
const getStyle = (props: Partial<WidthProps>) => (key: Key):string =>
|
const getStyle = (props: Partial<WidthProps>) => (key: Key):string =>
|
||||||
key in props
|
key in props
|
||||||
? typeof styles[key] === 'function'
|
? typeof styles[key] === 'function'
|
||||||
? styles[key](
|
// @ts-expect-error Typescript is hard. Make the typescript compiler understand `key in props`
|
||||||
// @ts-expect-error Typescript is hard. Make the typescript compiler understand `key in props`
|
? styles[key](props[key])
|
||||||
props[key]
|
|
||||||
)
|
|
||||||
: styles[key] as string
|
: styles[key] as string
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@ export const install: InitModule = ({ store }) => {
|
||||||
const { current } = store.state.radios
|
const { current } = store.state.radios
|
||||||
|
|
||||||
if (current.clientOnly) {
|
if (current.clientOnly) {
|
||||||
|
// TODO: Type this event
|
||||||
|
// @ts-expect-error untyped event
|
||||||
await CLIENT_RADIOS[current.type].handleListen(current, event)
|
await CLIENT_RADIOS[current.type].handleListen(current, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { type InjectionKey, type Ref } from 'vue'
|
import { type InjectionKey, type Ref } from 'vue'
|
||||||
import { type Props as TabProps } from '~/components/ui/Tab.vue'
|
import { type RouterLinkProps } from 'vue-router'
|
||||||
|
|
||||||
|
export type TabProps = {
|
||||||
|
title: string,
|
||||||
|
to?: RouterLinkProps['to']
|
||||||
|
icon?: string
|
||||||
|
}
|
||||||
|
|
||||||
export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{
|
export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{
|
||||||
tabs: TabProps[]
|
tabs: TabProps[]
|
||||||
|
|
|
@ -16,10 +16,6 @@ export interface State {
|
||||||
settings: Settings
|
settings: Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
type TotalCount = {
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// export interface NodeInfo {
|
// export interface NodeInfo {
|
||||||
// version: string;
|
// version: string;
|
||||||
// software: {
|
// software: {
|
||||||
|
|
|
@ -10,7 +10,8 @@ import Alert from '~/components/ui/Alert.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
|
|
||||||
|
// TODO: Delete this file once all upload functionality is moved to the new UI.
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
@ -38,13 +39,15 @@ const combinedFileSize = computed(() => bytesToHumanSize(
|
||||||
))
|
))
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const processFiles = (fileList: FileList) => {
|
|
||||||
if (!uploads.currentUploadGroup) return
|
|
||||||
|
|
||||||
for (const file of fileList) {
|
// TODO: Is this needed?
|
||||||
uploads.currentUploadGroup.queueUpload(file)
|
// const processFiles = (fileList: FileList) => {
|
||||||
}
|
// if (!uploads.currentUploadGroup) return
|
||||||
}
|
|
||||||
|
// for (const file of fileList) {
|
||||||
|
// uploads.currentUploadGroup.queueUpload(file)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
|
@ -53,14 +56,14 @@ const cancel = () => {
|
||||||
uploads.currentUploadGroup = undefined
|
uploads.currentUploadGroup = undefined
|
||||||
|
|
||||||
if (uploads.queue.length > 0) {
|
if (uploads.queue.length > 0) {
|
||||||
return router.push('/upload/running')
|
router.push('/upload/running')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const continueInBackground = () => {
|
const continueInBackground = () => {
|
||||||
libraryOpen.value = false
|
libraryOpen.value = false
|
||||||
uploads.currentUploadGroup = undefined
|
uploads.currentUploadGroup = undefined
|
||||||
return router.push('/upload/running')
|
router.push('/upload/running')
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (whole file): Delete this file, please.
|
// TODO (whole file): Delete this file, please.
|
||||||
|
@ -106,12 +109,16 @@ const isOpen = computed({
|
||||||
</Alert>
|
</Alert>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- TODO: Use a file input. We haven't implemented this yet.
|
||||||
|
We could say v-model can be of type `string | number | File | File[]`
|
||||||
|
and then implement this functionality. -->
|
||||||
|
<!-- v-model="processFiles" -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
:accept="['.flac', '.ogg', '.opus', '.mp3', '.aac', '.aif', '.aiff', '.m4a'].join(', ')"
|
:accept="['.flac', '.ogg', '.opus', '.mp3', '.aac', '.aif', '.aiff', '.m4a'].join(', ')"
|
||||||
multiple
|
multiple
|
||||||
auto-reset
|
auto-reset
|
||||||
@files="processFiles"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Upload path -->
|
<!-- Upload path -->
|
||||||
|
|
|
@ -14,7 +14,6 @@ import Popover from '~/components/ui/Popover.vue'
|
||||||
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue'
|
import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
import Pill from '~/components/ui/Pill.vue'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
// return Metadata.parseBlob(file).then(metadata => metadata.common)
|
// return Metadata.parseBlob(file).then(metadata => metadata.common)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// @ts-expect-error This is not installed...?
|
||||||
import * as jsmediaTags from 'jsmediatags/dist/jsmediatags.min.js'
|
import * as jsmediaTags from 'jsmediatags/dist/jsmediatags.min.js'
|
||||||
|
// @ts-expect-error This is not installed...?
|
||||||
import type { ShortcutTags } from 'jsmediatags'
|
import type { ShortcutTags } from 'jsmediatags'
|
||||||
|
|
||||||
const REQUIRED_TAGS = ['title', 'artist', 'album']
|
const REQUIRED_TAGS = ['title', 'artist', 'album']
|
||||||
|
@ -47,6 +49,7 @@ export const getCoverUrl = async (tags: Tags): Promise<string | undefined> => {
|
||||||
export const getTags = async (file: File) => {
|
export const getTags = async (file: File) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jsmediaTags.read(file, {
|
jsmediaTags.read(file, {
|
||||||
|
// @ts-expect-error Please type `tags`
|
||||||
onSuccess: ({ tags }) => {
|
onSuccess: ({ tags }) => {
|
||||||
if (tags.picture?.data) {
|
if (tags.picture?.data) {
|
||||||
tags.picture.data = new Uint8Array(tags.picture.data)
|
tags.picture.data = new Uint8Array(tags.picture.data)
|
||||||
|
@ -59,6 +62,7 @@ export const getTags = async (file: File) => {
|
||||||
|
|
||||||
resolve(tags)
|
resolve(tags)
|
||||||
},
|
},
|
||||||
|
// @ts-expect-error Please type `error`
|
||||||
onError: (error) => reject(error)
|
onError: (error) => reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { computed, ref, type Ref } from 'vue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
type Item = { type: 'custom' | 'preset', label: string }
|
export type Item = { type: 'custom' | 'preset', label: string }
|
||||||
type Model = { currents: Item[], others?: Item[] }
|
export type Model = { currents: Item[], others?: Item[] }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and cache all tags.
|
* Load and cache all tags.
|
||||||
|
|
|
@ -190,9 +190,13 @@ export const useUploadsStore = defineStore('uploads', () => {
|
||||||
const upload = group.queue.find(entry => entry.guid === event.upload.uuid)
|
const upload = group.queue.find(entry => entry.guid === event.upload.uuid)
|
||||||
if (!upload) continue
|
if (!upload) continue
|
||||||
|
|
||||||
if (event.new_status !== 'failed') {
|
if (event.new_status !== 'errored') {
|
||||||
|
// TODO: Find out what other field to use here
|
||||||
|
// @ts-expect-error wrong field
|
||||||
upload.importedAt = event.upload.import_date
|
upload.importedAt = event.upload.import_date
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Add second parameter `error`
|
||||||
|
// @ts-expect-error missing parameter
|
||||||
upload.fail('import-failed')
|
upload.fail('import-failed')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -16,7 +16,6 @@ import useMarkdown from '~/composables/useMarkdown'
|
||||||
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Loader from '~/components/ui/Loader.vue'
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
|
||||||
import Header from '~/components/ui/Header.vue'
|
import Header from '~/components/ui/Header.vue'
|
||||||
import Toggle from '~/components/ui/Toggle.vue'
|
import Toggle from '~/components/ui/Toggle.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
import EditsCardList from '~/components/manage/library/EditsCardList.vue'
|
import EditsCardList from '~/components/manage/library/EditsCardList.vue'
|
||||||
|
|
||||||
|
@ -15,9 +14,11 @@ withDefaults(defineProps<Props>(), {
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const labels = computed(() => ({
|
|
||||||
title: t('views.admin.library.EditsList.title')
|
// TODO: Do we want to use this title?
|
||||||
}))
|
// const labels = computed(() => ({
|
||||||
|
// title: t('views.admin.library.EditsList.title')
|
||||||
|
// }))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import $ from 'jquery'
|
|
||||||
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Loader from '~/components/ui/Loader.vue'
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
@ -108,6 +107,7 @@ fetchData()
|
||||||
const el = useCurrentElement()
|
const el = useCurrentElement()
|
||||||
watch(object, async () => {
|
watch(object, async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
// @ts-expect-error JQuery owhere to be found...
|
||||||
$(el.value).find('select.dropdown').dropdown()
|
$(el.value).find('select.dropdown').dropdown()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -47,12 +47,14 @@ const logger = useLogger()
|
||||||
|
|
||||||
const search = ref()
|
const search = ref()
|
||||||
|
|
||||||
const page = usePage()
|
|
||||||
const result = ref<BackendResponse<Report>>()
|
const result = ref<BackendResponse<Report>>()
|
||||||
|
|
||||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
|
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
|
||||||
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
|
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
|
||||||
|
|
||||||
|
const page = usePage()
|
||||||
|
const pages = computed(() => result.value?.count ? Math.ceil(result.value.count / paginateBy.value) : 0)
|
||||||
|
|
||||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||||
['creation_date', 'creation_date'],
|
['creation_date', 'creation_date'],
|
||||||
['applied_date', 'applied_date']
|
['applied_date', 'applied_date']
|
||||||
|
@ -214,8 +216,9 @@ const labels = computed(() => ({
|
||||||
</div>
|
</div>
|
||||||
<div class="ui center aligned basic segment">
|
<div class="ui center aligned basic segment">
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:current="page"
|
v-model:page="page"
|
||||||
|
:pages="pages"
|
||||||
:paginate-by="paginateBy"
|
:paginate-by="paginateBy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -110,10 +110,10 @@ const labels = computed(() => ({
|
||||||
<Input
|
<Input
|
||||||
id="requests-search"
|
id="requests-search"
|
||||||
ref="search"
|
ref="search"
|
||||||
|
v-model="query"
|
||||||
name="search"
|
name="search"
|
||||||
search
|
search
|
||||||
:label="t('views.admin.moderation.RequestsList.label.search')"
|
:label="t('views.admin.moderation.RequestsList.label.search')"
|
||||||
:value="query"
|
|
||||||
:placeholder="labels.searchPlaceholder"
|
:placeholder="labels.searchPlaceholder"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -194,8 +194,9 @@ const labels = computed(() => ({
|
||||||
@handled="fetchData"
|
@handled="fetchData"
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result.count > paginateBy"
|
v-if="page && result.count > paginateBy"
|
||||||
v-model:current="page"
|
v-model:page="page"
|
||||||
|
v-model:pages="result.count"
|
||||||
:paginate-by="paginateBy"
|
:paginate-by="paginateBy"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Nav from '~/components/ui/Nav.vue'
|
import Nav from '~/components/ui/Nav.vue'
|
||||||
|
@ -33,6 +34,6 @@ const tabs = ref([
|
||||||
>
|
>
|
||||||
<Nav v-model="tabs" />
|
<Nav v-model="tabs" />
|
||||||
|
|
||||||
<router-view :key="$route.fullPath" />
|
<router-view :key="useRoute().fullPath" />
|
||||||
</Layout>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ImportStatus, PrivacyLevel, Upload, BackendResponse } from '~/types'
|
import type { ImportStatus, PrivacyLevel, Upload } from '~/types'
|
||||||
import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
|
import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
|
||||||
import type { OrderingProps } from '~/composables/navigation/useOrdering'
|
import type { OrderingProps } from '~/composables/navigation/useOrdering'
|
||||||
import type { RouteRecordName } from 'vue-router'
|
import type { RouteRecordName } from 'vue-router'
|
||||||
|
@ -595,12 +595,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
|
||||||
</template>
|
</template>
|
||||||
</action-table>
|
</action-table>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="result && result.results.length > 0">
|
<span v-if="page && result && result.results.length > 0">
|
||||||
{{ t('components.manage.library.UploadsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
{{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BackendError } from '~/types'
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
import { computed, ref, onMounted, nextTick } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Link from '~/components/ui/Link.vue'
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import UserFollowButton from '~/components/federation/UserFollowButton.vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
import useReport from '~/composables/moderation/useReport'
|
|
||||||
import RenderedDescription from '~/components/common/RenderedDescription.vue'
|
import RenderedDescription from '~/components/common/RenderedDescription.vue'
|
||||||
|
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
@ -35,7 +34,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
domain: null
|
domain: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const { report, getReportableObjects } = useReport()
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const object = ref<components['schemas']['FullActor'] | null>(null)
|
const object = ref<components['schemas']['FullActor'] | null>(null)
|
||||||
|
@ -43,7 +41,9 @@ const object = ref<components['schemas']['FullActor'] | null>(null)
|
||||||
const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username)))
|
const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username)))
|
||||||
const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` }))
|
const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` }))
|
||||||
|
|
||||||
const displayName = computed(() => object.value?.name ?? object.value?.preferred_username)
|
// TODO: Check if still needed
|
||||||
|
//const displayName = computed(() => object.value?.name ?? object.value?.preferred_username)
|
||||||
|
|
||||||
const fullUsername = computed(() => props.domain
|
const fullUsername = computed(() => props.domain
|
||||||
? `${props.username}@${props.domain}`
|
? `${props.username}@${props.domain}`
|
||||||
: `${props.username}@${store.getters['instance/domain']}`
|
: `${props.username}@${store.getters['instance/domain']}`
|
||||||
|
@ -79,7 +79,6 @@ const fetchData = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(props, fetchData, { immediate: true })
|
watch(props, fetchData, { immediate: true })
|
||||||
const recentActivity = ref(0)
|
|
||||||
|
|
||||||
const { copy, copied, isSupported } = useClipboard()
|
const { copy, copied, isSupported } = useClipboard()
|
||||||
|
|
||||||
|
@ -106,13 +105,19 @@ const tabs = ref([{
|
||||||
main
|
main
|
||||||
>
|
>
|
||||||
<!-- TODO: Translate Edit Link -->
|
<!-- TODO: Translate Edit Link -->
|
||||||
|
<!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Header
|
<Header
|
||||||
:h1="props.username"
|
:h1="props.username"
|
||||||
:action="{
|
:action="{
|
||||||
text:'Edit profile',
|
text:'Edit profile',
|
||||||
|
// @ts-ignore
|
||||||
to:'/settings',
|
to:'/settings',
|
||||||
|
// @ts-ignore
|
||||||
primary: true,
|
primary: true,
|
||||||
|
// @ts-ignore
|
||||||
solid: true,
|
solid: true,
|
||||||
|
// @ts-ignore
|
||||||
icon: 'bi-pencil-fill'
|
icon: 'bi-pencil-fill'
|
||||||
}"
|
}"
|
||||||
style="margin-top: 58px;"
|
style="margin-top: 58px;"
|
||||||
|
@ -169,6 +174,8 @@ const tabs = ref([{
|
||||||
flex
|
flex
|
||||||
no-gap
|
no-gap
|
||||||
>
|
>
|
||||||
|
<!-- TODO: Fix error with `$event` not being the right type -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<RenderedDescription
|
<RenderedDescription
|
||||||
:content="{ html: object?.summary?.html || '' }"
|
:content="{ html: object?.summary?.html || '' }"
|
||||||
:field-name="'summary'"
|
:field-name="'summary'"
|
||||||
|
|
|
@ -142,6 +142,9 @@ const remove = async () => {
|
||||||
|
|
||||||
const updateSubscriptionCount = (delta: number) => {
|
const updateSubscriptionCount = (delta: number) => {
|
||||||
if (object.value) {
|
if (object.value) {
|
||||||
|
// TODO: Store a modified copy in the cache or on the db instead of mutating the object in-memory.
|
||||||
|
// #2438
|
||||||
|
// @ts-expect-error Property 'subscriptions_count' is readonly on type 'Channel'
|
||||||
object.value.subscriptions_count -= delta
|
object.value.subscriptions_count -= delta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,12 +139,17 @@ const showCreateModal = ref(false)
|
||||||
stack
|
stack
|
||||||
main
|
main
|
||||||
>
|
>
|
||||||
|
<!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Header
|
<Header
|
||||||
:h1="t('views.channels.SubscriptionsList.title')"
|
:h1="t('views.channels.SubscriptionsList.title')"
|
||||||
:action="{
|
:action="{
|
||||||
text: t('views.channels.SubscriptionsList.link.addNew'),
|
text: t('views.channels.SubscriptionsList.link.addNew'),
|
||||||
|
// @ts-ignore
|
||||||
onClick: () => { showSubscribeModal = true },
|
onClick: () => { showSubscribeModal = true },
|
||||||
|
// @ts-ignore
|
||||||
primary: true,
|
primary: true,
|
||||||
|
// @ts-ignore
|
||||||
icon: 'bi-plus'
|
icon: 'bi-plus'
|
||||||
}"
|
}"
|
||||||
large-section-heading
|
large-section-heading
|
||||||
|
@ -194,13 +199,17 @@ const showCreateModal = ref(false)
|
||||||
:show-modification-date="true"
|
:show-modification-date="true"
|
||||||
:filters="{q: subscribedQuery, subscribed: 'true'}"
|
:filters="{q: subscribedQuery, subscribed: 'true'}"
|
||||||
/>
|
/>
|
||||||
<!-- TODO: Translations -->
|
<!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Header
|
<Header
|
||||||
:h1="t('views.auth.ProfileOverview.header.channels')"
|
:h1="t('views.auth.ProfileOverview.header.channels')"
|
||||||
:action="{
|
:action="{
|
||||||
text: t('views.channels.SubscriptionsList.link.addNew'),
|
text: t('views.channels.SubscriptionsList.link.addNew'),
|
||||||
|
// @ts-ignore
|
||||||
onClick: () => { showCreateModal = true },
|
onClick: () => { showCreateModal = true },
|
||||||
|
// @ts-ignore
|
||||||
icon: 'bi-plus',
|
icon: 'bi-plus',
|
||||||
|
// @ts-ignore
|
||||||
primary: true
|
primary: true
|
||||||
}"
|
}"
|
||||||
large-section-heading
|
large-section-heading
|
||||||
|
|
|
@ -64,12 +64,17 @@ const showSubscribeModal = ref(false)
|
||||||
stack
|
stack
|
||||||
main
|
main
|
||||||
>
|
>
|
||||||
|
<!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Header
|
<Header
|
||||||
:h1="labels.title"
|
:h1="labels.title"
|
||||||
:action="{
|
:action="{
|
||||||
text: t('views.channels.SubscriptionsList.link.addNew'),
|
text: t('views.channels.SubscriptionsList.link.addNew'),
|
||||||
|
// @ts-ignore
|
||||||
onClick: () => { showSubscribeModal = true },
|
onClick: () => { showSubscribeModal = true },
|
||||||
|
// @ts-ignore
|
||||||
primary: true,
|
primary: true,
|
||||||
|
// @ts-ignore
|
||||||
icon: 'bi-plus'
|
icon: 'bi-plus'
|
||||||
}"
|
}"
|
||||||
page-heading
|
page-heading
|
||||||
|
|
|
@ -6,8 +6,6 @@ import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import moment from 'moment'
|
|
||||||
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -25,20 +23,20 @@ const imageUrl = computed(() => props.channel.artist?.cover
|
||||||
? store.getters['instance/absoluteUrl'](props.channel.artist?.cover.urls.medium_square_crop)
|
? store.getters['instance/absoluteUrl'](props.channel.artist?.cover.urls.medium_square_crop)
|
||||||
: fallbackImageUrl
|
: fallbackImageUrl
|
||||||
)
|
)
|
||||||
const urlId = computed(() => props.channel.actor?.is_local
|
|
||||||
? props.channel.actor.preferred_username
|
// TODO: Find out if still useful:
|
||||||
: props.channel.actor
|
// const urlId = computed(() => props.channel.actor?.is_local
|
||||||
? props.channel.actor.full_username
|
// ? props.channel.actor.preferred_username
|
||||||
: props.channel.uuid
|
// : props.channel.actor
|
||||||
)
|
// ? props.channel.actor.full_username
|
||||||
|
// : props.channel.uuid
|
||||||
|
// )
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const updatedTitle = computed(() => {
|
const updatedTitle = computed(() => {
|
||||||
const date = momentFormat(new Date(props.channel.artist?.modification_date ?? '1970-01-01'))
|
const date = momentFormat(new Date(props.channel.artist?.modification_date ?? '1970-01-01'))
|
||||||
return t('components.audio.ChannelCard.title', { date })
|
return t('components.audio.ChannelCard.title', { date })
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedAgo = computed(() => moment(props.channel.artist?.modification_date).fromNow())
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -55,18 +55,30 @@ const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fie
|
||||||
<human-date :date="library.creation_date" />
|
<human-date :date="library.creation_date" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO: Add `description` field to `Library` -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{ library.description }}
|
{{
|
||||||
|
// @ts-expect-error Property 'description' does not exist on type 'Library'
|
||||||
|
library.description
|
||||||
|
}}
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<!-- TODO: Add `size` field to `Library` (or find out how else to load size) -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<span
|
<span
|
||||||
v-if="library.size"
|
v-if="library.size"
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:data-tooltip="sizeLabel"
|
:data-tooltip="sizeLabel"
|
||||||
>
|
>
|
||||||
<i class="database icon" />
|
<i class="database icon" />
|
||||||
{{ humanSize(library.size) }}
|
{{
|
||||||
|
// @ts-expect-error Property 'size' does not exist on type 'Library'
|
||||||
|
humanSize(library.size)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<i class="music icon" />
|
<i class="music icon" />
|
||||||
{{ t('views.content.libraries.Card.meta.tracks', library.uploads_count) }}
|
{{ t('views.content.libraries.Card.meta.tracks', library.uploads_count) }}
|
||||||
|
|
|
@ -56,8 +56,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
orderingConfigName: undefined
|
orderingConfigName: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const search = ref()
|
|
||||||
|
|
||||||
const page = usePage()
|
const page = usePage()
|
||||||
const result = ref<BackendResponse<Upload>>()
|
const result = ref<BackendResponse<Upload>>()
|
||||||
|
|
||||||
|
@ -347,7 +345,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
||||||
</action-table>
|
</action-table>
|
||||||
<div>
|
<div>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
:pages="Math.ceil(result.count / paginateBy)"
|
:pages="Math.ceil(result.count / paginateBy)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -37,6 +37,9 @@ const labels = computed(() => ({
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me')
|
const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me')
|
||||||
|
|
||||||
|
// TODO: Add 'description' to the Library type
|
||||||
|
// @ts-expect-error Property 'description' does not exist on type 'Library'
|
||||||
const currentDescription = ref(props.library?.description ?? '')
|
const currentDescription = ref(props.library?.description ?? '')
|
||||||
const currentName = ref(props.library?.name ?? '')
|
const currentName = ref(props.library?.name ?? '')
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,9 @@ const isLoadingFollow = ref(false)
|
||||||
const showScan = ref(false)
|
const showScan = ref(false)
|
||||||
const latestScan = ref(props.initialLibrary.latest_scan)
|
const latestScan = ref(props.initialLibrary.latest_scan)
|
||||||
|
|
||||||
const scanProgress = computed(() => Math.min(latestScan.value.processed_files * 100 / latestScan.value.total_files, 100))
|
const scanProgress = computed(() => latestScan.value && latestScan.value.processed_files && latestScan.value.total_files
|
||||||
|
? Math.min(latestScan.value.processed_files * 100 / latestScan.value.total_files, 100)
|
||||||
|
: 0)
|
||||||
const scanStatus = computed(() => latestScan.value?.status ?? 'unknown')
|
const scanStatus = computed(() => latestScan.value?.status ?? 'unknown')
|
||||||
const canLaunchScan = computed(() => scanStatus.value !== 'pending' && scanStatus.value !== 'scanning')
|
const canLaunchScan = computed(() => scanStatus.value !== 'pending' && scanStatus.value !== 'scanning')
|
||||||
const radioPlayable = computed(() => (
|
const radioPlayable = computed(() => (
|
||||||
|
@ -191,8 +193,13 @@ const isOpen = ref(false)
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<!-- TODO: Add `description` field to `Library` -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{ library.description }}
|
{{
|
||||||
|
// @ts-ignore
|
||||||
|
library.description
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<Spacer :size="8" />
|
<Spacer :size="8" />
|
||||||
<div
|
<div
|
||||||
|
@ -215,7 +222,7 @@ const isOpen = ref(false)
|
||||||
<i class="bi bi-check-circle" />
|
<i class="bi bi-check-circle" />
|
||||||
{{ t('views.content.remote.Card.label.scanSuccess') }}
|
{{ t('views.content.remote.Card.label.scanSuccess') }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="latestScan.status === 'finished' && latestScan.errored_files > 0">
|
<template v-else-if="latestScan.status === 'finished' && latestScan.errored_files && latestScan.errored_files > 0">
|
||||||
<i class="bi bi-exclamation-circle" />
|
<i class="bi bi-exclamation-circle" />
|
||||||
{{ t('views.content.remote.Card.label.scanPartialSuccess') }}
|
{{ t('views.content.remote.Card.label.scanPartialSuccess') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -37,8 +37,9 @@ fetchData()
|
||||||
|
|
||||||
const getLibraryFromFollow = (follow: LibraryFollow) => {
|
const getLibraryFromFollow = (follow: LibraryFollow) => {
|
||||||
const { target } = follow
|
const { target } = follow
|
||||||
target.follow = follow
|
// TODO: Actually load the target from the database or the cache. Use `client` with cache.
|
||||||
return target as Library
|
// @ts-expect-error target is a string, not a library!
|
||||||
|
return ({ ...target, follow: follow } as Library)
|
||||||
}
|
}
|
||||||
|
|
||||||
const scanResult = ref()
|
const scanResult = ref()
|
||||||
|
|
|
@ -18,6 +18,7 @@ defineProps<Props>()
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<album-widget
|
<album-widget
|
||||||
|
v-if="object.uuid"
|
||||||
:key="String(object.uploads_count)"
|
:key="String(object.uploads_count)"
|
||||||
:header="false"
|
:header="false"
|
||||||
:search="true"
|
:search="true"
|
||||||
|
|
|
@ -4,10 +4,8 @@ import type { Library } from '~/types'
|
||||||
import ArtistWidget from '~/components/artist/Widget.vue'
|
import ArtistWidget from '~/components/artist/Widget.vue'
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
object: Library
|
object: Library
|
||||||
|
@ -20,12 +18,13 @@ defineProps<Props>()
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<artist-widget
|
<artist-widget
|
||||||
|
v-if="object.uuid"
|
||||||
:key="object.uploads_count"
|
:key="object.uploads_count"
|
||||||
ref="artists"
|
ref="artists"
|
||||||
:header="false"
|
:header="false"
|
||||||
:search="true"
|
:search="true"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:filters="{playable: true, ordering: '-creation_date', library: object.uuid}"
|
:filters="{ playable: true, ordering: '-creation_date', library: object.uuid }"
|
||||||
>
|
>
|
||||||
<template #empty-state>
|
<template #empty-state>
|
||||||
<empty-state>
|
<empty-state>
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
import type { Library } from '~/types'
|
import type { Library } from '~/types'
|
||||||
|
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
object: Library
|
object: Library
|
||||||
|
|
|
@ -51,6 +51,10 @@ fetchData()
|
||||||
const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`federation/follows/library/${follow.uuid}/${approved ? 'accept' : 'reject'}/`)
|
await axios.post(`federation/follows/library/${follow.uuid}/${approved ? 'accept' : 'reject'}/`)
|
||||||
|
|
||||||
|
// TODO: This is not how Axios works. You have to send a request with
|
||||||
|
// the correct type as a parameter.
|
||||||
|
// @ts-expect-error Post this with the axios payload: { ...follow, approved }
|
||||||
follow.approved = approved
|
follow.approved = approved
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useErrorHandler(error as Error)
|
useErrorHandler(error as Error)
|
||||||
|
|
|
@ -194,9 +194,9 @@ const tabs = ref([{
|
||||||
<i class="bi bi-music-note-list" />
|
<i class="bi bi-music-note-list" />
|
||||||
{{ t('views.library.LibraryBase.meta.tracks', object.uploads_count) }}
|
{{ t('views.library.LibraryBase.meta.tracks', object.uploads_count) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="object && 'size' in object && object.size">
|
<span v-if="object && 'size' in object && object.size && typeof object.size === 'number'">
|
||||||
<i class="bi bi-database-fill" />
|
<i class="bi bi-database-fill" />
|
||||||
{{ humanSize(object.size) }}
|
{{ humanSize(object.size as number) }}
|
||||||
</span>
|
</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
@ -213,15 +213,18 @@ const tabs = ref([{
|
||||||
v-if="!isOwner"
|
v-if="!isOwner"
|
||||||
>
|
>
|
||||||
<library-follow-button
|
<library-follow-button
|
||||||
v-if="store.state.auth.authenticated"
|
v-if="store.state.auth.authenticated && object"
|
||||||
:library="object"
|
:library="object"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<!-- TODO: Add `description` field to library -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<rendered-description
|
<rendered-description
|
||||||
:content="object?.description ? {html: object?.description} : null"
|
v-if="object && 'description' in object"
|
||||||
:update-url="`channels/${object?.uuid}/`"
|
:content="{ html: object.description }"
|
||||||
|
:update-url="`channels/${object.uuid}/`"
|
||||||
:can-update="false"
|
:can-update="false"
|
||||||
/>
|
/>
|
||||||
<Layout form>
|
<Layout form>
|
||||||
|
|
|
@ -117,16 +117,20 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
stack
|
stack
|
||||||
main
|
main
|
||||||
>
|
>
|
||||||
|
<!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! -->
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<Header
|
<Header
|
||||||
v-if="store.state.auth.authenticated"
|
v-if="store.state.auth.authenticated"
|
||||||
:h1="t('views.playlists.List.header.browse')"
|
:h1="t('views.playlists.List.header.browse')"
|
||||||
page-heading
|
page-heading
|
||||||
:action="{
|
:action="{
|
||||||
onClick: () => { store.commit('playlists/showModal', true) },
|
text: t('views.playlists.List.button.create'),
|
||||||
text: t('views.playlists.List.button.manage'),
|
// @ts-ignore
|
||||||
|
icon: 'bi-plus',
|
||||||
|
// @ts-ignore
|
||||||
primary: true,
|
primary: true,
|
||||||
icon: 'bi-music-note-list',
|
// @ts-ignore
|
||||||
ariaPressed: store.state.playlists.showModal || undefined
|
onClick: () => { store.commit('playlists/showModal', true) }
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<Header
|
<Header
|
||||||
|
@ -243,7 +247,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
</Button>
|
</Button>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
:pages="Math.ceil(result.count/paginateBy)"
|
:pages="Math.ceil(result.count/paginateBy)"
|
||||||
|
@ -255,10 +259,10 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
/>
|
/>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="result && result.count > paginateBy"
|
v-if="page && result && result.count > paginateBy"
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
style="grid-column: 1 / -1;"
|
|
||||||
:pages="Math.ceil(result.count/paginateBy)"
|
:pages="Math.ceil(result.count/paginateBy)"
|
||||||
|
style="grid-column: 1 / -1;"
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
Loading…
Reference in New Issue