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

Thumbnails for images and videos #231

Open
wants to merge 3 commits into
base: staging
Choose a base branch
from
Open
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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ Developers: you will likely want to run `npm run serve` in one terminal and `npm

Ops will want to use `npm run build`, `npm run test` or `npm run test:coverage`, `npm run prettier:check`

## NSFW api
## Configuration

This build uses an API to check nsfw content.
The following environment variables may be used to configure the frontend.

The default API endpoint to is: https://api.ipfs-search.com/nsfw/
. This can be overridden by injecting environment variable `VITE_NSFW_API`
### Variables

The API call should be: `<VITE_NSFW_API><CID>`, so e.g.

`https://api.ipfs-search.com/nsfw/QmSZzv7ux1LGwpehVcCMQ9ec945X6qE4qyjKDhCVwY25iw`
https://api.ipfs-search.com/v1/nsfw/classify/
- `VITE_IPFS_GATEWAY`: Gateway for URL generation. Default: `https://dweb.link`
- `VITE_NSFW_API`: Endpoint for [nsfw-server](https://github.com/ipfs-search/nsfw-server). Default: `https://api.ipfs-search.com/v1/nsfw/classify/`
- `VITE_NYATS_API`: Endpoint for [nyats](https://github.com/ipfs-search/nyats) (Not Yet Another Thumbnail Server) API. Default: `https://api.ipfs-search.com/v1/thumbnail/`
- `VITE_NYATS_IPFS_GATEWAY`: Gateway for nyats. Default: `https://gw.dwebsearch.com`
- `VITE_NYATS_IPNS_ROOT`: Root for thumbnails over IPNS. Default: `/ipns/12D3KooWPVobDRG9Mdmact3ejSe6UAFP8cevmw35HHR1ZDwCozSo/`
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"masonry-layout": "^4.2.2",
"mime": "^3.0.0",
"moment": "^2.29.4",
"nyats-client": "^0.2.0-alpha.2",
"pretty-bytes": "^6.0.0",
"regenerator-runtime": "^0.13.9",
"ts-node": "^10.8.0",
Expand Down Expand Up @@ -57,6 +58,7 @@
"prettier": "2.7.1",
"sass": "^1.53.0",
"typescript": "^4.7.3",
"url-join": "^5.0.0",
"vite": "^3.0.2",
"vite-plugin-vuetify": "^1.0.0-alpha.11",
"vitest": "^0.13.1"
Expand Down
19 changes: 15 additions & 4 deletions src/components/detailViewComponents/ImageDetail.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
<script setup>
import { mdiRobotDead } from "@mdi/js";

import NsfwTooltip from "@/components/shared/nsfwTooltip.vue";
import { detailProps } from "@/composables/useDetail";
defineProps(detailProps);
import getResourceURL from "@/helpers/resourceURL";

import GenericDetail from "@/components/detailViewComponents/GenericDetail.vue";
import { useBlurExplicit } from "@/composables/BlurExplicitImagesComposable";
const { blurExplicit } = useBlurExplicit();

import NyatsImg from "@/helpers/nyats/vuetify-img-cid.vue";
</script>

<template>
<generic-detail :file="file">
<v-row>
<v-col cols="12" md="10" offset-md="1" :class="{ blurExplicit: blurExplicit(file) }">
<v-img
<nyats-img
:cid="file.hash"
type="image"
:data-nsfw-classification="JSON.stringify(file.nsfwClassification)"
:data-nsfw="file.nsfw"
:src="getResourceURL(file.hash)"
max-height="400px"
class="image-wrapper"
:class="{ blurExplicit: blurExplicit(file) }"
Expand All @@ -26,8 +30,15 @@ const { blurExplicit } = useBlurExplicit();
<v-progress-circular indeterminate color="ipfsPrimary" />
</v-row>
</template>

<template #failed>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-icon color="grey" size="large" :icon="mdiRobotDead" />
</v-row>
</template>

<NsfwTooltip :file="file" />
</v-img>
</nyats-img>
</v-col>
</v-row>
</generic-detail>
Expand Down
21 changes: 14 additions & 7 deletions src/components/searchViewComponents/ImageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
import NsfwTooltip from "@/components/shared/nsfwTooltip.vue";
import ListBase from "./BaseList.vue";
import HoverCard from "./subcomponents/HoverCard.vue";
import { useFileListComposable, imports } from "@/composables/useFileListComposable";
import { useFileListComposable } from "@/composables/useFileListComposable";
import { useBlurExplicit } from "@/composables/BlurExplicitImagesComposable";
import { Types } from "@/helpers/typeHelper";
import { useDisplay } from "vuetify";
import { mdiRobotDead } from "@mdi/js";
import NyatsImg from "@/helpers/nyats/vuetify-img-cid.vue";

const fileType = Types.images;
const { xs, smAndDown, mdAndDown } = useDisplay();

const { slicedHits } = useFileListComposable({ fileType });

const { getResourceURL } = imports;

const { blurExplicit } = useBlurExplicit();
</script>

Expand All @@ -30,8 +29,9 @@ const { blurExplicit } = useBlurExplicit();
lg="2"
>
<hover-card :hit="hit" :index="index" :file-type="fileType">
<v-img
:src="getResourceURL(hit.hash)"
<nyats-img
:cid="hit.hash"
type="image"
aspect-ratio="1"
:class="{ blurExplicit: blurExplicit(hit) }"
:data-nsfw-classification="JSON.stringify(hit.nsfwClassification)"
Expand All @@ -43,8 +43,15 @@ const { blurExplicit } = useBlurExplicit();
<v-progress-circular indeterminate color="ipfsPrimary" />
</v-row>
</template>

<template #failed>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-icon color="grey" size="large" :icon="mdiRobotDead" />
</v-row>
</template>

<NsfwTooltip :file="hit" />
</v-img>
</nyats-img>
</hover-card>
</v-col>
</v-row>
Expand Down
59 changes: 40 additions & 19 deletions src/components/searchViewComponents/VideoList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,59 @@
import ListBase from "./BaseList.vue";
import HoverCard from "./subcomponents/HoverCard.vue";
import { useFileListComposable } from "@/composables/useFileListComposable";
import { useBlurExplicit } from "@/composables/BlurExplicitImagesComposable";
import CardContent from "@/components/searchViewComponents/subcomponents/genericCardContent.vue";
import MediaCenterIcon from "@/components/searchViewComponents/subcomponents/MediaCenterIcon.vue";
import { mdiVideo } from "@mdi/js";
import { Types } from "@/helpers/typeHelper";
import { picsum } from "@/helpers/picsum";
import { mdiRobotDead, mdiTimerSand } from "@mdi/js";
import NyatsImg from "@/helpers/nyats/vuetify-img-cid.vue";

const fileType = Types.video;

const { slicedHits } = useFileListComposable({ fileType });
const { blurExplicit } = useBlurExplicit();
</script>

<template>
<ListBase :file-type="fileType">
<v-col v-for="(hit, index) in slicedHits(3)" :key="index" cols="12" xl="8" offset-xl="2">
<hover-card :hit="hit" :index="index" :file-type="fileType">
<v-row>
<v-col cols="12" sm="4" md="3" lg="2">
<v-img
class="ma-3 mr-sm-0"
cover
aspect-ratio="1.778"
:src="hit.src || picsum({ seed: hit.hash })"
gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
<v-col id="resultsList" cols="12" xl="8" offset-xl="2">
<v-row dense>
<v-col
v-for="(hit, index) in slicedHits(xs ? 2 : smAndDown ? 6 : mdAndDown ? 8 : 12)"
:key="index"
cols="6"
sm="4"
md="3"
lg="2"
>
<hover-card :hit="hit" :index="index" :file-type="fileType">
<nyats-img
:cid="hit.hash"
type="video"
aspect-ratio="1"
:class="{ blurExplicit: blurExplicit(hit) }"
:data-nsfw-classification="JSON.stringify(hit.nsfwClassification)"
:data-nsfw="hit.nsfw"
class="rounded grey lighten-2"
>
<media-center-icon :icon="mdiVideo" />
</v-img>
</v-col>
<v-col cols="12" sm="8" md="9" lg="10" class="py-sm-0 ml-sm-n6">
<CardContent :hit="hit" />
</v-col>
</v-row>
</hover-card>
<template #placeholder>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-progress-circular indeterminate color="ipfsPrimary" />
</v-row>
</template>

<template #failed>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-icon color="grey" size="large" :icon="mdiRobotDead" />
</v-row>
</template>

<NsfwTooltip :file="hit" />
</nyats-img>
</hover-card>
</v-col>
</v-row>
</v-col>
</ListBase>
</template>
4 changes: 3 additions & 1 deletion src/composables/useFileListComposable.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const useFileListComposable = ({ fileType }) => {
return [Types.all, undefined].includes(route.query.type);
});

const infinite = computed(() => route.query.type === Types.images);
const infinite = computed(
() => route.query.type === Types.images || route.query.type === Types.video
);

const loadedPages = computed(() =>
Math.ceil(store.getters[`results/${fileType}/hits`].length / batchSize)
Expand Down
76 changes: 76 additions & 0 deletions src/helpers/nyats/vuetify-img-cid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<v-img ref="img" :src="thumbURL()" v-bind="$attrs" @error="thumbErr()">
<template #placeholder>
<slot v-if="generateErr" name="failed" />
<slot v-else name="placeholder" />
</template>

<template #failed> <slot name="failed" /> </template>

<template #default>
<slot name="default" />
</template>
</v-img>
</template>

<script>
import { DefaultConfig, GetClient } from "nyats-client";

const config = {
endpoint: import.meta.env.VITE_NYATS_API || DefaultConfig.endpoint,
gatewayURL: import.meta.env.VITE_NYATS_IPFS_GATEWAY || DefaultConfig.gatewayURL,
ipnsRoot: import.meta.env.VITE_NYATS_IPNS_ROOT || DefaultConfig.ipnsRoot,
};

const { IPNSThumbnailURL, GenerateThumbnailURL } = GetClient(config);

export default {
props: {
cid: {
type: String,
required: true,
},
type: {
type: String,
required: false,
default: null,
},
},
data: () => ({
ipnsErr: false,
generateErr: false,
width: null,
height: null,
}),
mounted() {
this.updateSize(this.$refs.img);
},
updated() {
this.updateSize(this.$refs.img);
},
methods: {
updateSize(el) {
if (el) {
this.height = el.$el.clientHeight;
this.width = el.$el.clientHeight;
}
},
thumbErr() {
if (this.ipnsErr) {
this.generateErr = true;
} else {
this.ipnsErr = true;
}
},
thumbURL() {
if (this.width && this.height) {
if (this.ipnsErr) {
return GenerateThumbnailURL(this.cid, this.width, this.height, this.type);
}

return IPNSThumbnailURL(this.cid, this.width, this.height);
}
},
},
};
</script>
9 changes: 4 additions & 5 deletions src/helpers/resourceURL.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const gatewayURL = "https://gateway.ipfs.io";
const gatewayURL = import.meta.env.VITE_IPFS_GATEWAY || "https://dweb.link";

function getResourceURL(hash) {
return `${gatewayURL}/ipfs/${hash}`;
export default function getResourceURL(hash) {
const resourcePath = `/ipfs/${hash}`;
return new URL(resourcePath, gatewayURL).toString();
}

export default getResourceURL;
2 changes: 1 addition & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import createVuePlugin from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [createVuePlugin(), vuetify()],
server: {
port: 8080,
port: 8082,
},
resolve: {
alias: {
Expand Down