Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F1262242
multiple-files.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
multiple-files.ts
View Options
import
Router
from
"@koa/router"
;
import
{
Collection
,
CollectionItem
,
Field
as
SealiousField
}
from
"sealious"
;
import
{
Context
}
from
"koa"
;
import
{
inputWrapper
}
from
"../../utils/input-wrapper.js"
;
import
{
FlatTemplatable
,
tempstream
}
from
"tempstream"
;
import
{
MultipleFiles
as
MultipleFilesField
}
from
"../fields/multiple-files.js"
;
import
{
FormFieldControl
}
from
"./form-field-control.js"
;
import
{
is
,
predicates
}
from
"@sealcode/ts-predicates"
;
import
{
renderAttributes
}
from
"../../utils/render-attributes.js"
;
import
{
FormControlContext
}
from
"./form-control.js"
;
import
{
FilePointer
,
PathFilePointer
}
from
"@sealcode/file-manager"
;
export
type
MultipleFilesOptions
=
{
label
?:
string
;
uploadLabel
?:
string
;
getAdditionalFields
:
(
ctx
:
Context
,
file
:
FilePointer
)
=>
Promise
<
Record
<
string
,
unknown
>>
;
};
export
class
MultipleFiles
extends
FormFieldControl
{
public
options
:
MultipleFilesOptions
;
constructor
(
public
field
:
MultipleFilesField
,
options
?:
Partial
<
MultipleFilesOptions
>
)
{
super
([
field
]);
this
.
options
=
{
getAdditionalFields
:
async
()
=>
({}),
...
options
};
}
getClassModifiers
()
:
string
[]
{
return
[];
}
mount
(
router
:
Router
)
{
router
.
get
(
this
.
getFrameRelativeURL
(),
async
(
ctx
)
=>
{
ctx
.
body
=
this
.
renderFrame
(
ctx
,
await
this
.
renderFrameContent
(
ctx
)
);
});
router
.
post
(
this
.
getFrameRelativeURL
()
+
"/delete/:file_item_id"
,
async
(
ctx
)
=>
{
await
this
.
deleteFileAssociation
(
ctx
,
ctx
.
params
.
file_item_id
);
ctx
.
body
=
this
.
renderFrame
(
ctx
,
await
this
.
renderFrameContent
(
ctx
)
);
}
);
router
.
post
(
this
.
getFrameRelativeURL
()
+
"/add"
,
async
(
ctx
)
=>
{
let
files
=
ctx
.
$body
.
files
as
FilePointer
|
FilePointer
[];
if
(
!
files
||
!
is
(
files
,
predicates
.
object
))
{
ctx
.
body
=
"Missing files"
;
return
;
}
if
(
!
Array
.
isArray
(
files
))
{
files
=
[
files
];
}
await
Promise
.
all
(
files
.
map
((
file
)
=>
this
.
addFileAssociation
(
ctx
,
file
as
unknown
as
FilePointer
)
)
);
ctx
.
body
=
this
.
renderFrame
(
ctx
,
await
this
.
renderFrameContent
(
ctx
)
);
});
}
async
deleteFileAssociation
(
ctx
:
Context
,
file_item_id
:
string
)
{
await
ctx
.
$app
.
collections
[
this
.
field
.
collection_field
.
referencing_collection
].
removeByID
(
ctx
.
$context
,
file_item_id
);
}
async
addFileAssociation
(
ctx
:
Context
,
file
:
FilePointer
)
{
const
file_field
=
this
.
getFileField
();
if
(
!
file_field
)
{
throw
new
Error
(
"No file field in referencing collection"
);
}
const
body
=
{
...(
await
this
.
options
.
getAdditionalFields
(
ctx
,
file
)),
[
this
.
field
.
collection_field
.
referencing_field
]
:
await
this
.
field
.
getItemId
(
ctx
),
[
file_field
.
name
]
:
file
,
};
await
ctx
.
$app
.
collections
[
this
.
field
.
collection_field
.
referencing_collection
].
create
(
ctx
.
$context
,
body
);
}
getFrameRelativeURL
()
{
return
`
${
this
.
field
.
name
}
_files`
;
}
getFrameID
()
:
string
{
// the "A" is necessary here
return
`A
${
this
.
field
.
name
}
__multiple-fields-control`
;
}
renderFrame
(
_ctx
:
Context
,
content
?:
FlatTemplatable
)
{
return
tempstream
/* HTML */
`<turbo-frame
${
content
?
""
:
`src="./
${
this
.
getFrameRelativeURL
()
}
"`
}
id="
${
this
.
getFrameID
()
}
"
target="_top"
>
${
content
||
""
}
</turbo-frame>`
;
}
render
(
fctx
:
FormControlContext
)
:
FlatTemplatable
|
Promise
<
FlatTemplatable
>
{
return
this
.
renderFrame
(
fctx
.
ctx
,
""
);
}
getReferencingCollection
()
{
const
result
=
this
.
field
.
collection_field
.
app
.
collections
[
this
.
field
.
collection_field
.
referencing_collection
];
return
result
;
}
getFileField
()
:
SealiousField
|
null
{
for
(
const
[
_
,
field
]
of
Object
.
entries
(
this
.
getReferencingCollection
().
fields
))
{
if
(
field
.
handles_large_data
)
{
return
field
;
}
}
return
null
;
}
async
extractFileFromItem
(
item
:
CollectionItem
<
Collection
>
)
:
Promise
<
FilePointer
|
null
>
{
const
sealious_field
=
this
.
getFileField
();
if
(
!
sealious_field
)
{
return
null
;
}
const
token
=
(
item
.
get
(
sealious_field
.
name
)
as
FilePointer
).
token
;
if
(
!
token
)
{
return
null
;
}
return
await
item
.
collection
.
app
.
FileManager
.
fromToken
(
token
);
}
async
renderFileItemPreview
(
_fileItem
:
CollectionItem
,
file
:
FilePointer
)
:
Promise
<
FlatTemplatable
>
{
return
file
.
getOriginalFilename
();
}
async
renderFileItem
(
fileItem
:
CollectionItem
,
file
:
FilePointer
)
:
Promise
<
FlatTemplatable
>
{
if
(
!
(
file
instanceof
PathFilePointer
))
{
return
""
;
}
return
tempstream
/* HTML */
`<li class="file-list-item">
<a
class="file-list-item__preview"
href="
${
file
.
getURL
()
}
"
data-turbo="false"
>
${
this
.
renderFileItemPreview
(
fileItem
,
file
)
}
</a>
${
this
.
renderFileRemoveButton
(
fileItem
)
}
</li>`
;
}
renderFileRemoveButton
(
fileItem
:
CollectionItem
)
:
FlatTemplatable
{
return
/* HTML */
` <form
data-turbo-frame="
${
this
.
getFrameID
()
}
"
action="
${
this
.
getFrameRelativeURL
()
}
/delete/
${
fileItem
.
id
}
"
method="POST"
>
<input
type="submit"
value="X"
class="file-list-action file-list-item__button file-list-item__button--delete"
/>
</form>`
;
}
async
getFileItems
(
ctx
:
Context
)
{
const
item_id
=
await
this
.
field
.
getItemId
(
ctx
);
const
{
items
:
[
item
],
}
=
await
this
.
field
.
collection_field
.
collection
.
list
(
ctx
.
$context
)
.
ids
([
item_id
])
.
attach
({
[
this
.
field
.
collection_field
.
name
]
:
true
})
.
fetch
();
return
item
.
getAttachments
(
this
.
field
.
collection_field
.
name
)
.
filter
((
f
)
=>
f
);
}
getInputAttributes
()
{
return
{
type
:
"file"
,
name
:
"files"
,
multiple
:
true
};
}
async
renderFrameContent
(
ctx
:
Context
)
:
Promise
<
FlatTemplatable
>
{
const
files
=
(
await
Promise
.
all
(
(
await
this
.
getFileItems
(
ctx
)
)
.
filter
((
item
)
=>
item
)
// filter out undefineds
.
map
(
async
(
item
:
CollectionItem
<
Collection
>
)
=>
[
item
,
await
this
.
extractFileFromItem
(
item
),
])
)
).
filter
(([
_
,
f
])
=>
f
!==
null
)
as
[
CollectionItem
<
Collection
>
,
FilePointer
][];
return
inputWrapper
(
[
"multiple-files"
,
this
.
field
.
name
,
...
this
.
getClassModifiers
()],
tempstream
/* HTML */
`
<label>
${
this
.
options
.
label
||
this
.
field
.
name
}
</label>
<ul class="multiple-files__list">
${
files
.
map
(([
item
,
file
])
=>
this
.
renderFileItem
(
item
,
file
)
)
}
</ul>
<form
action="
${
this
.
getFrameRelativeURL
()
}
/add"
method="POST"
enctype="multipart/form-data"
data-turbo-frame="
${
this
.
getFrameID
()
}
"
>
<input
${
renderAttributes
(
this
.
getInputAttributes
())
}
/>
<input
type="submit"
value="
${
this
.
options
.
uploadLabel
||
"Upload"
}
"
class="file-list-action"
/>
</form>
`
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Thu, Jan 23, 19:19 (20 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
601477
Default Alt Text
multiple-files.ts (6 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment