Skip to content

Instantly share code, notes, and snippets.

@ashfinal
Last active August 16, 2021 06:23
Show Gist options
  • Save ashfinal/30504de6dfddeaa5f3a35fb86f6a32f9 to your computer and use it in GitHub Desktop.
Save ashfinal/30504de6dfddeaa5f3a35fb86f6a32f9 to your computer and use it in GitHub Desktop.
Slim file manager that allows you to manipulate hidden files
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: gray; icon-glyph: folder-open;
const APPTABLE = new UITable()
const FM = FileManager.local()
const OFFSET = 2
const VOLICON = ["๐Ÿก", "๐Ÿ“•", "๐Ÿ“—", "๐Ÿ“˜", "๐Ÿ“™"]
var ACTIONROW
var REGISTER
var TIMER = new Timer()
var CURINSTA
var HEADCONTAINER = []
var INFOCACHE = {}
var LOCATIONROW
var OVOLIDX
var ROWSCONTAINER = []
var SELECTED
var VOLINDEX = 0
var VOLUMES = {}
var MSHEADROW
var MSMODE = false
var MSREGISTER = []
var MSARRAY
var LASTUTI
class FilesTree {
constructor(root, flavour=sbtm) {
this.root = root
this.chain = [root]
this.cursor = 0
this._nodes
this._location
this._flavour = flavour
this.structure = (() => {
let pkg = {}
pkg[root] = {}
let nodes = FM.listContents(root)
this.cacheBasicInfo(root, nodes)
nodes.forEach((e) => pkg[root][e] = {})
return pkg
})()
}
get nodes() {
return Object.keys(
this.getNode(this.structure)
).sort(this._flavour)
}
get location() {
return this.chain.reduce(
(l, r) => FM.joinPath(l, r)
)
}
set flavour(value) {
this._flavour = value
}
getNode(tree, start=0) {
if (start == this.cursor) {
return tree[this.chain[start]]
} else {
let ntree = tree[this.chain[start]]
start++
return this.getNode(ntree, start)
}
}
addNodes(tree, nodes, start=0) {
if (start == this.cursor) {
nodes.forEach(
(e) => tree[this.chain[start]][e] = {}
)
} else {
let ntree = tree[this.chain[start]]
start++
this.addNodes(ntree, nodes, start)
}
}
removeNodes(tree, nodes, start=0) {
if (start == this.cursor) {
nodes.forEach(
(e) => delete tree[this.chain[start]][e]
)
} else {
let ntree = tree[this.chain[start]]
start++
this.removeNodes(ntree, nodes, start)
}
}
renameNode(tree, name, newname, start=0) {
if (start == this.cursor) {
let clone = JSON.stringify(
tree[this.chain[start]][name]
)
delete tree[this.chain[start]][name]
tree[this.chain[start]][newname] = JSON.parse(clone)
} else {
let ntree = tree[this.chain[start]]
start++
this.renameNode(ntree, name, newname, start)
}
}
cacheBasicInfo(path, nodes) {
nodes.forEach(function(e) {
let npath = FM.joinPath(path, e)
if (INFOCACHE[npath] == undefined) {
INFOCACHE[npath] = {}
let type = FM.isDirectory(npath)
let mtime = FM.modificationDate(npath)
let size = FM.fileSize(npath)
INFOCACHE[npath]["type"] = type
INFOCACHE[npath]["mtime"] = mtime
INFOCACHE[npath]["size"] = size
}
})
}
cacheAllSubNodes(path, dirname) {
let npath = FM.joinPath(path, dirname)
let nodes = FM.listContents(npath)
this.cacheBasicInfo(npath, nodes)
INFOCACHE[npath]["count"] = nodes.length
nodes.forEach((e) => {
let subpath = FM.joinPath(npath, e)
if (FM.isDirectory(subpath)) {
this.cacheAllSubNodes(npath, e)
}
})
}
goHome() {
this.chain = [this.root]
this.cursor = 0
}
travelDownward(dirname) {
this.chain.push(dirname)
this.cursor++
let subnodes = this.getNode(this.structure)
if (Object.values(subnodes).length == 0) {
let nodes = FM.listContents(this.location)
this.cacheBasicInfo(this.location, nodes)
INFOCACHE[this.location]["count"] = nodes.length
this.addNodes(this.structure, nodes)
}
}
travelUpward() {
if (this.chain.length > 1 && this.cursor > 0) {
this.chain.pop()
this.cursor -= 1
}
}
createDirectory(dirname) {
let path = FM.joinPath(this.location, dirname)
try {
FM.createDirectory(path)
this.cacheBasicInfo(this.location, [dirname])
if (INFOCACHE[this.location]) {
INFOCACHE[this.location]["count"] += 1
}
this.addNodes(this.structure, [dirname])
} catch (err) {
let notify = new Notification()
notify.title = `Can't create directory: ${dirname}`
notify.body = err.message
notify.schedule()
}
}
createFile(filename) {
let path = FM.joinPath(this.location, filename)
try {
FM.writeString(path, "")
this.cacheBasicInfo(this.location, [filename])
if (INFOCACHE[this.location]) {
INFOCACHE[this.location]["count"] += 1
}
this.addNodes(this.structure, [filename])
} catch (err) {
let notify = new Notification()
notify.title = `Can't create file: ${filename}`
notify.body = err.message
notify.schedule()
}
}
remove(names) {
let failed = []
let curl = (" " + this.location).slice(1)
names.forEach((name) => {
let path = FM.joinPath(curl, name)
try {
FM.remove(path)
if (FM.isDirectory(path)) {
let items = Object.keys(INFOCACHE)
items.forEach((e) => {
if (e.startsWith(path)) {
delete INFOCACHE[e]
}
})
} else {
delete INFOCACHE[path]
}
if (INFOCACHE[curl]) {
INFOCACHE[curl]["count"] -= 1
}
} catch (err) {
logError(err.message)
failed.push(name)
}
})
let succeed = names.filter((e) => !failed.includes(e))
this.removeNodes(this.structure, succeed)
if (failed.length) {
let notify = new Notification()
let count = failed.length
let msg = failed.slice(0, 3).join(", ") +
(count >= 3 ? " ... " : "")
notify.title = `Can't delete ${count} items`
notify.body = `${msg}\nSee the log for more detail.`
notify.schedule()
}
}
rename(name, newname) {
if (name == newname) {
return
}
let path = FM.joinPath(this.location, name)
let npath = FM.joinPath(this.location, newname)
try {
FM.move(path, npath)
if (FM.isDirectory(path)) {
INFOCACHE[npath] = INFOCACHE[path]
let items = Object.keys(INFOCACHE)
items.forEach((e) => {
if (e.startsWith(path)) delete INFOCACHE[e]
})
} else {
INFOCACHE[npath] = INFOCACHE[path]
delete INFOCACHE[path]
}
this.renameNode(this.structure, name, newname)
} catch (err) {
let notify = new Notification()
notify.title = `Can't rename: ${name}`
notify.body = err.message
notify.schedule()
}
}
copy() {
let tasks = MSREGISTER.length ? MSREGISTER : [REGISTER]
if (!tasks.length) return
let failed = []
let curl = (" " + this.location).slice(1)
tasks.forEach((task) => {
let name = FM.fileName(task, true)
let path = FM.joinPath(curl, name)
try {
FM.copy(task, path)
INFOCACHE[path] = INFOCACHE[task]
if (INFOCACHE[curl]) {
INFOCACHE[curl]["count"] += 1
}
} catch (err) {
logError(err.message)
failed.push(task)
}
})
let succeed = tasks.filter((e) => !failed.includes(e))
let snames = succeed.map((e) => FM.fileName(e, true))
this.addNodes(this.structure, snames)
let fnames = failed.map((e) => FM.fileName(e, true))
if (fnames.length) {
let notify = new Notification()
let count = fnames.length
let msg = fnames.slice(0, 3).join(", ") +
(count >= 3 ? " ... " : "")
notify.title = `Can't copy ${count} items`
notify.body = `${msg}\nSee the log for more detail.`
notify.schedule()
}
}
}
function format(number) {
let units = ["KB", "MB", "GB"]
let idx = 0
let size = number
while (size > 1024) {
size /= 1024
idx++
}
return size.toFixed(2) + units[idx]
}
function sbtm(a, b) {
let patha = FM.joinPath(CURINSTA.location, a)
let pathb = FM.joinPath(CURINSTA.location, b)
let typea = INFOCACHE[patha]["type"]
let typeb = INFOCACHE[pathb]["type"]
let mtimea = INFOCACHE[patha]["mtime"]
let mtimeb = INFOCACHE[pathb]["mtime"]
// Sort by type and mtime
return (typeb - typea) || (mtimeb - mtimea)
}
function createActionRow() {
let row = new UITableRow()
let back = row.addButton("๐Ÿ”™")
back.centerAligned()
back.onTap = () => {
APPTABLE.removeRow(ACTIONROW)
ROWSCONTAINER.slice(SELECTED + 1 - OFFSET)
.forEach((e) => APPTABLE.removeRow(e[0]))
ROWSCONTAINER.slice(SELECTED - OFFSET)
. forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
SELECTED = undefined
}
let copy = row.addButton("๐Ÿ“‘")
copy.centerAligned()
copy.onTap = () => {
let name = CURINSTA.nodes[SELECTED-OFFSET]
let path = FM.joinPath(CURINSTA.location, name)
REGISTER = path
APPTABLE.removeAllRows()
renderTable()
APPTABLE.reload()
SELECTED = undefined
TIMER.invalidate()
TIMER.timeInterval = 60 * 1000
function callback() {
if (SELECTED != undefined || MSMODE) {
TIMER.invalidate()
TIMER.timeInterval = 5000
TIMER.schedule(callback)
} else {
APPTABLE.removeAllRows()
REGISTER = undefined
APPTABLE.addRow(HEADCONTAINER[0])
APPTABLE.addRow(LOCATIONROW)
ROWSCONTAINER.forEach((e) => {
APPTABLE.addRow(e[0])
})
APPTABLE.reload()
}
}
TIMER.schedule(callback)
}
let remove = row.addButton("๐Ÿงน")
remove.centerAligned()
remove.onTap = async () => {
let name = CURINSTA.nodes[SELECTED - OFFSET]
let alert = new Alert()
alert.title = "Really want to delete?"
alert.message = name
alert.addDestructiveAction("Sure")
alert.addCancelAction("Cancel")
let resp = await alert.present()
if (resp == 0) {
CURINSTA.remove([name])
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
SELECTED = undefined
}
}
let rename = row.addButton("๐Ÿ“")
rename.centerAligned()
rename.onTap = async () => {
let name = CURINSTA.nodes[SELECTED-OFFSET]
let alert = new Alert()
alert.title = "Rename this file/directory:"
alert.message = name
alert.addTextField(name, name)
alert.addDestructiveAction("OK")
alert.addCancelAction("Cancel")
let resp = await alert.present()
if (resp == 0) {
let text = alert.textFieldValue(0).trim()
if (text && text != name) {
CURINSTA.rename(name, text)
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
SELECTED = undefined
}
}
}
let info = row.addButton("๐Ÿฉบ")
info.centerAligned()
info.onTap = async () => {
let curnodes = [].concat(CURINSTA.nodes)
let name = curnodes[SELECTED-OFFSET]
let path = FM.joinPath(CURINSTA.location, name)
let df = new DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
let ctime = FM.creationDate(path)
INFOCACHE[path]["ctime"] = ctime
let mtime = INFOCACHE[path]["mtime"]
let fctime = df.string(new Date(ctime))
let fmtime = df.string(new Date(mtime))
let type = INFOCACHE[path]["type"]
let suffix = undefined
if (type) {
CURINSTA.cacheAllSubNodes(CURINSTA.location, name)
let idx = SELECTED - OFFSET
ROWSCONTAINER[idx][0] = createBaseRow(curnodes[idx])
ROWSCONTAINER[idx][1] = createAltRow(idx)
let items = Object.keys(INFOCACHE)
let filecount = 0
let dircount = 0
let size = 0
for (let v of items) {
if (v == path) continue
if (v.startsWith(path)) {
if (INFOCACHE[v]["type"]) {
dircount++
} else {
filecount++
size += INFOCACHE[v]["size"]
}
}
}
suffix = `Contains ${dircount} directories, ${filecount} files.\nTotal size: ${format(size)}`
} else {
let uti = INFOCACHE[path]["uti"]
if (!uti) {
uti = FM.getUTI(path)
INFOCACHE[path]["uti"] = uti
}
suffix = `UTI: ${uti}`
}
let msg = `Created: ${fctime}\nModified: ${fmtime}\n`
msg += suffix
let alert = new Alert()
alert.title = name
alert.message = msg
alert.addAction("Copy")
alert.addAction("OK")
let resp = await alert.present()
if (resp == 0) {
Pasteboard.copy(msg)
}
}
return row
}
function createHeadRow() {
let row = new UITableRow()
row.isHeader = true
row.dismissOnSelect = false
let top = row.addButton("๐Ÿ”")
top.onTap = () => {
if (SELECTED != undefined) return
CURINSTA.goHome()
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
for(let [k, v] of Object.entries(VOLUMES)) {
let vol = row.addButton(VOLICON[k])
vol.onTap = () => {
if (k == OVOLIDX) return
VOLINDEX = k
CURINSTA = VOLUMES[VOLINDEX]
OVOLIDX = new Number(k)
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
}
let mount = row.addButton("๐Ÿช„")
mount.onTap = async () => {
if (SELECTED != undefined) return
let root = await DocumentPicker.openFolder()
if (!root) return
let count = Object.keys(VOLUMES).length
if (count >= VOLICON.length) {
let err = new Notification()
err.title = "Too many tabs!"
err.body = "If you need to load this directory, please relaunch the app."
err.schedule()
return
}
let ninsta = new FilesTree(root)
VOLINDEX++
VOLUMES[VOLINDEX] = ninsta
CURINSTA = VOLUMES[VOLINDEX]
OVOLIDX = new Number(VOLINDEX)
APPTABLE.removeAllRows()
HEADCONTAINER[0] = createHeadRow()
HEADCONTAINER[1] = createAltHeadRow()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
let newfile = row.addButton("๐Ÿ—’")
newfile.onTap = async () => {
if (SELECTED != undefined) return
let alert = new Alert()
alert.title = "Create new file:"
alert.addTextField("")
alert.addDestructiveAction("OK")
alert.addCancelAction("Cancel")
let resp = await alert.present()
if (resp == 0) {
let text = alert.textFieldValue(0).trim()
if (text) {
CURINSTA.createFile(text)
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
}
}
let newdir = row.addButton("๐Ÿ“‚")
newdir.onTap = async () => {
if (SELECTED != undefined) return
let alert = new Alert()
alert.title = "Create new directory:"
alert.addTextField("")
alert.addDestructiveAction("OK")
alert.addCancelAction("Cancel")
let resp = await alert.present()
if (resp == 0) {
let text = alert.textFieldValue(0).trim()
if (text) {
CURINSTA.createDirectory(text)
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
}
}
let ms = row.addButton("โœ”๏ธ")
ms.onTap = () => {
if (SELECTED != undefined) return
if (ROWSCONTAINER.length == 0) return
MSMODE = true
MSREGISTER = []
MSARRAY = new Uint8Array(ROWSCONTAINER.length).fill(0)
APPTABLE.removeAllRows()
if (MSHEADROW == undefined) {
MSHEADROW = createMSHeadRow()
}
APPTABLE.addRow(MSHEADROW)
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
fillMSRows()
ROWSCONTAINER.forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
}
return row
}
function createAltHeadRow() {
let nrow = createHeadRow()
let paste = nrow.addButton("๐Ÿ“ฅ")
paste.onTap = () => {
if (SELECTED != undefined) return
APPTABLE.removeAllRows()
CURINSTA.copy()
MSREGISTER = []
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
return nrow
}
function createLocationRow() {
let row = new UITableRow()
row.dismissOnSelect = false
row.backgroundColor = new Color("#F2F2F2", 1)
row.height = 20
let parts = CURINSTA.location.split("/")
let last2 = parts.slice(parts.length - 2, parts.length)
let prefix
if (MSMODE) {
prefix = "โœ“ " + String(MSARRAY.reduce((a, b) => a + b)) + " "
} else {
prefix = CURINSTA.location != CURINSTA.root ? "โ‡ง " : "โ†บ "
}
let text = row.addText(prefix + last2.join("/"))
text.titleFont = Font.thinMonospacedSystemFont(14)
if (!MSMODE) {
row.onSelect = (number) => {
if (SELECTED != undefined) return
CURINSTA.travelUpward()
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
}
return row
}
function createBaseRow(name) {
let row = new UITableRow()
row.dismissOnSelect = false
let path = FM.joinPath(CURINSTA.location, name)
let type = INFOCACHE[path]["type"]
let icon = row.addText(type ? "๐Ÿ“" : "๐Ÿ“„")
icon.widthWeight = 10
let size = INFOCACHE[path]["size"]
let fsize = (size != undefined) ? format(size) : "? KB"
let mtime = INFOCACHE[path]["mtime"]
let df = new DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
let fmtime = df.string(new Date(mtime))
let count = INFOCACHE[path]["count"]
let fcount = (count != undefined) ? `${count} items` : "? items"
let subt = type ? `${fcount} ${fmtime}` : `${fsize} ${fmtime}`
let tcell = row.addText(name, subt)
tcell.widthWeight = 80
tcell.titleFont = Font.systemFont(16)
tcell.titleColor = Color.darkGray()
tcell.subtitleFont = Font.systemFont(12)
tcell.subtitleColor = Color.lightGray()
if (MSMODE) {
let ccell = row.addText("โœ“")
ccell.widthWeight = 10
ccell.centerAligned()
}
if (!MSMODE) {
row.onSelect = (number) => {
if (SELECTED != undefined) return
let idx = number - OFFSET
if (ROWSCONTAINER[idx][1] == undefined) {
let nrow = createAltRow(idx)
ROWSCONTAINER[idx][1] = nrow
}
ROWSCONTAINER.slice(idx)
.forEach((e) => APPTABLE.removeRow(e[0]))
APPTABLE.addRow(ROWSCONTAINER[idx][1])
ROWSCONTAINER.slice(idx + 1)
.forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
SELECTED = number
}
} else {
row.onSelect = (number) => {
APPTABLE.removeAllRows()
let idx = number - OFFSET
MSARRAY[idx] = Number(!Boolean(MSARRAY[idx]))
let name = CURINSTA.nodes[idx]
let path = FM.joinPath(CURINSTA.location, name)
if (!INFOCACHE[path]["type"]) {
let uti = INFOCACHE[path]["uti"]
if (!uti) {
uti = FM.getUTI(path)
INFOCACHE[path]["uti"] = uti
}
if (MSARRAY[idx]) LASTUTI = uti
}
APPTABLE.addRow(MSHEADROW)
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
ROWSCONTAINER.forEach(
(e, i) => APPTABLE.addRow(e[MSARRAY[i]])
)
APPTABLE.reload()
}
}
return row
}
function fillRows() {
CURINSTA.nodes.forEach(function(e) {
let rowholder = new Array(2)
let row = createBaseRow(e)
rowholder[0] = row
ROWSCONTAINER.push(rowholder)
})
}
function createAltRow(idx) {
let curnodes = [].concat(CURINSTA.nodes)
let nrow = createBaseRow(curnodes[idx])
let path = FM.joinPath(CURINSTA.location, curnodes[idx])
let back = nrow.addButton("๐Ÿ”™")
back.widthWeight = 15
back.onTap = () => {
APPTABLE.removeRow(ROWSCONTAINER[idx][1])
ROWSCONTAINER.slice(idx + 1)
.forEach((e) => APPTABLE.removeRow(e[0]))
ROWSCONTAINER.slice(idx)
.forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
SELECTED = undefined
}
if (CURINSTA.location != CURINSTA.root) {
let up = nrow.addButton("๐Ÿ”บ")
up.widthWeight = 15
up.onTap = () => {
CURINSTA.travelUpward()
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
SELECTED = undefined
}
}
let type = INFOCACHE[path]["type"]
if (type) {
let down = nrow.addButton("๐Ÿ”ป")
down.widthWeight = 15
down.onTap = () => {
let dirname = curnodes[idx]
CURINSTA.travelDownward(dirname)
APPTABLE.removeAllRows()
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
SELECTED = undefined
}
}
let more = nrow.addButton("๐Ÿ’ก")
more.widthWeight = 15
more.onTap = () => {
if (ACTIONROW == undefined) ACTIONROW = createActionRow()
APPTABLE.removeRow(ROWSCONTAINER[idx][1])
ROWSCONTAINER.slice(idx + 1)
.forEach((e) => APPTABLE.removeRow(e[0]))
APPTABLE.addRow(ACTIONROW)
ROWSCONTAINER.slice(idx + 1)
.forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
}
if (!type) {
let share = nrow.addButton("๐Ÿง")
share.widthWeight = 15
share.onTap = () => {
let fname = curnodes[idx]
let fpath = FM.joinPath(CURINSTA.location, fname)
QuickLook.present(fpath, false)
APPTABLE.removeRow(ROWSCONTAINER[idx][1])
ROWSCONTAINER.slice(idx + 1)
.forEach((e) => APPTABLE.removeRow(e[0]))
ROWSCONTAINER.slice(idx)
.forEach((e) => APPTABLE.addRow(e[0]))
APPTABLE.reload()
SELECTED = undefined
}
}
return nrow
}
function createMSHeadRow() {
let row = new UITableRow()
row.isHeader = true
row.dismissOnSelect = false
let all = row.addButton("๐Ÿ•ธ")
all.centerAligned()
all.onTap = () => {
if (MSARRAY.every((e) => e)) return
APPTABLE.removeAllRows()
MSARRAY.fill(1)
APPTABLE.addRow(MSHEADROW)
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
ROWSCONTAINER.forEach((e) => APPTABLE.addRow(e[1]))
APPTABLE.reload()
}
let same = row.addButton("๐Ÿงฒ")
same.centerAligned()
same.onTap = () => {
if (MSARRAY.every((e) => e)) return
if (!LASTUTI) return
APPTABLE.removeAllRows()
let curl = (" " + CURINSTA.location).slice(1)
CURINSTA.nodes.forEach((e, i) => {
let path = FM.joinPath(curl, e)
if (!INFOCACHE[path]["type"]) {
let uti = INFOCACHE[path]["uti"]
if (!uti) {
uti = FM.getUTI(path)
INFOCACHE[path]["uti"] = uti
}
if (uti == LASTUTI) MSARRAY[i] = 1
}
})
APPTABLE.addRow(MSHEADROW)
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
ROWSCONTAINER.forEach((e, i) => APPTABLE.addRow(e[MSARRAY[i]]))
APPTABLE.reload()
}
let reverse = row.addButton("โš–๏ธ")
reverse.centerAligned()
reverse.onTap = () => {
APPTABLE.removeAllRows()
MSARRAY.forEach(
(e, i) => MSARRAY[i] = Number(!Boolean(e))
)
APPTABLE.addRow(MSHEADROW)
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
ROWSCONTAINER.forEach(
(e, i) => APPTABLE.addRow(e[MSARRAY[i]])
)
APPTABLE.reload()
}
let copy = row.addButton("๐Ÿ“‘")
copy.centerAligned()
copy.onTap = () => {
if (MSARRAY.every((e) => !e)) return
let curl = (" " + CURINSTA.location).slice(1)
CURINSTA.nodes.forEach((e, i) => {
if (MSARRAY[i]) {
let path = FM.joinPath(curl, e)
MSREGISTER.push(path)
}
})
APPTABLE.removeAllRows()
MSMODE = false
LASTUTI = undefined
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
let del = row.addButton("๐Ÿงน")
del.centerAligned()
del.onTap = async () => {
if (MSARRAY.every((e) => !e)) return
let names = CURINSTA.nodes.filter((e, i) => MSARRAY[i])
let alert = new Alert()
let count = names.length
alert.title = `Really want to delete ${count} items?`
alert.message = names.slice(0, 3).join(", ") +
(count >= 3 ? " ... " : "")
alert.addDestructiveAction("Sure")
alert.addCancelAction("Cancel")
let resp = await alert.present()
if (resp == 0) {
CURINSTA.remove(names)
APPTABLE.removeAllRows()
MSMODE = false
MSREGISTER = []
LASTUTI = undefined
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
}
let end = row.addButton("๐Ÿ”š")
end.centerAligned()
end.onTap = () => {
APPTABLE.removeAllRows()
MSMODE = false
MSREGISTER = []
LASTUTI = undefined
ROWSCONTAINER = []
renderTable()
APPTABLE.reload()
}
return row
}
function fillMSRows() {
CURINSTA.nodes.forEach(function(e, i) {
let nrow = createBaseRow(e)
ROWSCONTAINER[i][0].onSelect = nrow.onSelect
ROWSCONTAINER[i][1] = nrow
})
}
function renderTable() {
if (REGISTER || MSREGISTER.length) {
if (HEADCONTAINER[1] == undefined) {
HEADCONTAINER[1] = createAltHeadRow()
}
APPTABLE.addRow(HEADCONTAINER[1])
} else {
if (HEADCONTAINER[0] == undefined) {
HEADCONTAINER[0] = createHeadRow()
}
APPTABLE.addRow(HEADCONTAINER[0])
}
LOCATIONROW = createLocationRow()
APPTABLE.addRow(LOCATIONROW)
if (ROWSCONTAINER.length == 0) fillRows()
ROWSCONTAINER.forEach((e) => APPTABLE.addRow(e[0]))
}
let home = FileManager.iCloud().documentsDirectory()
let scriptable = new FilesTree(home)
VOLUMES[VOLINDEX] = scriptable
CURINSTA = VOLUMES[VOLINDEX]
OVOLIDX = new Number(VOLINDEX)
renderTable()
await APPTABLE.present(true)
// vim:set et sw=2 ts=2:
@ashfinal
Copy link
Author

The script is meant to run in Scriptable app under iOS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment