diff options
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/NotepadStorageService.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/NotepadStorageService.qml | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/NotepadStorageService.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/NotepadStorageService.qml new file mode 100644 index 0000000..3a6257a --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/NotepadStorageService.qml @@ -0,0 +1,473 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import QtCore +import Quickshell +import Quickshell.Io +import qs.Common + +Singleton { + id: root + + property int refCount: 0 + + readonly property string baseDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell") + readonly property string filesDir: baseDir + "/notepad-files" + readonly property string metadataPath: baseDir + "/notepad-session.json" + + property var tabs: [] + property int currentTabIndex: 0 + property var tabsBeingCreated: ({}) + property bool metadataLoaded: false + + Component.onCompleted: { + ensureDirectories() + } + + FileView { + id: metadataFile + path: root.refCount > 0 ? root.metadataPath : "" + blockWrites: true + atomicWrites: true + + onLoaded: { + try { + var data = JSON.parse(text()) + root.tabs = data.tabs || [] + root.currentTabIndex = data.currentTabIndex || 0 + root.metadataLoaded = true + root.validateTabs() + } catch(e) { + console.warn("Failed to parse notepad metadata:", e) + root.createDefaultTab() + } + } + + onLoadFailed: { + root.createDefaultTab() + } + } + + onRefCountChanged: { + if (refCount === 1 && !metadataLoaded) { + metadataFile.path = "" + metadataFile.path = root.metadataPath + } + } + + function ensureDirectories() { + mkdirProcess.running = true + } + + function loadMetadata() { + metadataFile.path = "" + metadataFile.path = root.metadataPath + } + + function createDefaultTab() { + var id = Date.now() + var filePath = "notepad-files/untitled-" + id + ".txt" + var fullPath = baseDir + "/" + filePath + + var newTabsBeingCreated = Object.assign({}, tabsBeingCreated) + newTabsBeingCreated[id] = true + tabsBeingCreated = newTabsBeingCreated + + root.createEmptyFile(fullPath, function() { + root.tabs = [{ + id: id, + title: I18n.tr("Untitled"), + filePath: filePath, + isTemporary: true, + lastModified: new Date().toISOString(), + cursorPosition: 0, + scrollPosition: 0 + }] + root.currentTabIndex = 0 + + var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated) + delete updatedTabsBeingCreated[id] + tabsBeingCreated = updatedTabsBeingCreated + root.saveMetadata() + }) + } + + function saveMetadata() { + var metadata = { + version: 1, + currentTabIndex: currentTabIndex, + tabs: tabs + } + metadataFile.setText(JSON.stringify(metadata, null, 2)) + } + + function getTabById(tabId) { + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].id === tabId) + return tabs[i] + } + return null + } + + function loadTabContent(tabIndex, callback) { + if (tabIndex < 0 || tabIndex >= tabs.length) { + callback("") + return + } + + var tab = tabs[tabIndex] + var requestTabId = tab.id + var fullPath = tab.isTemporary + ? baseDir + "/" + tab.filePath + : tab.filePath + + if (tabsBeingCreated[tab.id]) { + Qt.callLater(() => { + loadTabContent(tabIndex, callback) + }) + return + } + + var fileChecker = fileExistsComponent.createObject(root, { + path: fullPath, + callback: (exists) => { + var currentTab = root.getTabById(requestTabId) + var currentPath = currentTab + ? (currentTab.isTemporary ? baseDir + "/" + currentTab.filePath : currentTab.filePath) + : "" + + if (!currentTab || currentPath !== fullPath) { + callback("") + return + } + + if (exists) { + var loader = tabFileLoaderComponent.createObject(root, { + path: fullPath, + callback: callback + }) + } else { + console.warn("Tab file does not exist:", fullPath) + callback("") + } + } + }) + } + + function saveTabContent(tabIndex, content) { + if (tabIndex < 0 || tabIndex >= tabs.length) return + + var tab = tabs[tabIndex] + var fullPath = tab.isTemporary + ? baseDir + "/" + tab.filePath + : tab.filePath + + var saver = tabFileSaverComponent.createObject(root, { + path: fullPath, + content: content, + tabIndex: tabIndex + }) + } + + function createNewTab() { + var id = Date.now() + var filePath = "notepad-files/untitled-" + id + ".txt" + var fullPath = baseDir + "/" + filePath + + var newTab = { + id: id, + title: I18n.tr("Untitled"), + filePath: filePath, + isTemporary: true, + lastModified: new Date().toISOString(), + cursorPosition: 0, + scrollPosition: 0 + } + + var newTabsBeingCreated = Object.assign({}, tabsBeingCreated) + newTabsBeingCreated[id] = true + tabsBeingCreated = newTabsBeingCreated + createEmptyFile(fullPath, function() { + var newTabs = tabs.slice() + newTabs.push(newTab) + tabs = newTabs + currentTabIndex = tabs.length - 1 + + var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated) + delete updatedTabsBeingCreated[id] + tabsBeingCreated = updatedTabsBeingCreated + saveMetadata() + }) + + return newTab + } + + function closeTab(tabIndex) { + if (tabIndex < 0 || tabIndex >= tabs.length) return + + var newTabs = tabs.slice() + + if (newTabs.length <= 1) { + var id = Date.now() + var filePath = "notepad-files/untitled-" + id + ".txt" + + var newTabsBeingCreated = Object.assign({}, tabsBeingCreated) + newTabsBeingCreated[id] = true + tabsBeingCreated = newTabsBeingCreated + createEmptyFile(baseDir + "/" + filePath, function() { + newTabs[0] = { + id: id, + title: I18n.tr("Untitled"), + filePath: filePath, + isTemporary: true, + lastModified: new Date().toISOString(), + cursorPosition: 0, + scrollPosition: 0 + } + currentTabIndex = 0 + tabs = newTabs + + var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated) + delete updatedTabsBeingCreated[id] + tabsBeingCreated = updatedTabsBeingCreated + saveMetadata() + }) + return + } else { + var tabToDelete = newTabs[tabIndex] + if (tabToDelete && tabToDelete.isTemporary) { + deleteFile(baseDir + "/" + tabToDelete.filePath) + } + + newTabs.splice(tabIndex, 1) + if (currentTabIndex >= newTabs.length) { + currentTabIndex = newTabs.length - 1 + } else if (currentTabIndex > tabIndex) { + currentTabIndex -= 1 + } + } + + tabs = newTabs + saveMetadata() + + } + + function switchToTab(tabIndex) { + if (tabIndex < 0 || tabIndex >= tabs.length) return + + currentTabIndex = tabIndex + saveMetadata() + } + + function reorderTab(fromIndex, toIndex) { + if (fromIndex < 0 || fromIndex >= tabs.length || toIndex < 0 || toIndex >= tabs.length) + return + if (fromIndex === toIndex) + return + + var newTabs = tabs.slice() + var moved = newTabs.splice(fromIndex, 1)[0] + newTabs.splice(toIndex, 0, moved) + tabs = newTabs + + if (currentTabIndex === fromIndex) { + currentTabIndex = toIndex + } else if (fromIndex < currentTabIndex && toIndex >= currentTabIndex) { + currentTabIndex-- + } else if (fromIndex > currentTabIndex && toIndex <= currentTabIndex) { + currentTabIndex++ + } + + saveMetadata() + } + + function saveTabAs(tabIndex, userPath) { + if (tabIndex < 0 || tabIndex >= tabs.length) return + + var tab = tabs[tabIndex] + var fileName = userPath.split('/').pop() + + if (tab.isTemporary) { + var tempPath = baseDir + "/" + tab.filePath + copyFile(tempPath, userPath) + deleteFile(tempPath) + } + + var newTabs = tabs.slice() + newTabs[tabIndex] = Object.assign({}, tab, { + title: fileName, + filePath: userPath, + isTemporary: false, + lastModified: new Date().toISOString() + }) + tabs = newTabs + saveMetadata() + + } + + function updateTabMetadata(tabIndex, properties) { + if (tabIndex < 0 || tabIndex >= tabs.length) return + + var newTabs = tabs.slice() + var updatedTab = Object.assign({}, newTabs[tabIndex], properties) + updatedTab.lastModified = new Date().toISOString() + newTabs[tabIndex] = updatedTab + tabs = newTabs + saveMetadata() + + } + + function validateTabs() { + var validTabs = [] + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i] + validTabs.push(tab) + } + tabs = validTabs + + if (tabs.length === 0) { + root.createDefaultTab() + } + } + + Component { + id: tabFileLoaderComponent + FileView { + property var callback + blockLoading: true + preload: true + + onLoaded: { + callback(text()) + destroy() + } + + onLoadFailed: { + callback("") + destroy() + } + } + } + + Component { + id: fileExistsComponent + Process { + property string path + property var callback + command: ["test", "-f", path] + + Component.onCompleted: running = true + + onExited: (exitCode) => { + callback(exitCode === 0) + destroy() + } + } + } + + Component { + id: tabFileSaverComponent + FileView { + property string content + property int tabIndex + property var creationCallback + + blockWrites: false + atomicWrites: true + + Component.onCompleted: setText(content) + + onSaved: { + if (tabIndex >= 0) { + root.updateTabMetadata(tabIndex, {}) + } + if (creationCallback) { + creationCallback() + } + destroy() + } + + onSaveFailed: { + console.error("Failed to save tab content") + if (creationCallback) { + creationCallback() + } + destroy() + } + } + } + + function createEmptyFile(path, callback) { + var cleanPath = decodeURI(path.toString()) + + if (!cleanPath.startsWith("/")) { + cleanPath = baseDir + "/" + cleanPath + } + + var creator = fileCreatorComponent.createObject(root, { + filePath: cleanPath, + creationCallback: callback + }) + } + + function copyFile(source, destination) { + copyProcess.source = source + copyProcess.destination = destination + copyProcess.running = true + } + + function deleteFile(path) { + deleteProcess.filePath = path + deleteProcess.running = true + } + + Component { + id: fileCreatorComponent + QtObject { + property string filePath + property var creationCallback + + Component.onCompleted: { + var touchProcess = touchProcessComponent.createObject(this, { + filePath: filePath, + callback: creationCallback + }) + } + } + } + + Component { + id: touchProcessComponent + Process { + property string filePath + property var callback + command: ["touch", filePath] + + Component.onCompleted: running = true + + onExited: (exitCode) => { + if (callback) callback() + destroy() + } + } + } + + Process { + id: copyProcess + property string source + property string destination + command: ["cp", source, destination] + } + + Process { + id: deleteProcess + property string filePath + command: ["rm", "-f", filePath] + } + + Process { + id: mkdirProcess + command: ["mkdir", "-p", root.baseDir, root.filesDir] + } +} |