Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/apps/components/masteroptions/MasterOptions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import ActionBar from "$lib/Window/ActionBar.svelte";
import ActionButton from "$lib/Window/ActionBar/ActionButton.svelte";
import ActionGroup from "$lib/Window/ActionBar/ActionGroup.svelte";
import ActionIconButton from "$lib/Window/ActionBar/ActionIconButton.svelte";
import { Daemon } from "$ts/daemon";
import { Server } from "$ts/env";
import MasterOptionButton from "./MasterOptions/MasterOptionButton.svelte";
import type { MasterOptionsRuntime } from "./runtime";
import { MasterOptionStore } from "./store";

const { process }: { process: MasterOptionsRuntime } = $props();
const { loading } = process;
</script>

<div class="header">
<img src={process.getIconCached("ElevationIcon")} alt="" />
<h1>ArcOS Master Options</h1>
</div>
<div class="content">
<p>Choose one of the following options to continue:</p>
<div class="options">
{#each MasterOptionStore as option (option.caption)}
<MasterOptionButton {process} {option}></MasterOptionButton>
{/each}
</div>
<div class="authorization">
<p class="username">Logged in as <b>{Daemon.userInfo.username}</b></p>
<p class="hostname">{Server.hostname ?? "No server"}</p>
</div>
</div>
<ActionBar>
{#snippet leftContent()}
<ActionGroup>
<ActionIconButton className="restart" icon="rotate-ccw" onclick={() => Daemon.power?.restart(true)} disabled={$loading} />
<ActionIconButton className="shutdown" icon="power" onclick={() => Daemon.power?.shutdown(true)} disabled={$loading} />
<ActionIconButton className="logout" icon="log-out" onclick={() => Daemon.power?.logoff(true)} disabled={$loading} />
</ActionGroup>
{/snippet}
{#snippet rightContent()}
<ActionButton suggested onclick={() => process.closeWindow()} disabled={$loading}>Close</ActionButton>
{/snippet}
</ActionBar>
43 changes: 43 additions & 0 deletions src/apps/components/masteroptions/MasterOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import "$css/apps/components/masteroptions.css";
import type { App } from "$types/app";
import MasterOptions from "./MasterOptions.svelte";
import { MasterOptionsRuntime } from "./runtime";

const MasterOptionsApp: App = {
metadata: {
name: "Master Options",
version: "1.0.0",
author: "Izaak Kuipers",
icon: "ElevationIcon",
},
position: { centered: true },
size: { w: 400, h: 490 },
minSize: { w: 400, h: 490 },
maxSize: { w: 400, h: 490 },
state: {
maximized: false,
minimized: false,
resizable: false,
fullscreen: false,
headless: true,
},
controls: {
minimize: false,
maximize: false,
close: true,
},
assets: {
runtime: MasterOptionsRuntime,
component: MasterOptions as any,
},
glass: false,
elevated: false,
hidden: true,
core: false,
overlay: false,
noSafeMode: false,
vital: true,
id: "MasterOptions",
};

export default MasterOptionsApp;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import type { MasterOptionsRuntime } from "../runtime";
import type { MasterOption } from "../types";

const { process, option }: { process: MasterOptionsRuntime; option: MasterOption } = $props();
const { loading } = process;

async function exec() {
$loading = true;
await option.action(process);
process.closeWindow();
}
</script>

<button class="master-option" title={option.caption} onclick={exec} disabled={$loading}>
<img src={process.getIconCached(option.image)} alt="" />
<span>{option.caption}</span>
</button>
66 changes: 66 additions & 0 deletions src/apps/components/masteroptions/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { IAppProcess } from "$interfaces/app";
import { AppProcess } from "$ts/apps/process";
import { Daemon } from "$ts/daemon";
import { Stack } from "$ts/env";
import { Plural } from "$ts/util";
import { Store } from "$ts/writable";
import type { AppProcessData } from "$types/app";

export class MasterOptionsRuntime extends AppProcess {
loading = Store<boolean>(false);

//#region LIFECYCLE

constructor(pid: number, parentPid: number, app: AppProcessData) {
super(pid, parentPid, app);

this.setSource(__SOURCE__);
}

async render() {
if (await this.closeIfSecondInstance()) return;
}

//#endregion LIFECYCLE

async killGhosts() {
const state = Stack.renderer?.currentState || [];
const ghosts = [];

for (const pid of state) {
const proc = Stack.getProcess(pid);
if (!proc) {
await Stack.renderer?.remove(pid);
ghosts.push(pid);
}
}

await Daemon.getShell()?.ShowToast(
{
content: `Removed ${ghosts.length} ${Plural("ghost", ghosts.length)} from the renderer.`,
icon: "ghost",
},
4000
);
}

async killUserApps() {
const userApps: IAppProcess[] = [...Stack.store()]
.map(([_, v]) => v as IAppProcess)
.filter(
(proc) => proc instanceof AppProcess && !proc.app.data.core && proc.app.id !== "arcShell" && proc.app.id !== "wallpaper"
);

for (const proc of userApps) {
await Stack.kill(proc.pid, true);
}

await Daemon.getShell()?.ShowToast(
{
content: `Forcefully terminated ${userApps.length} ${Plural("application", userApps.length)}.`,
icon: "power",
},
4000
);
}
}
35 changes: 35 additions & 0 deletions src/apps/components/masteroptions/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Env } from "$ts/env";
import type { MasterOption } from "./types";

export const MasterOptionStore: MasterOption[] = [
{
caption: "Manage Processes",
image: "ProcessManagerIcon",
action: async (runtime) => await runtime.spawnOverlayApp("processManager", +Env.get("shell_pid"), "Processes"),
},
{
caption: "Remove any ghost windows",
image: "RestartIcon",
action: async (runtime) => await runtime.killGhosts(),
},
{
caption: "Kill all user applications",
image: "ErrorIcon",
action: async (runtime) => await runtime.killUserApps(),
},
{
caption: "Launch ArcTerm",
image: "ArcTermIcon",
action: async (runtime) => await runtime.spawnApp("ArcTerm", +Env.get("shell_pid")),
},
{
caption: "Advanced System Settings",
image: "WindowSettingsIcon",
action: async (runtime) => await runtime.spawnApp("AdvSystemSettings", +Env.get("shell_pid")),
},
{
caption: "Report a bug",
image: "BugReportIcon",
action: async (runtime) => await runtime.spawnOverlayApp("BugHuntCreator", +Env.get("shell_pid")),
},
];
7 changes: 7 additions & 0 deletions src/apps/components/masteroptions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { MasterOptionsRuntime } from "./runtime";

export interface MasterOption {
caption: string;
image: string;
action: (process: MasterOptionsRuntime) => Promise<any>;
}
2 changes: 1 addition & 1 deletion src/apps/components/systemshortcuts/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class SystemShortcutsRuntime extends AppProcess {
alt: true,
key: "Backspace",
action: () => {
this.spawnOverlayApp("processManager", +Env.get("shell_pid"), "Processes");
this.spawnOverlayApp("MasterOptions", +Env.get("shell_pid"));
},
global: true,
}
Expand Down
8 changes: 8 additions & 0 deletions src/css/apps/admin/adminportal/page/scopes.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#AdminPortal .container.scopes div.scope-list {
height: 100%;
overflow-y: scroll;
}

#AdminPortal .container.scopes div.row {
min-height: 36px;
height: fit-content;
Expand All @@ -14,6 +19,9 @@
#AdminPortal .container.scopes div.row.header {
background-color: var(--button-bg);
font-weight: bold;
position: sticky;
top: 0;
z-index: 1000;
}

#AdminPortal .container.scopes div.row div.segment {
Expand Down
85 changes: 85 additions & 0 deletions src/css/apps/components/masteroptions.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
.shade-MasterOptions {
background-position: center;
background-size: cover;
background-image: linear-gradient(to bottom, #0006 0%, #0006 100%), var(--wallpaper);
z-index: 10000000000;
}

#MasterOptions > div.body {
padding: 0;
display: flex;
flex-direction: column;
}

#MasterOptions > div.body div.header {
padding: 10px;
border-bottom: var(--win-border);
display: flex;
align-items: center;
gap: 10px;
}

#MasterOptions > div.body div.header h1 {
font-size: 14px;
font-weight: bold;
}

#MasterOptions > div.body div.content {
height: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
padding: 15px;
}

#MasterOptions > div.body div.options {
height: 0;
flex-grow: 1;
margin: 15px 0;
padding: 10px;
border-radius: 15px;
border: var(--win-border);
}

#MasterOptions > div.body div.options button {
height: 40px;
width: 100%;
display: flex;
align-items: center;
gap: 15px;
padding: 0 10px;
}

#MasterOptions > div.body div.options button img {
height: 24px;
}

#MasterOptions > div.body div.options button:not(:hover):not(:active) {
outline: none;
background-color: transparent;
}

#MasterOptions > div.body div.authorization {
display: flex;
justify-content: space-between;
}

#MasterOptions > div.body div.authorization p.hostname {
opacity: 0.5;
}

#MasterOptions > div.body div.window-action-bar button.lucide {
color: var(--win-bg);
}

#MasterOptions > div.body div.window-action-bar button.restart {
background-color: var(--clr-orange-fg);
}

#MasterOptions > div.body div.window-action-bar button.shutdown {
background-color: var(--clr-red-fg);
}

#MasterOptions > div.body div.window-action-bar button.logout {
background-color: var(--clr-green-fg);
}
8 changes: 4 additions & 4 deletions src/interfaces/contexts/power.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import type { ReadableStore } from "$types/writable";

export interface IPowerUserContext extends IUserContext {
battery: ReadableStore<BatteryType | undefined>;
logoff(): Promise<void>;
shutdown(): Promise<void>;
restart(): Promise<void>;
logoffSafeMode(): Promise<void>;
logoff(force?: boolean): Promise<void>;
shutdown(force?: boolean): Promise<void>;
restart(force?: boolean): Promise<void>;
logoffSafeMode(force?: boolean): Promise<void>;
toLogin(type: string, props?: Record<string, any>, force?: boolean): Promise<void>;
closeOpenedApps(type: string, props?: Record<string, any>, force?: boolean): Promise<boolean>;
batteryInfo(): Promise<BatteryType | undefined>;
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/modules/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface IServerManager {
previewBranch?: string;
servers: ServerOption[];
url?: string;
hostname?: string;
authCode?: string;
checkUsernameAvailability(username: string): Promise<boolean>;
checkEmailAvailability(username: string): Promise<boolean>;
Expand Down
1 change: 1 addition & 0 deletions src/ts/apps/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const BuiltinAppImportPathAbsolutes = import.meta.glob([
"$apps/components/serviceinfo/ServiceInfo",
"$apps/components/iconeditdialog/IconEditDialog",
"$apps/components/arctermcolors/ArcTermColors",
"$apps/components/masteroptions/MasterOptions",
"$apps/user/advsystemsettings/AdvSystemSettings",
"$apps/user/appstore/AppStore",
"$apps/user/arcterm/ArcTerm",
Expand Down
Loading