Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

manually add external extension button #125

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/gql/Mutations.gql
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ mutation updateExtension(
isNsfw
extension {
pkgName
repo
}
}
}
Expand Down Expand Up @@ -412,3 +413,21 @@ mutation clearCachedImages {
clientMutationId
}
}

mutation installExternalExtension($extensionFile: Upload!) {
installExternalExtension(input: { extensionFile: $extensionFile }) {
extension {
...ExtensionTypeFragment
source {
nodes {
...SourceTypeFragment
isNsfw
extension {
pkgName
repo
}
}
}
}
}
}
1 change: 1 addition & 0 deletions src/gql/Queries.gql
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ query sources($isNsfw: Boolean = null) {
isNsfw
extension {
pkgName
repo
}
}
}
Expand Down
45 changes: 43 additions & 2 deletions src/lib/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2232,7 +2232,7 @@ export type UpdateExtensionMutationVariables = Exact<{
}>;


export type UpdateExtensionMutation = { __typename?: 'Mutation', updateExtension: { __typename?: 'UpdateExtensionPayload', extension?: { __typename?: 'ExtensionType', name: string, repo?: string | null, versionName: string, pkgName: string, lang: string, iconUrl: string, isNsfw: boolean, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean, source: { __typename?: 'SourceNodeList', nodes: Array<{ __typename?: 'SourceType', isNsfw: boolean, id: any, displayName: string, iconUrl: string, lang: string, extension: { __typename?: 'ExtensionType', pkgName: string } }> } } | null } };
export type UpdateExtensionMutation = { __typename?: 'Mutation', updateExtension: { __typename?: 'UpdateExtensionPayload', extension?: { __typename?: 'ExtensionType', name: string, repo?: string | null, versionName: string, pkgName: string, lang: string, iconUrl: string, isNsfw: boolean, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean, source: { __typename?: 'SourceNodeList', nodes: Array<{ __typename?: 'SourceType', isNsfw: boolean, id: any, displayName: string, iconUrl: string, lang: string, extension: { __typename?: 'ExtensionType', pkgName: string, repo?: string | null } }> } } | null } };

export type FetchSourceMangaMutationVariables = Exact<{
page: Scalars['Int']['input'];
Expand Down Expand Up @@ -2400,6 +2400,13 @@ export type ClearCachedImagesMutationVariables = Exact<{ [key: string]: never; }

export type ClearCachedImagesMutation = { __typename?: 'Mutation', clearCachedImages: { __typename?: 'ClearCachedImagesPayload', clientMutationId?: string | null } };

export type InstallExternalExtensionMutationVariables = Exact<{
extensionFile: Scalars['Upload']['input'];
}>;


export type InstallExternalExtensionMutation = { __typename?: 'Mutation', installExternalExtension: { __typename?: 'InstallExternalExtensionPayload', extension: { __typename?: 'ExtensionType', name: string, repo?: string | null, versionName: string, pkgName: string, lang: string, iconUrl: string, isNsfw: boolean, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean, source: { __typename?: 'SourceNodeList', nodes: Array<{ __typename?: 'SourceType', isNsfw: boolean, id: any, displayName: string, iconUrl: string, lang: string, extension: { __typename?: 'ExtensionType', pkgName: string, repo?: string | null } }> } } } };

export type CategoriesQueryVariables = Exact<{
notEqualTo?: InputMaybe<Scalars['Int']['input']>;
}>;
Expand Down Expand Up @@ -2440,7 +2447,7 @@ export type SourcesQueryVariables = Exact<{
}>;


export type SourcesQuery = { __typename?: 'Query', sources: { __typename?: 'SourceNodeList', nodes: Array<{ __typename?: 'SourceType', isNsfw: boolean, id: any, displayName: string, iconUrl: string, lang: string, extension: { __typename?: 'ExtensionType', pkgName: string } }> } };
export type SourcesQuery = { __typename?: 'Query', sources: { __typename?: 'SourceNodeList', nodes: Array<{ __typename?: 'SourceType', isNsfw: boolean, id: any, displayName: string, iconUrl: string, lang: string, extension: { __typename?: 'ExtensionType', pkgName: string, repo?: string | null } }> } };

export type SourcesMigrationQueryVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -2749,6 +2756,7 @@ export const UpdateExtensionDoc = gql`
isNsfw
extension {
pkgName
repo
}
}
}
Expand Down Expand Up @@ -3018,6 +3026,26 @@ export const ClearCachedImagesDoc = gql`
}
}
`;
export const InstallExternalExtensionDoc = gql`
mutation installExternalExtension($extensionFile: Upload!) {
installExternalExtension(input: {extensionFile: $extensionFile}) {
extension {
...ExtensionTypeFragment
source {
nodes {
...SourceTypeFragment
isNsfw
extension {
pkgName
repo
}
}
}
}
}
}
${ExtensionTypeFragmentFragmentDoc}
${SourceTypeFragmentFragmentDoc}`;
export const CategoriesDoc = gql`
query categories($notEqualTo: Int = null) {
categories(filter: {id: {notEqualTo: $notEqualTo}}) {
Expand Down Expand Up @@ -3087,6 +3115,7 @@ export const SourcesDoc = gql`
isNsfw
extension {
pkgName
repo
}
}
}
Expand Down Expand Up @@ -3830,6 +3859,18 @@ export const clearCachedImages = (
});
return m;
}
export const installExternalExtension = (
options: Omit<
MutationOptions<any, InstallExternalExtensionMutationVariables>,
"mutation"
>
) => {
const m = client.mutate<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>({
mutation: InstallExternalExtensionDoc,
...options,
});
return m;
}
export const categories = (
options: Omit<
WatchQueryOptions<CategoriesQueryVariables>,
Expand Down
37 changes: 11 additions & 26 deletions src/routes/(app)/browse/extensions/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
<script lang="ts">
import { AppBarData } from '$lib/MountTitleAction';
import {
ExtensionsDoc,
fetchExtensions,
extensions as getExtensions,
type Exact,
type ExtensionsQuery,
type FetchExtensionsMutation,
type InputMaybe
} from '$lib/generated';
import { ErrorHelp, Partition, groupBy } from '$lib/util';
Expand All @@ -24,10 +22,9 @@
import Nav from '../Nav.svelte';
import { FindLangName } from '../languages';
import ExtensionsActions from './ExtensionsActions.svelte';
import { langFilter, lastFetched } from './ExtensionsStores';
import { fetchExtensionsUpdater, langFilter, lastFetched } from './ExtensionsStores';
import ExtensionCard from './ExtensionCard.svelte';
import { Meta } from '$lib/simpleStores';
import type { ApolloCache, FetchResult } from '@apollo/client';

const query = queryParam('q', ssp.string(), { pushHistory: false });
type TExtension = ExtensionsQuery['extensions']['nodes'][0];
Expand All @@ -46,33 +43,21 @@
checkIfFetchNewExtensions();

async function checkIfFetchNewExtensions() {
await ErrorHelp(
'failed to fetch new extensions',
fetchExtensions({
update: fetchExtensionsUpdater
})
);
if ($lastFetched.getTime() > new Date().getTime() - 60000) {
await ErrorHelp(
'failed to fetch new extensions',
fetchExtensions({
update: fetchExtensionsUpdater
})
);
$lastFetched = new Date();
}
extensions = getExtensions({
variables: { isNsfw: $Meta.nsfw ? null : false },
fetchPolicy: 'cache-first'
});
}

function fetchExtensionsUpdater(
cache: ApolloCache<unknown>,
{ data }: FetchResult<FetchExtensionsMutation>
) {
if (!data) return;
let filteredExtensions = data.fetchExtensions.extensions;
if (!$Meta.nsfw) filteredExtensions = filteredExtensions.filter((e) => !e.isNsfw);
cache.writeQuery({
query: ExtensionsDoc,
data: { extensions: { nodes: filteredExtensions } },
variables: { isNsfw: $Meta.nsfw ? null : false }
});
$lastFetched = new Date();
}

$: langs = getLangs($extensions?.data);
$: AppBarData('Extensions', { component: ExtensionsActions, props: { langs } });

Expand Down Expand Up @@ -156,7 +141,7 @@
<h2 class="h2 p-2">
{FindLangName(lang)}
</h2>
{#each sause as ext (ext.pkgName)}
{#each sause as ext (ext.pkgName + ext.repo)}
<ExtensionCard {ext} {scrollingElement} />
{/each}
{/each}
Expand Down
102 changes: 22 additions & 80 deletions src/routes/(app)/browse/extensions/ExtensionCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,125 +10,67 @@
import IntersectionObserver from '$lib/components/IntersectionObserver.svelte';
import Image from '$lib/components/Image.svelte';
import {
ExtensionsDoc,
SourcesDoc,
updateExtension,
type ExtensionsQuery,
type SourcesQuery,
type UpdateExtensionMutation
} from '$lib/generated';
import type { ApolloCache, FetchResult } from '@apollo/client';
import { ErrorHelp } from '$lib/util';
import { ProgressRadial } from '@skeletonlabs/skeleton';
import { Meta } from '$lib/simpleStores';
import { UpdateExtensionUpdater } from './ExtensionsStores';
import type { ApolloCache, FetchResult } from '@apollo/client';

export let scrollingElement: HTMLDivElement;
export let ext: ExtensionsQuery['extensions']['nodes'][0];

function preUpdater(
cache: ApolloCache<unknown>,
{ data }: Omit<FetchResult<UpdateExtensionMutation>, 'context'>
) {
UpdateExtensionUpdater(cache, { data }, ext);
}

let loadingUpdate = false;
let loadingInstall = false;
let loadingUnInstall = false;

function UpdateExtensionUpdater(
cache: ApolloCache<unknown>,
{ data }: Omit<FetchResult<UpdateExtensionMutation>, 'context'>,
pkgName: string
): void {
if (!data) return;
try {
const extensionsData = structuredClone(
cache.readQuery<ExtensionsQuery>({
query: ExtensionsDoc,
variables: { isNsfw: $Meta.nsfw ? null : false }
})
);
if (!extensionsData) throw new Error('failed to read extensions');
const { extensions } = extensionsData;

if (data.updateExtension.extension)
extensions.nodes[extensions.nodes.findIndex((extension) => extension.pkgName === pkgName)] =
data.updateExtension.extension;

cache.writeQuery({
query: ExtensionsDoc,
data: { extensions },
variables: { isNsfw: $Meta.nsfw ? null : false }
});
} catch {}
const sourcesData = structuredClone(
cache.readQuery<SourcesQuery>({
query: SourcesDoc,
variables: { isNsfw: $Meta.nsfw ? null : false }
})
);
if (!sourcesData) return;
const { sources } = sourcesData;

if (data.updateExtension.extension?.isInstalled) {
const sourcesToPush: SourcesQuery['sources']['nodes'] = [];
data.updateExtension.extension.source.nodes.forEach((source) => {
if (!sources.nodes.find((existingSource) => existingSource.id === source.id)) {
sourcesToPush.push(source);
}
});
sources.nodes.push(...sourcesToPush);
} else {
sources.nodes = sources.nodes.filter(
(existingSource) => existingSource.extension.pkgName !== pkgName
);
}

cache.writeQuery({
query: SourcesDoc,
variables: { isNsfw: $Meta.nsfw ? null : false },
data: { sources }
});
}

async function unInstall(pkgName: string) {
async function unInstall() {
loadingUnInstall = true;
try {
await ErrorHelp(
'failed to Uninstall extension',
updateExtension({
variables: { pkgName, uninstall: true },
update: (cache, data) => {
UpdateExtensionUpdater(cache, data, pkgName);
}
variables: { pkgName: ext.pkgName, uninstall: true },
update: preUpdater
})
);
} finally {
loadingUnInstall = false;
}
}

async function Install(pkgName: string) {
async function Install() {
loadingInstall = true;
try {
await ErrorHelp(
'Failed to Install extension',
updateExtension({
variables: { pkgName, install: true },
update: (cache, data) => {
UpdateExtensionUpdater(cache, data, pkgName);
}
variables: { pkgName: ext.pkgName, install: true },
update: preUpdater
})
);
} finally {
loadingInstall = false;
}
}

async function Update(pkgName: string) {
async function Update() {
loadingUpdate = true;
try {
await ErrorHelp(
'Failed to Update extension',
updateExtension({
variables: { pkgName, update: true },
update: (cache, data) => {
UpdateExtensionUpdater(cache, data, pkgName);
}
variables: { pkgName: ext.pkgName, update: true },
update: preUpdater
})
);
} finally {
Expand Down Expand Up @@ -163,7 +105,7 @@
</div>
<div class="flex flex-wrap flex-1 justify-end">
{#if ext.isObsolete}
<button on:click={() => unInstall(ext.pkgName)} class="btn variant-ghost-error m-1">
<button on:click={() => unInstall()} class="btn variant-ghost-error m-1">
{#if loadingUnInstall}
Uninstalling<ProgressRadial class="ml-1 h-4 aspect-square w-auto" />
{:else}
Expand All @@ -172,23 +114,23 @@
</button>
{:else if ext.isInstalled}
{#if ext.hasUpdate}
<button on:click={() => Update(ext.pkgName)} class="btn variant-ghost-surface m-1">
<button on:click={() => Update()} class="btn variant-ghost-surface m-1">
{#if loadingUpdate}
Updating<ProgressRadial class="ml-1 h-4 aspect-square w-auto" />
{:else}
Update
{/if}
</button>
{/if}
<button on:click={() => unInstall(ext.pkgName)} class="btn variant-ghost-surface m-1">
<button on:click={() => unInstall()} class="btn variant-ghost-surface m-1">
{#if loadingUnInstall}
Uninstalling<ProgressRadial class="ml-1 h-4 aspect-square w-auto" />
{:else}
Uninstall
{/if}
</button>
{:else}
<button on:click={() => Install(ext.pkgName)} class="btn variant-ghost-surface m-1">
<button on:click={() => Install()} class="btn variant-ghost-surface m-1">
{#if loadingInstall}
Installing<ProgressRadial class="ml-1 h-4 aspect-square w-auto" />
{:else}
Expand Down
Loading