Skip to content
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
13 changes: 12 additions & 1 deletion locale/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export default {
},
list: {
searching: "Searching",
emote_count: "{0} emotes",
emote_count: "{0} emote | {0} emotes",
no_emotes_listed: "No emotes found",
fetching_slowly: "Sorry, it seems this is taking a while",
category: {
Expand All @@ -211,9 +211,20 @@ export default {
new: "New",
},
filters: {
hint: "Search Filters",
zero_width: "Overlaying / Zero-Width",
animated: "Animated",
case_sensitive: "Case Sensitive",
exact_match: "Exact Match",
ignore_tags: "Ignore Tags",
sorting: "Sorting",
sorting_ascending: "Ascending",
sorting_descending: "Descending",
aspect_ratio: "Aspect Ratio",
aspect_ratio_format: "Width to Height",
aspect_ratio_width: "Ratio Width",
aspect_ratio_height: "Ratio Height",
aspect_ratio_tolerance: "Ratio Tolerance %",
},
},
context: {
Expand Down
4 changes: 2 additions & 2 deletions src/assets/gql/emotes/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Emote } from "@/structures/Emote";
import gql from "graphql-tag";

export const SearchEmotes = gql`
query SearchEmotes($query: String!, $page: Int, $limit: Int, $filter: EmoteSearchFilter) {
emotes(query: $query, page: $page, limit: $limit, filter: $filter) {
query SearchEmotes($query: String!, $page: Int, $sort: Sort, $limit: Int, $filter: EmoteSearchFilter) {
emotes(query: $query, page: $page, sort: $sort, limit: $limit, filter: $filter) {
count
items {
id
Expand Down
43 changes: 40 additions & 3 deletions src/assets/scss/emotes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,59 @@ $headingDistance: 8.5em;
align-items: center;

> .search-filters-button {
cursor: pointer;
position: relative;
right: 2em;
cursor: pointer;

filter: drop-shadow(0 0 0.5em currentColor);
&:hover {
filter: drop-shadow(0 0 0.35em currentColor) brightness(1.5);
}
}
> .search-filters {
position: absolute;
display: grid;
justify-content: start;
top: 3em;
width: 9em;
width: 12em;
border-radius: 0.25em;
border: 0.01em solid currentColor;

> div {
display: grid;
row-gap: 1em;
row-gap: 1.25em;
padding: 0.5em;
font-size: 0.85rem;
}

.sort {
display: grid;
row-gap: 0.5em;
border-top: 0.01em solid currentColor;
padding-top: 0.33em;

> p:nth-child(1) {
font-weight: bold;
}
}

.search-ratio {
border-top: 0.01em solid currentColor;
padding-top: 0.33em;

> p:nth-child(1) {
font-weight: bold;
}
> p:nth-child(2) {
font-size: 0.65rem;
margin-bottom: 1em;
}

> .ratio-inputs {
display: grid;
row-gap: 1em;
}
}
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/components/form/Dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ interface OptionData {
@import "@scss/themes.scss";
.dropdown {
width: v-bind(width);
z-index: 50;

@include themify() {
> div.dropdown-selected {
Expand All @@ -118,18 +117,19 @@ interface OptionData {
}

> div.options {
background-color: lighten(themed("backgroundColor"), 4);
background-color: darken(themed("backgroundColor"), 4);

> option:hover {
background-color: darken(themed("backgroundColor"), 4);
background-color: darken(themed("backgroundColor"), 6);
}
> option.selected {
background-color: darken(themed("backgroundColor"), 4);
background-color: darken(themed("backgroundColor"), 8);
}
}
}

> div.dropdown-selected {
z-index: 50;
cursor: pointer;
padding: 0.5em;
border-radius: 0.25em;
Expand Down
131 changes: 127 additions & 4 deletions src/views/EmoteList/EmoteList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,27 @@
</template>
</TextInput>

<div class="search-filters-button" @click="searchFilterMenu = !searchFilterMenu">
<Icon icon="gear" />
<div
v-tooltip="t('emote.list.filters.hint')"
class="search-filters-button"
@click="searchFilterMenu = !searchFilterMenu"
>
<Icon icon="filter" />
</div>
<div v-if="searchFilterMenu" ref="searchFilterMenuRef" class="search-filters">
<div>
<Checkbox
v-model="queryVariables.filter.zero_width"
:checked="queryVariables.filter.zero_width"
:label="t('emote.list.filters.zero_width')"
:style="{ color: 'goldenrod' }"
/>
<Checkbox
v-model="queryVariables.filter.animated"
:checked="queryVariables.filter.animated"
:label="t('emote.list.filters.animated')"
/>

<Checkbox
v-model="queryVariables.filter.exact_match"
:checked="queryVariables.filter.exact_match"
Expand All @@ -44,6 +60,48 @@
:label="t('emote.list.filters.ignore_tags')"
:disabled="queryVariables.filter.exact_match"
></Checkbox>

<div class="sort">
<p>{{ t("emote.list.filters.sorting") }}</p>

<Dropdown
v-model="sort.value"
:options="[
{ id: 'popularity', name: 'Popularity' },
{ id: 'age', name: 'Date Created' },
]"
/>

<Dropdown
v-model="sort.order"
:options="[
{ id: 'DESCENDING', name: t('emote.list.filters.sorting_descending') },
{
id: 'ASCENDING',
name: t('emote.list.filters.sorting_ascending'),
},
]"
/>
</div>

<div class="search-ratio">
<p>{{ t("emote.list.filters.aspect_ratio") }}</p>
<p>{{ t("emote.list.filters.aspect_ratio_format") }}</p>
<div class="ratio-inputs">
<TextInput
v-model="aspectRatio.width"
:label="t('emote.list.filters.aspect_ratio_width')"
/>
<TextInput
v-model="aspectRatio.height"
:label="t('emote.list.filters.aspect_ratio_height')"
/>
<TextInput
v-model="aspectRatio.tolerance"
:label="t('emote.list.filters.aspect_ratio_tolerance')"
/>
</div>
</div>
</div>
</div>
</div>
Expand All @@ -63,7 +121,7 @@
</div>

<div class="heading-end">
<span> {{ t("emote.list.emote_count", [itemCount]) }} </span>
<span> {{ t("emote.list.emote_count", [itemCount], { plural: itemCount }) }} </span>
</div>
<div class="down-edge" />
</div>
Expand Down Expand Up @@ -124,6 +182,7 @@ import Icon from "@/components/utility/Icon.vue";
import Checkbox from "@/components/form/Checkbox.vue";
import EmoteCardList from "@/components/utility/EmoteCardList.vue";
import { useSizedRows } from "@/composable/calculate-sized-rows";
import Dropdown from "@/components/form/Dropdown.vue";

const { t } = useI18n();

Expand All @@ -144,15 +203,73 @@ const { update: updateSizing } = useSizedRows([128, 160]);
const getSizedRows = (): number => (emotelist.value ? updateSizing(emotelist.value).sum : 0);

const category = ref((route.query.category as string)?.toLowerCase() ?? "TOP");

// Aspect Ratio Search
const aspectRatio = reactive({
used: false,
width: 1,
height: 1,
tolerance: 10,
});
if (route.query.aspect_ratio) {
const [width, height, tolerance] = (route.query.aspect_ratio as string).split(":");
aspectRatio.width = Number(width);
aspectRatio.height = Number(height);
aspectRatio.tolerance = Number(tolerance);
}

const computedRatio = computed(() =>
aspectRatio.used ? `${aspectRatio.width}:${aspectRatio.height}:${aspectRatio.tolerance}` : "",
);

watch(aspectRatio, (v) => {
if (v.width === 1 && v.height === 0 && v.tolerance === 0) {
aspectRatio.used = false;
return;
}

v.used = true;

queryVariables.filter.aspect_ratio = computedRatio.value;
});

// Sort
const sort = reactive({
used: false,
value: "popularity",
order: "DESCENDING",
});
if (route.query.sort) {
const [name, order] = (route.query.sort as string).split(":");

sort.value = name;
sort.order = order?.toUpperCase();
}

const computedSort = computed(() => (sort.used ? `${sort.value}:${sort.order.toLowerCase()}` : ""));

watch(sort, (v) => {
v.used = v.value === "popularity" && v.order === "DESCENDING";

queryVariables.sort = { value: v.value, order: v.order };
});

const queryVariables = reactive({
query: initQuery,
limit: Math.max(1, getSizedRows()),
page: initPage,
sort: {
value: sort.value,
order: sort.order,
},
filter: {
category: category.value.toUpperCase(),
exact_match: initFilter.includes("exact_match"),
case_sensitive: initFilter.includes("case_sensitive"),
ignore_tags: initFilter.includes("ignore_tags"),
zero_width: initFilter.includes("zero_width"),
animated: initFilter.includes("animated"),
aspect_ratio: computedRatio.value,
},
});

Expand Down Expand Up @@ -319,14 +436,20 @@ watch(queryVariables, (_, old) => {
}

const filter = Object.keys(queryVariables.filter)
.filter((k) => k !== "category" && queryVariables.filter[k as keyof typeof queryVariables.filter])
.filter(
(k) =>
!["category", "aspect_ratio"].includes(k) &&
queryVariables.filter[k as keyof typeof queryVariables.filter],
)
.join(",");

router[act]({
query: {
page: queryVariables.page,
query: queryVariables.query || undefined,
sort: computedSort.value || undefined,
category: category.value !== "TOP" ? category.value.toLowerCase() : undefined,
aspect_ratio: computedRatio.value || undefined,
filter: filter.length > 0 ? filter : undefined,
},
});
Expand Down