Skip to content

Commit ac95529

Browse files
Merge pull request #1 from JoshuaSkootsky/work-in-progress
fix(wip): work in progress for allowing free paste
2 parents 5cfb1e7 + 94359bd commit ac95529

File tree

1 file changed

+66
-18
lines changed

1 file changed

+66
-18
lines changed

src/VarManager.tsx

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useEffect, useState } from 'react'
2-
import type { ChangeEvent } from 'react'
1+
import { useEffect, useState } from 'react'
2+
import type { ChangeEvent, ClipboardEvent } from 'react'
33
import { v4 as uuidv4 } from 'uuid'
44

55
// EnvironmentVariable is a single row in the table
@@ -9,7 +9,7 @@ type EnvironmentVariable = {
99
value: string
1010
}
1111

12-
const INITIAL_ROW_COUNT = 20
12+
const INITIAL_ROW_COUNT = 5
1313
const KEY_REGEX = /^[A-Za-z0-9_]+$/
1414

1515
// createEmptyVariable is a helper function to create an empty row
@@ -40,7 +40,49 @@ export const VarManager = () => {
4040
setRawText(event.target.value)
4141
}
4242

43+
// Parse lines with multiple vars
44+
const parseLinesToItems = (text: string) => {
45+
return text
46+
.split(/\r?\n/)
47+
.map((l) => l.trim())
48+
.filter((l) => l && !l.startsWith('#'))
49+
.map((l) => {
50+
const idx = l.indexOf('=')
51+
if (idx === -1) {
52+
return { key: l, value: '' }
53+
}
54+
return {
55+
key: l.slice(0, idx).trim(),
56+
value: l.slice(idx + 1).trim(),
57+
}
58+
})
59+
}
4360

61+
// onPaste handler for any row's input
62+
const handleRowPaste = (
63+
e: ClipboardEvent<HTMLTextAreaElement | HTMLInputElement>,
64+
rowIndex: number
65+
) => {
66+
const clipText = e.clipboardData.getData('text/plain')
67+
if (!clipText.includes('\n')) {
68+
return
69+
}
70+
e.preventDefault()
71+
72+
const items = parseLinesToItems(clipText)
73+
setVariables((prev) => {
74+
const next = [...prev]
75+
items.forEach((it, i) => {
76+
const idx = rowIndex + i
77+
if (idx < next.length) {
78+
next[idx] = { id: uuidv4(), key: it.key, value: it.value }
79+
} else {
80+
next.push({ id: uuidv4(), key: it.key, value: it.value })
81+
}
82+
})
83+
return next
84+
})
85+
}
4486

4587
// parseAndPopulate parses the raw text for env vars
4688
const parseAndPopulate = () => {
@@ -49,7 +91,7 @@ export const VarManager = () => {
4991
const lines = rawText.split('\n')
5092
const parsedItems: { key: string; value: string; line: number }[] = []
5193
const parseWarningMessages: string[] = []
52-
const keyCounts: Record<string, number> = {}
94+
const keyCounts: Record<string, number[]> = {}
5395

5496
lines.forEach((line, index) => {
5597
const lineNum = index + 1
@@ -72,6 +114,11 @@ export const VarManager = () => {
72114

73115
if (key) {
74116
parsedItems.push({ key, value, line: lineNum })
117+
// Track line numbers for duplicate detection
118+
if (!keyCounts[key]) {
119+
keyCounts[key] = []
120+
}
121+
keyCounts[key].push(lineNum)
75122
} else {
76123
// Key is empty even if '=' was present
77124
parseWarningMessages.push(
@@ -83,10 +130,10 @@ export const VarManager = () => {
83130
})
84131

85132
// Detect duplicates
86-
Object.entries(keyCounts).forEach(([k, occ]) => {
87-
if (occ > 1 && Array.isArray(keyCounts[k])) {
133+
Object.entries(keyCounts).forEach(([k, lineNumbers]) => {
134+
if (lineNumbers.length > 1) {
88135
parseWarningMessages.push(
89-
`Duplicate key "${k}" on lines ${keyCounts[k].join(', ')}`
136+
`Duplicate key "${k}" on lines ${lineNumbers.join(', ')}` // ✅ Correct!
90137
)
91138
}
92139
})
@@ -137,10 +184,11 @@ export const VarManager = () => {
137184
[id]: 'Only letters, digits, and underscore allowed.',
138185
}))
139186
} else {
140-
setRowErrors((e) => ({
141-
...e,
142-
[id]: `Error in ${id}`,
143-
}))
187+
setRowErrors((e) => {
188+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
189+
const { [id]: _, ...rest } = e // ✅ Disable the rule for this line
190+
return rest
191+
})
144192
}
145193
}
146194
}
@@ -184,12 +232,12 @@ export const VarManager = () => {
184232
}
185233
}, [status])
186234

187-
useEffect(() => {
188-
189-
if (rawText !== '') {
190-
parseAndPopulate()
191-
}
192-
}, [rawText, parseAndPopulate])
235+
useEffect(() => {
236+
if (rawText !== '') {
237+
parseAndPopulate()
238+
}
239+
// eslint-disable-next-line react-hooks/exhaustive-deps
240+
}, [rawText])
193241

194242
return (
195243
<div className="w-full p-4 md:p-6 bg-gray-800 shadow-xl rounded-lg">
@@ -210,7 +258,6 @@ export const VarManager = () => {
210258
/>
211259

212260
<div className="flex flex-wrap gap-2 mb-4">
213-
214261
<button
215262
onClick={copyToClipboard}
216263
className="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded"
@@ -267,6 +314,7 @@ export const VarManager = () => {
267314
onChange={(e) =>
268315
handleVariableChange(row.id, 'key', e.target.value)
269316
}
317+
onPaste={(e) => handleRowPaste(e, i)}
270318
className={`w-full text-sm p-1 rounded border ${
271319
rowErrors[row.id]
272320
? 'border-red-500 bg-red-50'

0 commit comments

Comments
 (0)