Skip to content

Commit

Permalink
feature: adicionado indicador de estado
Browse files Browse the repository at this point in the history
  • Loading branch information
Cristian-Sknz committed May 23, 2022
1 parent 63e80a7 commit a298933
Show file tree
Hide file tree
Showing 12 changed files with 679 additions and 77 deletions.
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kashi-extension",
"version": "1.1.0",
"version": "1.2.0",
"description": "Romanized Lyrics for Spotify",
"author": "Cristian-SknZ",
"license": "MIT",
Expand All @@ -12,6 +12,8 @@
"devDependencies": {
"@types/chrome": "^0.0.185",
"@types/pinyin": "^2.10.0",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.4",
"copy-webpack-plugin": "^10.2.4",
"path-browserify": "^1.0.1",
"ts-loader": "^4.0.1",
Expand All @@ -20,12 +22,18 @@
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@emotion/css": "^11.9.0",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"aromanize": "^0.1.5",
"axios": "^0.27.2",
"cyrillic-to-translit-js": "^3.2.1",
"framer-motion": "^6.3.3",
"kuroshiro": "^1.2.0",
"kuroshiro-analyzer-kuromoji": "^1.1.0",
"pinyin": "^2.11.2"
"pinyin": "^2.11.2",
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"browserslist": {
"production": [
Expand Down
5 changes: 1 addition & 4 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
"short_name": "Kashi",
"description": "Romanized Lyrics for Spotify",
"manifest_version": 3,
"version": "1.1.0",
"background": {
"service_worker": "background.js"
},
"version": "1.2.0",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
Expand Down
62 changes: 0 additions & 62 deletions src/content.ts

This file was deleted.

56 changes: 56 additions & 0 deletions src/event/EventEmitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
type EmitterType = 'state';
type Callback<T> = (value: CustomEvent<T>) => any;

type MapValue<T> = {
name: EmitterType;
callback: Callback<T>;
}

type EmitOptions<T> = {
state: T,
element?: HTMLElement
}

class EventEmitter<T extends string> {
private emitter: EventTarget;
private map = new Map<Number, MapValue<T>>();
private next = 0;

constructor() {
this.emitter = new EventTarget();
}

public emit({ state, element }: EmitOptions<T>) {
if (element) {
element.setAttribute('kashi', state);
}
this.emitter.dispatchEvent(new CustomEvent<T>('state', {
detail: state
}));
}

public on(name: EmitterType, callback: Callback<T>) {
const value = this.next++;
this.map.set(value, { name, callback });
this.emitter.addEventListener(name, callback);
return value;
}

public once(name: EmitterType, callback: Callback<T>) {
const value = this.next++;
this.map.set(value, { name, callback });
this.emitter.addEventListener(name, callback, {
once: true
});
return value;
}

public off(value: number) {
if(this.map.has(value)) {
const { name, callback} = this.map.get(value);
this.emitter.removeEventListener(name, callback);
}
}
}

export default EventEmitter;
82 changes: 82 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import { createRoot } from 'react-dom/client'

import { Lyric, romanize } from './services';
import { LyricsState } from './react/hooks/lyrics-state';
import { Root } from './react/Indicator.style';
import Indicator, { IndicatorProps } from './react/Indicator';
import EventEmitter from './event/EventEmitter';

getRootElement().then((array) => {
const portal = document.createElement('div');
array[0].appendChild(portal).classList.add(Root);

const react = createRoot(portal);
react.render(React.createElement<IndicatorProps>(Indicator, {
listener: initializeLyricObserver(array[1])
}))
});

function getRootElement() {
return new Promise<Element[]>((resolve) => {
var observer = new MutationObserver((context) => {
const mutation = context[context.length - 1];
const target = mutation.target as HTMLBodyElement;
const root = target.querySelector('.main-view-container');
const main = root.querySelector(['.os-padding','main'].join(' '));

if (main) {
resolve([root, main]);
observer.disconnect();
}
});

observer.observe(document.body, {
attributes: true,
childList: true,
characterData: true,
});
});
}

function initializeLyricObserver(node: Element) {
const emitter = new EventEmitter<LyricsState>();
const observer = new MutationObserver(async (records) => {
const record = records.filter(({ addedNodes }) => addedNodes.length !== 0)
.map<HTMLElement>((record) => record.target as HTMLElement)
.find((e) => e.querySelector('[data-testid="fullscreen-lyric"]'));

if (!record) {
emitter.emit({ state: LyricsState.Idle });
return;
}

const lyric = record.querySelector('[data-testid="fullscreen-lyric"]')
.parentElement;
if (lyric.hasAttribute('kashi')) {
emitter.emit({ state: LyricsState[lyric.getAttribute('kashi')] });
return;
}

const lyrics = Array.from(lyric.childNodes) as HTMLElement[];
emitter.emit({
state: LyricsState.Loading,
element: lyric
});

await romanize(lyrics
.filter((node) => node.hasAttribute('data-testid'))
.map<Lyric>((value, index) => ({
index: index,
node: value,
text: value.textContent
})));

emitter.emit({
state: LyricsState.Loaded,
element: lyric
});
});
observer.observe(node, { childList: true, subtree: true });
return emitter;
}
56 changes: 56 additions & 0 deletions src/react/Indicator.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { css } from '@emotion/css';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';

export const Root = css`
position: absolute;
bottom: 5px;
right: 2.5%;
`

export const Container = styled(motion.div)`
background: #8325db;
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 0.4rem;
padding: 0.2rem 0.6rem;
user-select: none;
box-shadow: 0px 0px 2px 0px #0006;
`;

Container.defaultProps = {
initial: {
y: -25,
opacity: 0
},
animate: {
y: 0,
opacity: 1
},
exit: {
y: -25,
opacity: 0
},
whileHover: {
opacity: 0.8,
scale: 1.03
}
};

export const Link = styled.a`
cursor: pointer;
`;

export const Image = styled.img`
width: 22px;
height: 22px;
border-radius: 50%;
display: block;
`;

export const Text = styled.span`
color: #fff;
font-weight: 600;
font-family: "spotify-circular";
`;
30 changes: 30 additions & 0 deletions src/react/Indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { AnimatePresence } from 'framer-motion';
import { Container, Image, Link, Text } from './Indicator.style';
import { LyricsState, useLyricsState } from './hooks/lyrics-state';
import EventEmitter from '../event/EventEmitter';

export type IndicatorProps = {
listener: EventEmitter<LyricsState>;
};

const REPOSITORY_URL = 'https://github.com/Cristian-Sknz/kashi-extension';

const Indicator: React.FC<IndicatorProps> = ({ listener }) => {
const state = useLyricsState(listener);

return (
<AnimatePresence>
{state !== 'Idle' && (
<Container layout key={state}>
<Link href={REPOSITORY_URL} rel='external' target={'_blank'}>
<Image src={`${REPOSITORY_URL}/raw/master/public/icons/icon48.png`}/>
</Link>
<Text>{state}</Text>
</Container>
)}
</AnimatePresence>
);
};

export default Indicator;
23 changes: 23 additions & 0 deletions src/react/hooks/lyrics-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';
import EventEmitter from '../../event/EventEmitter';

export enum LyricsState {
Idle = 'Idle',
Loading = 'Loading',
Loaded = 'Loaded',
};


export function useLyricsState(listener: EventEmitter<LyricsState>) {
const [state, setState] = useState<LyricsState>(LyricsState.Idle);

useEffect(() => {
const value = listener.on('state', (e) => {
setState(e.detail);
});

return () => listener.off(value);
}, [listener])

return state;
}
2 changes: 1 addition & 1 deletion src/services/kuroshiro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type APIData = {
export default class KuroshiroService implements RomanizeService {

async romanize(lyrics: Lyrics): Promise<Lyrics> {
return this.useKuromojiDict(lyrics).catch(() => this.useKashiAPI(lyrics))
return this.useKuromojiDict(lyrics).catch(async () => await this.useKashiAPI(lyrics))
}

async useKuromojiDict(lyrics: Lyrics) {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"exclude": ["v"],
"compilerOptions": {
"jsx": "react",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
Expand Down
Loading

0 comments on commit a298933

Please sign in to comment.