diff --git a/src/AudioFiles.cpp b/src/AudioFiles.cpp index dd427ca..6825241 100644 --- a/src/AudioFiles.cpp +++ b/src/AudioFiles.cpp @@ -207,7 +207,13 @@ std::vector AudioDirectoryInfoImpl::QueryTracks() std::vector AudioDirectoryInfoImpl::GetFiles() { OpenAudioDb(); - std::vector dbFiles = UpdateDbFiles(); + std::vector dbFiles; + try { + dbFiles = UpdateDbFiles(); + } catch (const std::exception &e) { + Lv2Log::error("Error updating audio file info: %s - %s", + path.string().c_str(), e.what()); + } std::vector metadataResults; for (const auto &dbFile : dbFiles) { @@ -298,7 +304,16 @@ std::vector AudioDirectoryInfoImpl::UpdateDbFiles() for (auto dirEntry : fs::directory_iterator(path)) { - if (!dirEntry.is_directory()) + bool isDirectory; + try { + isDirectory = dirEntry.is_directory(); + } catch ( + const std::filesystem::filesystem_error &e) { + Lv2Log::error("Error accessing directory entry: %s - %s", dirEntry.path().string().c_str(), e.what()); + continue; // skip this entry if we cannot access it. + } + + if (!isDirectory) { auto path = dirEntry.path(); std::string name = path.filename(); diff --git a/src/FileMetadataFeature.cpp b/src/FileMetadataFeature.cpp index d118120..10c0019 100644 --- a/src/FileMetadataFeature.cpp +++ b/src/FileMetadataFeature.cpp @@ -142,25 +142,27 @@ uint32_t FileMetadataFeature::getFileMetadata( json_variant jsonData; std::string result; - if (fs::exists(path)) + try { - try + if (fs::exists(path)) { std::ifstream f(path); json_reader reader(f); reader.read(&jsonData); result = jsonData.as_object()->at(std::string(key)).as_string(); } - catch (const std::exception &e) + else { + Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key)); return 0; } } - else + catch (const std::exception &e) { - Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key)); + Lv2Log::debug(SS("Permission denied for " << absolute_path << " key: " << key)); return 0; } + size_t len = result.length() + 1; // +1 for null terminator if (len > fileMetadataSize || fileMetadata == nullptr) { @@ -168,7 +170,7 @@ uint32_t FileMetadataFeature::getFileMetadata( return len; } memcpy(fileMetadata, result.c_str(), len); - Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result)); + Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result)); return len; } PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata( @@ -181,52 +183,57 @@ PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata( } Lv2Log::debug(SS("Delete file metadata for " << absolute_path << " key: " << key)); - std::filesystem::path path{SS(absolute_path << ".mdata")}; + try { + std::filesystem::path path{SS(absolute_path << ".mdata")}; - if (!fs::exists(path)) - { - return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist - } + if (!fs::exists(path)) + { + return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist + } - json_variant jsonData; - try - { - std::ifstream f(path); - json_reader reader(f); - reader.read(&jsonData); - } - catch (const std::exception &e) - { - return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata - } + json_variant jsonData; + try + { + std::ifstream f(path); + json_reader reader(f); + reader.read(&jsonData); + } + catch (const std::exception &e) + { + return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata + } - auto obj = jsonData.as_object(); - auto it = obj->find(std::string(key)); - if (it == obj->end()) - { - return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found - } + auto obj = jsonData.as_object(); + auto it = obj->find(std::string(key)); + if (it == obj->end()) + { + return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found + } - obj->erase(it); + obj->erase(it); - if (obj->begin() == obj->end()) - { - // If the object is empty, delete the metadata file - fs::remove(path); - return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully - } + if (obj->begin() == obj->end()) + { + // If the object is empty, delete the metadata file + fs::remove(path); + return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully + } - std::ofstream f(path); - if (!f.is_open()) - { - Lv2Log::error(SS("Failed to write metadata file " << path)); + std::ofstream f(path); + if (!f.is_open()) + { + Lv2Log::error(SS("Failed to write metadata file " << path)); + return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata + } + + json_writer writer(f); + writer.write(jsonData); + + return PIPEDAL_FILE_METADATA_SUCCESS; + } catch (const std::exception &e) { + Lv2Log::error(SS("Exception while deleting metadata: " << e.what())); return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata } - - json_writer writer(f); - writer.write(jsonData); - - return PIPEDAL_FILE_METADATA_SUCCESS; } PIPEDAL_FileMetadata_Status FileMetadataFeature::S_setFileMetadata( diff --git a/src/Storage.cpp b/src/Storage.cpp index c3c5dcd..5d058ca 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -1690,7 +1690,8 @@ static void AddFilesToResult( } catch (const std::exception &error) { - throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string()); + throw std::logic_error( + SS("GetFileList failed. " << rootPath.string() << " - " << error.what())); } // sort lexicographically @@ -1723,7 +1724,7 @@ static void AddTracksToResult( std::set validExtensions = fileProperty.GetPermittedFileExtensions( modDirectoryInfo ? modDirectoryInfo->modType : ""); - if (validExtensions.size() == 0) + if (validExtensions.size() == 0) { const auto &audioExtensions = MimeTypes::instance().AudioExtensions(); validExtensions.insert(audioExtensions.begin(), audioExtensions.end()); @@ -1733,54 +1734,76 @@ static void AddTracksToResult( try { // Add directories first. - for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath)) + try { - if (!IsValidUtf8(dir_entry.path().string())) + for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath)) { - Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string()); - continue; // skip invalid UTF-8 names. - } - const auto &path = dir_entry.path(); - auto name = path.filename().string(); - if (dir_entry.is_directory()) - { - resultFiles.push_back(FileEntry{path, name, true, fs::is_symlink(path)}); + if (!IsValidUtf8(dir_entry.path().string())) + { + Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string()); + continue; // skip invalid UTF-8 names. + } + const auto &path = dir_entry.path(); + auto name = path.filename().string(); + try { + if (dir_entry.is_directory()) + { + resultFiles.push_back(FileEntry{path, name, true, dir_entry.is_symlink()}); + } + } catch (const std::exception &e) + { + Lv2Log::warning(SS("Failed to add directory entry: " << path.string() << " - " << e.what())); + } } } + catch (const std::exception &error) + { + throw std::logic_error( + SS("AddTracksToResult failed to enumerate directories. " << rootPath.string() << " - " << error.what())); + } auto collator = Locale::GetInstance()->GetCollator(); std::sort( - resultFiles.begin(), + resultFiles.begin(), resultFiles.end(), [collator](const FileEntry &l, const FileEntry &r) - { - if (l.isDirectory_ != r.isDirectory_) { - return l.isDirectory_ > r.isDirectory_; - } - return collator->Compare(l.displayName_ ,r.displayName_) < 0; - }); + if (l.isDirectory_ != r.isDirectory_) + { + return l.isDirectory_ > r.isDirectory_; + } + return collator->Compare(l.displayName_, r.displayName_) < 0; + }); // Add audio files. auto audioFiles = AudioDirectoryInfo::Create(rootPath, - GetShadowIndexDirectory(audioRootDirectory,rootPath) - ); - for (const auto &audioFile : audioFiles->GetFiles()) + GetShadowIndexDirectory(audioRootDirectory, rootPath)); + + try { - fs::path audioFilePath = rootPath / audioFile.fileName(); - std::string extension = UiFileProperty::GetFileExtension(audioFilePath); - if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*")) + for (const auto &audioFile : audioFiles->GetFiles()) { - resultFiles.push_back( - FileEntry( - audioFilePath, - audioFile.title(), - false, - std::make_shared(audioFile))); + fs::path audioFilePath = rootPath / audioFile.fileName(); + std::string extension = UiFileProperty::GetFileExtension(audioFilePath); + if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*")) + { + resultFiles.push_back( + FileEntry( + audioFilePath, + audioFile.title(), + false, + std::make_shared(audioFile))); + } } } + catch (const std::exception &error) + { + throw std::logic_error( + SS("AddTracksToResult failed to enumerate audio files. " << rootPath.string() << " - " << error.what())); + } + } catch (const std::exception &error) { - throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string()); + throw std::logic_error(SS("GetFileList failed. " << error.what() << "(" << rootPath.string() << ")")); } } @@ -1869,7 +1892,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons if (IsInAudioTracksDirectory(relativePath)) { - AddTracksToResult(this->GetPluginUploadDirectory(),result, rootModDirectory, fileProperty, relativePath); + AddTracksToResult(this->GetPluginUploadDirectory(), result, rootModDirectory, fileProperty, relativePath); } else { @@ -2046,7 +2069,7 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName) else { std::filesystem::remove(fileName); - if (IsInAudioTracksDirectory(fileName)) + if (IsInAudioTracksDirectory(fileName)) { // remove the metadata file as well. std::filesystem::remove(fileName.string() + ".mdata"); @@ -2074,13 +2097,12 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co return result; } - -bool Storage::IsValidArtworkFile(const std::filesystem::path& fullPath) +bool Storage::IsValidArtworkFile(const std::filesystem::path &fullPath) { if (IsInAudioTracksDirectory(fullPath) && isArtworkFileName(fullPath.filename().string())) { // allow artwork files. - return true; + return true; } return false; } @@ -2208,9 +2230,10 @@ std::string Storage::RenameFilePropertyFile( } std::filesystem::rename(oldPath, newPath); - if (fs::exists(oldPath.string()+".mdata")) { + if (fs::exists(oldPath.string() + ".mdata")) + { // rename the metadata file as well. - std::filesystem::rename(oldPath.string()+".mdata", newPath.string()+".mdata"); + std::filesystem::rename(oldPath.string() + ".mdata", newPath.string() + ".mdata"); } return newPath; } @@ -2222,12 +2245,13 @@ fs::path MakeVersionedPath(const fs::path &path) return path; // no need to version a non-existing file. } fs::path newPath = path; - auto stem = newPath.stem().string();; + auto stem = newPath.stem().string(); + ; if (stem.ends_with(")")) { // remove the trailing (n) from the file name. size_t pos = stem.find_last_of('('); - std::string stemVersion = stem.substr(pos+1, stem.length()-1-(pos+1)); + std::string stemVersion = stem.substr(pos + 1, stem.length() - 1 - (pos + 1)); // check if it is a number. if (stemVersion.find_first_not_of("0123456789") == std::string::npos) { @@ -2237,7 +2261,7 @@ fs::path MakeVersionedPath(const fs::path &path) { // remove trailing space. stem = stem.substr(0, stem.length() - 1); - } + } } } @@ -2246,12 +2270,15 @@ fs::path MakeVersionedPath(const fs::path &path) { // append (n) to the file name. std::string newFileName; - if (version == 0) { + if (version == 0) + { newFileName = SS(stem << newPath.extension().string()); - } else { + } + else + { // append (n) to the file name. newFileName = SS(stem << " (" << std::to_string(version) << ")" << newPath.extension().string()); - } + } newPath = newPath.parent_path() / newFileName; ++version; } @@ -2280,7 +2307,6 @@ std::string Storage::CopyFilePropertyFile( std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath; - if (!this->IsValidSampleFileName(newPath)) { throw std::runtime_error("Invalid file name."); @@ -2299,14 +2325,14 @@ std::string Storage::CopyFilePropertyFile( std::filesystem::create_hard_link(oldPath, newPath); - if (IsInAudioTracksDirectory(oldPath) - && IsInAudioTracksDirectory(newPath)) + if (IsInAudioTracksDirectory(oldPath) && IsInAudioTracksDirectory(newPath)) { std::filesystem::path metadataPath = SS(oldPath.string() << ".mdata"); - if (fs::exists(metadataPath)) { + if (fs::exists(metadataPath)) + { // copy the metadata file as well. std::filesystem::copy_file( - metadataPath, + metadataPath, SS(newPath.string() << ".mdata"), fs::copy_options::overwrite_existing); } diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index d53e27e..657529a 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -52,15 +52,13 @@ static const std::string PLUGIN_PRESETS_MIME_TYPE = "application/vnd.pipedal.plu static const std::string PRESET_MIME_TYPE = "application/vnd.pipedal.preset"; static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank"; -static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year -static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes - +static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year +static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes using namespace pipedal; using namespace boost::system; namespace fs = std::filesystem; - class UserUploadResponse { public: @@ -87,7 +85,7 @@ int32_t ConvertThumbnailSize(const std::string ¶m) { return 0; } - return static_cast (std::stoi(param)); + return static_cast(std::stoi(param)); } static bool IsZipFile(const std::filesystem::path &path) @@ -128,7 +126,6 @@ private: std::vector extensions; }; - class DownloadIntercept : public RequestHandler { PiPedalModel *model; @@ -392,16 +389,15 @@ public: { auto lastModified = std::filesystem::last_write_time(path); res.set(HttpField::LastModified, HtmlHelper::timeToHttpDate(lastModified)); - } - AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path) { + } + AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path) + { return AudioDirectoryInfo::Create(path, - GetShadowIndexDirectory( - this->model->GetPluginUploadDirectory(), - path)); + GetShadowIndexDirectory( + this->model->GetPluginUploadDirectory(), + path)); } - - virtual void get_response( const uri &request_uri, HttpRequest &req, @@ -500,89 +496,98 @@ public: throw PiPedalException("File not found."); } AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(path.parent_path()); - auto files = audioDirectory->GetFiles(); - for (const auto &file : files) + try { - std::string fileNameOnly = path.filename(); - if (file.fileName() == fileNameOnly) + auto files = audioDirectory->GetFiles(); + for (const auto &file : files) { - std::stringstream ss; - json_writer writer(ss); - writer.write(file); - res.setBody(ss.str()); - return; + std::string fileNameOnly = path.filename(); + if (file.fileName() == fileNameOnly) + { + std::stringstream ss; + json_writer writer(ss); + writer.write(file); + res.setBody(ss.str()); + return; + } } } + catch (const std::exception &e) + { + Lv2Log::error("Error getting audio directory info: %s - (%s)", path.c_str(), e.what()); + throw e; + } // If we get here, the file was not found in the directory. throw PiPedalException("File not found in directory."); } else if (segment == "Thumbnail") { + ThumbnailTemporaryFile thumbnailTemporaryFile; try { - fs::path path = request_uri.query("path"); - if (path.empty() || - !fs::exists(path) || - !this->model->IsInUploadsDirectory(path) || - HasDotDot(path)) + std::shared_ptr thumbnail; + try { - // path for folder thumbnails. - path = request_uri.query("ffile"); - - std::shared_ptr thumbnail; - - if (!path.empty() && fs::exists(path) && - this->model->IsInUploadsDirectory(path) && - !HasDotDot(path)) + fs::path path = request_uri.query("path"); + if (path.empty() || + !fs::exists(path) || + !this->model->IsInUploadsDirectory(path) || + HasDotDot(path)) { - // path is a folder. - thumbnail = std::make_shared(); - thumbnail->SetNonDeletedPath(path); - } - else - { - auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile(); - thumbnail = std::make_shared(); - thumbnail->SetNonDeletedPath(t.Path()); - + std::shared_ptr thumbnail; + // path for folder thumbnails. + path = request_uri.query("ffile"); + + if (!path.empty() && fs::exists(path) && + this->model->IsInUploadsDirectory(path) && + !HasDotDot(path)) + { + // path is a folder. + thumbnail = std::make_shared(); + thumbnail->SetNonDeletedPath(path); + } + else + { + auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile(); + thumbnail = std::make_shared(); + thumbnail->SetNonDeletedPath(t.Path()); + } + + res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension())); + res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified. + setLastModifiedFromFile(res, thumbnail->Path()); + res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path()))); + res.setBodyFile(thumbnail); + return; } - res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension())); - res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified. - setLastModifiedFromFile(res,thumbnail->Path()); - res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path()))); - res.setBodyFile(thumbnail); - return; + int32_t width = ConvertThumbnailSize(request_uri.query("w")); + int32_t height = ConvertThumbnailSize(request_uri.query("h")); + AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo( + path.parent_path()); + audioDirectory->GetFiles(); // ensure that the .index file is up to date. + + thumbnailTemporaryFile = audioDirectory->GetThumbnail(path.filename(), width, height); } - - - int32_t width = ConvertThumbnailSize(request_uri.query("w")); - int32_t height = ConvertThumbnailSize(request_uri.query("h")); - - AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo( - path.parent_path()); - audioDirectory->GetFiles(); // ensure that the .index file is up to date. - - ThumbnailTemporaryFile thumbnail; - try { - thumbnail = audioDirectory->GetThumbnail(path.filename(), width, height); - } catch (const std::exception &e) { - thumbnail = audioDirectory->DefaultThumbnailTemporaryFile(); + catch (const std::exception &e) + { + fs::path defaultThumbnail = model->GetWebRoot() / "img/missing_thumbnail.jpg"; + thumbnailTemporaryFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg"); } - res.set(HttpField::content_type, thumbnail.GetMimeType()); + res.set(HttpField::content_type, thumbnailTemporaryFile.GetMimeType()); res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted with time-stamp. - setLastModifiedFromFile(res, path); - res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail.Path()))); + setLastModifiedFromFile(res, thumbnailTemporaryFile.Path()); + res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnailTemporaryFile.Path()))); - std::filesystem::path t = thumbnail.Path(); - res.setBodyFile(t,thumbnail.DeleteFile()); - thumbnail.Detach(); + std::filesystem::path t = thumbnailTemporaryFile.Path(); + res.setBodyFile(t, thumbnailTemporaryFile.DeleteFile()); + thumbnailTemporaryFile.Detach(); } catch (const std::exception &e) { - Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what()); + Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what()); throw e; } } @@ -605,11 +610,11 @@ public: { result = (directoryPath / result).string(); } - + // json-encode the result. std::stringstream ss; json_writer writer(ss); - writer.write(result); + writer.write(result); res.setBody(ss.str()); } @@ -637,11 +642,11 @@ public: { result = (directoryPath / result).string(); } - + // json-encode the result. std::stringstream ss; json_writer writer(ss); - writer.write(result); + writer.write(result); res.setBody(ss.str()); } diff --git a/vite/CMakeLists.txt b/vite/CMakeLists.txt index 214ac2c..f5f87e3 100644 --- a/vite/CMakeLists.txt +++ b/vite/CMakeLists.txt @@ -105,7 +105,6 @@ add_custom_command( public/var/current_pedalboard.json public/var/config.json public/favicon.ico - public/index.html public/iso_codes.json public/logo512.png public/sample_lv2_plugins.json diff --git a/vite/index.html b/vite/index.html index 2841b21..462145c 100644 --- a/vite/index.html +++ b/vite/index.html @@ -3,7 +3,7 @@ - + diff --git a/vite/public/index.html b/vite/public/index.html deleted file mode 100644 index 1b1c682..0000000 --- a/vite/public/index.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - PiPedal - - - - - - - - -
- - - - \ No newline at end of file diff --git a/vite/src/pipedal/DraggableButtonBase.tsx b/vite/src/pipedal/DraggableButtonBase.tsx index 66c5fae..bf5d9b5 100644 --- a/vite/src/pipedal/DraggableButtonBase.tsx +++ b/vite/src/pipedal/DraggableButtonBase.tsx @@ -26,6 +26,7 @@ import ButtonBase, { ButtonBaseProps } from "@mui/material/ButtonBase"; export interface DraggableButtonBaseProps extends ButtonBaseProps { onClick?: (e: React.MouseEvent) => void; + onDoubleClick?: (e: React.MouseEvent) => void; onLongPressStart?: (currentTarget: HTMLButtonElement, e: React.PointerEvent | React.MouseEvent) => boolean; onLongPressMove?: (e: React.PointerEvent) => void; onLongPressEnd?: (e: React.PointerEvent| React.MouseEvent) => void; @@ -59,7 +60,7 @@ function screenToClient(e: React.PointerEvent): Point { export default function DraggableButtonBase(props: DraggableButtonBaseProps) { // Ensure that the props are spread correctly - const { onClick, onLongPressStart, onLongPressMove: + const { onClick, onDoubleClick, onLongPressStart, onLongPressMove: doLongPressMove,onLongPressEnd, longPressDelay,instantMouseLongPress, ...rest } = props; let [hTimeout, setHTimeout] = React.useState(null); @@ -68,6 +69,7 @@ export default function DraggableButtonBase(props: DraggableButtonBaseProps) { let [clickSuppressed, setClickSuppressed] = React.useState(false); let [pointerDownPoint, setPointerDownPoint] = React.useState({ x: 0, y: 0 }); let [ longPressedElement, setLongPressedElement ] = React.useState(null); + let [lastClick, setLastClick] = React.useState(null); function handleSuppressClick(e: MouseEvent) { @@ -278,8 +280,26 @@ export default function DraggableButtonBase(props: DraggableButtonBaseProps) { if (onClick) { onClick(e); } + // check to see whether this is a double-click + let time = Date.now(); + if (lastClick && time-lastClick < 500) { + if (onDoubleClick) { + onDoubleClick(e); + } + setLastClick(null); + } else { + setLastClick(time); + } } }} + onDoubleClick={(e) => { + // chrome doesnt' do double-click for touch events + // so we need to re-implement it on the onclick handler. + e.stopPropagation(); + e.preventDefault(); + return false; + + }} onPointerCancelCapture={(e) => { if (pointerId !== null) { e.currentTarget.releasePointerCapture(e.pointerId); diff --git a/vite/src/pipedal/PluginControl.tsx b/vite/src/pipedal/PluginControl.tsx index 3c66e92..3aa0c33 100644 --- a/vite/src/pipedal/PluginControl.tsx +++ b/vite/src/pipedal/PluginControl.tsx @@ -31,7 +31,7 @@ import Typography from '@mui/material/Typography'; import Input from '@mui/material/Input'; import Select from '@mui/material/Select'; import Switch from '@mui/material/Switch'; -import Utility, { nullCast } from './Utility'; +import Utility from './Utility'; import MenuItem from '@mui/material/MenuItem'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import DialIcon from './svg/fx_dial.svg?react'; @@ -231,12 +231,12 @@ const PluginControl = onInputFocus(event: SyntheticEvent): void { //this.displayValueRef.current!.style.display = "none"; this.setState({ editFocused: true }); - if (Utility.hasIMEKeyboard()) { - event.preventDefault(); - event.stopPropagation(); - this.inputRef.current?.blur(); - this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value) - } + // if (Utility.hasIMEKeyboard()) { + // event.preventDefault(); + // event.stopPropagation(); + // this.inputRef.current?.blur(); + // this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value) + // } } onInputKeyPress(e: any): void { if (e.charCode === 13 && this.inputChanged) { diff --git a/vite/src/pipedal/ToobPlayerControl.tsx b/vite/src/pipedal/ToobPlayerControl.tsx index 80da5eb..f97af4c 100644 --- a/vite/src/pipedal/ToobPlayerControl.tsx +++ b/vite/src/pipedal/ToobPlayerControl.tsx @@ -713,24 +713,17 @@ export default function ToobPlayerControl( bottom: 0 }} > - {/** - * - sx={{ - '& .MuiTouchRipple-root': { - }, - '& .MuiTouchRipple-ripple': { - transform: 'scale(1.9) !important', - } - }} - */} )} onClick={() => { - setShowLoopDialog(true); + if (audioFile !== "") { + setShowLoopDialog(true); + } }} >