Skip to content

Commit bb26b09

Browse files
authored
feat: component transfer
1 parent 9cdc929 commit bb26b09

30 files changed

+1078
-61
lines changed

.devcontainer/devcontainer.json

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
1-
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2-
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
31
{
4-
"name": "design-angular-kit",
5-
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6-
// "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm",
7-
"build": {
8-
"dockerfile": "Dockerfile"
9-
},
10-
// Features to add to the dev container. More info: https://containers.dev/features.
11-
"features": {
12-
"ghcr.io/devcontainers-contrib/features/angular-cli:2": {
13-
"version": "18.0.7"
14-
},
15-
"ghcr.io/devcontainers-contrib/features/cz-cli:1": {}
16-
},
17-
"mounts": [
18-
"source=design-angular-kit-bundle-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" // deps volume
19-
],
20-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
21-
// "forwardPorts": [],
22-
// Use 'postCreateCommand' to run commands after the container is created.
23-
"postCreateCommand": "sh .devcontainer/scripts/postCreateCommand.sh",
24-
// Configure tool-specific properties.
25-
"customizations": {
26-
"vscode": {
27-
"extensions": [
28-
"angular.ng-template",
29-
"cyrilletuzi.angular-schematics",
30-
"esbenp.prettier-vscode",
31-
"dbaeumer.vscode-eslint",
32-
"vivaxy.vscode-conventional-commits"
33-
]
34-
}
35-
}
36-
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
37-
// "remoteUser": "root"
2+
"name": "design-angular-kit",
3+
"build": {
4+
"dockerfile": "Dockerfile"
5+
},
6+
"mounts": [
7+
"source=design-angular-kit-bundle-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" // deps volume
8+
],
9+
"remoteUser": "root",
10+
"postCreateCommand": "sh .devcontainer/scripts/postCreateCommand.sh",
11+
// "postStartCommand": "/bin/bash -c '.devcontainer/scripts/postStartCommand.sh \"${containerWorkspaceFolder}\"'",
12+
"postStartCommand": "sh .devcontainer/scripts/postStartCommand.sh \"${containerWorkspaceFolder}\"",
13+
"customizations": {
14+
"vscode": {
15+
"extensions": [
16+
"angular.ng-template",
17+
"cyrilletuzi.angular-schematics",
18+
"esbenp.prettier-vscode",
19+
"dbaeumer.vscode-eslint",
20+
"vivaxy.vscode-conventional-commits"
21+
],
22+
"settings": {
23+
"editor.formatOnSave": true,
24+
"editor.codeActionsOnSave": {
25+
"source.organizeImports": "always"
26+
}
27+
}
28+
}
29+
},
30+
"features": {
31+
"ghcr.io/devcontainers-contrib/features/angular-cli:2": {
32+
"version": "18.0.7"
33+
},
34+
"ghcr.io/devcontainers-contrib/features/cz-cli:1": {}
35+
}
3836
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#/bin/bash
2+
echo "lifecycle hook: postCreateCommand => start"
23

3-
echo "Set node_modules permission"
4-
sudo chown -R node:node node_modules
5-
echo "Set node_modules permission: done"
4+
# echo "Set node_modules permission"
5+
# sudo chown -R node:node node_modules
6+
# echo "Set node_modules permission: done"
67

78
echo "Installing deps"
89
npm i
9-
echo "Installing deps: done"
10+
echo "Installing deps: done"
11+
12+
echo "lifecycle hook: postCreateCommand => done"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
WORKSPACE_FOLDER=$1
4+
5+
echo "lifecycle hook: postStartCommand => start"
6+
echo "Set $WORKSPACE_FOLDER as git safe directory"
7+
git config --global --add safe.directory $WORKSPACE_FOLDER
8+
# git config --global --add safe.directory /workspaces/design-angular-kit
9+
echo "lifecycle hook: postStartCommand => done"

angular.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
"serve": {
107107
"builder": "@angular-devkit/build-angular:dev-server",
108108
"options": {
109-
"buildTarget": "design-angular-kit-bundle:build"
109+
"buildTarget": "design-angular-kit-bundle:build",
110+
"host": "127.0.0.1"
110111
},
111112
"configurations": {
112113
"production": {

projects/design-angular-kit/src/lib/components/form/form.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ItTextareaComponent } from './textarea/textarea.component';
1010
import { ItUploadDragDropComponent } from './upload-drag-drop/upload-drag-drop.component';
1111
import { ItUploadFileListComponent } from './upload-file-list/upload-file-list.component';
1212
import { ItAutocompleteComponent } from './autocomplete/autocomplete.component';
13+
import { ItTransferComponent } from './transfer/transfer.component';
1314

1415
const formComponents = [
1516
ItAutocompleteComponent,
@@ -21,6 +22,7 @@ const formComponents = [
2122
ItRatingComponent,
2223
ItSelectComponent,
2324
ItTextareaComponent,
25+
ItTransferComponent,
2426
ItUploadDragDropComponent,
2527
ItUploadFileListComponent,
2628
];
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { TransferItem, TransferItemSelection } from '../transfer.model';
2+
import { SelectionState, State } from './transfer.state';
3+
4+
//#region private utility functions
5+
const generateSelectAll = <T>(checked: boolean, items: TransferItem<T>[]) => {
6+
const selected = new Set<TransferItem<T>>();
7+
if (checked) {
8+
items.forEach(item => selected.add(item));
9+
}
10+
11+
return selected;
12+
};
13+
14+
const updateSelected = <T>(set: Set<TransferItem<T>>, item: TransferItem<T>) => {
15+
if (set.has(item)) {
16+
set.delete(item);
17+
} else {
18+
set.add(item);
19+
}
20+
21+
return set;
22+
};
23+
//#endregion
24+
25+
//#region reducers
26+
const init = <T>(state: State<T>, { source, target }: SelectionState<T>) => ({
27+
...state,
28+
initialItems: {
29+
source: [...source],
30+
target: [...target],
31+
},
32+
current: {
33+
source: [...source],
34+
target: [...target],
35+
},
36+
});
37+
38+
const transfer = <T>(state: State<T>) => {
39+
return {
40+
...state,
41+
current: {
42+
...state.current,
43+
source: state.current.source.filter(i => !state.selections.source.has(i)),
44+
target: Array.from(new Set([...state.current.target, ...Array.from(state.selections.source)] as TransferItemSelection<T>)),
45+
},
46+
selections: {
47+
...state.selections,
48+
source: new Set<TransferItem<T>>(),
49+
},
50+
operationsEnabled: {
51+
...state.operationsEnabled,
52+
transfer: false,
53+
reset: true,
54+
},
55+
} satisfies State<T>;
56+
};
57+
58+
const backtransfer = <T>(state: State<T>) => {
59+
return {
60+
...state,
61+
current: {
62+
...state.current,
63+
target: state.current.target.filter(i => !state.selections.target.has(i)),
64+
source: Array.from(new Set([...state.current.source, ...Array.from(state.selections.target)] as TransferItemSelection<T>)),
65+
},
66+
selections: {
67+
...state.selections,
68+
target: new Set<TransferItem<T>>(),
69+
},
70+
operationsEnabled: {
71+
...state.operationsEnabled,
72+
backtransfer: false,
73+
reset: true,
74+
},
75+
} satisfies State<T>;
76+
};
77+
78+
const reset = <T>(state: State<T>) => {
79+
return {
80+
...state,
81+
current: {
82+
source: [...state.initialItems.source],
83+
target: [...state.initialItems.target],
84+
},
85+
operationsEnabled: {
86+
...state.operationsEnabled,
87+
reset: false,
88+
},
89+
} satisfies State<T>;
90+
};
91+
92+
const selectAllSource = <T>(state: State<T>, { checked }: { checked: boolean }) => {
93+
const items = state.current.source;
94+
const selected = generateSelectAll(checked, items);
95+
const transfer = Boolean(selected.size);
96+
97+
return {
98+
...state,
99+
selections: {
100+
...state.selections,
101+
source: selected,
102+
},
103+
operationsEnabled: {
104+
...state.operationsEnabled,
105+
transfer,
106+
},
107+
} satisfies State<T>;
108+
};
109+
110+
const selectAllTarget = <T>(state: State<T>, { checked }: { checked: boolean }) => {
111+
const items = state.current.target;
112+
const selected = generateSelectAll(checked, items);
113+
const backtransfer = Boolean(selected.size);
114+
115+
return {
116+
...state,
117+
selections: {
118+
...state.selections,
119+
target: selected,
120+
},
121+
operationsEnabled: {
122+
...state.operationsEnabled,
123+
backtransfer,
124+
},
125+
} satisfies State<T>;
126+
};
127+
128+
const selectionItemSource = <T>(previousState: State<T>, { item }: { item: TransferItem<T> }) => {
129+
const selected = updateSelected(previousState.selections.source, item);
130+
const selectedItems = Array.from(selected);
131+
const transfer = Boolean(selectedItems.length);
132+
const source = new Set([...selectedItems]);
133+
134+
const state = {
135+
...previousState,
136+
selections: {
137+
...previousState.selections,
138+
source,
139+
},
140+
operationsEnabled: {
141+
...previousState.operationsEnabled,
142+
transfer,
143+
},
144+
} satisfies State<T>;
145+
146+
return state;
147+
};
148+
149+
const selectionItemTarget = <T>(previousState: State<T>, { item }: { item: TransferItem<T> }) => {
150+
const selected = updateSelected(previousState.selections.target, item);
151+
const selectedItems = Array.from(selected);
152+
const backtransfer = Boolean(selectedItems.length);
153+
const target = new Set([...selectedItems]);
154+
155+
const state = {
156+
...previousState,
157+
selections: {
158+
...previousState.selections,
159+
target,
160+
},
161+
operationsEnabled: {
162+
...previousState.operationsEnabled,
163+
backtransfer,
164+
},
165+
} satisfies State<T>;
166+
167+
return state;
168+
};
169+
//#endregion reducers
170+
171+
//#region public reducers
172+
const initialStateFn = <T>() => ({
173+
initialItems: {
174+
source: [],
175+
target: [],
176+
},
177+
current: {
178+
source: [],
179+
target: [],
180+
},
181+
selections: {
182+
source: new Set<TransferItem<T>>(),
183+
target: new Set<TransferItem<T>>(),
184+
},
185+
operationsEnabled: {
186+
transfer: false,
187+
backtransfer: false,
188+
reset: false,
189+
},
190+
});
191+
const initFn =
192+
<T>(payload: SelectionState<T>) =>
193+
(state: State<T>) =>
194+
init(state, payload);
195+
196+
const transferFn =
197+
<T>() =>
198+
(state: State<T>) =>
199+
transfer(state);
200+
201+
const backtransferFn =
202+
<T>() =>
203+
(state: State<T>) =>
204+
backtransfer(state);
205+
206+
const resetFn =
207+
<T>() =>
208+
(state: State<T>) =>
209+
reset(state);
210+
211+
const selectAllSourceFn =
212+
<T>({ checked }: { checked: boolean }) =>
213+
(state: State<T>) =>
214+
selectAllSource(state, { checked }) as State<T>;
215+
216+
const selectAllTargetFn =
217+
<T>({ checked }: { checked: boolean }) =>
218+
(state: State<T>) =>
219+
selectAllTarget(state, { checked }) as State<T>;
220+
221+
const selectionItemSourceFn =
222+
<T>({ item }: { item: TransferItem<T> }) =>
223+
(state: State<T>) =>
224+
selectionItemSource(state, { item }) as State<T>;
225+
226+
const selectionItemTargetFn =
227+
<T>({ item }: { item: TransferItem<T> }) =>
228+
(state: State<T>) =>
229+
selectionItemTarget(state, { item }) as State<T>;
230+
//#endregion
231+
232+
export default {
233+
initialStateFn,
234+
initFn,
235+
transferFn,
236+
backtransferFn,
237+
resetFn,
238+
selectAllSourceFn,
239+
selectAllTargetFn,
240+
selectionItemSourceFn,
241+
selectionItemTargetFn,
242+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { TransferItem } from '../transfer.model';
2+
3+
export interface SelectionState<T> {
4+
source: Array<TransferItem<T>>;
5+
target: Array<TransferItem<T>>;
6+
}
7+
8+
export interface State<T> {
9+
initialItems: SelectionState<T>;
10+
current: SelectionState<T>;
11+
selections: {
12+
source: Set<TransferItem<T>>;
13+
target: Set<TransferItem<T>>;
14+
};
15+
operationsEnabled: {
16+
transfer: boolean;
17+
backtransfer: boolean;
18+
reset: boolean;
19+
};
20+
}

0 commit comments

Comments
 (0)