Skip to content

Commit

Permalink
#12 - Drag and Drop
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmad1702 committed May 3, 2023
1 parent 1934251 commit 88ae4fb
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 31 deletions.
18 changes: 12 additions & 6 deletions src/app/jsx-json/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"use client"
import { useMemo, useRef, useState } from "react";

import ConversionLayout from "@/components/conversion-layout";
import EditorToolbar from '@/components/EditorToolbar';
import ConversionInput from "@/components/conversion-input";
import ConversionLayout from "@/components/conversion-layout";
import JSXson from "@/components/jsxson/JSXToJSON";
import { Separator } from "@/components/ui/separator";
import ShowHide from "@/components/ui/show-hide";
Expand Down Expand Up @@ -63,16 +64,21 @@ export default function JSXsonPage() {
}
}

const inputSection = (
<ConversionInput
ref={inputRef}
onFromContentsChange={handleFromContentsChange}
acceptableFileExtensions={' .json, .tsx or .jsx'}
/>
)

return (
<ConversionLayout
ref={inputRef}
title={`JSX <--> JSON`}
description="A converter between JSX Props and a JSON object whose keys are the same as the JSX Props."
onFromContentsChange={handleFromContentsChange}
collapsed={showSecondEditor}
secondEditor={
<JSXson value={toContents} />
}
secondEditor={<JSXson value={toContents} />}
inputSection={inputSection}
>
<ShowHide show={showSecondEditor} className="w-full flex h-8">
<div className="flex-1 font-bold text-xl flex justify-end">
Expand Down
14 changes: 10 additions & 4 deletions src/app/jupyter-python/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { useMemo, useRef, useState } from "react";

import EditorToolbar from '@/components/EditorToolbar';
import ConversionInput from "@/components/conversion-input";
import ConversionLayout from "@/components/conversion-layout";
import JupyterToPython from '@/components/jupyswapp/JupyterToPython';
import PythonToJupyter from '@/components/jupyswapp/PythonToJupyter';
Expand All @@ -16,8 +17,6 @@ import { sampleJupyterNotebook } from '@/data/sampleJupyterNotebook';
import { Notebook } from "@/types/jupyter";
import pythonToJupyter, { jupyterToPython } from '@/utils/jupyter';



export type JupySwapEditMode = 'jupyter-to-py' | 'py-to-jupyter' | 'none'
export default function JupyterPythonPage() {
const inputRef = useRef<HTMLTextAreaElement>(null)
Expand Down Expand Up @@ -85,12 +84,18 @@ export default function JupyterPythonPage() {
}
}

const inputSection = (
<ConversionInput
ref={inputRef}
onFromContentsChange={handleFromContentsChange}
acceptableFileExtensions={' .py or .ipynb'}
/>
)

return (
<ConversionLayout
ref={inputRef}
title={"Jupyter ↔ Python"}
description={"Convert Jupyter Notebooks to Python scripts and vice versa."}
onFromContentsChange={handleFromContentsChange}
collapsed={showSecondEditor}
secondEditor={
editMode === 'py-to-jupyter' ? (
Expand All @@ -103,6 +108,7 @@ export default function JupyterPythonPage() {
/>
)
}
inputSection={inputSection}
>
<ShowHide show={showSecondEditor} className="w-full flex h-8">
<div className="flex-1 font-bold text-xl flex justify-end">
Expand Down
111 changes: 111 additions & 0 deletions src/components/conversion-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { RefObject, forwardRef, useState } from 'react';

import { cn } from '@/utils/cn';
import { ClipboardEdit, Paperclip, Upload } from 'lucide-react';
import { buttonVariants } from './ui/button';
import { Textarea } from './ui/text-area';

type ConversionInputProps = {
onFromContentsChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
acceptableFileExtensions?: string
}

const ConversionInput = forwardRef<HTMLTextAreaElement, ConversionInputProps>(({ onFromContentsChange, acceptableFileExtensions = '', ...props }, ref) => {
// Idealy we would just want this is as a formatted variable. But due to next.js hydration, we need to use state
const [isDragActive, setIsDragActive] = useState(false)

const textAreaRef = (ref as RefObject<HTMLTextAreaElement>).current
const handleFile = (file: File) => {
console.log()
const reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (evt) {
if (evt.target) {
const result = evt.target.result as string
onFromContentsChange({
target: {
value: result
}
} as React.ChangeEvent<HTMLTextAreaElement>)

if (textAreaRef) {
textAreaRef.value = result
}
}
}
reader.onerror = function (evt) {
console.error(evt.target?.error)
}
setIsDragActive(false)
}

const handleDrop = (e: React.DragEvent<HTMLTextAreaElement>) => {
e.preventDefault()
e.stopPropagation()
if (e && e.dataTransfer && e.dataTransfer.files) {
const file = e.dataTransfer.files?.[0]
if (file) {
handleFile(file)
}
}
}


return (
<div className='relative h-full'>
<div className='absolute h-full w-full flex items-center justify-center pointer-events-none flex-col gap-2'>
{
textAreaRef?.value === '' && (
isDragActive ? (
<>
<Paperclip className='h-10 w-10' />
<div className='font-semibold'>
Drop it to convert!
</div>
</>
) : (
<>
<ClipboardEdit className='h-10 w-10 pointer-events-none' />
<div className='font-semibold pointer-events-none'>
Drag and drop or Paste in the contents of a{acceptableFileExtensions} file here
</div>
<input
id="from-contents-file-upload-input"
type='file'
className='hidden'
onChange={(e) => {
const file = e.target.files?.[0]
if (file) {
handleFile(file)
}
}}
/>
<label htmlFor='from-contents-file-upload-input' className={buttonVariants({ className: 'pointer-events-auto cursor-pointer' })}>
<Upload className='h-5 w-5 mr-2' />
Upload Here
</label>
</>
)
)
}
</div>
<Textarea
ref={ref}
className={cn("h-full resize-none duration-300", isDragActive ? 'bg-input border-dashed border-4' : '')}
placeholder={isDragActive ? '' : `Type or paste in the contents of a${acceptableFileExtensions} file here`}
onChange={onFromContentsChange}
onDrop={handleDrop}
onDragOver={(e) => {
setIsDragActive(true)
}}
onDragLeave={(e) => {
setIsDragActive(false)
}}
/>
</div>
)
})

ConversionInput.displayName = 'ConversionInput'

export default ConversionInput
18 changes: 6 additions & 12 deletions src/components/conversion-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable react-hooks/rules-of-hooks */
"use client"
import { forwardRef, useRef } from "react";
import { forwardRef } from "react";

import ShowHide from "@/components/ui/show-hide";
import { Textarea } from "@/components/ui/text-area";
import { cn } from "@/utils/cn";
import React from 'react';
import ConversionPageHeader, { ConversionPageHeaderProps } from "./conversion-page-header";
Expand All @@ -12,19 +11,19 @@ type ConversionLayoutProps = ConversionPageHeaderProps & {
collapsed: boolean;
secondEditor: React.ReactNode;
children: React.ReactNode | React.ReactNodeArray;
onFromContentsChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
inputSection: React.ReactNode;
}

const ConversionLayout = forwardRef<HTMLTextAreaElement, ConversionLayoutProps>(({
const ConversionLayout = forwardRef<HTMLDivElement, ConversionLayoutProps>(({
collapsed,
children,
onFromContentsChange,
secondEditor,
inputSection,
...conversionPageHeaderProps
}, ref) => {
const inputRef = ref || useRef<HTMLTextAreaElement>(null)
return (
<section
ref={ref}
className={cn(
"overflow-hidden h-[calc(100vh-4rem)] flex items-center flex-col container",
collapsed ? 'p-5 gap-4' : 'gap-6 pb-8 pt-6 md:py-10',
Expand All @@ -42,12 +41,7 @@ const ConversionLayout = forwardRef<HTMLTextAreaElement, ConversionLayoutProps>(
collapsed ? 'w-1/2' : 'w-full',
)}
>
<Textarea
ref={inputRef}
className="h-full resize-none"
placeholder="Drag and drop or Paste in the contents of a .ipynb or .py file here"
onChange={onFromContentsChange}
/>
{inputSection}
</div>
<div
className={cn(
Expand Down
29 changes: 20 additions & 9 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,28 @@ const buttonVariants = cva(

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { }
VariantProps<typeof buttonVariants> {
as?: JSX.Element
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
({ className, variant, size, as, ...props }, ref) => {
const genProps = {
className: cn(buttonVariants({ variant, size, className })),
ref: ref,
...props,
}
if (as !== undefined) {
return React.cloneElement(as, genProps)
} else {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
}
);
Button.displayName = "Button";
Expand Down
10 changes: 10 additions & 0 deletions src/utils/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
export const listWordsInSentenceFormat = (
words: string[],
conjunction = "or"
): string => {
if (words.length === 0) return "";
if (words.length === 1) return words[0]!;
const lastWord = words.pop();
return [words.join(", "), lastWord].join(` ${conjunction} `);
};

0 comments on commit 88ae4fb

Please sign in to comment.