diff --git a/src/main.js b/src/main.js
index 89ceee6..0dee7ca 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,7 +2,9 @@ import 'https://cdn.jsdelivr.net/npm/@radio4000/components/dist/r4.js'
import 'https://cdn.jsdelivr.net/gh/oskarrough/rough-spinner/rough-spinner.js'
import SpotifyToYoutube from './spotify-to-youtube.js'
+import TextToYoutube from './text-to-youtube.js'
import R4BatchImport from './r4-batch-import.js'
customElements.define('spotify-to-youtube', SpotifyToYoutube)
+customElements.define('text-to-youtube', TextToYoutube)
customElements.define('r4-batch-import', R4BatchImport)
diff --git a/src/text-to-youtube.js b/src/text-to-youtube.js
new file mode 100644
index 0000000..e14b5d9
--- /dev/null
+++ b/src/text-to-youtube.js
@@ -0,0 +1,206 @@
+import { LitElement, html } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js'
+import { searchYoutube } from './helpers.js'
+
+export default class TextToYoutube extends LitElement {
+ static get properties() {
+ return {
+ inputLines: { type: Array, state: true },
+ youtubeResults: { type: Array, state: true },
+ loading: { type: Boolean, state: true },
+ didConfirmYoutubeResults: { type: Boolean, state: true },
+ error: { type: String },
+ i: { type: Number },
+ }
+ }
+
+ maxSearchResults = 3
+
+ // Updates this.tracks
+ async findMatches(event) {
+ event.preventDefault()
+ this.loading = true
+ const $form = event.target
+ const formData = new FormData($form)
+ const lines = formData.get('text_playlist').trim().split('\n')
+ if (!lines?.length) throw new Error('Failed to parse your playlist')
+ this.inputLines = lines.map((line) => {
+ return {
+ id: self.crypto.randomUUID(), // to keep track of the track
+ title: line,
+ searchResults: [], // for later
+ }
+ })
+
+ // Search YouTube in parallel and render as results come in
+ await Promise.allSettled(
+ this.inputLines.map((track, i) =>
+ searchYoutube(track.title, this.maxSearchResults)
+ .then((results) => {
+ this.i = i
+ this.inputLines[i].searchResults = results
+ })
+ .catch((error) => {
+ console.error('An error occurred:', error)
+ this.error = error.message
+ })
+ )
+ )
+ this.loading = false
+ console.log('updated inputLines', this.inputLines)
+ }
+
+ confirmMatches(event) {
+ event.preventDefault()
+ this.saveMatchingVideos()
+ this.didConfirmMatches = true
+ console.log('confirmed matches')
+ }
+
+ // Inserts a newline with the YouTube URL for every matched track
+ saveMatchingVideos() {
+ const fd = new FormData(document.querySelector('form#tracksform'))
+ const results = []
+ for (const [id, youtubeId] of fd.entries()) {
+ const internalTrack = this.inputLines.find((t) => t.id === id)
+ const track = { ...internalTrack, youtubeId, url: 'https://www.youtube.com/watch?v=' + youtubeId }
+ results.push(track)
+ }
+ this.youtubeResults = results
+ console.log('saved matches', this.youtubeResults)
+ }
+
+ clearMatches() {
+ this.inputLines = []
+ this.youtubeResults = []
+ }
+
+ skipTrack(event, track) {
+ event.preventDefault()
+ this.inputLines = this.inputLines.filter((t) => t.id !== track.id)
+ localStorage.setItem('syr.tracks', JSON.stringify(this.inputLines))
+ }
+
+ render() {
+ return html`
+ Error! Could not fetch this playlist. Is it public?
+ Matching ${Number(this.i || 0) + 1}/${this.inputLines?.length}...
+ For each track decide which matching YouTube video to keep, or skip. Here are the tracks you chose. Do with it as you please. Copy paste as CSV Copy paste the YouTube IDs Copy paste the YouTube URLsStep 1. Write the tracks you want
+
+ ${this.error
+ ? html`
+ ${this.error}
+ Step 2. Confirm your YouTube tracks
+ Results
+
+ ${this.youtubeResults?.map(
+ (match, i) => html`
+
+