mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-29 05:48:42 +00:00
feat: custom default font
This commit is contained in:
parent
bd6e302705
commit
a19920729f
6 changed files with 188 additions and 64 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Miru",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
|
||||
"description": "Stream anime torrents, real-time with no waiting for downloads.",
|
||||
"main": "src/index.js",
|
||||
|
|
|
|||
81
src/renderer/src/lib/FontSelect.svelte
Normal file
81
src/renderer/src/lib/FontSelect.svelte
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<script context='module'>
|
||||
import { writable } from 'simple-store-svelte'
|
||||
|
||||
const fonts = writable({})
|
||||
let availableFonts = null
|
||||
async function loadFonts () {
|
||||
const _fonts = {}
|
||||
if (availableFonts) return
|
||||
const styleSheet = new CSSStyleSheet()
|
||||
try {
|
||||
availableFonts = await queryLocalFonts()
|
||||
for (const metadata of availableFonts) {
|
||||
if (!_fonts[metadata.family]) {
|
||||
_fonts[metadata.family] = []
|
||||
}
|
||||
_fonts[metadata.family].push(metadata)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err.name, err.message)
|
||||
}
|
||||
for (const fontFamily of Object.keys(_fonts)) {
|
||||
_fonts[fontFamily] = _fonts[fontFamily]
|
||||
.map(font => {
|
||||
// Replace font variation name "Arial" with "Arial Regular".
|
||||
const variationName = font.fullName
|
||||
.replace(fontFamily, '')
|
||||
.replaceAll('-', '')
|
||||
.trim()
|
||||
font.variationName = variationName || 'Regular'
|
||||
return font
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// "Regular" always comes first, else use alphabetic order.
|
||||
if (a.variationName === 'Regular') {
|
||||
return -1
|
||||
} else if (b.variationName === 'Regular') {
|
||||
return 1
|
||||
} else if (a.variationName < b.variationName) {
|
||||
return -1
|
||||
} else if (a.variationName > b.variationName) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
for (const font of _fonts[fontFamily]) {
|
||||
styleSheet.insertRule(`@font-face { font-family: '${font.postscriptName}'; src: local('${font.postscriptName}'), local('${font.fullName}'); }`)
|
||||
}
|
||||
}
|
||||
|
||||
document.adoptedStyleSheets = [
|
||||
styleSheet,
|
||||
...document.adoptedStyleSheets
|
||||
]
|
||||
fonts.value = _fonts
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function fontChange ({ target }) {
|
||||
const value = target.value
|
||||
const font = availableFonts.find(({ postscriptName }) => postscriptName === value)
|
||||
dispatch('change', font)
|
||||
}
|
||||
export let value
|
||||
loadFonts()
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<select on:click|once={loadFonts} on:change={fontChange} {...$$restProps} style='font-family: {value}' bind:value>
|
||||
{#each Object.entries($fonts) as [fontName, fonts]}
|
||||
<optgroup label={fontName} style='font-family: {fontName}'>
|
||||
{#each fonts as fontData}
|
||||
<option style='font-family: {fontData.postscriptName}' value={fontData.postscriptName}>{fontData.fullName}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
{/each}
|
||||
</select>
|
||||
|
|
@ -1,54 +1,54 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { traceAnime } from '@/modules/anime.js'
|
||||
import { getContext } from 'svelte'
|
||||
import { traceAnime } from '@/modules/anime.js'
|
||||
|
||||
export let search
|
||||
export let current
|
||||
export let media = null
|
||||
export let loadCurrent
|
||||
let searchTimeout = null
|
||||
let searchTextInput
|
||||
export let search
|
||||
export let current
|
||||
export let media = null
|
||||
export let loadCurrent
|
||||
let searchTimeout = null
|
||||
let searchTextInput
|
||||
|
||||
const view = getContext('view')
|
||||
const view = getContext('view')
|
||||
|
||||
$: !$view && searchTextInput?.focus()
|
||||
function searchClear () {
|
||||
search = {
|
||||
format: '',
|
||||
genre: '',
|
||||
season: '',
|
||||
sort: '',
|
||||
status: ''
|
||||
}
|
||||
current = null
|
||||
searchTextInput?.focus()
|
||||
}
|
||||
function input () {
|
||||
if (!searchTimeout) {
|
||||
if (Object.values(search).filter(v => v).length) media = [new Promise(() => {})]
|
||||
} else {
|
||||
clearTimeout(searchTimeout)
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
if (current === null) {
|
||||
if (Object.values(search).filter(v => v).length) current = 'search'
|
||||
} else {
|
||||
if (Object.values(search).filter(v => v).length) {
|
||||
loadCurrent(false)
|
||||
} else {
|
||||
current = null
|
||||
}
|
||||
$: !$view && searchTextInput?.focus()
|
||||
function searchClear () {
|
||||
search = {
|
||||
format: '',
|
||||
genre: '',
|
||||
season: '',
|
||||
sort: '',
|
||||
status: ''
|
||||
}
|
||||
current = null
|
||||
searchTextInput?.focus()
|
||||
}
|
||||
function input () {
|
||||
if (!searchTimeout) {
|
||||
if (Object.values(search).filter(v => v).length) media = [new Promise(() => {})]
|
||||
} else {
|
||||
clearTimeout(searchTimeout)
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
if (current === null) {
|
||||
if (Object.values(search).filter(v => v).length) current = 'search'
|
||||
} else {
|
||||
if (Object.values(search).filter(v => v).length) {
|
||||
loadCurrent(false)
|
||||
} else {
|
||||
current = null
|
||||
}
|
||||
}
|
||||
searchTimeout = null
|
||||
}, 500)
|
||||
}
|
||||
function handleFile ({ target }) {
|
||||
const { files } = target
|
||||
if (files?.[0]) {
|
||||
traceAnime(files[0], 'file')
|
||||
target.value = null
|
||||
}
|
||||
searchTimeout = null
|
||||
}, 500)
|
||||
}
|
||||
function handleFile ({ target }) {
|
||||
const { files } = target
|
||||
if (files?.[0]) {
|
||||
traceAnime(files[0], 'file')
|
||||
target.value = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='container-fluid row p-20' on:input={input}>
|
||||
|
|
@ -61,7 +61,6 @@ function handleFile ({ target }) {
|
|||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text d-flex material-icons bg-dark pr-0 font-size-18'>search</span>
|
||||
</div>
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
on:input={({ target }) => {
|
||||
|
|
@ -170,7 +169,7 @@ function handleFile ({ target }) {
|
|||
<option value='UPDATED_TIME_DESC' disabled hidden>Updated Date</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type='file' class='d-none' id='search-image' accept='image/*' on:input={handleFile}>
|
||||
<input type='file' class='d-none' id='search-image' accept='image/*' on:input={handleFile} />
|
||||
<div class='col-auto p-10 d-flex'>
|
||||
<button class='btn bg-dark material-icons font-size-18 px-5 align-self-end shadow-lg border-0' type='button'>
|
||||
<label for='search-image' class='pointer'>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
|
||||
<script>
|
||||
import { Tabs, TabLabel, Tab } from './Tabination.js'
|
||||
import FontSelect from './FontSelect.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
onDestroy(() => {
|
||||
|
|
@ -110,6 +111,25 @@
|
|||
window.IPC.on('path', data => {
|
||||
settings.torrentPath = data
|
||||
})
|
||||
async function changeFont ({ detail }) {
|
||||
try {
|
||||
const blob = await detail.blob()
|
||||
const data = await blob.arrayBuffer()
|
||||
settings.font = {
|
||||
name: detail.fullName,
|
||||
value: detail.postscriptName,
|
||||
data: [...new Uint8Array(data)]
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
addToast({
|
||||
text: /* html */`${error.message}<br>Try using a different font.`,
|
||||
title: 'File Error',
|
||||
type: 'secondary',
|
||||
duration: 8000
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Tabs>
|
||||
|
|
@ -161,6 +181,19 @@
|
|||
<div class='h-full p-20 m-20'>
|
||||
<Tab>
|
||||
<div class='root'>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-icons mr-10 font-size-30'>font_download</div>
|
||||
Default Subtitle Font
|
||||
</div>
|
||||
<FontSelect class='form-control bg-dark shadow-lg w-300' on:change={changeFont} value={settings.font?.value} />
|
||||
</div>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-icons mr-10 font-size-30'>play_arrow</div>
|
||||
Playback Settings
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class='custom-switch mb-10 pl-10 font-size-16 w-300'
|
||||
data-toggle='tooltip'
|
||||
|
|
@ -177,6 +210,12 @@
|
|||
<input type='checkbox' id='player-pause' bind:checked={settings.playerPause} />
|
||||
<label for='player-pause'>Pause When Tabbing Out</label>
|
||||
</div>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-icons mr-10 font-size-30'>list</div>
|
||||
Anilist Settings
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class='custom-switch mb-10 pl-10 font-size-16 w-300'
|
||||
data-toggle='tooltip'
|
||||
|
|
|
|||
|
|
@ -92,15 +92,14 @@ const handleRequest = limiter.wrap(async opts => {
|
|||
return json
|
||||
})
|
||||
|
||||
export const alID = !!alToken && alRequest({ method: 'Viewer', token: alToken })
|
||||
if (alID) {
|
||||
alID.then(result => {
|
||||
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
alRequest({ method: 'CustomList', lists })
|
||||
}
|
||||
})
|
||||
}
|
||||
export let alID = !!alToken
|
||||
alID = alRequest({ method: 'Viewer', token: alToken }).then(result => {
|
||||
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
alRequest({ method: 'CustomList', lists })
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
function printError (error) {
|
||||
console.warn(error)
|
||||
|
|
@ -186,6 +185,7 @@ export async function alRequest (opts) {
|
|||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
const userId = (await alID)?.data?.Viewer.id
|
||||
const queryObjects = /* js */`
|
||||
id,
|
||||
title {
|
||||
|
|
@ -349,7 +349,7 @@ query{
|
|||
}`
|
||||
break
|
||||
} case 'UserLists': {
|
||||
variables.id = (await alID)?.data?.Viewer?.id
|
||||
variables.id = userId
|
||||
query = /* js */`
|
||||
query($page: Int, $perPage: Int, $id: Int, $status_in: [MediaListStatus]){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
|
|
@ -365,7 +365,7 @@ query($page: Int, $perPage: Int, $id: Int, $status_in: [MediaListStatus]){
|
|||
}`
|
||||
break
|
||||
} case 'NewSeasons': {
|
||||
variables.id = (await alID)?.data?.Viewer?.id
|
||||
variables.id = userId
|
||||
query = /* js */`
|
||||
query($id: Int){
|
||||
MediaListCollection(userId: $id, status_in: [REPEATING, COMPLETED], type: ANIME, forceSingleCompletedList: true){
|
||||
|
|
@ -387,7 +387,7 @@ query($id: Int){
|
|||
}`
|
||||
break
|
||||
} case 'SearchIDStatus': {
|
||||
variables.id = (await alID)?.data?.Viewer?.id
|
||||
variables.id = userId
|
||||
variables.mediaId = opts.id
|
||||
query = /* js */`
|
||||
query($id: Int, $mediaId: Int){
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import JASSUB from 'jassub'
|
|||
import workerUrl from 'jassub/dist/jassub-worker.js?url'
|
||||
import 'jassub/dist/jassub-worker.wasm?url'
|
||||
import { toTS, videoRx, subRx } from './util.js'
|
||||
import { set } from '@/lib/Settings.svelte'
|
||||
|
||||
import { client } from '@/modules/torrent.js'
|
||||
|
||||
const defaultHeader = `[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default, Roboto Medium,26,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,1.3,0,2,20,20,23,1
|
||||
Style: Default, ${set.font?.name || 'Roboto Medium'},26,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,1.3,0,2,20,20,23,1
|
||||
[Events]
|
||||
|
||||
`
|
||||
|
|
@ -130,16 +131,20 @@ export default class Subtitles {
|
|||
|
||||
initSubtitleRenderer () {
|
||||
if (!this.renderer) {
|
||||
this.renderer = new JASSUB({
|
||||
const options = {
|
||||
video: this.video,
|
||||
subContent: this.headers[this.current].header.slice(0, -1),
|
||||
fonts: this.fonts,
|
||||
fallbackFont: 'roboto medium',
|
||||
fallbackFont: set.font?.name || 'roboto medium',
|
||||
availableFonts: {
|
||||
'roboto medium': './Roboto.ttf'
|
||||
},
|
||||
workerUrl
|
||||
})
|
||||
}
|
||||
if (set.font) {
|
||||
options.availableFonts[set.font.name.toLowerCase()] = new Uint8Array(set.font.data)
|
||||
}
|
||||
this.renderer = new JASSUB(options)
|
||||
this.selectCaptions(this.current)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue