Finalizing image handling in tiptap
This commit is contained in:
parent
50808b27b0
commit
1f15181995
@ -1,10 +1,13 @@
|
|||||||
#######################Django related imports####################
|
#######################Django related imports####################
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from rest_framework import generics, status
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from rest_framework import generics, status
|
from django.conf import settings
|
||||||
import random
|
from django.http import JsonResponse
|
||||||
#################################################################
|
#################################################################
|
||||||
#API related imports
|
#API related imports
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -113,77 +116,50 @@ class MediaUpload(APIView):
|
|||||||
files = request.FILES.getlist('media')
|
files = request.FILES.getlist('media')
|
||||||
resource_type = file_serializer.validated_data['resource_type']
|
resource_type = file_serializer.validated_data['resource_type']
|
||||||
resource_id = file_serializer.validated_data['resource_id']
|
resource_id = file_serializer.validated_data['resource_id']
|
||||||
file_path_base = f'static/rangolio_data'
|
file_path_base = f'rangolio_data'
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
file_unique_slug = ''.join(random.choices('ABCDEabcde1234', k=5))
|
file_unique_slug = ''.join(random.choices('ABCDEabcde12345', k=6))
|
||||||
file_path = f"{file_path_base}/{resource_type}/{resource_id}/media/{file_unique_slug+resource_id+f.name}"
|
if resource_id != resource_type:
|
||||||
|
file_path = f"{file_path_base}/{resource_type}/{resource_id}/media/{file_unique_slug+resource_id+f.name}"
|
||||||
|
else:
|
||||||
|
file_path = f"{file_path_base}/{resource_type}/media/{file_unique_slug+resource_id+f.name}"
|
||||||
default_storage.save(file_path, f)
|
default_storage.save(file_path, f)
|
||||||
|
|
||||||
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
|
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
|
||||||
else:
|
else:
|
||||||
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class ListMedia(APIView):
|
||||||
|
def get(self, request, resource_type, resource_id, format=None):
|
||||||
|
if resource_id != resource_type:
|
||||||
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, resource_id, 'media')
|
||||||
|
else:
|
||||||
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, 'media')
|
||||||
|
|
||||||
|
if not os.path.exists(media_folder):
|
||||||
|
return Response({'error': 'Media directory not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
'''
|
media_files = [f for f in os.listdir(media_folder) if f.endswith(('.png', '.jpg', '.jpeg'))]
|
||||||
class ETLFunctions(GenericAPIView):
|
if resource_id != resource_type:
|
||||||
|
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}rangolio_data/{resource_type}/{resource_id}/media/' + f) for f in media_files]
|
||||||
|
else:
|
||||||
|
media_urls = [request.build_absolute_uri(f'{settings.MEDIA_URL}rangolio_data/{resource_type}/media/' + f) for f in media_files]
|
||||||
|
|
||||||
serializer_class = ETLData
|
return Response({'media': media_urls}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def delete(self, request, resource_type, resource_id, format=None):
|
||||||
serializer = self.get_serializer(data=request.data)
|
if resource_id != resource_type:
|
||||||
serializer.is_valid(raise_exception=True)
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, resource_id, 'media')
|
||||||
data = serializer.data
|
else:
|
||||||
if data['operation'] == "create-folder":
|
media_folder = os.path.join(settings.MEDIA_ROOT, 'rangolio_data', resource_type, 'media')
|
||||||
os.mkdir('../Analysis/'+data['postData'])
|
file_name = request.query_params.get('file')
|
||||||
with open('../Analysis/'+data['postData']+'/'+data['postData']+'-run.log', 'w') as fp:
|
if not file_name or not file_name.endswith(('.png', '.jpg', '.jpeg')):
|
||||||
pass
|
return Response({'error': 'Invalid or no file name provided'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
fp.close()
|
|
||||||
|
|
||||||
if data['operation'] == "rename-folder":
|
file_path = os.path.join(media_folder, file_name)
|
||||||
os.rename('../Analysis/'+data['oldTitle'], '../Analysis/'+data['postData'])
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
if data['operation'] == "create-partition-file":
|
return Response({'message': 'File deleted successfully'}, status=status.HTTP_204_NO_CONTENT)
|
||||||
print(os.getcwd())
|
else:
|
||||||
print(os.listdir(os.getcwd()))
|
return Response({'error': 'File not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
partInfo = (data['postData'].split('|'))
|
|
||||||
with open(f'"{partInfo[0]}.part"', "w") as fpp:
|
|
||||||
pass
|
|
||||||
fpp.close()
|
|
||||||
partitionFile = open(f'"{partInfo[0]}.part"', "w+")
|
|
||||||
partitionFile.write(partInfo[1])
|
|
||||||
partitionFile.close()
|
|
||||||
|
|
||||||
if data['operation'] == "move-file":
|
|
||||||
pass
|
|
||||||
|
|
||||||
return Response("Success", status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
class BioTools(APIView):
|
|
||||||
def get(self, request):
|
|
||||||
params = request.GET.get('function', '')
|
|
||||||
params = params.split(";")
|
|
||||||
output = subprocess.check_output(f'seqmagick extract-ids ../Analysis/"{params[1]}"/"{(params[2])[:-1]}"', shell=True)
|
|
||||||
outgroups = (output.decode("utf-8")).split('\n')
|
|
||||||
outgroups = outgroups[:len(outgroups)-1]
|
|
||||||
return Response({'outgroups': outgroups})
|
|
||||||
|
|
||||||
class CommandRunner(GenericAPIView):
|
|
||||||
|
|
||||||
serializer_class = InterimData
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.data
|
|
||||||
runLogFile = f'../Analysis/{data["nodeName"]}/{data["nodeName"]}-run.log'
|
|
||||||
|
|
||||||
try:
|
|
||||||
commands = ast.literal_eval(data['finalParameter'])
|
|
||||||
for key, value in commands.items():
|
|
||||||
process = subprocess.Popen(value+f" > {runLogFile}", shell=True)
|
|
||||||
except:
|
|
||||||
process = subprocess.Popen(data['finalParameter']+f" > {runLogFile}", shell=True)
|
|
||||||
return Response("Command successfully sent for execution", status=status.HTTP_200_OK)
|
|
||||||
'''
|
|
||||||
@ -159,6 +159,8 @@ STATIC_URL = 'static/'
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
|
|||||||
@ -16,8 +16,8 @@ Including another URLconf
|
|||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from django.conf.urls import include
|
from django.conf import settings
|
||||||
from .views import my_form
|
from django.conf.urls.static import static
|
||||||
from apimanager.views import (
|
from apimanager.views import (
|
||||||
UserDataUpdateAPIView,
|
UserDataUpdateAPIView,
|
||||||
UserDataListAPIView,
|
UserDataListAPIView,
|
||||||
@ -32,7 +32,8 @@ from apimanager.views import (
|
|||||||
BlogRetrieveAPIView,
|
BlogRetrieveAPIView,
|
||||||
BlogDeleteAPIView,
|
BlogDeleteAPIView,
|
||||||
BlogsByCategoryAPIView,
|
BlogsByCategoryAPIView,
|
||||||
MediaUpload
|
MediaUpload,
|
||||||
|
ListMedia
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -51,4 +52,6 @@ urlpatterns = [
|
|||||||
path('data/blog/update/<slug:blog_id>/', BlogUpdateAPIView.as_view(), name='blog-update-view'),
|
path('data/blog/update/<slug:blog_id>/', BlogUpdateAPIView.as_view(), name='blog-update-view'),
|
||||||
path('data/blog/delete/<slug:blog_id>/', BlogDeleteAPIView.as_view(), name='blog-delete-view'),
|
path('data/blog/delete/<slug:blog_id>/', BlogDeleteAPIView.as_view(), name='blog-delete-view'),
|
||||||
path('data/upload/', MediaUpload.as_view(), name='media-upload'),
|
path('data/upload/', MediaUpload.as_view(), name='media-upload'),
|
||||||
]
|
path('data/media/<str:resource_type>/<str:resource_id>/', ListMedia.as_view(), name='list-media'),
|
||||||
|
]
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 593 KiB |
@ -55,7 +55,7 @@ function HomePage(props) {
|
|||||||
This field cannot be empty
|
This field cannot be empty
|
||||||
</FormFeedback>:''}
|
</FormFeedback>:''}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<EditorComponent GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent}/>
|
<EditorComponent notificationToggler={props.notificationToggler} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent} resourceType='homepage' resourceId='homepage'/>
|
||||||
</>
|
</>
|
||||||
<ButtonGroup className={`mt-4`}>
|
<ButtonGroup className={`mt-4`}>
|
||||||
<Button onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
<Button onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||||
|
|||||||
52
frontend/src/components/editable/shared/media-lister.jsx
Normal file
52
frontend/src/components/editable/shared/media-lister.jsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import EditableDataService from '../../../services/editable-data-service';
|
||||||
|
|
||||||
|
function MediaLister(props) {
|
||||||
|
const [media, setMedia] = useState([]);
|
||||||
|
|
||||||
|
const fetchMedia = async () => {
|
||||||
|
try {
|
||||||
|
const response = await EditableDataService.getData(`/data/media/${props.resourceType}/${props.resourceId}/`);
|
||||||
|
setMedia(response.data.media);
|
||||||
|
} catch (error) {
|
||||||
|
props.notificationToggler('Error fetching media', 'danger')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMedia();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteMedia = (mediaUrl) => {
|
||||||
|
// Extract the file name from the mediaUrl
|
||||||
|
const fileName = mediaUrl.substring(mediaUrl.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
EditableDataService.deleteData(`/data/media/${props.resourceType}/${props.resourceId}/?file=${fileName}`)
|
||||||
|
.then(() => {
|
||||||
|
props.notificationToggler('Media deleted')
|
||||||
|
fetchMedia()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
props.notificationToggler('Error deleting media', 'danger')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
Choose media to insert
|
||||||
|
</h4>
|
||||||
|
{media.map(image => (
|
||||||
|
<div className={'mb-2'} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: '1px' }} key={image}>
|
||||||
|
<img src={image} style={{ width: '150px', height: 'auto' }} />
|
||||||
|
<Button color={'success'} onClick={() => props.setMedia(image)} style={{ cursor: 'pointer' }}>Upload</Button>
|
||||||
|
<Button color={'danger'} onClick={() => deleteMedia(image)} style={{ cursor: 'pointer' }}>Remove</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MediaLister;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import FileComponent from './file-component.jsx';
|
import FileComponent from './file-component.jsx';
|
||||||
|
import MediaLister from './media-lister.jsx';
|
||||||
import { Button, ButtonGroup, Modal, ModalHeader, ModalBody, ModalFooter} from 'reactstrap';
|
import { Button, ButtonGroup, Modal, ModalHeader, ModalBody, ModalFooter} from 'reactstrap';
|
||||||
|
|
||||||
function MediaUpload(props) {
|
function MediaUpload(props) {
|
||||||
@ -36,9 +37,7 @@ function MediaUpload(props) {
|
|||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
{ action === 'insert' ?
|
{ action === 'insert' ?
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<MediaLister setMedia={props.setMedia} notificationToggler={props.notificationToggler} resourceType={props.resourceType} resourceId={props.resourceId} />
|
||||||
Choose media to insert
|
|
||||||
</h4>
|
|
||||||
</div>:
|
</div>:
|
||||||
<div>
|
<div>
|
||||||
<FileComponent notificationToggler={props.notificationToggler} resourceType={props.resourceType} resourceId={props.resourceId} />
|
<FileComponent notificationToggler={props.notificationToggler} resourceType={props.resourceType} resourceId={props.resourceId} />
|
||||||
|
|||||||
@ -24,7 +24,7 @@ function ImageNode(props) {
|
|||||||
return (
|
return (
|
||||||
<NodeViewWrapper className={className} data-drag-handle>
|
<NodeViewWrapper className={className} data-drag-handle>
|
||||||
<div className="image-container">
|
<div className="image-container">
|
||||||
<img onClick={() => onEditAlt()} className='mx-auto d-block' src={src} alt={alt} />
|
<img className='mx-auto d-block' src={src} alt={alt} />
|
||||||
<div className="image-overlay">
|
<div className="image-overlay">
|
||||||
<span className="image-text mx-auto d-block">
|
<span className="image-text mx-auto d-block">
|
||||||
{ alt ?
|
{ alt ?
|
||||||
|
|||||||
@ -29,6 +29,16 @@ import MediaUpload from './media-upload.jsx'
|
|||||||
const MenuBar = (props) => {
|
const MenuBar = (props) => {
|
||||||
const { editor } = useCurrentEditor()
|
const { editor } = useCurrentEditor()
|
||||||
|
|
||||||
|
const addMedia = (url) => {
|
||||||
|
editor.commands.setImage({
|
||||||
|
src: url,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!editor) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editor){
|
if (editor){
|
||||||
const handleChange = () => {
|
const handleChange = () => {
|
||||||
@ -321,6 +331,7 @@ const MenuBar = (props) => {
|
|||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faImage}/>
|
<FontAwesomeIcon icon={faImage}/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<MediaUpload setMedia={addMedia} notificationToggler={props.notificationToggler} modal={props.modal} toggle={props.toggle} resourceType={props.resourceType} resourceId={props.resourceId}></MediaUpload>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -361,8 +372,7 @@ export default (props) => {
|
|||||||
if (props.content && GlobalTheme && ThemeConfig)
|
if (props.content && GlobalTheme && ThemeConfig)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MediaUpload notificationToggler={props.notificationToggler} modal={modal} toggle={toggle} resourceType={props.resourceType} resourceId={props.resourceId}></MediaUpload>
|
<EditorProvider slotBefore={<MenuBar resourceType={props.resourceType} resourceId={props.resourceId} modal={modal} toggle={toggle} notificationToggler={props.notificationToggler} setContent={props.setContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>} extensions={extensions} content={props.content}></EditorProvider>
|
||||||
<EditorProvider slotBefore={<MenuBar modal={modal} toggle={toggle} setContent={props.setContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>} extensions={extensions} content={props.content}></EditorProvider>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user