Skip to content

Commit 84da6c6

Browse files
Merge remote-tracking branch 'origin/develop' into wip/jtulach/StateInContext7115
2 parents 952125c + a829bdb commit 84da6c6

File tree

32 files changed

+726
-381
lines changed

32 files changed

+726
-381
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
- [When editing cells or header names in Table Editor Widget, `tab` and `enter`
1717
keys jumps to next cell/ next row respectively.][12129]
1818
- [Fixed bugs occurring after renaming project from within graph editor][12106].
19+
- [Users having "Team" plan or above may now access shared directories in Cloud
20+
File Browser][12208]
1921
- [Added support for rendering numbered and nested lists][12190].
2022
- [Removed `#` from default colum name][12222]
2123

@@ -29,6 +31,7 @@
2931
[12064]: https://github.com/enso-org/enso/pull/12064
3032
[12129]: https://github.com/enso-org/enso/pull/12129
3133
[12106]: https://github.com/enso-org/enso/pull/12106
34+
[12208]: https://github.com/enso-org/enso/pull/12208
3235
[12190]: https://github.com/enso-org/enso/pull/12190
3336
[12222]: https://github.com/enso-org/enso/pull/12222
3437

@@ -39,11 +42,17 @@
3942
- [Reducing helper methods in `Standard.Base.Meta`.][12031]
4043
- [Added Table.Offset][12071]
4144
- [Added Column.Offset][12092]
45+
- [When reading a Delimited file, if a row with more columns than expected is
46+
encountered, extra columns can be added to the result.][12231]
47+
- In `Delimited` format, the `keep_invalid_rows` setting has been renamed to
48+
`on_invalid_rows`. The default behaviour was also changed to add any extra
49+
columns instead of discarding them.
4250

4351
[11926]: https://github.com/enso-org/enso/pull/11926
4452
[12031]: https://github.com/enso-org/enso/pull/12031
4553
[12071]: https://github.com/enso-org/enso/pull/12071
4654
[12092]: https://github.com/enso-org/enso/pull/12092
55+
[12231]: https://github.com/enso-org/enso/pull/12231
4756

4857
#### Enso Language & Runtime
4958

app/gui/src/project-view/assets/base.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,18 @@
158158
--code-editor-default-height: 30%;
159159
--scrollbar-scrollable-opacity: 100%;
160160
}
161+
162+
/* FIXME: Due to some bug in vue, when a component is used both as web components and normally,
163+
It's styles are applied only on the former. Therefore additional "global" definitions must
164+
be added here. */
165+
.LoadingSpinner {
166+
border-radius: 50%;
167+
border: 4px solid;
168+
border-color: rgba(0, 0, 0, 30%) #0000;
169+
animation: s1 0.8s infinite;
170+
}
171+
@keyframes s1 {
172+
to {
173+
transform: rotate(0.5turn);
174+
}
175+
}

app/gui/src/project-view/components/GraphEditor/widgets/WidgetIcon.vue

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,8 @@ export const widgetDefinition = defineWidget(
6767
position: absolute;
6868
}
6969
.LoadingSpinner {
70-
border: 4px solid;
7170
border-radius: 100%;
7271
border-color: rgba(255, 255, 255, 90%) #0000;
73-
animation: s1 0.8s infinite;
74-
}
75-
@keyframes s1 {
76-
to {
77-
transform: rotate(0.5turn);
78-
}
7972
}
8073
.v-enter-active,
8174
.v-leave-active {

app/gui/src/project-view/components/widgets/FileBrowserWidget.vue

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,56 @@ import LoadingSpinner from '@/components/shared/LoadingSpinner.vue'
33
import SvgButton from '@/components/SvgButton.vue'
44
import SvgIcon from '@/components/SvgIcon.vue'
55
import { useBackend } from '@/composables/backend'
6+
import { injectBackend } from '@/providers/backend'
67
import type { ToValue } from '@/util/reactivity'
8+
import { useToast } from '@/util/toast'
79
import type {
10+
DatalinkAsset,
11+
DatalinkId,
812
DirectoryAsset,
913
DirectoryId,
1014
FileAsset,
1115
FileId,
1216
} from 'enso-common/src/services/Backend'
13-
import Backend, { assetIsDirectory, assetIsFile } from 'enso-common/src/services/Backend'
17+
import Backend, {
18+
assetIsDatalink,
19+
assetIsDirectory,
20+
assetIsFile,
21+
} from 'enso-common/src/services/Backend'
1422
import { computed, ref, toValue, watch } from 'vue'
23+
import { Err, Ok, Result } from 'ydoc-shared/util/data/result'
1524
1625
const emit = defineEmits<{
1726
pathSelected: [path: string]
1827
}>()
1928
20-
const { query, ensureQueryData } = useBackend('remote')
29+
const { query, fetch, ensureQueryData } = useBackend('remote')
30+
const { remote: backend } = injectBackend()
31+
32+
const errorToast = useToast.error()
2133
2234
// === Current Directory ===
2335
2436
interface Directory {
25-
id: DirectoryId | null
37+
id: DirectoryId
2638
title: string
2739
}
2840
29-
const directoryStack = ref<Directory[]>([
30-
{
31-
id: null,
32-
title: 'Cloud',
33-
},
34-
])
35-
const currentDirectory = computed(() => directoryStack.value[directoryStack.value.length - 1]!)
3641
const currentUser = query('usersMe', [])
37-
const currentPath = computed(
38-
() =>
39-
currentUser.data.value &&
40-
`enso://Users/${currentUser.data.value.name}${Array.from(directoryStack.value.slice(1), (frame) => '/' + frame.title).join()}`,
41-
)
42+
const currentOrganization = query('getOrganization', [])
43+
const directoryStack = ref<Directory[]>([])
44+
const isDirectoryStackInitializing = computed(() => directoryStack.value.length === 0)
45+
const currentDirectory = computed(() => directoryStack.value[directoryStack.value.length - 1])
46+
47+
const currentPath = computed(() => {
48+
if (!currentUser.data.value) return
49+
let root = backend?.rootPath(currentUser.data.value) ?? 'enso://'
50+
if (!root.endsWith('/')) root += '/'
51+
return `${root}${directoryStack.value
52+
.slice(1)
53+
.map((dir) => `${dir.title}/`)
54+
.join('')}`
55+
})
4256
4357
// === Directory Contents ===
4458
@@ -65,17 +79,19 @@ const { isPending, isError, data, error } = query(
6579
)
6680
const compareTitle = (a: { title: string }, b: { title: string }) => a.title.localeCompare(b.title)
6781
const directories = computed(
68-
() => data.value && data.value.filter<DirectoryAsset>(assetIsDirectory).sort(compareTitle),
82+
() => data.value && data.value.filter((asset) => assetIsDirectory(asset)).sort(compareTitle),
6983
)
7084
const files = computed(
71-
() => data.value && data.value.filter<FileAsset>(assetIsFile).sort(compareTitle),
85+
() =>
86+
data.value &&
87+
data.value.filter((asset) => assetIsFile(asset) || assetIsDatalink(asset)).sort(compareTitle),
7288
)
7389
const isEmpty = computed(() => directories.value?.length === 0 && files.value?.length === 0)
7490
7591
// === Selected File ===
7692
7793
interface File {
78-
id: FileId
94+
id: FileId | DatalinkId
7995
title: string
8096
}
8197
@@ -97,16 +113,27 @@ function enterDir(dir: DirectoryAsset) {
97113
directoryStack.value.push(dir)
98114
}
99115
116+
class DirNotFoundError {
117+
constructor(public dirName: string) {}
118+
119+
toString() {
120+
return `Directory "${this.dirName}" not found`
121+
}
122+
}
123+
100124
function popTo(index: number) {
101125
directoryStack.value.splice(index + 1)
102126
}
103127
104-
function chooseFile(file: FileAsset) {
128+
function chooseFile(file: FileAsset | DatalinkAsset) {
105129
selectedFile.value = file
106130
}
107131
108132
const isBusy = computed(
109-
() => isPending.value || (selectedFile.value && currentUser.isPending.value),
133+
() =>
134+
isDirectoryStackInitializing.value ||
135+
isPending.value ||
136+
(selectedFile.value && currentUser.isPending.value),
110137
)
111138
112139
const anyError = computed(() =>
@@ -117,12 +144,44 @@ const anyError = computed(() =>
117144
118145
const selectedFilePath = computed(
119146
() =>
120-
selectedFile.value && currentPath.value && `${currentPath.value}/${selectedFile.value.title}`,
147+
selectedFile.value && currentPath.value && `${currentPath.value}${selectedFile.value.title}`,
121148
)
122149
123150
watch(selectedFilePath, (path) => {
124151
if (path) emit('pathSelected', path)
125152
})
153+
154+
// === Initialization ===
155+
156+
async function enterDirByName(name: string, stack: Directory[]): Promise<Result> {
157+
const currentDir = stack[stack.length - 1]
158+
if (currentDir == null) return Err('Stack is empty')
159+
const content = await fetch('listDirectory', listDirectoryArgs(currentDir))
160+
const nextDir = content.find(
161+
(asset): asset is DirectoryAsset => assetIsDirectory(asset) && asset.title === name,
162+
)
163+
if (!nextDir) return Err(new DirNotFoundError(name))
164+
stack.push(nextDir)
165+
return Ok()
166+
}
167+
168+
Promise.all([currentUser.promise.value, currentOrganization.promise.value]).then(
169+
async ([user, organization]) => {
170+
if (!user) {
171+
errorToast.show('Cannot load file list: not logged in.')
172+
return
173+
}
174+
const rootDirectoryId =
175+
backend?.rootDirectoryId(user, organization, null) ?? user.rootDirectoryId
176+
const stack = [{ id: rootDirectoryId, title: 'Cloud' }]
177+
if (rootDirectoryId != user.rootDirectoryId) {
178+
let result = await enterDirByName('Users', stack)
179+
result = result.ok ? await enterDirByName(user.name, stack) : result
180+
if (!result.ok) errorToast.reportError(result.error, 'Cannot enter home directory')
181+
}
182+
directoryStack.value = stack
183+
},
184+
)
126185
</script>
127186

128187
<template>
@@ -143,7 +202,7 @@ watch(selectedFilePath, (path) => {
143202
<div v-if="isBusy" class="centerContent contents"><LoadingSpinner /></div>
144203
<div v-else-if="anyError" class="centerContent contents">Error: {{ anyError }}</div>
145204
<div v-else-if="isEmpty" class="centerContent contents">Directory is empty</div>
146-
<div v-else :key="currentDirectory.id ?? 'root'" class="listing contents">
205+
<div v-else :key="currentDirectory?.id ?? 'root'" class="listing contents">
147206
<TransitionGroup>
148207
<div v-for="entry in directories" :key="entry.id">
149208
<SvgButton :label="entry.title" name="folder" class="entry" @click="enterDir(entry)" />

app/gui/src/project-view/composables/backend.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ export function useBackend(which: 'remote' | 'project') {
5151
return useQuery(backendQueryOptions(method, args, backend))
5252
}
5353

54+
function fetch<Method extends BackendMethods>(
55+
method: Method,
56+
args: ToValue<Parameters<Backend[Method]> | undefined>,
57+
): Promise<Awaited<ReturnType<Backend[Method]>>> {
58+
return queryClient.fetchQuery(backendQueryOptions(method, args, backend))
59+
}
60+
5461
/** Enable prefetching of the specified query. */
5562
function prefetch<Method extends BackendMethods>(
5663
method: Method,
@@ -67,5 +74,5 @@ export function useBackend(which: 'remote' | 'project') {
6774
return queryClient.ensureQueryData(backendQueryOptions(method, args, backend))
6875
}
6976

70-
return { query, prefetch, ensureQueryData }
77+
return { query, fetch, prefetch, ensureQueryData }
7178
}

distribution/lib/Standard/AWS/0.0.0-dev/src/AWS.enso

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type AWS
3838
- region_service: The region and service to use for signing the request.
3939
Defaults to the region and service parsed from the URI.
4040
@uri (Text_Input display=..Always)
41+
@method HTTP_Method.default_fetch_widget
4142
@headers Header.default_widget
4243
@format File_Format.default_widget
4344
@credentials AWS_Credential.default_widget
@@ -71,6 +72,7 @@ type AWS
7172
- region_service: The region and service to use for signing the request.
7273
Defaults to the region and service parsed from the URI.
7374
@uri (Text_Input display=..Always)
75+
@method HTTP_Method.default_post_widget
7476
@headers Header.default_widget
7577
@format File_Format.default_widget
7678
@credentials AWS_Credential.default_widget

distribution/lib/Standard/Base/0.0.0-dev/src/Data.enso

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ list (directory=enso_project.root) (name_filter:Text="") recursive:Boolean=False
300300
file = enso_project.data / "spreadsheet.xls"
301301
Data.fetch URL . body . write file
302302
@uri (Text_Input display=..Always)
303+
@method HTTP_Method.default_fetch_widget
303304
@format Data_Read_Helpers.format_widget_with_raw_response
304305
@headers Header.default_widget
305306
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Cache_Policy -> Any ! Request_Error | HTTP_Error
@@ -427,6 +428,7 @@ fetch (uri:(URI | Text)=(Missing_Argument.throw "uri")) (method:HTTP_Method=..Ge
427428
form_data = Dictionary.from_vector [["key", "val"], ["a_file", test_file]]
428429
response = Data.post url_post (Request_Body.Form_Data form_data url_encoded=True)
429430
@uri (Text_Input display=..Always)
431+
@method HTTP_Method.default_post_widget
430432
@headers Header.default_widget
431433
@response_format Data_Read_Helpers.format_widget_with_raw_response
432434
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any ! Request_Error | HTTP_Error
@@ -456,6 +458,7 @@ post (uri:(URI | Text)=(Missing_Argument.throw "uri")) (body:Request_Body=..Empt
456458
Defaults to `HTTP_Method.Get`.
457459
- headers: The headers to send with the request. Defaults to an empty vector.
458460
@uri (Text_Input display=..Always)
461+
@method HTTP_Method.default_fetch_widget
459462
@headers Header.default_widget
460463
download : (URI | Text) -> Writable_File -> Download_Mode -> HTTP_Method -> Vector (Header | Pair Text Text) -> File ! Request_Error | HTTP_Error
461464
download (uri:(URI | Text)=(Missing_Argument.throw "uri")) file:Writable_File (replace_existing:Download_Mode=..If_Not_Exists) (method:HTTP_Method=..Get) (headers:(Vector (Header | Pair Text Text))=[]) =

distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -474,41 +474,26 @@ type Missing_Argument
474474
## PRIVATE
475475
Indicates that an expression cannot be evaluated because somewhere within
476476
it, a function does not have all required arguments provided.
477-
Error (argument_name : Text) (function_name : Text | Nothing = Nothing) (call_location : Source_Location | Nothing = Nothing)
477+
Error (argument_name : Text) (function_name : Text | Nothing = Nothing) (call_location : Source_Location | Nothing = Nothing) (message : Text = "Provide a value for the argument `" + argument_name + "`.")
478478

479479
## PRIVATE
480480
to_display_text : Text
481-
to_display_text self = case self.function_name of
482-
Nothing -> "Provide a value for the argument `" + self.argument_name + "`."
483-
_ -> "Missing required argument `" + self.argument_name + "` in function `" + self.function_name + "`."
484-
485-
## PRIVATE
486-
Throws an error saying that a required argument has not been provided.
487-
throw : Text -> Nothing ! Missing_Argument
488-
throw argument_name:Text =
489-
Error.throw (Missing_Argument.Error argument_name)
481+
to_display_text self = self.message
490482

491483
## PRIVATE
492484
Throws an error saying that a required argument has not been provided.
493-
494-
This function is supposed to be used as a default value for arguments
495-
that are supposed to be required and should prevent errors stemming from
496-
a not-fully-applied function being passed around. Instead, this error is
497-
raised and propagated to the top, so that the IDE can see that the node
498-
still has some missing arguments within it.
499-
500-
> Example
501-
502-
my_function (arg1 = Missing_Argument.ensure_present "arg1") (arg2 = 100) = arg1+arg2
503-
ensure_present : Text -> Nothing ! Missing_Argument
504-
ensure_present (argument_name : Text = Missing_Argument.ensure_present "argument_name") =
485+
throw : Text -> Text | Nothing -> Nothing ! Missing_Argument
486+
throw argument_name:Text message_override:Text|Nothing=Nothing =
505487
stack_trace = Runtime.get_stack_trace
506488
function_frame = stack_trace . at 1
507489
caller_frame = stack_trace . at 2
508490

509491
function_name = function_frame.name
510492
call_location = caller_frame.source_location
511-
Error.throw (Missing_Argument.Error argument_name function_name call_location)
493+
494+
error = if message_override.is_nothing then Missing_Argument.Error argument_name function_name call_location else
495+
Missing_Argument.Error argument_name function_name call_location message_override
496+
Error.throw error
512497

513498
## Warning when additional warnings occurred.
514499
@Builtin_Type

distribution/lib/Standard/Base/0.0.0-dev/src/Network/Extensions.enso

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Text.to_uri self = URI.parse self
4343
Defaults to `Auto_Detect`. If `Raw_Response` is selected or if the format
4444
cannot be determined automatically, a raw HTTP `Response` will be returned.
4545
@format Data_Read_Helpers.format_widget_with_raw_response
46+
@method HTTP_Method.default_fetch_widget
4647
@headers Header.default_widget
4748
URI.fetch : HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any
4849
URI.fetch self (method:HTTP_Method=..Get) headers=[] format=Auto_Detect =
@@ -87,6 +88,7 @@ URI.fetch self (method:HTTP_Method=..Get) headers=[] format=Auto_Detect =
8788
- File: shorthand for `Request_Body.Binary that_file`.
8889
- Any other Enso object: shorthand for `Request_Body.Json that_object`.
8990
@headers Header.default_widget
91+
@method HTTP_Method.default_post_widget
9092
@response_format Data_Read_Helpers.format_widget_with_raw_response
9193
URI.post : Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any
9294
URI.post self (body:Request_Body=..Empty) (method:HTTP_Method=..Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =

distribution/lib/Standard/Base/0.0.0-dev/src/Network/HTTP.enso

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ private _resolve_form_body (form_data:(Dictionary Text (Text | File))) (url_enco
297297
## PRIVATE
298298
if_fetch_method : HTTP_Method -> Function -> Any -> Any ! Illegal_Argument
299299
if_fetch_method method:HTTP_Method ~action ~if_not=(Error.throw (Illegal_Argument.Error ("Unsupported method " + method.to_display_text))) =
300-
if [HTTP_Method.Get, HTTP_Method.Head, HTTP_Method.Options].contains method then action else
300+
if [HTTP_Method.Get, HTTP_Method.Head, HTTP_Method.Options, HTTP_Method.Trace].contains method then action else
301301
if_not
302302

303303
## PRIVATE

0 commit comments

Comments
 (0)