Skip to content

Commit 6e47e32

Browse files
authored
Merge pull request #1 from AndreiTelteu/feature/taskbar
Added taskbar with basic design, focus and close function
2 parents caf3a75 + d8099da commit 6e47e32

File tree

7 files changed

+219
-38
lines changed

7 files changed

+219
-38
lines changed

example/src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ export default function App(props: any): JSX.Element {
3434
open All posts
3535
</button>
3636
</p>
37+
<style>{`
38+
html {
39+
overflow: hidden;
40+
}
41+
body {
42+
margin: 0;
43+
padding: 0;
44+
overflow: hidden;
45+
font-family: sans-serif;
46+
}
47+
`}</style>
3748
</WindowManager>
3849
);
3950
}

src/Taskbar.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Component, For } from 'solid-js';
2+
import windowStore from './store/windowStore';
3+
import WindowIcon from './WindowIcon';
4+
5+
const Taskbar: Component = (props) => {
6+
const [store, actions] = windowStore();
7+
const openedWindows = () => Object.keys(store.windows);
8+
return (
9+
<>
10+
<div class="window-manager-taskbar-placeholder"></div>
11+
<div class="window-manager-taskbar">
12+
<div class="window-manager-taskbar-wrapper">
13+
<For each={openedWindows()}>
14+
{(item, index) => {
15+
const windowProps = store.windows[item];
16+
return (
17+
<div
18+
class={`${'window-manager-taskbar-item'} ${windowProps?.attrs?.zIndex == 1 ? 'is-active' : ''}`}
19+
onClick={() => {
20+
actions.focusWindow(windowProps?.attrs?.key);
21+
}}
22+
>
23+
<div class="window-manager-taskbar-icon">
24+
<WindowIcon icon={windowProps?.attrs?.icon} name={windowProps?.component} />
25+
</div>
26+
<div class="window-manager-taskbar-name">
27+
{windowProps?.attrs?.state?.title || windowProps?.component || ''}
28+
</div>
29+
<button
30+
class="window-manager-taskbar-btn"
31+
onClick={(e) => {
32+
e.stopPropagation();
33+
actions.closeWindow(windowProps?.attrs?.key);
34+
}}
35+
>
36+
X
37+
</button>
38+
</div>
39+
);
40+
}}
41+
</For>
42+
</div>
43+
</div>
44+
<style>{`
45+
.window-manager-taskbar-placeholder {
46+
width: 100%;
47+
height: 60px;
48+
}
49+
.window-manager-taskbar {
50+
position: fixed;
51+
top: 0;
52+
left: 0;
53+
right: 0;
54+
width: 100%;
55+
height: 60px;
56+
z-index: 100;
57+
background: rgba(255, 255, 255, 0.6);
58+
backdrop-filter: blur(5px);
59+
padding: 10px;
60+
}
61+
.window-manager-taskbar-wrapper {
62+
display: flex;
63+
flex-direction: row;
64+
flex-wrap: wrap;
65+
align-items: stretch;
66+
height: 100%;
67+
gap: 10px;
68+
}
69+
.window-manager-taskbar-item {
70+
display: flex;
71+
flex-direction: row;
72+
align-items: center;
73+
height: 40px;
74+
width: 200px;
75+
border-radius: 6px;
76+
cursor: pointer;
77+
background: rgba(0, 0, 0, 0.1);
78+
border: 1px solid rgba(0, 0, 0, 0.1);
79+
backdrop-filter: blur(5px);
80+
transition: all 150ms ease-in-out;
81+
}
82+
.window-manager-taskbar-item:hover {
83+
background: rgba(255, 255, 255, 0.1);
84+
border-color: rgba(255, 255, 255, 0.6);
85+
}
86+
.window-manager-taskbar-item.is-active {
87+
background: rgba(255, 255, 255, 0.6);
88+
}
89+
.window-manager-taskbar-icon {
90+
flex-shrink: 0;
91+
}
92+
.window-manager-taskbar-name {
93+
flex-grow: 1;
94+
font-size: 13px;
95+
overflow: hidden;
96+
text-overflow: ellipsis;
97+
max-height: 1.9rem;
98+
word-break: break-all;
99+
}
100+
.window-manager-taskbar-btn {
101+
flex-shrink: 0;
102+
margin: 10px;
103+
appearance: none;
104+
cursor: pointer;
105+
display: flex;
106+
flex-direction: row;
107+
align-items: center;
108+
justify-content: center;
109+
font-size: 11px;
110+
color: rgba(0, 0, 0, 0.3);
111+
font-family: sans-serif;
112+
font-weight: 600;
113+
text-transform: uppercase;
114+
background: rgba(204, 204, 204, 1);
115+
border-radius: 100%;
116+
width: 18px;
117+
height: 18px;
118+
border: none;
119+
outline: none;
120+
transition: all 150ms ease-in-out;
121+
}
122+
.window-manager-taskbar-btn:hover {
123+
color: rgba(0, 0, 0, 0.6);
124+
}
125+
`}</style>
126+
</>
127+
);
128+
};
129+
export default Taskbar;

src/WindowControls.tsx

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1-
import { JSX, createSignal, ErrorBoundary, Show, splitProps, Suspense } from 'solid-js';
1+
import { JSX, createSignal, ErrorBoundary, Show, splitProps, Suspense, createEffect } from 'solid-js';
22
import { Dynamic } from 'solid-js/web';
3-
import windowState from './store/windowState';
3+
import windowStore from './store/windowStore';
4+
import WindowIcon from './WindowIcon';
45

56
export function WindowControls(attrs: any): JSX.Element {
67
const [props, rest] = splitProps(attrs, ['component', 'props', 'attrs', 'controls', 'loadWindow', 'windowApi']);
8+
const [store, actions] = windowStore();
79
const [windowState, setWindowState] = createSignal({
810
title: 'Loading...',
911
loading: true,
1012
});
1113

14+
createEffect(() => {
15+
let newState = windowState();
16+
const syncKeys = ['title', 'loading'];
17+
let shouldUpdate = false;
18+
let newAttrState = {};
19+
syncKeys.forEach((key) => {
20+
if (newState?.[key] !== props?.attrs?.state?.[key]) {
21+
shouldUpdate = true;
22+
newAttrState[key] = newState[key];
23+
}
24+
});
25+
if (shouldUpdate) {
26+
actions.updateWindowState(props?.attrs?.key, newAttrState);
27+
}
28+
});
29+
1230
const NewComponent = props?.loadWindow?.(props);
1331
return (
1432
<div
@@ -34,12 +52,7 @@ export function WindowControls(attrs: any): JSX.Element {
3452
<div class="window-controller-container">
3553
<div class="window-controller-header">
3654
<div class="window-controller-header-icon">
37-
<Show
38-
when={props.attrs.icon}
39-
fallback={<span class="icon-empty">{String(props?.component || ' ').charAt(0)}</span>}
40-
>
41-
icon
42-
</Show>
55+
<WindowIcon icon={props?.attrs?.icon} name={props?.component} />
4356
</div>
4457
<div class="window-controller-header-title">
4558
<span>{windowState().title}</span>
@@ -125,22 +138,6 @@ export function WindowControls(attrs: any): JSX.Element {
125138
flex-shrink: 0;
126139
cursor: default;
127140
}
128-
.window-controller-header-icon .icon-empty {
129-
display: flex;
130-
flex-direction: row;
131-
align-items: center;
132-
justify-content: center;
133-
font-size: 11px;
134-
color: #5c5c5c;
135-
font-family: sans-serif;
136-
text-transform: uppercase;
137-
background: #ccc;
138-
border-radius: 100%;
139-
width: 18px;
140-
height: 18px;
141-
margin: 10px;
142-
cursor: default;
143-
}
144141
.window-controller-header-title {
145142
flex: 1;
146143
cursor: default;

src/WindowIcon.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Component, ErrorBoundary, JSX, Show, splitProps, Suspense } from 'solid-js';
2+
import { Dynamic } from 'solid-js/web';
3+
4+
export default function WindowIcon(attrs: any): JSX.Element {
5+
const [props] = splitProps(attrs, ['icon', 'name']);
6+
return (
7+
<>
8+
<Show
9+
when={props.icon}
10+
fallback={<span class="window-manager-icon-empty">{String(props?.name || ' ').charAt(0)}</span>}
11+
>
12+
<ErrorBoundary fallback={(error) => <></>}>
13+
<Suspense fallback={<></>}>
14+
<Dynamic component={props.icon} />
15+
</Suspense>
16+
</ErrorBoundary>
17+
</Show>
18+
<style>{`
19+
.window-manager-icon-empty {
20+
display: flex;
21+
flex-direction: row;
22+
align-items: center;
23+
justify-content: center;
24+
font-size: 11px;
25+
color: #5c5c5c;
26+
font-family: sans-serif;
27+
text-transform: uppercase;
28+
background: #ccc;
29+
border-radius: 100%;
30+
width: 18px;
31+
height: 18px;
32+
margin: 10px;
33+
cursor: inherit;
34+
}
35+
`}</style>
36+
</>
37+
);
38+
}

src/WindowManager.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { children, JSX, For, onMount, splitProps } from 'solid-js';
2-
import windowState from './store/windowState';
2+
import windowStore from './store/windowStore';
3+
import Taskbar from './Taskbar';
34
import { WindowControls } from './WindowControls';
45

56
export function WindowManager(attrs: any): JSX.Element {
67
const child = children(() => attrs.children);
78
const [props, rest] = splitProps(attrs, ['onReady', 'loadWindow', 'options']);
8-
const [store, actions] = windowState();
9+
const [store, actions] = windowStore();
910
const openedWindows = () => Object.keys(store.windows);
1011

1112
const openWindow = (component, props = {}) => {
@@ -156,6 +157,7 @@ export function WindowManager(attrs: any): JSX.Element {
156157

157158
return (
158159
<div class="window-manager-wrapper" ref={mainWrapper} {...events}>
160+
<Taskbar />
159161
{child()}
160162
<For
161163
each={openedWindows()}
@@ -174,14 +176,8 @@ export function WindowManager(attrs: any): JSX.Element {
174176
}}
175177
/>
176178
<style>{`
177-
html {
178-
overflow: hidden;
179-
}
180-
body {
181-
margin: 0;
182-
padding: 0;
183-
overflow: hidden;
184-
font-family: sans-serif;
179+
.window-manager-wrapper, .window-manager-wrapper * {
180+
box-sizing: border-box;
185181
}
186182
.window-manager-wrapper {
187183
width: 100vw;

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { WindowManager } from './WindowManager';
22
export * from './types';
3-
export { default as windowState } from './store/windowState';
3+
export { default as windowStore } from './store/windowStore';

src/store/windowState.ts renamed to src/store/windowStore.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { produce } from 'solid-js/store';
22
import { defineStore } from 'solidjs-storex';
33
import windowPreferencesStore, { findWindowPreferences } from './windowPreferencesStore';
44

5-
type WindowState = {
5+
type WindowStore = {
66
windows: WindowsObject;
77
};
88
interface WindowsObject {
@@ -13,21 +13,25 @@ interface WindowProps {
1313
props: any;
1414
attrs: WindowAttrs;
1515
}
16+
interface WindowState {
17+
[key: string]: any;
18+
}
1619
interface WindowAttrs {
1720
minimized: boolean;
1821
pos: [number, number];
1922
size: [number, number];
2023
zIndex: number;
2124
icon?: null | string;
2225
key: string;
26+
state: WindowState;
2327
}
2428

2529
const [windowPreferencesState, { save: saveWindowPreferences }] = windowPreferencesStore();
2630

2731
const initStore = defineStore({
2832
state: {
2933
windows: {},
30-
} as WindowState,
34+
} as WindowStore,
3135
options: {
3236
persistance: true,
3337
storageKey: 'windows-state',
@@ -47,7 +51,7 @@ const initStore = defineStore({
4751
},
4852
closeWindow: (key) => {
4953
set(
50-
produce((state: WindowState) => {
54+
produce((state: WindowStore) => {
5155
if (state?.windows?.hasOwnProperty(key)) {
5256
delete state.windows[key];
5357
}
@@ -76,6 +80,11 @@ const initStore = defineStore({
7680
});
7781
set('windows', key, 'attrs', 'zIndex', 1);
7882
},
83+
updateWindowState: (key, newState) => {
84+
set('windows', key, 'attrs', 'state', (s) => {
85+
return { ...s, ...newState };
86+
});
87+
},
7988
}),
8089
});
8190

@@ -89,6 +98,7 @@ const getDefaultWindowsAttrs = (props): WindowAttrs => {
8998
zIndex: 1,
9099
icon: null,
91100
key: '',
101+
state: { title: '', loading: true },
92102
};
93103
if (props.component) {
94104
let preferences = findWindowPreferences(windowPreferencesState, props.component);

0 commit comments

Comments
 (0)