Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion Procfile.dev
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
web: env RUBY_DEBUG_OPEN=true bin/rails server
js: yarn build --watch
js: env NODE_ENV=development yarn build --watch
5 changes: 3 additions & 2 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ nav.navbar {
.navbar-search-dropdown {
max-height: 400px;
height: auto;
overflow: scroll;
// Don't display a horizontal scrollbar.
// only display the overflow when necessary.
overflow: auto;
// Don't display a horizontal scrollbar ever.
overflow-x: hidden;
}

Expand Down
20 changes: 10 additions & 10 deletions app/javascript/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import Rails from '@rails/ujs';
import Turbolinks from 'turbolinks';
import * as ActiveStorage from '@rails/activestorage';
import '../assets/stylesheets/application.scss';
import Vue from 'vue';
import * as Sentry from "@sentry/vue";
// import Vue from 'vue';
// import * as Sentry from "@sentry/vue";
import './src/vue-loader';
import './src/toggleable-buttons';
import './src/bulma';
import './src/settings';

if (process.env.NODE_ENV === 'production') {
Sentry.init({
Vue,
dsn: process.env.SENTRY_DSN_JS,
integrations: [],
environment: process.env.NODE_ENV
});
}
// if (process.env.NODE_ENV === 'production') {
// Sentry.init({
// Vue,
// dsn: process.env.SENTRY_DSN_JS,
// integrations: [],
// environment: process.env.NODE_ENV
// });
// }

Rails.start();
Turbolinks.start();
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/components/avatar-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<div class="user-avatar" v-if="existingAvatar">
<img :src="existingAvatar" width="150px" height="150px">
</div>
<file-select v-model="avatar" @input="onChange" :fileClass="'user-avatar'"></file-select>
<file-select v-model="avatar" @update:modelValue="onChange" :fileClass="'user-avatar'"></file-select>
</div>
<div class="field">
<button
Expand Down
9 changes: 4 additions & 5 deletions app/javascript/src/components/compare-libraries.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<vue-good-table
<p>TODO: Replace me once we have a Vue Good Table replacement</p>
<!-- <vue-good-table
:theme="theme"
:columns="columns"
:rows="rows"
Expand Down Expand Up @@ -35,13 +36,11 @@
<span v-if="isLoading">Loading...</span>
<span v-else-if="!isLoading" class="vgt-text-disabled">No games to compare.</span>
</div>
</vue-good-table>
</vue-good-table> -->
</template>

<script setup lang="ts">
// @ts-expect-error - vue-good-table doesn't have TypeScript declarations
import { VueGoodTable } from 'vue-good-table';
import 'vue-good-table/dist/vue-good-table.css';

import { concat } from 'lodash-es';
import { ref, computed, onMounted } from 'vue';

Expand Down
23 changes: 11 additions & 12 deletions app/javascript/src/components/fields/date-field.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<template>
<div class="field">
<label class="label" v-bind:for="dateFieldId">{{ label }}</label>
<label class="label" :for="dateFieldId">{{ label }}</label>
<div class="control">
<input
autocomplete="off"
class="input"
type="date"
:required="required"
v-bind:name="dateFieldName"
v-bind:id="dateFieldId"
v-bind:value="dataValue"
v-on:input="handleInput"
:name="dateFieldName"
:id="dateFieldId"
:value="dataValue"
@input="handleInput"
>
</div>
</div>
Expand All @@ -23,7 +23,7 @@ interface Props {
formClass: string;
attribute: string;
label: string;
value?: string;
modelValue?: string;
required?: boolean;
}

Expand All @@ -33,17 +33,16 @@ const props = withDefaults(defineProps<Props>(), {
required: false
});

const emit = defineEmits(['input']);
const emit = defineEmits(['update:modelValue']);

const dataValue = ref(props.value);
const dataValue = ref(props.modelValue);

// Handle input events with proper typing
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
emit('input', target.value);
function handleInput(event: InputEvent) {
emit('update:modelValue', (event.target as HTMLInputElement).value);
}

watch(() => props.value, (newVal) => {
watch(() => props.modelValue, (newVal) => {
dataValue.value = newVal;
});

Expand Down
10 changes: 5 additions & 5 deletions app/javascript/src/components/fields/file-select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<!-- We can't use a normal button element here, as it would become the target of the label. -->
<div class="button">
<!-- Display the filename if a file has been selected. -->
<span v-if="value">Selected File: {{ value.name }}</span>
<span v-if="modelValue">Selected File: {{ modelValue.name }}</span>
<span v-else>Select File</span>
</div>
<!-- We hide this file input. -->
Expand All @@ -21,7 +21,7 @@
import { ref } from 'vue';

interface Props {
value?: File;
modelValue?: File;
label?: string;
fileClass?: string;
}
Expand All @@ -30,7 +30,7 @@ const props = withDefaults(defineProps<Props>(), {
fileClass: 'game-cover'
});

const emit = defineEmits(['input']);
const emit = defineEmits(['update:modelValue']);

const image = ref<string | null>(null);

Expand All @@ -45,8 +45,8 @@ const handleFileChange = (e: Event) => {
};
reader.readAsDataURL(file);

// Whenever the file changes, emit the 'input' event with the file data.
emit('input', file);
// Whenever the file changes, emit the 'update:modelValue' event with the file data.
emit('update:modelValue', file);
}
};
</script>
29 changes: 13 additions & 16 deletions app/javascript/src/components/fields/multi-select-generic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,40 @@
<div class="field">
<label class="label" :for="inputId">{{ label }}</label>
<div class="control">
<v-select
multiple
:taggable="true"
<!-- TODO: Handle changes -->
<vue-select
:is-multi="true"
:is-taggable="true"
:inputId="inputId"
:label="vSelectLabel"
@change="handleChange"
v-bind:value="value"
v-on:input="$emit('input', $event)"
></v-select>
:modelValue="modelValue"
@update:modelValue="$emit('update:modelValue', $event)"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
import { computed } from 'vue';
import VueSelect from 'vue3-select-component';
import { snakeCase } from 'lodash-es';

interface Props {
label: string;
value: any[];
modelValue: any[];
// TODO: I think we just need to map the value to this in the option?
vSelectLabel?: string;
}

const props = withDefaults(defineProps<Props>(), {
vSelectLabel: "name"
});

const emit = defineEmits(['input']);

// Reactive data
const options = ref<any[]>([]);
const emit = defineEmits(['update:modelValue']);

// Methods
function handleChange(selectedItems: any[]) {
emit('input', selectedItems);
emit('update:modelValue', selectedItems);
}

// Computed properties
Expand Down
72 changes: 43 additions & 29 deletions app/javascript/src/components/fields/multi-select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,84 @@
<div class="field">
<label v-if="label" class="label" :for="inputId">{{ label }}</label>
<div class="control">
<v-select
multiple
<!-- TODO: Make this work by adding events for state changes -->
<vue-select
:is-multi="true"
:options="options"
@search="onSearch"
:isLoading="isLoading"
:inputId="inputId"
label="name"
:placeholder="placeholder"
@change="handleChange"
v-bind:value="value"
v-on:input="$emit('input', $event)"
@optionSelected="optionSelected"
@optionDeselected="optionDeselected"
:isClearable="true"
v-model="modelValue"
@update:modelValue="$emit('update:modelValue', $event)"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
import VueSelect, { type Option } from 'vue3-select-component';
import { debounce, snakeCase } from 'lodash-es';

interface Props {
label?: string;
value: any[];
searchPathIdentifier: string;
modelValue: any[];
searchPathIdentifier: 'genres' | 'platforms' | 'engines' | 'series' | 'companies' | 'stores';
placeholder?: string;
}

const props = defineProps<Props>();

const emit = defineEmits(['input']);
const emit = defineEmits(['update:modelValue']);

// Reactive data
const options = ref<any[]>([]);
const searchPath = `${window.location.origin}/${props.searchPathIdentifier}/search.json`;
const options = ref<Option<string>[]>([]);
const searchPath = computed(() => {
return `${window.location.origin}/${props.searchPathIdentifier}/search.json`;
});

// Methods
function handleChange(selectedItems: any[]) {
emit('input', selectedItems);
emit('update:modelValue', selectedItems);
}

const isLoading = ref(false);

/*
* @param {search} String Current search text
* @param {loading} Function Toggle loading class
* @param search Current search text
*/
const onSearch = debounce((search: string, loading: (state: boolean) => void) => {
loading(true);
let searchUrl = new URL(searchPath);
const onSearch = debounce(async (search: string) => {
isLoading.value = true;
const searchUrl = new URL(searchPath.value);
searchUrl.searchParams.append('query', search);
// TODO: Add error handling.
fetch(searchUrl.toString(), {
const response = await fetch(searchUrl.toString(), {
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
return response.json();
})
.then(items => {
options.value = items;
loading(false);
});
});

// TODO: Add error handling.
const data = await response.json();

// Map the returned objects to the format vue-select likes (objects with label and value keys).
options.value = data.map((item: { id: number; name: string }) => ({ label: item.name, value: item.id })) ?? [];
isLoading.value = false;
}, 250);

const optionDeselected = (option: Option<string>) => {
const newValue = props.modelValue.filter((item: any) => item.id !== option.value);
handleChange(newValue);
};

const optionSelected = (option: Option<string>) => {
const newValue = [...props.modelValue, { id: option.value, name: option.label }];
handleChange(newValue);
};

// Computed properties
const inputId = computed(() => {
return snakeCase(props.label);
Expand Down
Loading