Merge pull request #52 from bees/front-ci-basic

Add CI checks for the frontend for linting and formatting
This commit is contained in:
FrenchGithubUser
2025-04-16 16:54:59 +02:00
committed by GitHub
28 changed files with 7742 additions and 88 deletions

View File

@@ -48,3 +48,23 @@ jobs:
- name: Ensure schema and query metadata are in sync
run: cargo sqlx prepare --check
lint-format-frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- name: Setup
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run check-format

1
frontend/.gitignore vendored
View File

@@ -8,7 +8,6 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
package-lock.json
.DS_Store
dist
dist-ssr

7627
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,8 @@
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"lint": "run-s lint:*",
"format": "prettier --write src/"
"format": "prettier --write src/",
"check-format": "prettier --check src"
},
"dependencies": {
"@primeuix/themes": "^1.0.0",

View File

@@ -23,7 +23,6 @@
/* semantic color variables for this project */
:root {
--color-primary: #5fe1ab;
--color-secondary: rgb(135, 110, 208);
--color-background-primary: #2f2f2f;

View File

@@ -1,15 +1,14 @@
@import './base.css';
body{
body {
margin: 0;
background-color: var(--color-background-primary); /* Dark theme */
}
#app{
#app {
max-width: 2100px;
margin: 0 auto;
font-weight: normal;
}
#app-container {
@@ -20,21 +19,21 @@ body{
color: white;
}
#view-container{
#view-container {
width: 75%;
}
a:hover{
a:hover {
background-color: transparent !important;
/* color: var(--color-primary); */
}
a{
a {
color: var(--color-secondary);
text-decoration: none;
}
.p-datatable-table,
.p-accordionpanel{
.p-accordionpanel {
border-radius: 7px;
overflow: hidden;
}
@@ -42,10 +41,10 @@ a{
width: 100% !important;
} */
.dense-accordion .p-accordionheader{
.dense-accordion .p-accordionheader {
padding: 0.5em;
}
.dense-accordion .p-accordioncontent-content{
.dense-accordion .p-accordioncontent-content {
padding-top: 1em;
}
@@ -59,14 +58,14 @@ a{
overflow-wrap: break-word !important;
}
.bold{
.bold {
font-weight: bold;
}
.warning{
.warning {
color: rgb(213, 128, 31);
}
.danger{
color:rgb(214, 52, 52);
.danger {
color: rgb(214, 52, 52);
}
.line {

View File

@@ -80,7 +80,7 @@ export default {
this.new_comment.answers_to_comment_id = null
data.created_by = {}
data.created_by = useUserStore()
// TODO: don't mutate the prop
// eslint-disable-next-line vue/no-mutating-props -- TODO: don't mutate the prop
this.comments.push(data)
this.sending_comment = false
})

View File

@@ -235,13 +235,13 @@ export default {
addCover() {
this.editionGroupForm.covers.push('')
},
removeCover(index: Number) {
removeCover(index: number) {
this.editionGroupForm.covers.splice(index, 1)
},
addLink() {
this.editionGroupForm.external_links.push('')
},
removeLink(index: Number) {
removeLink(index: number) {
this.editionGroupForm.external_links.splice(index, 1)
},
},

View File

@@ -28,7 +28,7 @@ export default {
torrent_requests: {},
},
methods: {
bytesToReadable(bytes: Number) {
bytesToReadable(bytes: number) {
return bytesToReadable(bytes)
},
},

View File

@@ -286,13 +286,13 @@ export default {
addLink() {
this.titleGroupForm.external_links.push('')
},
removeLink(index: Number) {
removeLink(index: number) {
this.titleGroupForm.external_links.splice(index, 1)
},
addCover() {
this.titleGroupForm.covers.push('')
},
removeCover(index: Number) {
removeCover(index: number) {
this.titleGroupForm.covers.splice(index, 1)
},
},

View File

@@ -191,7 +191,7 @@ export default {
return { titleGroupStore }
},
methods: {
getExternalDatabaseData(item_id: string | Number, database: string) {
getExternalDatabaseData(item_id: string | number, database: string) {
this.gettingExternalDatabaseData = true
getExternalDatabaseData(item_id, database).then((data) => {
data.title_group.original_release_date = new Date(data.title_group.original_release_date)
@@ -204,7 +204,7 @@ export default {
this.gettingExternalDatabaseData = false
})
},
async sendTitleGroup(titleGroupForm: Object) {
async sendTitleGroup(titleGroupForm: object) {
if (this.action == 'select') {
this.gettingTitleGroupInfo = true
if (!this.titleGroupStore.id) {

View File

@@ -11,9 +11,7 @@
</div>
</template>
<script lang="ts">
import { Image } from 'primevue'
export default {
components: { Image },
props: {
title_group: {},
},

View File

@@ -179,9 +179,11 @@ export default {
const reportedTorrent = this.title_group.edition_groups
.flatMap((edition_group) => edition_group.torrents)
.find((torrent) => torrent.id == torrentReport.reported_torrent_id)
reportedTorrent.reports
? reportedTorrent.reports.push(torrentReport)
: (reportedTorrent.reports = [torrentReport])
if (reportedTorrent.reports) {
reportedTorrent.reports.push(torrentReport)
} else {
reportedTorrent.reports = [torrentReport]
}
},
reportTorrent(id: number) {
this.reportingTorrentId = id
@@ -197,13 +199,13 @@ export default {
timeAgo(date: string) {
return timeAgo(date)
},
bytesToReadable(bytes: Number) {
bytesToReadable(bytes: number) {
return bytesToReadable(bytes)
},
purifyHtml(html: string) {
return DOMPurify.sanitize(html)
},
downloadTorrent(torrentId: Number) {
downloadTorrent(torrentId: number) {
downloadTorrent(torrentId)
},
},
@@ -218,9 +220,9 @@ export default {
},
computed: {
getEditionGroupSlug() {
return (id: Number) => {
return (id: number) => {
return getEditionGroupSlug(
this.title_group.edition_groups.find((group: Object) => group.id === id),
this.title_group.edition_groups.find((group: object) => group.id === id),
)
}
},

View File

@@ -1,4 +1,4 @@
import axios from 'axios';
import axios from 'axios'
const api = axios.create({
baseURL: 'http://localhost:8080/api',
@@ -6,16 +6,19 @@ const api = axios.create({
headers: {
'Content-Type': 'application/json',
},
});
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token && !config.url?.includes('/login') && !config.url?.includes('/register')) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token && !config.url?.includes('/login') && !config.url?.includes('/register')) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
},
)
export default api;
export default api

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const getArtist = async (id: string | Number) => {
export const getArtist = async (id: string | number) => {
try {
return (await api.get('/artist?id=' + id)).data
} catch (error) {

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const login = async (form: Object) => {
export const login = async (form: object) => {
try {
return (await api.post('/login', form)).data
} catch (error) {
@@ -9,7 +9,7 @@ export const login = async (form: Object) => {
}
}
export const register = async (form: Object) => {
export const register = async (form: object) => {
try {
return (await api.post('/register', form)).data
} catch (error) {

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const getExternalDatabaseData = async (item_id: string | Number, database: string) => {
export const getExternalDatabaseData = async (item_id: string | number, database: string) => {
try {
switch (database) {
case 'openlibrary': {

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const subscribeToItem = async (item_id: string | Number, item: string) => {
export const subscribeToItem = async (item_id: string | number, item: string) => {
try {
return (await api.post('/subscription?item=' + item + '&item_id=' + item_id)).data
} catch (error) {
@@ -8,7 +8,7 @@ export const subscribeToItem = async (item_id: string | Number, item: string) =>
throw error
}
}
export const unsubscribeToItem = async (item_id: string | Number, item: string) => {
export const unsubscribeToItem = async (item_id: string | number, item: string) => {
try {
return (await api.delete('/subscription?item=' + item + '&item_id=' + item_id)).data
} catch (error) {

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const getSeries = async (id: string | Number) => {
export const getSeries = async (id: string | number) => {
try {
return (await api.get('/series?id=' + id)).data
} catch (error) {

View File

@@ -1,6 +1,6 @@
import api from './api.ts'
export const getTitleGroup = async (id: string | Number) => {
export const getTitleGroup = async (id: string | number) => {
try {
return (await api.get('/title-group?id=' + id)).data
} catch (error) {
@@ -8,7 +8,7 @@ export const getTitleGroup = async (id: string | Number) => {
throw error
}
}
export const getTitleGroupLite = async (id: string | Number) => {
export const getTitleGroupLite = async (id: string | number) => {
try {
return (await api.get('/title-group/lite?id=' + id)).data
} catch (error) {
@@ -52,7 +52,7 @@ export const uploadTorrent = async (torrentForm: object) => {
throw error
}
}
export const downloadTorrent = async (torrentId: Number | string) => {
export const downloadTorrent = async (torrentId: number | string) => {
try {
const response = await api.get('/torrent?id=' + torrentId, {
responseType: 'blob',

View File

@@ -15,7 +15,11 @@ function preProcess(text) {
function getType(text) {
text = preProcess(text)
return text.match(/Disc (Title|Label)\s*:/i) ? 'bdinfo' : text.match(/Complete name\s*:/i) ? 'mediainfo' : null
return text.match(/Disc (Title|Label)\s*:/i)
? 'bdinfo'
: text.match(/Complete name\s*:/i)
? 'mediainfo'
: null
}
export const getFileInfo = (text) => {
@@ -43,4 +47,3 @@ export const getFileInfo = (text) => {
return null
}
}

View File

@@ -160,8 +160,8 @@ export default class MediainfoConverter {
/2160p/i.test(completeName) || width === '3840'
? '2160p'
: /1080i/i.test(completeName) ||
((width === '1920' || (width < 1920 && height === '1080')) &&
(scanType === 'Interlaced' || scanType === 'MBAFF'))
((width === '1920' || (width < 1920 && height === '1080')) &&
(scanType === 'Interlaced' || scanType === 'MBAFF'))
? '1080i'
: /1080p/i.test(completeName) || width === '1920' || (width < 1920 && height === '1080')
? '1080p'

View File

@@ -35,7 +35,7 @@ export default class MediainfoParser {
return result
}
extractFields({ sectionName, sectionBody }) {
extractFields({ sectionName: _sectionName, sectionBody }) {
const result = {}
const lines = splitIntoLines(sectionBody)
for (const line of lines) {
@@ -47,4 +47,4 @@ export default class MediainfoParser {
}
return result
}
}
}

View File

@@ -32,7 +32,7 @@ export function splitIntoBDInfoSections(text) {
.trim()
.replace(/\n\r/, '\n')
.split(/([a-zA-Z ]*:\n)/)
.map((v) => v.trim())
.map((v) => v.trim()),
)
}
@@ -42,7 +42,7 @@ export function splitIntoSections(text) {
.trim()
.replace(/\n\r/, '\n')
.split(/\n\s*\n/)
.map((v) => v.trim())
.map((v) => v.trim()),
)
}
@@ -52,7 +52,7 @@ export function splitIntoLines(text) {
.trim()
.replace(/\n\r/, '\n')
.split(/\n/)
.map((v) => v.trim())
.map((v) => v.trim()),
)
}
@@ -78,4 +78,4 @@ export function calcDiskType(size) {
} else if (size <= DISK_SIZE.BD100) {
return 'BD100'
}
}
}

View File

@@ -8,7 +8,7 @@ export const timeAgo = (date: string) => {
? `${Math.floor(diff / 3600)}h ago`
: `${Math.floor(diff / 86400)}d ago`
}
export const bytesToReadable = (bytes: Number) => {
export const bytesToReadable = (bytes: number) => {
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
let size = bytes
let unitIndex = 0
@@ -69,7 +69,7 @@ export const isValidUrl = (url: string) => {
try {
new URL(url)
return true
} catch (_) {
} catch {
return false
}
}

View File

@@ -22,13 +22,14 @@
/>
</div>
</ContentContainer>
<TitleGroupPreviewTable
v-for="title_group in title_groups"
:key="title_group.id"
:title_group="title_group"
v-if="title_group_preview_mode == 'table'"
class="preview-table"
/>
<div v-if="title_group_preview_mode == 'table'">
<TitleGroupPreviewTable
v-for="title_group in title_groups"
:key="title_group.id"
:title_group="title_group"
class="preview-table"
/>
</div>
</div>
<ArtistSidebar :artist />
</div>

View File

@@ -22,13 +22,14 @@
/>
</div>
</ContentContainer>
<TitleGroupPreviewTable
v-for="title_group in title_groups"
:key="title_group.id"
:title_group="title_group"
v-if="title_group_preview_mode == 'table'"
class="preview-table"
/>
<div v-if="title_group_preview_mode == 'table'">
<TitleGroupPreviewTable
v-for="title_group in title_groups"
:key="title_group.id"
:title_group="title_group"
class="preview-table"
/>
</div>
</div>
<SeriesSidebar :series />
</div>

View File

@@ -15,13 +15,14 @@
/>
</div>
</ContentContainer>
<TitleGroupPreviewTable
v-for="title_group in search_results.title_groups"
:key="title_group.id"
:title_group="title_group"
v-if="title_group_preview_mode == 'table'"
class="preview-table"
/>
<div v-if="title_group_preview_mode == 'table'">
<TitleGroupPreviewTable
v-for="title_group in search_results.title_groups"
:key="title_group.id"
:title_group="title_group"
class="preview-table"
/>
</div>
</div>
</template>