Skip to content

Commit fcafb53

Browse files
yangzhang75claude
andcommitted
feat(workflow-form): Form View page shell, load, and the shared spec harness
The read-only shell of the Form View page: behind the feature flag it loads the workflow the URL names, redirects to the operator canvas when the workflow does not open in the form (or the flag is off), shows it read-only, hands back to the canvas, and releases the shared singleton services on the way out. The title bar's rename/save, the read-only preview, the inputs, running and results are added on top by later PRs. Also adds the shared spec harness (workflow-form.spec-harness.ts) these specs build the component from; later slices extend it with the mocks they introduce. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1facefb commit fcafb53

7 files changed

Lines changed: 776 additions & 0 deletions

File tree

frontend/src/app/app-routing.module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { UserQuotaComponent } from "./dashboard/component/user/user-quota/user-q
2525
import { UserComputingUnitComponent } from "./dashboard/component/user/user-computing-unit/user-computing-unit.component";
2626
import { UserVenvComponent } from "./dashboard/component/user/user-venv/user-venv.component";
2727
import { WorkspaceComponent } from "./workspace/component/workspace.component";
28+
import { WorkflowFormComponent } from "./workspace/component/workflow-form/workflow-form.component";
2829
import { AboutComponent } from "./hub/component/about/about.component";
2930
import { TexeraLoginComponent } from "./hub/component/login/texera-login.component";
3031
import { AuthGuardService } from "./common/service/user/auth-guard.service";
@@ -134,6 +135,13 @@ routes.push({
134135
path: "workflow",
135136
component: UserWorkflowComponent,
136137
},
138+
{
139+
// Must precede "workflow/:id" so the trailing "form" segment is not swallowed by
140+
// the canvas route. The page guards itself (the feature flag off, or a workflow
141+
// that does not open in the form, hands back to the canvas).
142+
path: "workflow/:id/form",
143+
component: WorkflowFormComponent,
144+
},
137145
{
138146
path: "workflow/:id",
139147
component: WorkspaceComponent,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
<!-- The operator canvas's title row, rebuilt here to the same measurements: same
21+
height, same rule underneath, same full-bleed width, and the view switch pinned to
22+
the same right inset ahead of the same user icon. The name is shown here; making it
23+
editable (with the same rename/save the canvas gives) is the next PR. -->
24+
<div class="pc-topbar">
25+
<nz-avatar
26+
*ngIf="wid"
27+
class="wid"
28+
[nzText]="wid.toString()"></nz-avatar>
29+
<span class="wf-name">{{ workflowName || "Untitled workflow" }}</span>
30+
<span class="topbar-spacer"></span>
31+
<div class="view-switch">
32+
<button
33+
type="button"
34+
aria-pressed="false"
35+
(click)="openRegularCanvas()">
36+
Canvas
37+
</button>
38+
<button
39+
type="button"
40+
class="on"
41+
aria-pressed="true">
42+
Form View
43+
</button>
44+
</div>
45+
<!-- Live collaborators, the same avatars the operator canvas shows -- Form view is just
46+
another view of the same shared workflow, so co-editors belong here too. -->
47+
<ng-container *ngFor="let user of coeditorPresenceService.coeditors">
48+
<texera-coeditor-user-icon [coeditor]="user"></texera-coeditor-user-icon>
49+
</ng-container>
50+
<texera-user-icon></texera-user-icon>
51+
</div>
52+
53+
<div class="pc-page">
54+
<div class="pc-rule"></div>
55+
56+
<div
57+
*ngIf="loading"
58+
class="pc-loading">
59+
Loading…
60+
</div>
61+
62+
<!-- Body is filled in by the following PRs: the read-only workflow preview, the inputs,
63+
running and the results. This PR is the page shell -- load, show, hand back. -->
64+
<div [hidden]="loading"></div>
65+
</div>
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
/* Colours are Texera's own: ng-zorro blue and the #d9d9d9 / #f0f0f0 neutrals. */
21+
$blue: #1890ff;
22+
$text: rgba(0, 0, 0, 0.85);
23+
$text-2: rgba(0, 0, 0, 0.45);
24+
$divider: #f0f0f0;
25+
26+
:host {
27+
display: block;
28+
background: #fff;
29+
color: $text;
30+
overflow-y: auto;
31+
height: 100%;
32+
}
33+
34+
/* Measured against the operator canvas's #menu-user so the two rows are the same row:
35+
36px tall, a 1px rule underneath, full bleed across the content area. Anything the
36+
view switch needs to line up with lives here, not in the padded column below. */
37+
.pc-topbar {
38+
height: 36px;
39+
margin: 1px;
40+
border-bottom: 1px solid #cacaca;
41+
display: flex;
42+
align-items: center;
43+
// Pinned to the top so the Canvas / Form view switch (and the workflow name) stay
44+
// reachable while scrolling the preview/results below.
45+
position: sticky;
46+
top: 0;
47+
z-index: 20;
48+
background: #fff;
49+
50+
/* Trims the difference between this row's right edge and the operator canvas's, so
51+
the switch and the user icon land on the same x in both views. */
52+
padding-right: 10px;
53+
54+
/* The operator canvas pins its avatar to the top of the row with a 1px nudge rather
55+
than centring it; copying that is what puts the two on the same pixel. */
56+
.wid {
57+
flex: none;
58+
margin-left: 3px;
59+
align-self: flex-start;
60+
margin-top: 1px;
61+
}
62+
63+
/* The operator canvas puts 0 gap between its avatar and name, which carries its own
64+
padding; matching that is what lines the two titles up. */
65+
gap: 0;
66+
67+
/* Every value here is the operator canvas's .workflow-name, so the two title rows read
68+
as the same row: the same size, weight and padding. Shown here; the editable input is
69+
the next PR. */
70+
.wf-name {
71+
min-width: 100px;
72+
max-width: 800px;
73+
font-size: 18px;
74+
font-weight: 400;
75+
padding: 2px 8px;
76+
color: $text;
77+
}
78+
79+
.topbar-spacer {
80+
flex: 1;
81+
}
82+
83+
/* Sized by its own content, exactly as on the operator canvas -- reserving a fixed
84+
width here pushed the switch 74px further left than its counterpart. */
85+
texera-user-icon {
86+
flex: none;
87+
}
88+
}
89+
90+
/* One workflow, two ways of working on it. Rendered identically in the operator canvas
91+
and the Form View, in the same slot of the same title row, so the control never moves
92+
when the view does -- that stillness is what makes the two read as two views of one
93+
thing rather than two pages.
94+
95+
Deliberately quiet: the indicator is a rule sitting on the row's own bottom border,
96+
not a filled button. This is secondary navigation and it shares a screen with Run,
97+
which is the one thing here that should be solid blue. The current view is inert --
98+
clicking the view you are already in should do nothing. */
99+
.view-switch {
100+
display: inline-flex;
101+
align-self: stretch;
102+
align-items: stretch;
103+
flex: none;
104+
gap: 20px;
105+
margin-right: 20px;
106+
107+
button {
108+
appearance: none;
109+
border: 0;
110+
background: none;
111+
cursor: pointer;
112+
font: inherit;
113+
font-size: 13px;
114+
color: rgba(0, 0, 0, 0.45);
115+
padding: 0;
116+
position: relative;
117+
display: inline-flex;
118+
align-items: center;
119+
white-space: nowrap;
120+
transition: color 0.15s;
121+
122+
/* Sits on the row's bottom rule, so the two read as tabs of the row rather than
123+
as a widget dropped into it. */
124+
&::after {
125+
content: "";
126+
position: absolute;
127+
left: -2px;
128+
right: -2px;
129+
bottom: -1px;
130+
height: 2px;
131+
background: transparent;
132+
transition: background 0.15s;
133+
}
134+
135+
&:hover:not(.on) {
136+
color: rgba(0, 0, 0, 0.85);
137+
}
138+
}
139+
140+
button.on {
141+
color: rgba(0, 0, 0, 0.85);
142+
font-weight: 500;
143+
cursor: default;
144+
145+
&::after {
146+
background: $blue;
147+
}
148+
}
149+
}
150+
151+
.pc-page {
152+
max-width: 1320px;
153+
margin: 0 auto;
154+
padding: 22px 24px 80px;
155+
}
156+
157+
.pc-rule {
158+
height: 1px;
159+
background: $divider;
160+
margin: 20px 0 24px;
161+
}
162+
163+
.pc-loading {
164+
color: $text-2;
165+
padding: 40px 0;
166+
}

0 commit comments

Comments
 (0)