Skip to content

Commit

Permalink
feat: enable rhyme copy-paste; jsonify errors
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfromyeg committed Mar 31, 2024
1 parent 28b7cbb commit 8826032
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 21 deletions.
4 changes: 3 additions & 1 deletion client/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const Footer = () => {
const year = new Date().getFullYear();

return (
<div className="footer">
<p className="footer-text">Deployed on Railway. 🚂</p>
<p className="footer-text">&copy; 2023 RapViz</p>
<p className="footer-text">&copy; {year} RapViz</p>
</div>
);
};
Expand Down
50 changes: 48 additions & 2 deletions client/src/components/PoetryVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useState, useMemo } from "react";
import { useState, useMemo, useRef } from "react";

import { serverEndpoint } from "../config";
import { buildRhymeOutput } from "../util/rhymes";
import { convertHtmlToRtf } from "../util/rtf";

const PoetryVisualizer = ({ onBack }: any) => {
const [lyrics, setLyrics] = useState("");
const [rhymes, setRhymes] = useState([]);
const [isLoading, setIsLoading] = useState(false);

const contentRef = useRef<HTMLDivElement>(null);

const handleLyrics = (event: any) => {
setLyrics(event.target.value);
};
Expand Down Expand Up @@ -42,6 +45,43 @@ const PoetryVisualizer = ({ onBack }: any) => {
}
};

const handleRichCopy = async () => {
if (!contentRef.current) {
return;
}

const htmlContent = contentRef.current.innerHTML;

// Use the Clipboard API if available; else, download an RTF file
if (navigator.clipboard && navigator.clipboard.write) {
const blob = new Blob([htmlContent], { type: 'text/html' });
const item = new ClipboardItem({ 'text/html': blob });

try {
await navigator.clipboard.write([item]);
console.log("Rich text copied to clipboard.")
} catch (error) {
console.error("Failed to copy rich text: ", error);
}
} else {
const rtfContent = convertHtmlToRtf(htmlContent);

const blob = new Blob([rtfContent], { type: 'application/rtf' });

const filename = "lyrics.rtf";

const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(blob);
anchor.download = filename;

document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);

URL.revokeObjectURL(anchor.href);
}
}

const rhymeOutput = useMemo(
() => buildRhymeOutput(lyrics, rhymes),
[lyrics, rhymes]
Expand All @@ -60,13 +100,19 @@ const PoetryVisualizer = ({ onBack }: any) => {
>
ANALYZE
</button>
<button
className="btn btn--loginApp-link Submit-lyrics"
onClick={handleRichCopy}
>
COPY
</button>
{isLoading && <p className="loading">(one sec...)</p>}
<textarea
value={lyrics}
onChange={handleLyrics}
placeholder="Enter your lyrics here!"
/>
<div className="output">{rhymeOutput}</div>
<div ref={contentRef} className="output">{rhymeOutput}</div>
</div>
);
};
Expand Down
50 changes: 48 additions & 2 deletions client/src/components/RhymeVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useState, useMemo } from "react";
import { useState, useMemo, useRef } from "react";

import Player from "./Player";
import { buildRhymeOutput } from "../util/rhymes";
import { serverEndpoint } from "../config";
import { convertHtmlToRtf } from "../util/rtf";

const RhymeVisualizer = ({ onBack, lyrics, item, isPlaying, progressMs }: any) => {
const [rhymes, setRhymes] = useState([]);
const [isLoading, setIsLoading] = useState(false);

const contentRef = useRef<HTMLDivElement>(null);

const handleSubmit = async () => {
try {
setIsLoading(true);
Expand All @@ -31,6 +34,43 @@ const RhymeVisualizer = ({ onBack, lyrics, item, isPlaying, progressMs }: any) =
}
};

const handleRichCopy = async () => {
if (!contentRef.current) {
return;
}

const htmlContent = contentRef.current.innerHTML;

// Use the Clipboard API if available; else, download an RTF file
if (navigator.clipboard && navigator.clipboard.write) {
const blob = new Blob([htmlContent], { type: 'text/html' });
const item = new ClipboardItem({ 'text/html': blob });

try {
await navigator.clipboard.write([item]);
console.log("Rich text copied to clipboard.")
} catch (error) {
console.error("Failed to copy rich text: ", error);
}
} else {
const rtfContent = convertHtmlToRtf(htmlContent);

const blob = new Blob([rtfContent], { type: 'application/rtf' });

const filename = "lyrics.rtf";

const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(blob);
anchor.download = filename;

document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);

URL.revokeObjectURL(anchor.href);
}
}

const encodedLyrics = useMemo(() => encodeURIComponent(lyrics), [lyrics]);
const rhymeOutput = useMemo(
() => buildRhymeOutput(lyrics, rhymes),
Expand All @@ -54,10 +94,16 @@ const RhymeVisualizer = ({ onBack, lyrics, item, isPlaying, progressMs }: any) =
>
ANALYZE
</button>
<button
className="btn btn--loginApp-link Submit-lyrics"
onClick={handleRichCopy}
>
COPY
</button>
{isLoading && <p className="loading">(one sec...)</p>}
<div className="rap-wrapper">
<Player item={item} isPlaying={isPlaying} progressMs={progressMs} />
<section className="output">{rhymeOutput}</section>
<div ref={contentRef} className="output">{rhymeOutput}</div>
</div>
</div>
);
Expand Down
34 changes: 19 additions & 15 deletions client/src/util/generateWords.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { shuffle } from "./helpers";

const COLORS = [
"aqua",
"aquamarine",
"blue",
"blueviolet",
"cadetblue",
"chartreuse",
"coral",
"cornflowerblue",
"crimson",
"darkorange",
"fuchsia",
"gold",
"goldenrod",
"green",
"#00FFFF", // aqua
"#7FFFD4", // aquamarine
"#0000FF", // blue
"#8A2BE2", // blueviolet
"#5F9EA0", // cadetblue
"#7FFF00", // chartreuse
"#FF7F50", // coral
"#6495ED", // cornflowerblue
"#DC143C", // crimson
"#FF8C00", // darkorange
"#FF00FF", // fuchsia
"#FFD700", // gold
"#DAA520", // goldenrod
"#008000", // green
];

/**
Expand Down Expand Up @@ -44,9 +44,13 @@ const COLORS = [
* @param {string[][]} rhymes - a list of rhyming words
* @returns {Object[][]} - list of data containing words and their color for each line of the song
*/
const generateRhymes = (lyrics: any, rhymes: any) => {
const generateRhymes = (lyrics: string, rhymes: string[][]): any => {
const colors = shuffle(COLORS);

if (!lyrics) {
return [];
}

const wordData = [];
const words = lyrics.replace(/ +/g, " y").split(/\n| /);
const parsedWords = lyrics
Expand Down
2 changes: 1 addition & 1 deletion client/src/util/rhymes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const buildRhymeOutput = (lyrics: any, rhymes: any) => {

let retval: ReactElement[] = [];
for (const array of lyricsWithRhymes) {
const words = array.map((word) => {
const words = array.map((word: any) => {
return <Word color={word.color} word={word.text} />;
});

Expand Down
44 changes: 44 additions & 0 deletions client/src/util/rtf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* As title. ChatGPT wrote this.
*
* @param {string} html
* @returns {string}
*/
export const convertHtmlToRtf = (html: string): string => {
let rtfHeader = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}";
let colorTable = "{\\colortbl ;";
let content = "\\viewkind4\\uc1\\pard\\f0\\fs22 ";
let colorMap: Record<string, number> = {};

const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const spans = doc.querySelectorAll('span');
let colorIndex = 1;

spans.forEach((span: HTMLSpanElement) => {
let bgColor = span.style.backgroundColor;
let rtfColorIndex = 0;

if (bgColor && bgColor !== 'inherit' && bgColor.startsWith('rgb')) {
// Convert RGB CSS color to RTF color table entry and index
if (!colorMap[bgColor]) {
const rgbMatch = bgColor.match(/\d+/g); // Extract RGB values
if (rgbMatch) {
const [r, g, b] = rgbMatch.map(Number);
colorTable += `\\red${r}\\green${g}\\blue${b};`;
colorMap[bgColor] = colorIndex++;
}
}
rtfColorIndex = colorMap[bgColor];
}

const text = span.textContent?.replace(/\n/g, '\\par\n') || '';
content += rtfColorIndex > 0 ? `\\highlight${rtfColorIndex} ${text}` : `${text}`;
content += " ";
});

const rtfContent = `${rtfHeader}${colorTable}}${content}\\cf0\\highlight0 }`;

console.log({ html, rtfContent });
return rtfContent;
}
21 changes: 21 additions & 0 deletions server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,27 @@ def find_rhymes():

return json.dumps(song_instance.find_all_rhyme_clusters())

@app.errorhandler(400)
def bad_request(error):
"""
Surface JSON for 400 errors.
"""
return jsonify({'error': 'Bad Request', 'message': str(error)}), 400

@app.errorhandler(404)
def not_found(error):
"""
Surface JSON for 404 errors.
"""
return jsonify({'error': 'Not Found', 'message': str(error)}), 404

@app.errorhandler(500)
def internal_server_error(error):
"""
Surface JSON for 500 errors.
"""
return jsonify({'error': 'Internal Server Error', 'message': str(error)}), 500


if __name__ == "__main__":
if IS_DEVELOPMENT:
Expand Down

0 comments on commit 8826032

Please sign in to comment.