) {
17 | const container = createElement("div");
18 | container.classList.add("top-bar");
19 |
20 | const left = document.createElement("div");
21 |
22 | let backButton: HTMLButtonElement;
23 | if (!opts?.noBack) {
24 | backButton = Button({
25 | style: "icon-large",
26 | iconLeft: "Arrow"
27 | });
28 | backButton.classList.add(BACK_BUTTON_CLASS);
29 | backButton.onclick = () => {
30 | if (opts?.onBack && !opts.onBack()) {
31 | return;
32 | }
33 |
34 | stackNavigation.back();
35 | };
36 | left.append(backButton);
37 | } else {
38 | container.classList.add("no-back");
39 | }
40 |
41 | const titlesContainer = document.createElement("div");
42 | titlesContainer.classList.add("titles");
43 | left.append(titlesContainer);
44 |
45 | let title: HTMLHeadingElement;
46 | if (opts?.title) {
47 | title = document.createElement("h1");
48 | title.innerText = opts.title;
49 | titlesContainer.append(title);
50 | }
51 | if (opts?.subtitle) {
52 | const subtitle = document.createElement("p");
53 | subtitle.innerText = opts.subtitle;
54 | titlesContainer.append(subtitle);
55 | }
56 |
57 | const right = document.createElement("div");
58 | right.classList.add("top-bar-actions");
59 |
60 | if (opts?.actions) {
61 | right.append(...opts.actions);
62 | }
63 |
64 | container.append(left, right);
65 |
66 | const setHeight = () => {
67 | if (!h1Height) {
68 | const getH1Height = () => {
69 | if (h1Height) {
70 | clearInterval(getH1HeightInterval);
71 | getH1HeightInterval = null;
72 | return;
73 | }
74 |
75 | h1Height = title.getBoundingClientRect().height;
76 | setHeight();
77 | };
78 | getH1HeightInterval = setInterval(getH1Height, 1000);
79 | } else {
80 | if (backButton) {
81 | backButton.style.height = h1Height + "px";
82 | }
83 |
84 | titlesContainer.style.minHeight = h1Height + "px";
85 | }
86 | };
87 | setHeight();
88 |
89 | return container;
90 | }
91 |
--------------------------------------------------------------------------------
/editor/components/view-scrollable.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
2 |
3 | .view-scrollable {
4 | display: flex;
5 | flex-direction: column;
6 | height: 100%;
7 | overflow: hidden;
8 |
9 | .scrollable {
10 | overflow: auto;
11 | margin-left: 0 - spacing.$medium;
12 | margin-right: 0 - spacing.$medium;
13 | margin-bottom: 0 - spacing.$medium;
14 | padding-left: spacing.$medium;
15 | padding-right: spacing.$medium;
16 | padding-bottom: spacing.$medium;
17 | display: flex;
18 | flex-direction: column;
19 | flex: 1;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/editor/components/view-scrollable.ts:
--------------------------------------------------------------------------------
1 | export function ViewScrollable() {
2 | const container = document.createElement("div");
3 | container.classList.add("view-scrollable");
4 |
5 | const scrollable = document.createElement("div");
6 | scrollable.classList.add("scrollable");
7 |
8 | container.append(scrollable);
9 |
10 | return { container, scrollable };
11 | }
12 |
--------------------------------------------------------------------------------
/editor/constants.ts:
--------------------------------------------------------------------------------
1 | export const PROJECTS_TITLE = "Projects";
2 | export const PROJECTS_VIEW_ID = "projects-view";
3 | export const PROJECT_VIEW_ID = "project";
4 | export const PEERS_VIEW_ID = "peers";
5 | export const SETTINGS_VIEW_ID = "settings";
6 | export const NEW_PROJECT_ID = "new-project";
7 | export const IMPORT_ZIP_ID = "import-zip";
8 | export const IMPORT_PROJECT_FILE_INPUT_ID = "import-project";
9 | export const RUN_PROJECT_ID = "run-project";
10 | export const NEW_FILE_ID = "new-file";
11 | export const SETTINGS_BUTTON_ID = "settings";
12 | export const BACK_BUTTON_CLASS = "back-button";
13 | export const PROJECT_TITLE_ID = "project-title";
14 | export const TYPESCRIPT_ICON_ID = "typescript-icon";
15 | export const PEERS_BUTTON_ID = "peers-connectivity";
16 | export const INCOMING_PEER_CONNECTION_REQUEST_DIALOG =
17 | "peer-connection-request-dialog";
18 | export const MANUAL_PEER_CONNECT_DIALOG = "manual-peer-connect";
19 | export const PEER_PAIR_BUTTON_CLASS = "peer-pair";
20 | export const PEER_PAIRING_CODE_CLASS = "peer-pairing-code";
21 | export const PEER_TRUST_BUTTON_ID = "peer-trust";
22 | export const PEER_DISCONNECT_BUTTON_CLASS = "peer-disconnect";
23 | export const PEER_CONNECTIVITY_BACK_BUTTON_ID = "peer-back";
24 | export const BG_COLOR = "#1e293b";
25 |
--------------------------------------------------------------------------------
/editor/deeplink.ts:
--------------------------------------------------------------------------------
1 | import { Button, Dialog } from "@fullstacked/ui";
2 | import { createElement } from "./components/element";
3 | import config from "./lib/config";
4 | import { Store } from "./store";
5 | import { CONFIG_TYPE, Project as ProjectType } from "./types";
6 | import { CloneGit } from "./views/add-project/clone-git";
7 | import { Project } from "./views/project";
8 |
9 | // fullstacked://http//github.....git
10 | export async function deeplink(fullstackedUrl: string) {
11 | console.log(fullstackedUrl);
12 |
13 | let url = fullstackedUrl.slice("fullstacked://".length);
14 |
15 | const [protocol, ...rest] = url.split("//");
16 | const [hostAndPath] = rest.join("//").split("?");
17 | url = protocol + (protocol.endsWith(":") ? "" : ":") + "//" + hostAndPath;
18 |
19 | if (!url.endsWith(".git")) {
20 | url += ".git";
21 | }
22 |
23 | const runProjectIfFound = (projects: ProjectType[]) => {
24 | console.log(projects, url);
25 | const existingProject = projects?.find(
26 | (p) => p.gitRepository?.url === url
27 | );
28 | if (existingProject) {
29 | Store.projects.list.unsubscribe(runProjectIfFound);
30 |
31 | let isUserMode = Store.preferences.isUserMode.check();
32 | if (!isUserMode) {
33 | Project(existingProject);
34 | }
35 |
36 | Store.projects.build(existingProject);
37 | return true;
38 | }
39 |
40 | return false;
41 | };
42 |
43 | const { projects } = await config.get(CONFIG_TYPE.PROJECTS);
44 |
45 | if (runProjectIfFound(projects)) return;
46 |
47 | Store.projects.list.subscribe(runProjectIfFound);
48 | CloneGit(url);
49 | }
50 |
51 | export function WindowsAskForAdmin() {
52 | const container = createElement("div");
53 | container.classList.add("win-admin-dialog");
54 | container.innerHTML = `
55 | Welcome,
56 | Please close FullStacked and reopen it with Run as administrator.
57 |
It will register the FullStacked deeplink and enable the Open in FullStacked feature in your system.
58 | You can open FullStacked normally afterwards.
59 | You only have to do this operation once.
60 | `;
61 | const closeButton = Button({
62 | text: "Close"
63 | });
64 | container.append(closeButton);
65 | const { remove } = Dialog(container);
66 | closeButton.onclick = () => {
67 | remove();
68 | fetch("/restart-admin");
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/editor/demo.ts:
--------------------------------------------------------------------------------
1 | import { bridge } from "../lib/bridge";
2 | import { serializeArgs } from "../lib/bridge/serialization";
3 | import core_fetch from "../lib/fetch";
4 | import core_message from "../lib/core_message";
5 | import git from "./lib/git";
6 | import {
7 | createAndMoveProject,
8 | randomStr,
9 | tmpDir
10 | } from "./views/add-project/import-zip";
11 | import archive from "../lib/archive";
12 |
13 | export async function Demo() {
14 | try {
15 | await core_fetch("https://github.com/fullstackedorg", { timeout: 3 });
16 | } catch (e) {
17 | return demoFromZip();
18 | }
19 |
20 | return demoFromGitHub();
21 | }
22 |
23 | async function demoFromZip() {
24 | const payload = new Uint8Array([
25 | 1, // static file serving
26 |
27 | ...serializeArgs(["Demo.zip"])
28 | ]);
29 |
30 | const [_, demoZipData] = (await bridge(payload)) as [string, Uint8Array];
31 | const tmpDirectory = tmpDir + "/" + randomStr(6);
32 | await archive.unzip(demoZipData, tmpDirectory);
33 | createAndMoveProject(
34 | tmpDirectory,
35 | {
36 | container: null,
37 | logger: () => {},
38 | text: null
39 | },
40 | "Demo",
41 | null
42 | );
43 | }
44 |
45 | const demoRepoUrl = "https://github.com/fullstackedorg/demo.git";
46 |
47 | async function demoFromGitHub() {
48 | let checkForDone: (message: string) => void;
49 | const donePromise = new Promise((resolve) => {
50 | checkForDone = (gitProgress: string) => {
51 | let json: { Url: string; Data: string };
52 | try {
53 | json = JSON.parse(gitProgress);
54 | } catch (e) {
55 | return;
56 | }
57 |
58 | if (json.Url !== demoRepoUrl) return;
59 |
60 | if (json.Data.trim().endsWith("done")) {
61 | resolve();
62 | }
63 | };
64 | });
65 |
66 | core_message.addListener("git-clone", checkForDone);
67 |
68 | const tmpDirectory = tmpDir + "/" + randomStr(6);
69 | git.clone(demoRepoUrl, tmpDirectory);
70 |
71 | await donePromise;
72 |
73 | core_message.removeListener("git-clone", checkForDone);
74 |
75 | await createAndMoveProject(
76 | tmpDirectory,
77 | {
78 | container: null,
79 | logger: () => {},
80 | text: null
81 | },
82 | "Demo",
83 | demoRepoUrl
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/editor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
31 |
32 | FullStacked
33 |
34 |
35 |
36 |
37 |

38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/editor/index.scss:
--------------------------------------------------------------------------------
1 | /*
2 | All styles follows the official figma design file
3 | https://www.figma.com/design/xb3JBRCvEWpbwGda03T5QQ/Mockups
4 | */
5 |
6 | // globals
7 | @use "style/colors.scss";
8 | @use "style/spacing.scss";
9 | @use "style/list.scss";
10 | @use "style/winbox.scss";
11 |
12 | // components
13 | @use "components/top-bar.scss";
14 | @use "components/view-scrollable.scss";
15 | @use "../lib/components/snackbar.scss";
16 |
17 | // views
18 | @use "views/projects/index.scss";
19 | @use "views/project-settings.scss";
20 | @use "views/add-project/index.scss" as index2;
21 | @use "views/settings/index.scss" as index3;
22 | @use "views/project/index.scss" as index4;
23 | @use "views/project/git/index.scss" as index5;
24 | @use "views/packages/index.scss" as index6;
25 | @use "views/prompt/index.scss" as index7;
26 |
27 | // deps
28 | @use "../node_modules/@fullstacked/ui/ui.scss";
29 | @use "../node_modules/@fullstacked/code-editor/editor.scss";
30 | @use "../node_modules/@fullstacked/file-tree/file-tree.scss";
31 |
32 | html,
33 | body {
34 | height: 100%;
35 | }
36 |
--------------------------------------------------------------------------------
/editor/index.ts:
--------------------------------------------------------------------------------
1 | import "./init";
2 | import core_message from "../lib/core_message";
3 | import { deeplink, WindowsAskForAdmin } from "./deeplink";
4 | import { Demo } from "./demo";
5 | import config from "./lib/config";
6 | import { CONFIG_TYPE } from "./types";
7 | import { updatePackagesView } from "./views/packages";
8 | import { Projects } from "./views/projects";
9 | import platform, { Platform } from "../lib/platform";
10 | import { InitPrompt } from "./views/prompt";
11 | import { Store } from "./store";
12 | import { Project } from "./views/project";
13 |
14 | core_message.addListener("deeplink", deeplink);
15 |
16 | // fix windows scrollbars
17 | if (navigator.userAgent.includes("Windows")) {
18 | const link = document.createElement("link");
19 | link.rel = "stylesheet";
20 | link.href = "/windows.css";
21 | document.head.append(link);
22 | }
23 |
24 | document.querySelector("#splash")?.remove();
25 | Projects();
26 | InitPrompt();
27 | Store.projects.current.subscribe(Project);
28 |
29 | core_message.addListener("package", (dataStr) => {
30 | try {
31 | updatePackagesView(JSON.parse(dataStr));
32 | } catch (e) {
33 | console.log(dataStr);
34 | }
35 | });
36 |
37 | const checkProjectsConfigExists = await config.get(CONFIG_TYPE.PROJECTS, true);
38 | if (!checkProjectsConfigExists) {
39 | if (platform === Platform.WINDOWS) {
40 | WindowsAskForAdmin();
41 | }
42 |
43 | Demo();
44 | }
45 |
--------------------------------------------------------------------------------
/editor/init.ts:
--------------------------------------------------------------------------------
1 | import "./index.css";
2 | import * as UI from "@fullstacked/ui";
3 | UI.setIconsDirectory("/icons");
4 |
--------------------------------------------------------------------------------
/editor/lib/config/config.ts:
--------------------------------------------------------------------------------
1 | import { bridge } from "../../../lib/bridge";
2 | import { serializeArgs } from "../../../lib/bridge/serialization";
3 | import { CONFIG_DATA_TYPE, CONFIG_TYPE } from "../../types";
4 |
5 | export function get(
6 | configType: T,
7 | checkExists: boolean = false
8 | ): Promise {
9 | const payload = new Uint8Array([50, ...serializeArgs([configType])]);
10 |
11 | const transformer = ([string]) => {
12 | if (!string) {
13 | return checkExists ? null : {};
14 | }
15 |
16 | return JSON.parse(string);
17 | };
18 |
19 | return bridge(payload, transformer);
20 | }
21 |
22 | export function save(
23 | configType: T,
24 | configData: CONFIG_DATA_TYPE[T]
25 | ): Promise {
26 | const payload = new Uint8Array([
27 | 51,
28 | ...serializeArgs([configType, JSON.stringify(configData)])
29 | ]);
30 |
31 | return bridge(payload, ([success]) => success);
32 | }
33 |
--------------------------------------------------------------------------------
/editor/lib/config/index.ts:
--------------------------------------------------------------------------------
1 | import * as config from "./config";
2 | export default config;
3 | export * from "./config";
4 |
--------------------------------------------------------------------------------
/editor/lib/core_open.ts:
--------------------------------------------------------------------------------
1 | import { bridge } from "../../lib/bridge";
2 | import { serializeArgs } from "../../lib/bridge/serialization";
3 |
4 | // 100
5 | export default function core_open(projectId: string) {
6 | const payload = new Uint8Array([100, ...serializeArgs([projectId])]);
7 |
8 | return bridge(payload);
9 | }
10 |
--------------------------------------------------------------------------------
/editor/lib/esbuild/esbuild.ts:
--------------------------------------------------------------------------------
1 | import { bridge } from "../../../lib/bridge";
2 | import {
3 | deserializeArgs,
4 | getLowestKeyIdAvailable,
5 | serializeArgs
6 | } from "../../../lib/bridge/serialization";
7 | import { Project } from "../../types";
8 | import type { Message } from "esbuild";
9 | import core_message from "../../../lib/core_message";
10 | import { toByteArray } from "../../../lib/base64";
11 |
12 | // 55
13 | export function version(): Promise {
14 | const payload = new Uint8Array([55]);
15 | return bridge(payload, ([str]) => str);
16 | }
17 |
18 | let addedListener = false;
19 | const activeBuilds = new Map<
20 | number,
21 | { project: Project; resolve: (buildErrors: Message[]) => void }
22 | >();
23 |
24 | function buildResponse(buildResult: string) {
25 | const responseData = toByteArray(buildResult);
26 | const [id, errorsStr] = deserializeArgs(responseData);
27 | const activeBuild = activeBuilds.get(id);
28 |
29 | if (!errorsStr) {
30 | return;
31 | }
32 |
33 | const errors = JSON.parse(errorsStr);
34 | const messages = errors?.map(uncapitalizeKeys).map((error) => ({
35 | ...error,
36 | location: error.location
37 | ? {
38 | ...error.location,
39 | file: error.location.file.includes(activeBuild.project.id)
40 | ? activeBuild.project.id +
41 | error.location.file.split(activeBuild.project.id).pop()
42 | : error.location.file
43 | }
44 | : null
45 | }));
46 | activeBuild.resolve(messages);
47 |
48 | activeBuilds.delete(id);
49 | }
50 |
51 | // 56
52 | export function build(project: Project): Promise {
53 | if (!addedListener) {
54 | core_message.addListener("build", buildResponse);
55 | addedListener = true;
56 | }
57 |
58 | const buildId = getLowestKeyIdAvailable(activeBuilds);
59 | const payload = new Uint8Array([
60 | 56,
61 | ...serializeArgs([project.id, buildId])
62 | ]);
63 |
64 | return new Promise((resolve) => {
65 | activeBuilds.set(buildId, {
66 | project,
67 | resolve
68 | });
69 | bridge(payload);
70 | });
71 | }
72 |
73 | function isPlainObject(input: any) {
74 | return input && !Array.isArray(input) && typeof input === "object";
75 | }
76 |
77 | function uncapitalizeKeys(obj: T) {
78 | const final = {};
79 | for (const [key, value] of Object.entries(obj)) {
80 | final[key.at(0).toLowerCase() + key.slice(1)] = isPlainObject(value)
81 | ? uncapitalizeKeys(value)
82 | : value;
83 | }
84 | return final as T;
85 | }
86 |
--------------------------------------------------------------------------------
/editor/lib/esbuild/index.ts:
--------------------------------------------------------------------------------
1 | import * as esbuild from "./esbuild";
2 | export default esbuild;
3 | export * from "./esbuild";
4 |
--------------------------------------------------------------------------------
/editor/lib/esbuild/sass.ts:
--------------------------------------------------------------------------------
1 | import * as sass from "sass";
2 | import type { Message } from "esbuild";
3 | import { Project } from "../../types";
4 | import fs from "../../../lib/fs";
5 |
6 | export async function buildSASS(project: Project): Promise> {
7 | const writeOutputCSS = async (css: string) => {
8 | const buildDirectory = `${project.id}/.build`;
9 | await fs.mkdir(buildDirectory);
10 | await fs.writeFile(buildDirectory + "/index.css", css);
11 | };
12 |
13 | const contents = await fs.readdir(project.id);
14 | const entryPointSASS = contents.find(
15 | (item) => item === "index.sass" || item === "index.scss"
16 | );
17 |
18 | // check for css file and write to output
19 | // esbuild will pick it up and merge with css in js
20 | if (!entryPointSASS) {
21 | const entryPointCSS = contents.find((item) => item === "index.css");
22 | if (entryPointCSS) {
23 | // TODO: fs.copyFile
24 | await writeOutputCSS(
25 | await fs.readFile(`${project.id}/${entryPointCSS}`, {
26 | encoding: "utf8"
27 | })
28 | );
29 | } else {
30 | await writeOutputCSS("");
31 | }
32 |
33 | return;
34 | }
35 |
36 | const entryData = await fs.readFile(`${project.id}/${entryPointSASS}`, {
37 | encoding: "utf8"
38 | });
39 | let result: sass.CompileResult;
40 | try {
41 | result = await sass.compileStringAsync(entryData, {
42 | importer: {
43 | load: async (url) => {
44 | const filePath = `${project.id}${url.pathname}`;
45 | const contents = await fs.readFile(filePath, {
46 | encoding: "utf8"
47 | });
48 | return {
49 | syntax: filePath.endsWith(".sass")
50 | ? "indented"
51 | : filePath.endsWith(".scss")
52 | ? "scss"
53 | : "css",
54 | contents
55 | };
56 | },
57 | canonicalize: (path) => new URL(path, window.location.href)
58 | }
59 | });
60 | } catch (e) {
61 | const error = e as unknown as sass.Exception;
62 | const file =
63 | project.id + (error.span.url?.pathname || "/" + entryPointSASS);
64 | const line = error.span.start.line + 1;
65 | const column = error.span.start.column;
66 | const length = error.span.text.length;
67 | return {
68 | text: error.message,
69 | location: {
70 | file,
71 | line,
72 | column,
73 | length,
74 | namespace: "SASS",
75 | lineText: error.message,
76 | suggestion: ""
77 | }
78 | };
79 | }
80 |
81 | await writeOutputCSS(result.css);
82 | return null;
83 | }
84 |
--------------------------------------------------------------------------------
/editor/lib/fs_sync/fs_sync.ts:
--------------------------------------------------------------------------------
1 | import { fromByteArray } from "../../../lib/base64";
2 | import {
3 | deserializeArgs,
4 | serializeArgs
5 | } from "../../../lib/bridge/serialization";
6 |
7 | function syncRequest(method: number, ...args: any[]) {
8 | const request = new XMLHttpRequest();
9 | const searchParams = new URLSearchParams();
10 | const payload = new Uint8Array([method, ...serializeArgs(args)]);
11 | searchParams.set("payload", encodeURIComponent(fromByteArray(payload)));
12 | request.open("GET", "/call-sync?" + searchParams.toString(), false);
13 | request.responseType = "arraybuffer";
14 | request.send();
15 |
16 | return deserializeArgs(new Uint8Array(request.response));
17 | }
18 |
19 | // only for WASM
20 | export let cache: Map = null;
21 | export function initCache() {
22 | if (cache) return;
23 | cache = new Map();
24 | }
25 |
26 | const debug = false;
27 |
28 | export function staticFile(path: string) {
29 | if (debug) {
30 | console.log("staticFile", path);
31 | }
32 |
33 | if (cache) {
34 | return cache.get(path);
35 | }
36 |
37 | const request = new XMLHttpRequest();
38 | request.open("GET", "/" + path, false);
39 | request.send();
40 | return request.responseText;
41 | }
42 |
43 | // 2
44 | export function readFile(path: string): string {
45 | if (debug) {
46 | console.log("readFile", path);
47 | }
48 |
49 | if (cache) {
50 | return cache.get(path);
51 | }
52 |
53 | return syncRequest(
54 | 2,
55 | path,
56 | true // encoding == "utf8"
57 | ).at(0);
58 | }
59 |
60 | // 5
61 | export function readdir(path: string, skip: string[]): string[] {
62 | if (debug) {
63 | console.log("readdir", path);
64 | }
65 |
66 | if (cache) {
67 | const items = [];
68 | for (const i of cache.keys()) {
69 | if (
70 | i.startsWith(path) &&
71 | !skip.find((s) => i.startsWith(path + "/" + s))
72 | ) {
73 | items.push(i.slice(path.length + 1));
74 | }
75 | }
76 | return items;
77 | }
78 |
79 | return syncRequest(
80 | 5,
81 | path,
82 | true, // recursive
83 | false, // withFileType
84 | ...skip
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/editor/lib/fs_sync/index.ts:
--------------------------------------------------------------------------------
1 | import * as fs_sync from "./fs_sync";
2 | export default fs_sync;
3 | export * from "./fs_sync";
4 |
--------------------------------------------------------------------------------
/editor/lib/git/index.ts:
--------------------------------------------------------------------------------
1 | import * as git from "./git";
2 | export default git;
3 | export * from "./git";
4 |
--------------------------------------------------------------------------------
/editor/lib/packages/index.ts:
--------------------------------------------------------------------------------
1 | import * as packages from "./packages";
2 | export default packages;
3 | export * from "./packages";
4 |
--------------------------------------------------------------------------------
/editor/stack-navigation.ts:
--------------------------------------------------------------------------------
1 | import StackNavigation from "@fullstacked/stack-navigation";
2 |
3 | export default new StackNavigation();
4 |
--------------------------------------------------------------------------------
/editor/store/editor.ts:
--------------------------------------------------------------------------------
1 | import { createSubscribable } from ".";
2 |
3 | let sidePanelClosed = false;
4 | const sidePanel = createSubscribable(() => sidePanelClosed);
5 |
6 | const codeEditorOpenedFiles = new Set();
7 | const openedFiles = createSubscribable(() => codeEditorOpenedFiles);
8 |
9 | let codeEditorFocusedFile: string;
10 | const focusedFile = createSubscribable(() => codeEditorFocusedFile);
11 |
12 | export type BuildError = {
13 | file: string;
14 | line: number;
15 | col: number;
16 | length: number;
17 | message: string;
18 | };
19 | let codeEditorBuildErrors: BuildError[] = [];
20 | const buildErrors = createSubscribable(() => codeEditorBuildErrors);
21 |
22 | export const editor = {
23 | sidePanelClosed: sidePanel.subscription,
24 | setSidePanelClosed,
25 |
26 | codeEditor: {
27 | openedFiles: openedFiles.subscription,
28 | openFile,
29 | closeFile,
30 | closeFilesUnderDirectory,
31 |
32 | focusedFile: focusedFile.subscription,
33 | focusFile,
34 |
35 | clearFiles,
36 |
37 | buildErrors: buildErrors.subscription,
38 | addBuildErrors,
39 | clearAllBuildErrors
40 | }
41 | };
42 |
43 | function setSidePanelClosed(closed: boolean) {
44 | sidePanelClosed = closed;
45 | sidePanel.notify();
46 | }
47 |
48 | function openFile(path: string) {
49 | codeEditorOpenedFiles.add(path);
50 | openedFiles.notify();
51 | }
52 |
53 | function closeFile(path: string) {
54 | codeEditorOpenedFiles.delete(path);
55 | if (path === codeEditorFocusedFile) {
56 | if (codeEditorOpenedFiles.size > 0) {
57 | codeEditorFocusedFile = Array.from(codeEditorOpenedFiles).at(-1);
58 | } else {
59 | codeEditorFocusedFile = null;
60 | }
61 | focusedFile.notify();
62 | }
63 | openedFiles.notify();
64 | }
65 |
66 | function closeFilesUnderDirectory(path: string) {
67 | for (const openedFile of codeEditorOpenedFiles.values()) {
68 | if (openedFile.startsWith(path)) {
69 | codeEditorOpenedFiles.delete(openedFile);
70 | }
71 | }
72 | openedFiles.notify();
73 | }
74 |
75 | function focusFile(path: string) {
76 | codeEditorFocusedFile = path;
77 | focusedFile.notify();
78 | }
79 |
80 | function clearFiles() {
81 | codeEditorOpenedFiles.clear();
82 | codeEditorFocusedFile = null;
83 | openedFiles.notify();
84 | focusedFile.notify();
85 | }
86 |
87 | function addBuildErrors(errors: BuildError[]) {
88 | codeEditorBuildErrors.push(...errors);
89 | buildErrors.notify();
90 | }
91 |
92 | function clearAllBuildErrors() {
93 | codeEditorBuildErrors = [];
94 | buildErrors.notify();
95 | }
96 |
--------------------------------------------------------------------------------
/editor/store/index.ts:
--------------------------------------------------------------------------------
1 | import { editor } from "./editor";
2 | import { projects } from "./projects";
3 | import { preferences } from "./preferences";
4 |
5 | export const Store = {
6 | preferences,
7 | projects,
8 | editor
9 | };
10 |
11 | export function createSequential>(
12 | fn: (...args: T) => R
13 | ) {
14 | let toRun: {
15 | args: T;
16 | fn: (...args: T) => R;
17 | resolve: (args: Awaited) => void;
18 | }[] = [];
19 |
20 | let lock = false;
21 |
22 | const execute = async () => {
23 | if (lock) return;
24 |
25 | lock = true;
26 |
27 | while (toRun.length) {
28 | const toExecute = toRun.shift();
29 | const result = await toExecute.fn(...toExecute.args);
30 | toExecute.resolve(result);
31 | }
32 |
33 | lock = false;
34 | };
35 |
36 | return (...args: T) => {
37 | const promise = new Promise>((resolve) => {
38 | toRun.push({
39 | args,
40 | fn,
41 | resolve
42 | });
43 | });
44 |
45 | execute();
46 |
47 | return promise;
48 | };
49 | }
50 |
51 | export function createSubscribable(
52 | getter: () => T,
53 | placeolderValue?: Awaited
54 | ): {
55 | notify: () => void;
56 | subscription: {
57 | check: () => Awaited;
58 | subscribe: (onUpdate: (value: Awaited) => void) => void;
59 | unsubscribe: (onUpdate: (value: Awaited) => void) => void;
60 | };
61 | } {
62 | const subscribers = new Set<(value: Awaited) => void>();
63 |
64 | let value: Awaited = placeolderValue;
65 |
66 | const notifySubscribers = (updatedValue: Awaited | undefined) => {
67 | value = updatedValue;
68 | subscribers.forEach((subscriber) => subscriber(value));
69 | };
70 |
71 | const notify = () => {
72 | const maybePromise = getter();
73 |
74 | if (maybePromise instanceof Promise) {
75 | maybePromise.then(notifySubscribers);
76 | } else {
77 | notifySubscribers(maybePromise as Awaited);
78 | }
79 | };
80 |
81 | const subscribe = (onUpdate: (value: Awaited) => void) => {
82 | subscribers.add(onUpdate);
83 | onUpdate(value);
84 | };
85 |
86 | const unsubscribe = (onUpdate: (value: Awaited) => void) => {
87 | subscribers.delete(onUpdate);
88 | };
89 |
90 | const initialValue = getter();
91 | if (initialValue instanceof Promise) {
92 | initialValue.then(notifySubscribers);
93 | } else {
94 | value = initialValue as Awaited;
95 | }
96 |
97 | return {
98 | notify,
99 | subscription: {
100 | check: () => value,
101 | subscribe,
102 | unsubscribe
103 | }
104 | };
105 | }
106 |
--------------------------------------------------------------------------------
/editor/store/preferences.ts:
--------------------------------------------------------------------------------
1 | import { createSubscribable } from ".";
2 | import config from "../lib/config";
3 | import { CONFIG_TYPE } from "../types";
4 |
5 | const isUserMode = createSubscribable(getUserMode, false);
6 |
7 | export const preferences = {
8 | setUserMode,
9 | isUserMode: isUserMode.subscription
10 | };
11 |
12 | let userMode: boolean;
13 | let userModePromise: Promise;
14 | async function getUserMode() {
15 | if (typeof userMode != "boolean") {
16 | if (!userModePromise) {
17 | userModePromise = new Promise(async (resolve) => {
18 | const c = await config.get(CONFIG_TYPE.GENERAL);
19 | userMode = c?.userMode || false;
20 | resolve();
21 | });
22 | }
23 | await userModePromise;
24 | }
25 |
26 | return userMode;
27 | }
28 |
29 | async function setUserMode(um: boolean) {
30 | userMode = um;
31 | const c = await config.get(CONFIG_TYPE.GENERAL);
32 | c.userMode = userMode;
33 | await config.save(CONFIG_TYPE.GENERAL, c);
34 | isUserMode.notify();
35 | }
36 |
--------------------------------------------------------------------------------
/editor/style/colors.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/colors.scss";
2 |
3 | html,
4 | body {
5 | background-color: colors.$blue-dark;
6 | color: colors.$light;
7 | }
8 |
--------------------------------------------------------------------------------
/editor/style/list.scss:
--------------------------------------------------------------------------------
1 | ul {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/editor/style/spacing.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
2 |
3 | html,
4 | body {
5 | margin: 0;
6 | width: 100%;
7 | }
8 |
9 | .view,
10 | .view-scrollable {
11 | padding: spacing.$small spacing.$medium spacing.$medium;
12 | }
13 |
--------------------------------------------------------------------------------
/editor/style/winbox.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/colors.scss";
2 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
3 |
4 | .winbox {
5 | border-radius: spacing.$small;
6 | background-color: colors.$gray-dark;
7 | overflow: hidden;
8 | }
9 |
--------------------------------------------------------------------------------
/editor/style/windows.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/colors.scss";
2 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
3 |
4 | ::-webkit-scrollbar {
5 | width: 8px;
6 | height: 5px;
7 | background-color: colors.opacity(colors.$gray-dark, 0);
8 | }
9 |
10 | ::-webkit-scrollbar:hover {
11 | background-color: colors.opacity(colors.$gray-dark, 50);
12 | }
13 |
14 | ::-webkit-scrollbar-thumb {
15 | background: colors.opacity(colors.$gray, 50);
16 | border-radius: 10px;
17 | }
18 |
19 | ::-webkit-scrollbar-thumb:active {
20 | background: colors.opacity(colors.$gray, 80);
21 | border-radius: 10px;
22 | }
23 |
24 | ::-webkit-scrollbar-corner {
25 | background-color: colors.opacity(colors.$gray-dark, 0);
26 | }
27 |
28 | .win-admin-dialog {
29 | display: flex;
30 | flex-direction: column;
31 |
32 | h1 {
33 | margin-bottom: spacing.$small;
34 | }
35 |
36 | p {
37 | margin-bottom: spacing.$x-small;
38 | }
39 |
40 | button {
41 | align-self: flex-end;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/editor/types/index.ts:
--------------------------------------------------------------------------------
1 | export enum CONFIG_TYPE {
2 | GENERAL = "general",
3 | PROJECTS = "projects",
4 | GIT = "git",
5 | AGENT = "agent"
6 | // CONNECTIVITY = "connectivity"
7 | }
8 |
9 | export type CONFIG_DATA_TYPE = {
10 | [CONFIG_TYPE.GENERAL]: {
11 | userMode: boolean;
12 | };
13 | [CONFIG_TYPE.PROJECTS]: {
14 | projects: Project[];
15 | };
16 | [CONFIG_TYPE.GIT]: GitAuths;
17 |
18 | [CONFIG_TYPE.AGENT]: any;
19 | // [CONFIG_TYPE.CONNECTIVITY]: Connectivity;
20 | };
21 |
22 | export type Project = {
23 | title: string;
24 | id: string;
25 | createdDate: number;
26 | location?: string;
27 | gitRepository?: {
28 | url: string;
29 | name?: string;
30 | email?: string;
31 | merging?: string;
32 | };
33 | };
34 |
35 | export type GitAuths = {
36 | [hostname: string]: {
37 | username: string;
38 | password?: string;
39 | email?: string;
40 | };
41 | };
42 |
43 | // export type Connectivity = {
44 | // me: Peer;
45 | // autoConnect: boolean;
46 | // defaultNetworkInterface: string;
47 | // webAddresses: WebAddress[];
48 | // peersTrusted: PeerTrusted[];
49 | // };
50 |
--------------------------------------------------------------------------------
/editor/views/add-project/create-empty.ts:
--------------------------------------------------------------------------------
1 | import slugify from "slugify";
2 | import { TopBar } from "../../components/top-bar";
3 | import { Store } from "../../store";
4 | import stackNavigation from "../../stack-navigation";
5 | import { BG_COLOR } from "../../constants";
6 | import fs from "../../../lib/fs";
7 | import { Button, InputText } from "@fullstacked/ui";
8 |
9 | export function CreateEmpty() {
10 | const container = document.createElement("div");
11 | container.classList.add("view", "create-form");
12 |
13 | const topBar = TopBar({
14 | title: "Create empty project"
15 | });
16 |
17 | container.append(topBar);
18 |
19 | const form = document.createElement("form");
20 |
21 | const inputTitle = InputText({
22 | label: "Title"
23 | });
24 | const inputIdentifier = InputText({
25 | label: "Identifier"
26 | });
27 |
28 | inputTitle.input.onblur = () => {
29 | if (!inputIdentifier.input.value) {
30 | inputIdentifier.input.value = slugify(inputTitle.input.value, {
31 | lower: true
32 | });
33 | }
34 | };
35 |
36 | const createButton = Button({
37 | text: "Create"
38 | });
39 |
40 | form.onsubmit = (e) => {
41 | e.preventDefault();
42 | createButton.disabled = true;
43 |
44 | let id = inputIdentifier.input.value
45 | ? slugify(inputIdentifier.input.value, { lower: true })
46 | : slugify(inputTitle.input.value, { lower: true });
47 | id = id || "no-identifier";
48 |
49 | const title = inputTitle.input.value || "Empty Project";
50 |
51 | Promise.all([fs.mkdir(id), Store.projects.create({ title, id })]).then(
52 | () => stackNavigation.back()
53 | );
54 | };
55 |
56 | form.append(inputTitle.container, inputIdentifier.container, createButton);
57 |
58 | container.append(form);
59 |
60 | setTimeout(() => inputTitle.input.focus(), 1);
61 |
62 | stackNavigation.navigate(container, {
63 | bgColor: BG_COLOR
64 | });
65 | }
66 |
--------------------------------------------------------------------------------
/editor/views/add-project/index.scss:
--------------------------------------------------------------------------------
1 | @use "../../../node_modules/@fullstacked/ui/values/spacing.scss";
2 | @use "../../../node_modules/@fullstacked/ui/values/colors.scss";
3 |
4 | #add-project {
5 | padding-bottom: spacing.$small;
6 |
7 | .buttons {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | gap: spacing.$large;
12 | padding-top: spacing.$large;
13 | }
14 | }
15 |
16 | .create-form {
17 | min-height: 100%;
18 | display: flex;
19 | flex-direction: column;
20 |
21 | .top-bar {
22 | padding-bottom: spacing.$small;
23 | }
24 |
25 | form {
26 | padding-top: spacing.$medium;
27 | padding-bottom: spacing.$medium;
28 |
29 | display: flex;
30 | flex-direction: column;
31 | gap: spacing.$small;
32 |
33 | width: 100%;
34 | max-width: spacing.$max-width;
35 |
36 | margin: 0 auto;
37 |
38 | button {
39 | align-self: flex-end;
40 | }
41 | }
42 | }
43 |
44 | .create-loader {
45 | display: flex;
46 | flex-direction: column;
47 | align-items: center;
48 | justify-content: center;
49 | text-align: center;
50 | gap: spacing.$medium;
51 | padding-bottom: spacing.$medium;
52 | .loader {
53 | width: 60px;
54 | }
55 | }
56 |
57 | .create-terminal {
58 | background-color: colors.$dark;
59 | border: 1px solid colors.$gray;
60 | color: colors.$light;
61 | overflow: auto;
62 | height: 100%;
63 | border-radius: spacing.$x-small;
64 | min-height: 250px;
65 | flex: 1;
66 | > pre {
67 | padding: spacing.$medium;
68 | min-height: 100%;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/editor/views/add-project/index.ts:
--------------------------------------------------------------------------------
1 | import { Button } from "@fullstacked/ui";
2 | import { TopBar } from "../../components/top-bar";
3 | import { BG_COLOR, IMPORT_ZIP_ID } from "../../constants";
4 | import stackNavigation from "../../stack-navigation";
5 | import { Store } from "../../store";
6 | import { CloneGit } from "./clone-git";
7 | import { CreateEmpty } from "./create-empty";
8 | import { ImportZip } from "./import-zip";
9 |
10 | export function AddProject() {
11 | const container = document.createElement("div");
12 | container.id = "add-project";
13 | container.classList.add("view");
14 |
15 | const topBar = TopBar({
16 | title: "Add Project"
17 | });
18 |
19 | container.append(topBar);
20 |
21 | const buttonsContainer = document.createElement("div");
22 | buttonsContainer.classList.add("buttons");
23 |
24 | const cloneGitButton = Button({
25 | text: "Clone git repository",
26 | iconLeft: "Git"
27 | });
28 | cloneGitButton.onclick = () => CloneGit();
29 | // cloneGitButton.disabled = platform === Platform.WASM;
30 |
31 | const importZipButton = Button({
32 | text: "Import zip",
33 | iconLeft: "Archive"
34 | });
35 | importZipButton.id = IMPORT_ZIP_ID;
36 | importZipButton.onclick = ImportZip;
37 |
38 | const createEmptyButton = Button({
39 | text: "Create empty project",
40 | iconLeft: "Glitter"
41 | });
42 | createEmptyButton.onclick = CreateEmpty;
43 |
44 | buttonsContainer.append(cloneGitButton, importZipButton, createEmptyButton);
45 | container.append(buttonsContainer);
46 |
47 | // on project list update (most probably new project created)
48 | // go back
49 | const goBackOnNewProject = () => stackNavigation.back();
50 | Store.projects.list.subscribe(goBackOnNewProject);
51 |
52 | stackNavigation.navigate(container, {
53 | bgColor: BG_COLOR,
54 | onDestroy: () => {
55 | Store.projects.list.unsubscribe(goBackOnNewProject);
56 | }
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/editor/views/packages/index.scss:
--------------------------------------------------------------------------------
1 | @use "../../../node_modules/@fullstacked/ui/values/spacing.scss";
2 | @use "../../../node_modules/@fullstacked/ui/values/colors.scss";
3 | @use "../../../node_modules/@fullstacked/ui/values/typography.scss";
4 |
5 | .packages-view {
6 | h3 {
7 | padding-bottom: spacing.$small;
8 | }
9 |
10 | ul {
11 | display: flex;
12 | flex-direction: column;
13 | gap: spacing.$small;
14 |
15 | li {
16 | background-color: colors.$gray-dark;
17 | border: 1px solid colors.$gray;
18 | display: flex;
19 | align-items: center;
20 | justify-content: space-between;
21 | position: relative;
22 | padding: spacing.$small;
23 | border-radius: spacing.$x-small;
24 | overflow: hidden;
25 |
26 | > div:nth-child(2) {
27 | font-size: typography.$small;
28 | }
29 |
30 | .progress-bar {
31 | position: absolute;
32 | left: 0;
33 | bottom: 0;
34 | width: 0;
35 | height: spacing.$x-small;
36 | background-color: colors.$blue;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/editor/views/project-settings.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
2 |
3 | .project-settings {
4 | form {
5 | width: 100%;
6 | max-width: spacing.$max-width;
7 | margin: 0 auto;
8 |
9 | display: flex;
10 | flex-direction: column;
11 | gap: spacing.$small;
12 | padding-top: spacing.$medium;
13 |
14 | > button {
15 | align-self: flex-end;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/editor/views/project-settings.ts:
--------------------------------------------------------------------------------
1 | import { TopBar } from "../components/top-bar";
2 | import { ViewScrollable } from "../components/view-scrollable";
3 | import slugify from "slugify";
4 | import stackNavigation from "../stack-navigation";
5 | import { BG_COLOR } from "../constants";
6 | import { Project } from "../types";
7 | import { Store } from "../store";
8 | import { Button, InputText } from "@fullstacked/ui";
9 |
10 | export function ProjectSettings(project: Project) {
11 | const { container, scrollable } = ViewScrollable();
12 | container.classList.add("project-settings");
13 |
14 | container.prepend(
15 | TopBar({
16 | title: "Project Settings"
17 | })
18 | );
19 |
20 | const form = document.createElement("form");
21 |
22 | const titleInput = InputText({
23 | label: "Title"
24 | });
25 | titleInput.input.value = project.title;
26 | const identifierInput = InputText({
27 | label: "Identifier"
28 | });
29 | identifierInput.input.value = project.id;
30 | identifierInput.input.onblur = () => {
31 | identifierInput.input.value = slugify(identifierInput.input.value, {
32 | lower: true
33 | });
34 | };
35 |
36 | const updateButton = Button({
37 | text: "Update"
38 | });
39 |
40 | form.append(titleInput.container, identifierInput.container, updateButton);
41 |
42 | form.onsubmit = (e) => {
43 | e.preventDefault();
44 |
45 | updateButton.disabled = true;
46 | identifierInput.input.value = slugify(identifierInput.input.value, {
47 | lower: true
48 | });
49 |
50 | const updatedProject = {
51 | ...project
52 | };
53 |
54 | updatedProject.title = titleInput.input.value;
55 | updatedProject.id = identifierInput.input.value;
56 |
57 | if (
58 | updatedProject.title === project.title &&
59 | updatedProject.id === project.id
60 | ) {
61 | stackNavigation.back();
62 | return;
63 | }
64 |
65 | Store.projects
66 | .update(project, updatedProject)
67 | .then(() => stackNavigation.back());
68 | };
69 |
70 | scrollable.append(form);
71 |
72 | stackNavigation.navigate(container, {
73 | bgColor: BG_COLOR
74 | });
75 | }
76 |
--------------------------------------------------------------------------------
/editor/views/project/dev-icons.scss:
--------------------------------------------------------------------------------
1 | .dev-icon {
2 | &.typescript::before {
3 | content: "\E099";
4 | color: #519aba;
5 | }
6 |
7 | &.javascript::before {
8 | content: "\E051";
9 | color: #cbcb41;
10 | }
11 |
12 | &.sass::before {
13 | content: "\E084";
14 | color: #f55385;
15 | }
16 |
17 | &.css::before {
18 | content: "\E01D";
19 | color: #519aba;
20 | }
21 |
22 | &.json::before {
23 | content: "\E055";
24 | color: #cbcb41;
25 | }
26 |
27 | &.markdown::before {
28 | content: "\E060";
29 | color: #519aba;
30 | }
31 |
32 | &.liquid::before {
33 | content: "\E05B";
34 | color: #8dc149;
35 | }
36 |
37 | &.html::before {
38 | content: "\E048";
39 | color: #e37933;
40 | }
41 |
42 | &.image::before {
43 | content: "\E04C";
44 | color: #a074c4;
45 | }
46 |
47 | &.svg::before {
48 | content: "\E04C";
49 | color: #a074c4;
50 | }
51 |
52 | &.react::before {
53 | content: "\E07D";
54 | color: #519aba;
55 | }
56 |
57 | &.npm::before {
58 | content: "\E067";
59 | color: #cc3e44;
60 | }
61 |
62 | &.default::before {
63 | content: "\E023";
64 | color: #d4d7d6;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/editor/views/project/file-event.ts:
--------------------------------------------------------------------------------
1 | export enum FileEventType {
2 | UNKNOWN = 0,
3 | CREATED = 1,
4 | MODIFIED = 2,
5 | RENAME = 3,
6 | DELETED = 4
7 | }
8 |
9 | export type FileEvent = {
10 | isFile: boolean;
11 | origin: string;
12 | paths: string[];
13 | type: FileEventType;
14 | };
15 |
--------------------------------------------------------------------------------
/editor/views/project/git/auth/index.ts:
--------------------------------------------------------------------------------
1 | import { GitHubDeviceFlow } from "./github";
2 | import { createElement } from "../../../../components/element";
3 | import { CONFIG_TYPE } from "../../../../types";
4 | import config from "../../../../lib/config";
5 | import { Button, Dialog, InputText } from "@fullstacked/ui";
6 |
7 | export function GitAuth(hostname: string): Promise {
8 | if (hostname === "github.com") {
9 | return GitHubDeviceFlow();
10 | }
11 |
12 | const container = createElement("div");
13 | container.classList.add("git-auth");
14 |
15 | container.innerHTML = `Git Authentication
16 | Authenticate for ${hostname}
`;
17 |
18 | const form = document.createElement("form");
19 |
20 | const usernameInput = InputText({
21 | label: "Username"
22 | });
23 | const emailInput = InputText({
24 | label: "Email (optional)"
25 | });
26 | emailInput.input.type = "email";
27 | const passwordInput = InputText({
28 | label: "Password"
29 | });
30 | passwordInput.input.type = "password";
31 |
32 | const buttons = document.createElement("div");
33 |
34 | const cancelButton = Button({
35 | text: "Cancel",
36 | style: "text"
37 | });
38 | cancelButton.type = "button";
39 |
40 | const authButton = Button({
41 | text: "Authenticate"
42 | });
43 | buttons.append(cancelButton, authButton);
44 | form.append(
45 | usernameInput.container,
46 | emailInput.container,
47 | passwordInput.container,
48 | buttons
49 | );
50 |
51 | container.append(form);
52 |
53 | const { remove } = Dialog(container);
54 |
55 | return new Promise((resolve) => {
56 | cancelButton.onclick = () => {
57 | resolve(false);
58 | remove();
59 | };
60 |
61 | form.onsubmit = async (e) => {
62 | e.preventDefault();
63 |
64 | authButton.disabled = true;
65 |
66 | const gitAuthConfigs = await config.get(CONFIG_TYPE.GIT);
67 | gitAuthConfigs[hostname] = {
68 | username: usernameInput.input.value,
69 | email: emailInput.input.value,
70 | password: passwordInput.input.value
71 | };
72 | await config.save(CONFIG_TYPE.GIT, gitAuthConfigs);
73 |
74 | resolve(true);
75 | remove();
76 | };
77 | });
78 | }
79 |
--------------------------------------------------------------------------------
/editor/views/projects/index.scss:
--------------------------------------------------------------------------------
1 | @use "../../../node_modules/@fullstacked/ui/values/spacing.scss";
2 | @use "../../../node_modules/@fullstacked/ui/values/colors.scss";
3 | @use "../../../node_modules/@fullstacked/ui/values/breakpoints.scss";
4 |
5 | #projects-view {
6 | .top-bar {
7 | padding-bottom: spacing.$small;
8 | }
9 | .search-and-add {
10 | padding-top: spacing.$small;
11 |
12 | width: 100%;
13 | display: flex;
14 | align-items: flex-end;
15 | justify-content: space-between;
16 | gap: spacing.$small;
17 |
18 | padding-bottom: spacing.$medium;
19 |
20 | > div:first-child {
21 | flex: 1;
22 | max-width: spacing.$max-width;
23 | }
24 | }
25 |
26 | .projects-list {
27 | display: grid;
28 | gap: spacing.$medium;
29 | grid-template-columns: repeat(4, 1fr);
30 |
31 | @media (max-width: breakpoints.$x-large) {
32 | grid-template-columns: repeat(3, 1fr);
33 | }
34 |
35 | @media (max-width: breakpoints.$large) {
36 | grid-template-columns: repeat(2, 1fr);
37 | }
38 |
39 | @media (max-width: breakpoints.$small-med) {
40 | grid-template-columns: repeat(1, 1fr);
41 | }
42 |
43 | .project-tile {
44 | position: relative;
45 |
46 | cursor: pointer;
47 |
48 | background-color: colors.opacity(colors.$light, 15);
49 | width: 100%;
50 | aspect-ratio: 39 / 22;
51 |
52 | display: flex;
53 | align-items: center;
54 | justify-content: center;
55 |
56 | text-align: center;
57 | overflow: hidden;
58 |
59 | padding: spacing.$small;
60 |
61 | &.loading {
62 | background-color: colors.opacity(colors.$light, 25);
63 | }
64 |
65 | > .title-id {
66 | width: 100%;
67 | display: flex;
68 | flex-direction: column;
69 | align-items: center;
70 | gap: spacing.$x-small;
71 |
72 | > h2 {
73 | width: 100%;
74 | text-overflow: ellipsis;
75 | overflow: hidden;
76 | direction: rtl;
77 | white-space: nowrap;
78 | }
79 | }
80 |
81 | > button {
82 | position: absolute;
83 | color: white;
84 | bottom: 0;
85 | right: spacing.$x-small;
86 | }
87 |
88 | .options-popover {
89 | padding: spacing.$x-small;
90 | }
91 |
92 | .loader {
93 | position: absolute;
94 | top: spacing.$x-small;
95 | left: spacing.$x-small;
96 | height: 24px;
97 | width: 24px;
98 | }
99 | }
100 | }
101 |
102 | .peers-widget {
103 | display: flex;
104 | font-weight: bold;
105 | justify-content: flex-end;
106 | text-align: right;
107 | align-items: center;
108 | color: colors.$blue;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/editor/views/projects/index.ts:
--------------------------------------------------------------------------------
1 | import { ViewScrollable } from "../../components/view-scrollable";
2 | import {
3 | BG_COLOR,
4 | PROJECTS_TITLE,
5 | PROJECTS_VIEW_ID,
6 | SETTINGS_BUTTON_ID
7 | } from "../../constants";
8 | import stackNavigation from "../../stack-navigation";
9 | import { List } from "./list";
10 | import { SearchAdd } from "./search-add";
11 | import { TopBar as TopBarComponent } from "../../components/top-bar";
12 | import { PeersWidget } from "./peers-widget";
13 | import { Settings } from "../settings";
14 | import { Button } from "@fullstacked/ui";
15 |
16 | export function Projects() {
17 | const { container, scrollable } = ViewScrollable();
18 | container.id = PROJECTS_VIEW_ID;
19 |
20 | const topBar = TopBar();
21 | container.prepend(topBar);
22 |
23 | const list = List();
24 |
25 | scrollable.append(SearchAdd(), list);
26 |
27 | stackNavigation.navigate(container, {
28 | bgColor: BG_COLOR,
29 | onDestroy: () => {
30 | topBar.destroy();
31 | list.destroy();
32 | }
33 | });
34 | }
35 |
36 | function TopBar() {
37 | const settings = Button({
38 | style: "icon-large",
39 | iconLeft: "Settings"
40 | });
41 | settings.id = SETTINGS_BUTTON_ID;
42 | settings.onclick = Settings;
43 |
44 | const peersWidget = PeersWidget();
45 |
46 | const topBar = TopBarComponent({
47 | noBack: true,
48 | title: PROJECTS_TITLE,
49 | actions: [peersWidget, settings]
50 | });
51 |
52 | topBar.ondestroy = peersWidget.destroy;
53 |
54 | return topBar;
55 | }
56 |
--------------------------------------------------------------------------------
/editor/views/projects/peers-widget.ts:
--------------------------------------------------------------------------------
1 | import { createElement } from "../../components/element";
2 |
3 | export function PeersWidget() {
4 | return createElement("div");
5 | }
6 |
--------------------------------------------------------------------------------
/editor/views/projects/search-add.ts:
--------------------------------------------------------------------------------
1 | import { Button, InputText } from "@fullstacked/ui";
2 | import { NEW_PROJECT_ID } from "../../constants";
3 | import { AddProject } from "../add-project";
4 | import { filterProjects } from "./list";
5 |
6 | export function SearchAdd() {
7 | const container = document.createElement("div");
8 | container.classList.add("search-and-add");
9 |
10 | const search = Search();
11 | const add = Add();
12 | container.append(search, add);
13 | return container;
14 | }
15 |
16 | function Search() {
17 | const inputSearch = InputText({
18 | label: "Search"
19 | });
20 |
21 | inputSearch.input.onkeyup = () => {
22 | filterProjects(inputSearch.input.value);
23 | };
24 |
25 | return inputSearch.container;
26 | }
27 |
28 | function Add() {
29 | const addButton = Button({
30 | style: "icon-large",
31 | iconLeft: "Plus"
32 | });
33 | addButton.id = NEW_PROJECT_ID;
34 |
35 | addButton.onclick = AddProject;
36 |
37 | return addButton;
38 | }
39 |
--------------------------------------------------------------------------------
/editor/views/prompt/index.scss:
--------------------------------------------------------------------------------
1 | @use "../../../node_modules/@fullstacked/ui/values/spacing.scss";
2 |
3 | .prompt-container {
4 | display: flex;
5 | flex-direction: column;
6 | gap: spacing.$small;
7 | > button {
8 | align-self: flex-end;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/editor/views/settings/index.ts:
--------------------------------------------------------------------------------
1 | import { TopBar } from "../../components/top-bar";
2 | import { ViewScrollable } from "../../components/view-scrollable";
3 | import stackNavigation from "../../stack-navigation";
4 | import { BG_COLOR, SETTINGS_VIEW_ID } from "../../constants";
5 | import { Version } from "./version";
6 | import { GitAuthentications } from "./git-authentications";
7 | import { createElement } from "../../components/element";
8 | import { Store } from "../../store";
9 | import { InputSwitch } from "@fullstacked/ui";
10 | import { codeEditor } from "../../code-editor";
11 |
12 | export function Settings() {
13 | const { container, scrollable } = ViewScrollable();
14 | container.id = SETTINGS_VIEW_ID;
15 | container.classList.add("view");
16 |
17 | const topBar = TopBar({
18 | title: "Settings"
19 | });
20 |
21 | container.prepend(topBar);
22 |
23 | const userMode = UserMode();
24 |
25 | scrollable.append(
26 | userMode,
27 | AgentProvider(),
28 | GitAuthentications(),
29 | Version()
30 | );
31 |
32 | stackNavigation.navigate(container, {
33 | bgColor: BG_COLOR,
34 | onDestroy: userMode.destroy
35 | });
36 | }
37 |
38 | function UserMode() {
39 | const container = createElement("div");
40 | container.classList.add("user-mode");
41 |
42 | const top = document.createElement("div");
43 | top.innerHTML = `User Mode
`;
44 |
45 | const inputSwitch = InputSwitch();
46 | top.append(inputSwitch.container);
47 |
48 | const p = document.createElement("p");
49 | p.innerText = `Simpler interface, removes all developer-related elements.
50 | Projects start faster, builds only when needed.`;
51 |
52 | container.append(top, p);
53 |
54 | const cb = (userMode: boolean) => {
55 | inputSwitch.input.checked = userMode;
56 | };
57 | Store.preferences.isUserMode.subscribe(cb);
58 | container.ondestroy = () => {
59 | Store.preferences.isUserMode.unsubscribe(cb);
60 | };
61 | inputSwitch.input.onchange = () => {
62 | Store.preferences.setUserMode(inputSwitch.input.checked);
63 | };
64 |
65 | return container;
66 | }
67 |
68 | function AgentProvider() {
69 | const container = document.createElement("div");
70 | container.classList.add("agent-provider-config");
71 | container.innerHTML = `Configure Agent Providers
`;
72 |
73 | container.append(codeEditor.agentConfigurator);
74 |
75 | return container;
76 | }
77 |
--------------------------------------------------------------------------------
/lib/archive/index.ts:
--------------------------------------------------------------------------------
1 | import * as archive from "./archive";
2 |
3 | export default archive;
4 | export * from "./archive";
5 |
--------------------------------------------------------------------------------
/lib/bridge/index.ts:
--------------------------------------------------------------------------------
1 | import "../core_message";
2 | import platform, { Platform } from "../platform";
3 | import { BridgeAndroid } from "./platform/android";
4 | import { BridgeApple, initRespondApple } from "./platform/apple";
5 | import { BridgeLinux, initRespondLinux } from "./platform/linux";
6 | import { BridgeNode, initCallbackNode } from "./platform/node";
7 | import { BridgeWasm } from "./platform/wasm";
8 | import { BridgeWindows, initRespondWindows } from "./platform/windows";
9 |
10 | export type Bridge = (
11 | payload: Uint8Array,
12 | transformer?: (args: any) => any
13 | ) => Promise;
14 |
15 | export let bridge: Bridge;
16 |
17 | switch (platform) {
18 | case Platform.NODE:
19 | bridge = BridgeNode;
20 | initCallbackNode();
21 | break;
22 | case Platform.APPLE:
23 | bridge = BridgeApple;
24 | initRespondApple();
25 | break;
26 | case Platform.ANDROID:
27 | bridge = BridgeAndroid;
28 | break;
29 | case Platform.WASM:
30 | bridge = BridgeWasm;
31 | break;
32 | case Platform.WINDOWS:
33 | bridge = BridgeWindows;
34 | initRespondWindows();
35 | break;
36 | case Platform.LINUX:
37 | bridge = BridgeLinux;
38 | initRespondLinux();
39 | break;
40 | case Platform.DOCKER:
41 | console.log("Bridge not yet implemented");
42 | }
43 |
44 | console.log("FullStacked");
45 | bridge(new Uint8Array([0]));
46 |
--------------------------------------------------------------------------------
/lib/bridge/platform/android.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { fromByteArray, toByteArray } from "../../base64";
3 | import { deserializeArgs } from "../serialization";
4 |
5 | export const BridgeAndroid: Bridge = async (
6 | payload: Uint8Array,
7 | transformer?: (responseArgs: any[]) => any
8 | ) => {
9 | const base64 = fromByteArray(payload);
10 | const response = toByteArray(globalThis.android.bridge(base64));
11 | const args = deserializeArgs(response);
12 |
13 | if (transformer) {
14 | return transformer(args);
15 | }
16 |
17 | return args;
18 | };
19 |
--------------------------------------------------------------------------------
/lib/bridge/platform/apple.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { fromByteArray, toByteArray } from "../../base64";
3 | import {
4 | bytesToNumber,
5 | deserializeArgs,
6 | getLowestKeyIdAvailable,
7 | numberTo4Bytes
8 | } from "../serialization";
9 |
10 | const requests = new Map void>();
11 |
12 | export const BridgeApple: Bridge = (
13 | payload: Uint8Array,
14 | transformer?: (responseArgs: any[]) => any
15 | ) => {
16 | const requestId = getLowestKeyIdAvailable(requests);
17 |
18 | const base64 = fromByteArray(
19 | new Uint8Array([...numberTo4Bytes(requestId), ...payload])
20 | );
21 |
22 | return new Promise((resolve, reject) => {
23 | requests.set(requestId, (data) => {
24 | try {
25 | const args = deserializeArgs(data);
26 | if (transformer) {
27 | return resolve(transformer(args));
28 | }
29 | resolve(args);
30 | } catch (e) {
31 | reject(e);
32 | }
33 | });
34 | globalThis.webkit.messageHandlers.bridge.postMessage(base64);
35 | });
36 | };
37 |
38 | export function initRespondApple() {
39 | globalThis.respond = (base64: string) => {
40 | const data = toByteArray(base64);
41 | const id = bytesToNumber(data.slice(0, 4));
42 | const resolver = requests.get(id);
43 | resolver(data.slice(4));
44 | requests.delete(id);
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/lib/bridge/platform/linux.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { fromByteArray, toByteArray } from "../../base64";
3 | import {
4 | bytesToNumber,
5 | deserializeArgs,
6 | getLowestKeyIdAvailable,
7 | numberTo4Bytes
8 | } from "../serialization";
9 |
10 | const requests = new Map void>();
11 |
12 | export const BridgeLinux: Bridge = (
13 | payload: Uint8Array,
14 | transformer?: (responseArgs: any[]) => any
15 | ) => {
16 | const requestId = getLowestKeyIdAvailable(requests);
17 |
18 | const base64 = fromByteArray(
19 | new Uint8Array([...numberTo4Bytes(requestId), ...payload])
20 | );
21 |
22 | return new Promise((resolve, reject) => {
23 | requests.set(requestId, (data) => {
24 | try {
25 | const args = deserializeArgs(data);
26 | if (transformer) {
27 | return resolve(transformer(args));
28 | }
29 | resolve(args);
30 | } catch (e) {
31 | reject(e);
32 | }
33 | });
34 | globalThis.webkit.messageHandlers.bridge.postMessage(base64);
35 | });
36 | };
37 |
38 | export function initRespondLinux() {
39 | globalThis.respond = (base64: string) => {
40 | const data = toByteArray(base64);
41 | const id = bytesToNumber(data.slice(0, 4));
42 | const resolver = requests.get(id);
43 | resolver(data.slice(4));
44 | requests.delete(id);
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/lib/bridge/platform/node.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { deserializeArgs } from "../serialization";
3 |
4 | export const BridgeNode: Bridge = async (
5 | payload: Uint8Array,
6 | transformer?: (responseArgs: any[]) => any
7 | ) => {
8 | const response = await fetch("/call", {
9 | method: "POST",
10 | body: payload
11 | });
12 | const data = new Uint8Array(await response.arrayBuffer());
13 | const args = deserializeArgs(data);
14 |
15 | if (transformer) {
16 | return transformer(args);
17 | }
18 |
19 | return args;
20 | };
21 |
22 | export function initCallbackNode() {
23 | const url = new URL(globalThis.location.href);
24 | url.protocol = "ws:";
25 | const ws = new WebSocket(url.toString());
26 | ws.onmessage = (e) => {
27 | const [type, message] = JSON.parse(e.data);
28 | globalThis.oncoremessage(type, message);
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/lib/bridge/platform/wasm.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { toByteArray } from "../../base64";
3 | import { deserializeArgs } from "../serialization";
4 |
5 | export const BridgeWasm: Bridge = async (
6 | payload: Uint8Array,
7 | transformer?: (responseArgs: any[]) => any
8 | ) => {
9 | const response = await globalThis.lib.call(payload);
10 | const args = deserializeArgs(toByteArray(response));
11 |
12 | if (transformer) {
13 | return transformer(args);
14 | }
15 |
16 | return args;
17 | };
18 |
--------------------------------------------------------------------------------
/lib/bridge/platform/windows.ts:
--------------------------------------------------------------------------------
1 | import { Bridge } from "..";
2 | import { fromByteArray, toByteArray } from "../../base64";
3 | import {
4 | bytesToNumber,
5 | deserializeArgs,
6 | getLowestKeyIdAvailable,
7 | numberTo4Bytes
8 | } from "../serialization";
9 |
10 | const requests = new Map void>();
11 |
12 | export const BridgeWindows: Bridge = (
13 | payload: Uint8Array,
14 | transformer?: (responseArgs: any[]) => any
15 | ) => {
16 | const requestId = getLowestKeyIdAvailable(requests);
17 |
18 | requests.set(requestId, null);
19 |
20 | const base64 = fromByteArray(
21 | new Uint8Array([...numberTo4Bytes(requestId), ...payload])
22 | );
23 |
24 | return new Promise((resolve, reject) => {
25 | requests.set(requestId, (data) => {
26 | try {
27 | const args = deserializeArgs(data);
28 | if (transformer) {
29 | return resolve(transformer(args));
30 | }
31 | resolve(args);
32 | } catch (e) {
33 | reject(e);
34 | }
35 | });
36 |
37 | globalThis.chrome.webview.postMessage(base64);
38 | });
39 | };
40 |
41 | export function initRespondWindows() {
42 | globalThis.respond = (base64: string) => {
43 | const data = toByteArray(base64);
44 | const id = bytesToNumber(data.slice(0, 4));
45 | const resolver = requests.get(id);
46 | resolver(data.slice(4));
47 | requests.delete(id);
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/lib/components/snackbar.scss:
--------------------------------------------------------------------------------
1 | @use "../../node_modules/@fullstacked/ui/values/spacing.scss";
2 | @use "../../node_modules/@fullstacked/ui/values/colors.scss";
3 | @use "../../node_modules/@fullstacked/ui/values/typography.scss";
4 |
5 | .snack-bars-container {
6 | display: flex;
7 | flex-direction: column;
8 | gap: spacing.$small;
9 | position: fixed;
10 | bottom: 0;
11 | left: 0;
12 | z-index: 100;
13 | padding: 0 spacing.$medium spacing.$medium;
14 | align-items: flex-start;
15 | width: 100%;
16 | pointer-events: none;
17 | text-align: left;
18 | font-family: typography.$fonts;
19 | font-size: typography.$medium;
20 |
21 | max-width: 450px;
22 | @media (max-width: 450px) {
23 | align-items: center;
24 | }
25 |
26 | .snack-bar {
27 | pointer-events: all;
28 |
29 | background-color: colors.$gray-dark;
30 | border-radius: spacing.$x-small;
31 | color: colors.$light;
32 |
33 | min-height: 42px;
34 | max-width: 100%;
35 | width: max-content;
36 |
37 | display: flex;
38 | align-items: center;
39 | justify-content: space-between;
40 | gap: spacing.$small;
41 |
42 | padding: spacing.$x-small spacing.$small;
43 |
44 | box-shadow: 0px 4px 10px colors.opacity(colors.$dark, 60);
45 |
46 | > div:first-child {
47 | padding: spacing.$x-small 0;
48 | width: 100%;
49 | overflow-wrap: break-word;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/components/snackbar.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This file must follow the figma design
3 | https://www.figma.com/design/xb3JBRCvEWpbwGda03T5QQ/Mockups?node-id=415-3655
4 | */
5 |
6 | import type { Button } from "@fullstacked/ui";
7 |
8 | type SnackBarOpt = {
9 | message: string;
10 | autoDismissTimeout?: number;
11 | button?: ReturnType;
12 | };
13 |
14 | let snackBarsContainer: HTMLDivElement;
15 |
16 | export function SnackBar(opts: SnackBarOpt) {
17 | if (!snackBarsContainer) {
18 | snackBarsContainer = document.createElement("div");
19 | snackBarsContainer.classList.add("snack-bars-container");
20 | document.body.append(snackBarsContainer);
21 | }
22 |
23 | const container = document.createElement("div");
24 | container.classList.add("snack-bar");
25 |
26 | const text = document.createElement("div");
27 | text.innerHTML = opts.message;
28 | container.append(text);
29 |
30 | if (opts.button) {
31 | container.append(opts.button);
32 | }
33 |
34 | container.style.transform = "translateY(100%)";
35 | container.style.transition = "300ms transform";
36 | snackBarsContainer.append(container);
37 | setTimeout(() => (container.style.transform = "translateY(0%)"));
38 |
39 | let timeout: ReturnType;
40 | const dismiss = () => {
41 | clearTimeout(timeout);
42 |
43 | const animDuration = 500;
44 | container.style.transition = `${animDuration}ms opacity`;
45 | container.style.opacity = "0";
46 | setTimeout(() => {
47 | container.remove();
48 | if (snackBarsContainer?.children.length === 0) {
49 | snackBarsContainer.remove();
50 | snackBarsContainer = null;
51 | }
52 | }, animDuration);
53 | };
54 |
55 | if (opts.autoDismissTimeout) {
56 | setTimeout(dismiss, opts.autoDismissTimeout);
57 | }
58 |
59 | return { dismiss };
60 | }
61 |
--------------------------------------------------------------------------------
/lib/core_message/core_message.ts:
--------------------------------------------------------------------------------
1 | import { SnackBar } from "../components/snackbar";
2 |
3 | const coreMessageListeners = new Map void>>();
4 | export const addListener = (
5 | messageType: string,
6 | cb: (message: string) => void
7 | ) => {
8 | let listeners = coreMessageListeners.get(messageType);
9 | if (!listeners) {
10 | listeners = new Set();
11 | coreMessageListeners.set(messageType, listeners);
12 | }
13 | listeners.add(cb);
14 |
15 | const pending = pendingMessages.get(messageType);
16 | if (pending?.length) {
17 | for (const m of pending) {
18 | cb(m);
19 | }
20 | }
21 | };
22 | export const removeListener = (
23 | messageType: string,
24 | cb: (message: string) => void
25 | ) => {
26 | let listeners = coreMessageListeners.get(messageType);
27 | listeners?.delete(cb);
28 | if (listeners?.size === 0) {
29 | coreMessageListeners.delete(messageType);
30 | }
31 | };
32 |
33 | const pendingMessages = new Map();
34 | setInterval(() => pendingMessages.clear(), 10 * 1000); // 10s
35 |
36 | globalThis.oncoremessage = (messageType: string, message: string) => {
37 | const listeners = coreMessageListeners.get(messageType);
38 | if (!listeners?.size) {
39 | let pending = pendingMessages.get(messageType);
40 | if (!pending) {
41 | pending = [];
42 | pendingMessages.set(messageType, pending);
43 | }
44 | pending.push(message);
45 | } else {
46 | listeners?.forEach((cb) => cb(message));
47 | }
48 | };
49 |
50 | addListener("hello", console.log);
51 | addListener("log", console.log);
52 | addListener("alert", (message) => {
53 | SnackBar({
54 | message,
55 | autoDismissTimeout: 4000
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/lib/core_message/index.ts:
--------------------------------------------------------------------------------
1 | import * as core_message from "./core_message";
2 | export default core_message;
3 | export * from "./core_message";
4 |
--------------------------------------------------------------------------------
/lib/fs/index.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "./fs";
2 | export default fs;
3 | export * from "./fs";
4 |
--------------------------------------------------------------------------------
/lib/platform/index.ts:
--------------------------------------------------------------------------------
1 | export enum Platform {
2 | NODE = "node",
3 | APPLE = "apple",
4 | ANDROID = "android",
5 | DOCKER = "docker",
6 | WINDOWS = "windows",
7 | WASM = "wasm",
8 | LINUX = "linux"
9 | }
10 |
11 | const platform = await (await fetch("/platform")).text();
12 | export default platform;
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullstacked/fullstacked",
3 | "version": "0.11.2",
4 | "scripts": {
5 | "build": "esbuild build.ts --bundle --outfile=.cache/build.js --platform=node --format=esm --packages=external && node .cache/build.js",
6 | "start": "npm run build -- --no-zip && npm start -w platform/node",
7 | "fmt": "prettier . --write && cd core && gofmt -l -w .",
8 | "typecheck": "tsc --noEmit",
9 | "test": "esbuild test/index.ts --bundle --outfile=.cache/test.js --platform=node --format=esm --packages=external && node .cache/test.js "
10 | },
11 | "workspaces": [
12 | "platform/node",
13 | "platform/wasm"
14 | ],
15 | "author": "FullStacked",
16 | "license": "GPL-3.0",
17 | "type": "module",
18 | "prettier": {
19 | "tabWidth": 4,
20 | "trailingComma": "none"
21 | },
22 | "dependencies": {
23 | "@fullstacked/code-editor": "github:fullstackedorg/code-editor#3ce05f26fbf14f2ccda5a67ef53d8f45e7017393",
24 | "@fullstacked/file-tree": "github:fullstackedorg/file-tree#8fc6d5e587507c631a68525659745eeda414bcf3",
25 | "@fullstacked/stack-navigation": "github:fullstackedorg/stack-navigation#fd824bc6625401bf49b9030861a3c4150e6e8b82",
26 | "@fullstacked/ui": "github:fullstackedorg/ui#729a7ade46afdd6d58b9d44794705dbd59c1ed30",
27 | "@types/adm-zip": "^0.5.6",
28 | "@types/node": "^22.10.1",
29 | "@types/semver": "^7.5.8",
30 | "adm-zip": "^0.5.16",
31 | "dotenv": "^16.4.5",
32 | "esbuild": "^0.25.3",
33 | "fuse.js": "^7.0.0",
34 | "open": "^10.1.0",
35 | "prettier": "^3.3.3",
36 | "pretty-bytes": "^6.1.1",
37 | "pretty-ms": "^9.1.0",
38 | "puppeteer": "^23.4.1",
39 | "sass": "^1.83.4",
40 | "slugify": "^1.6.6",
41 | "typescript": "^5.8.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/platform/android/.gitignore:
--------------------------------------------------------------------------------
1 | androidpublisher.dat
2 | client_secrets.json
--------------------------------------------------------------------------------
/platform/android/publish.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import url from "node:url";
3 | import fs from "node:fs";
4 | import child_process from "node:child_process";
5 | import dotenv from "dotenv";
6 | import version from "../../version.js";
7 |
8 | const currentDirectory = path.dirname(url.fileURLToPath(import.meta.url));
9 | const rootDirectory = path.resolve(currentDirectory, "..", "..");
10 |
11 | // build editor
12 |
13 | child_process.execSync("npm run build -- --production", {
14 | cwd: rootDirectory,
15 | stdio: "inherit"
16 | });
17 |
18 | // build core
19 |
20 | child_process.execSync("make android -j4", {
21 | cwd: path.resolve(rootDirectory, "core", "build"),
22 | stdio: "inherit"
23 | });
24 |
25 | // update version
26 |
27 | const studioDirectory = path.resolve(currentDirectory, "studio");
28 | const gradleFile = path.resolve(studioDirectory, "app", "build.gradle.kts");
29 | const gradleFileContent = fs.readFileSync(gradleFile, {
30 | encoding: "utf-8"
31 | });
32 | const gradleFileUpdated = gradleFileContent
33 | .replace(
34 | /versionName = ".*?"/g,
35 | `versionName = "${version.major}.${version.minor}.${version.patch}"`
36 | )
37 | .replace(/versionCode = .*?\n/g, `versionCode = ${version.build}\n`);
38 | fs.writeFileSync(gradleFile, gradleFileUpdated);
39 |
40 | const androidKeys = dotenv.parse(
41 | fs.readFileSync(path.resolve(currentDirectory, "ANDROID_KEYS.env"))
42 | );
43 |
44 | // gradle build
45 |
46 | child_process.execSync("./gradlew bundleRelease", {
47 | cwd: studioDirectory,
48 | stdio: "inherit",
49 | env: {
50 | JAVA_HOME: androidKeys.JAVA_HOME
51 | }
52 | });
53 |
54 | // pkg sign
55 |
56 | const bundle = path.resolve(
57 | studioDirectory,
58 | "app",
59 | "build",
60 | "outputs",
61 | "bundle",
62 | "release",
63 | "app-release.aab"
64 | );
65 |
66 | child_process.execSync(
67 | `jarsigner -keystore ${androidKeys.FILE} -storepass ${androidKeys.PASSPHRASE} ${bundle} ${androidKeys.KEY}`,
68 | {
69 | cwd: studioDirectory,
70 | stdio: "inherit"
71 | }
72 | );
73 |
74 | // play console upload
75 |
76 | child_process.execSync(
77 | `python upload.py org.fullstacked.editor ${bundle} ${version.major}.${version.minor}.${version.patch}`,
78 | {
79 | stdio: "inherit",
80 | cwd: currentDirectory
81 | }
82 | );
83 |
--------------------------------------------------------------------------------
/platform/android/studio/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/platform/android/studio/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /src/main/cpp/core
3 | /release
--------------------------------------------------------------------------------
/platform/android/studio/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application") version "8.7.3"
3 | id("org.jetbrains.kotlin.android") version "1.9.25"
4 | }
5 |
6 | android {
7 | namespace = "org.fullstacked.editor"
8 | compileSdk = 34
9 |
10 | ndkVersion = "26.1.10909125"
11 |
12 | defaultConfig {
13 | applicationId = "org.fullstacked.editor"
14 | minSdk = 29
15 | targetSdk = 34
16 | versionCode = 1020
17 | versionName = "0.11.2"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary = true
22 | }
23 | ndk {
24 | abiFilters += listOf("armeabi-v7a","arm64-v8a", "x86_64")
25 | }
26 | }
27 |
28 | buildTypes {
29 | release {
30 | isMinifyEnabled = false
31 | proguardFiles(
32 | getDefaultProguardFile("proguard-android-optimize.txt"),
33 | "proguard-rules.pro"
34 | )
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility = JavaVersion.VERSION_20
39 | targetCompatibility = JavaVersion.VERSION_20
40 | }
41 | kotlinOptions {
42 | jvmTarget = "20"
43 | }
44 | buildFeatures {
45 | compose = true
46 | }
47 | composeOptions {
48 | kotlinCompilerExtensionVersion = "1.5.15"
49 | }
50 | sourceSets {
51 | getByName("main") {
52 | assets {
53 | srcDirs("../../../../out/zip")
54 | }
55 | }
56 | }
57 | externalNativeBuild {
58 | cmake {
59 | path = file("src/main/cpp/CMakeLists.txt")
60 | version = "3.22.1"
61 | }
62 | }
63 | }
64 | //noinspection UseTomlInstead
65 | dependencies {
66 | implementation(libs.androidx.core.ktx)
67 | implementation(libs.androidx.lifecycle.runtime.ktx)
68 | implementation(libs.androidx.activity.compose)
69 | implementation(platform(libs.androidx.compose.bom))
70 | implementation(libs.androidx.ui)
71 | implementation(libs.androidx.ui.graphics)
72 | implementation(libs.androidx.ui.tooling.preview)
73 | implementation(libs.androidx.material3)
74 | testImplementation(libs.junit)
75 | androidTestImplementation(libs.androidx.junit)
76 | androidTestImplementation(libs.androidx.espresso.core)
77 | androidTestImplementation(platform(libs.androidx.compose.bom))
78 | androidTestImplementation(libs.androidx.ui.test.junit4)
79 | debugImplementation(libs.androidx.ui.tooling)
80 | debugImplementation(libs.androidx.ui.test.manifest)
81 | }
--------------------------------------------------------------------------------
/platform/android/studio/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
16 |
17 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | # For more information about using CMake with Android Studio, read the
3 | # documentation: https://d.android.com/studio/projects/add-native-code.html.
4 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples.
5 |
6 | # Sets the minimum CMake version required for this project.
7 | cmake_minimum_required(VERSION 3.22.1)
8 |
9 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
10 | # Since this is the top level CMakeLists.txt, the project name is also accessible
11 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
12 | # build script scope).
13 | project(editor-core)
14 |
15 | set(core_DIR ${CMAKE_CURRENT_SOURCE_DIR}/core)
16 |
17 | if(${ANDROID_ABI} MATCHES "arm64-v8a")
18 | add_compile_definitions(ANDROID_ABI_arm64)
19 | endif ()
20 |
21 | if(${ANDROID_ABI} MATCHES "x86_64")
22 | add_compile_definitions(ANDROID_ABI_x64)
23 | endif ()
24 |
25 | add_library(core SHARED IMPORTED)
26 | set_target_properties(core PROPERTIES IMPORTED_LOCATION
27 | ${core_DIR}/${ANDROID_ABI}/core.so)
28 |
29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
30 |
31 | # Creates and names a library, sets it as either STATIC
32 | # or SHARED, and provides the relative paths to its source code.
33 | # You can define multiple libraries, and CMake builds them for you.
34 | # Gradle automatically packages shared libraries with your APK.
35 | #
36 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
37 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
38 | # is preferred for the same purpose.
39 | #
40 | # In order to load a library into your app from Java/Kotlin, you must call
41 | # System.loadLibrary() and pass the name of the library defined here;
42 | # for GameActivity/NativeActivity derived applications, the same library name must be
43 | # used in the AndroidManifest.xml file.
44 | add_library(editor-core SHARED
45 | # List C/C++ source files with relative paths to this CMakeLists.txt.
46 | bridge.cpp)
47 |
48 | # Specifies libraries CMake should link to your target library. You
49 | # can link libraries from various origins, such as libraries defined in this
50 | # build script, prebuilt third-party libraries, or Android system libraries.
51 | target_link_libraries(editor-core
52 | android
53 | core
54 | log)
55 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/cpp/bridge.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Charles-Philippe Lepage on 2024-07-25.
3 | //
4 |
5 | #ifdef ANDROID_ABI_arm64
6 | #include "core/arm64-v8a/core.h"
7 | #elif ANDROID_ABI_x64
8 | #include "core/x86_64/core.h"
9 | #else
10 | #include "core/armeabi-v7a/core.h"
11 | #endif
12 |
13 | #include
14 |
15 | #ifndef FULLSTACKED_EDITOR_EDITOR_H
16 | #define FULLSTACKED_EDITOR_EDITOR_H
17 |
18 | extern "C" {
19 | JNIEXPORT void JNICALL Java_org_fullstacked_editor_MainActivity_directories
20 | (JNIEnv *env, jobject jobj, jstring root, jstring config, jstring editor);
21 |
22 | JNIEXPORT jbyteArray JNICALL Java_org_fullstacked_editor_Instance_call
23 | (JNIEnv *env, jobject jobj, jbyteArray buffer);
24 |
25 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
26 |
27 | JNIEXPORT void JNICALL Java_org_fullstacked_editor_MainActivity_addCallback(JNIEnv *env, jobject thiz, jint id);
28 | JNIEXPORT void JNICALL Java_org_fullstacked_editor_MainActivity_removeCallback(JNIEnv *env, jobject thiz, jint id);
29 |
30 | }
31 |
32 | #endif //FULLSTACKED_EDITOR_EDITOR_H
33 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/java/org/fullstacked/editor/Instance.kt:
--------------------------------------------------------------------------------
1 | package org.fullstacked.editor
2 |
3 | class Instance(val projectId: String, val isEditor: Boolean = false) {
4 | private var headerRequest: ByteArray
5 |
6 | private external fun call(buffer: ByteArray): ByteArray
7 |
8 | init {
9 | if(this.isEditor) {
10 | this.headerRequest = byteArrayOf(
11 | 1 // isEditor
12 | )
13 | this.headerRequest += numberToBytes(0) // no project id
14 | } else {
15 | this.headerRequest = byteArrayOf(
16 | 0 // is not Editor
17 | )
18 | val idData = this.projectId.toByteArray()
19 | this.headerRequest += numberToBytes(idData.size)
20 | this.headerRequest += idData
21 | }
22 | }
23 |
24 | fun callLib(payload: ByteArray) : ByteArray {
25 | return this.call(this.headerRequest + payload)
26 | }
27 | }
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/drawable/fullstacked_app_icon_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #007AFF
4 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FullStacked
3 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/platform/android/studio/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/platform/android/studio/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.7.3" apply false
4 | id("org.jetbrains.kotlin.android") version "1.9.25" apply false
5 | }
--------------------------------------------------------------------------------
/platform/android/studio/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/platform/android/studio/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.1"
3 | kotlin = "1.9.0"
4 | coreKtx = "1.10.1"
5 | junit = "4.13.2"
6 | junitVersion = "1.1.5"
7 | espressoCore = "3.5.1"
8 | lifecycleRuntimeKtx = "2.6.1"
9 | activityCompose = "1.8.0"
10 | composeBom = "2024.04.01"
11 |
12 | [libraries]
13 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
14 | junit = { group = "junit", name = "junit", version.ref = "junit" }
15 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
16 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
17 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
18 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
19 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
20 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
21 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
22 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
23 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
24 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
25 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
26 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
27 |
28 | [plugins]
29 | android-application = { id = "com.android.application", version.ref = "agp" }
30 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
31 |
32 |
--------------------------------------------------------------------------------
/platform/android/studio/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/android/studio/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/platform/android/studio/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 03 11:55:39 EDT 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/platform/android/studio/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/platform/android/studio/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "FullStacked Editor"
23 | include(":app")
24 |
--------------------------------------------------------------------------------
/platform/android/upload.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # Copyright 2014 Google Inc. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the 'License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Uploads an Android App Bundle to the internal test track."""
18 |
19 | import argparse
20 | import sys
21 | from apiclient import sample_tools
22 | from oauth2client import client
23 | import mimetypes
24 | mimetypes.add_type('application/octet-stream', '.aab')
25 |
26 | TRACK = 'internal' # Can be 'internal', 'alpha', beta', 'production' or 'rollout'
27 |
28 | # Declare command-line flags.
29 | argparser = argparse.ArgumentParser(add_help=False)
30 | argparser.add_argument('package_name',
31 | help='The package name. Example: com.android.sample')
32 | argparser.add_argument('aab_file',
33 | help='The path to the .aab file to upload.')
34 | argparser.add_argument('app_version',
35 | help='x.x.x')
36 |
37 |
38 | def main(argv):
39 | # Authenticate and construct service.
40 | service, flags = sample_tools.init(
41 | argv,
42 | 'androidpublisher',
43 | 'v3',
44 | __doc__,
45 | __file__, parents=[argparser],
46 | scope='https://www.googleapis.com/auth/androidpublisher')
47 |
48 | # Process flags and read their values.
49 | package_name = flags.package_name
50 | aab_file = flags.aab_file
51 | app_version = flags.app_version
52 |
53 | try:
54 | edit_request = service.edits().insert(body={}, packageName=package_name)
55 | result = edit_request.execute()
56 | edit_id = result['id']
57 |
58 | aab_response = service.edits().bundles().upload(
59 | editId=edit_id,
60 | packageName=package_name,
61 | media_body=aab_file).execute()
62 |
63 | print 'Version code %d has been uploaded' % aab_response['versionCode']
64 |
65 | track_response = service.edits().tracks().update(
66 | editId=edit_id,
67 | track=TRACK,
68 | packageName=package_name,
69 | body={u'releases': [{
70 | u'name': str(app_version),
71 | u'versionCodes': [str(aab_response['versionCode'])],
72 | u'status': u'completed',
73 | }]}).execute()
74 |
75 | print 'Track %s is set with releases: %s' % (
76 | track_response['track'], str(track_response['releases']))
77 |
78 | commit_request = service.edits().commit(
79 | editId=edit_id, packageName=package_name).execute()
80 |
81 | print 'Edit "%s" has been committed' % (commit_request['id'])
82 |
83 | except client.AccessTokenRefreshError:
84 | print ('The credentials have been revoked or expired, please re-run the '
85 | 'application to re-authorize')
86 |
87 | if __name__ == '__main__':
88 | main(sys.argv)
--------------------------------------------------------------------------------
/platform/apple/.gitignore:
--------------------------------------------------------------------------------
1 | xcuserdata
2 | xcshareddata
3 | *.xcarchive
4 | pkg*
5 | exportOptions.plist
--------------------------------------------------------------------------------
/platform/apple/FullStacked.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/100-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/100-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/1024-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/1024-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/114-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/114-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/120-bleed 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/120-bleed 1.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/120-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/120-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/144-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/144-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/152-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/152-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/167-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/167-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/180-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/180-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/20-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/20-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/29-bleed 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/29-bleed 1.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/29-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/29-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed 1.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed 2.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/40-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/50-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/50-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/57-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/57-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/58-bleed 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/58-bleed 1.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/58-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/58-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/60-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/60-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/72-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/72-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/76-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/76-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/80-bleed 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/80-bleed 1.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/80-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/80-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/87-bleed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/apple/FullStacked/Assets.xcassets/AppIcon.appiconset/87-bleed.png
--------------------------------------------------------------------------------
/platform/apple/FullStacked/FullStacked.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-write
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/FullStacked.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct FullStackedApp: App {
5 | static var singleton: FullStackedApp?
6 | @ObservedObject var webViews = WebViews()
7 |
8 | init() {
9 | FullStackedApp.singleton = self;
10 |
11 | setDirectories()
12 | setCallback()
13 | }
14 |
15 | var body: some Scene {
16 | #if os(macOS)
17 | Window("FullStacked", id: "Editor"){
18 | WebViewsStacked(webViews: self.webViews)
19 | .onDisappear {
20 | exit(0)
21 | }
22 | }
23 | #else
24 | WindowGroup("FullStacked"){
25 | if #available(iOS 16.0, *) {
26 | WebViewsStacked(webViews: self.webViews)
27 | .onDisappear {
28 | exit(0)
29 | }
30 | } else {
31 | WebViewsStackedLegacy(webViews: self.webViews)
32 | .onDisappear{
33 | exit(0)
34 | }
35 | }
36 | }
37 | #endif
38 |
39 | if #available(iOS 16.1, *) {
40 | WindowGroup(id: "window-webview", for: String.self) { $projectId in
41 | WebViewSingle(projectId: projectId)
42 | }
43 | .commands {
44 | CommandGroup(replacing: CommandGroupPlacement.newItem) {
45 | EmptyView()
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleTypeRole
9 | Editor
10 | CFBundleURLName
11 | editor.FullStacked
12 | CFBundleURLSchemes
13 |
14 | fullstacked
15 |
16 |
17 |
18 | ITSAppUsesNonExemptEncryption
19 |
20 | NSBonjourServices
21 |
22 | _fullstacked._tcp
23 | _fullstacked-ios._tcp
24 |
25 | UIFileSharingEnabled
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/Instance.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Instance.swift
3 | // FullStacked
4 | //
5 | // Created by Charles-Philippe Lepage on 2024-11-06.
6 | //
7 |
8 | import SwiftUI
9 |
10 | class Instance {
11 | public let isEditor: Bool
12 | public let id: String
13 | private var header: Data
14 |
15 |
16 | init (projectId: String, isEditor: Bool = false) {
17 | self.isEditor = isEditor
18 | self.id = projectId
19 |
20 | self.header = Data()
21 | if(isEditor) {
22 | self.header.append(Data([1])) // isEditor
23 | self.header.append(0.toBytes()) // no project id
24 | } else {
25 | self.header.append(Data([0]))
26 | let projectIdData = self.id.data(using: .utf8)!
27 | self.header.append(projectIdData.count.toBytes())
28 | self.header.append(projectIdData)
29 | }
30 | }
31 |
32 | func callLib(payload: Data) -> Data {
33 | var data = Data()
34 | data.append(self.header)
35 | data.append(payload)
36 |
37 | var responsePtr = Data().ptr()
38 | let size = call(data.ptr(), Int32(data.count), &responsePtr)
39 | let responseDataPtr = UnsafeBufferPointer(start: responsePtr!.assumingMemoryBound(to: UInt8.self), count: Int(size))
40 | let responseData = Data(responseDataPtr)
41 | freePtr(responsePtr)
42 |
43 | return responseData
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryFileTimestamp
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | C617.1
13 |
14 |
15 |
16 | NSPrivacyAccessedAPIType
17 | NSPrivacyAccessedAPICategorySystemBootTime
18 | NSPrivacyAccessedAPITypeReasons
19 |
20 | 35F9.1
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/WebViewAppKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewRepresentable.swift
3 | // FullStacked
4 | //
5 | // Created by Charles-Philippe Lepage on 2024-11-27.
6 | //
7 | import SwiftUI
8 | @preconcurrency import WebKit
9 |
10 | // MacOS
11 |
12 | class WebViewExtended: WKWebView, WKUIDelegate {
13 | override init(frame: CGRect, configuration: WKWebViewConfiguration){
14 | super.init(frame: frame, configuration: configuration)
15 | self.uiDelegate = self
16 | }
17 |
18 | required init?(coder: NSCoder) {
19 | fatalError("init(coder:) has not been implemented")
20 | }
21 |
22 | func openBrowserURL(_ url: URL){
23 | NSWorkspace.shared.open(url)
24 | }
25 |
26 | func openDownloadDirectory(){
27 | NSWorkspace.shared.open(URL(fileURLWithPath: downloadDirectory))
28 | }
29 |
30 | func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {
31 | let openPanel = NSOpenPanel()
32 | openPanel.canChooseFiles = true
33 | openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection
34 | openPanel.begin { (result) in
35 | if result == NSApplication.ModalResponse.OK {
36 | completionHandler(openPanel.urls)
37 | } else if result == NSApplication.ModalResponse.cancel {
38 | completionHandler(nil)
39 | }
40 | }
41 | }
42 | }
43 |
44 | // suppress "funk" noise
45 | // source: https://stackoverflow.com/a/69858444
46 | class KeyView: NSView {
47 | override var acceptsFirstResponder: Bool { true }
48 | override func keyDown(with event: NSEvent) {}
49 | }
50 |
51 | struct WebViewRepresentable: NSViewRepresentable {
52 | private let webview: WebView;
53 | init(webView: WebView) {
54 | self.webview = webView
55 | }
56 |
57 | func makeNSView(context: Context) -> NSView {
58 | let view = KeyView()
59 | DispatchQueue.main.async {
60 | view.window?.makeFirstResponder(view)
61 | }
62 | self.webview.autoresizingMask = [.width, .height]
63 | view.addSubview(self.webview);
64 | return view
65 | }
66 |
67 |
68 | func updateNSView(_ uiView: NSView, context: Context) { }
69 | }
70 |
--------------------------------------------------------------------------------
/platform/apple/FullStacked/WebViewUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewRepresentable.swift
3 | // FullStacked
4 | //
5 | // Created by Charles-Philippe Lepage on 2024-11-27.
6 | //
7 | import SwiftUI
8 | import WebKit
9 |
10 | // iOS
11 |
12 | class WebViewExtended: WKWebView {
13 | override var safeAreaInsets: UIEdgeInsets {
14 | return UIEdgeInsets(top: super.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
15 | }
16 |
17 | func openBrowserURL(_ url: URL){
18 | if( UIApplication.shared.canOpenURL(url)) {
19 | UIApplication.shared.open(url)
20 | }
21 | }
22 |
23 | func openDownloadDirectory(){
24 | UIApplication.shared.open(URL(string: "shareddocuments://" + downloadDirectory)!)
25 | }
26 | }
27 |
28 | struct WebViewRepresentable: UIViewRepresentable {
29 | private let webView: WebView;
30 | init(webView: WebView) {
31 | self.webView = webView
32 | }
33 |
34 | func makeUIView(context: Context) -> WebView {
35 | return webView
36 | }
37 |
38 | func updateUIView(_ uiView: WebView, context: Context) {
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/platform/linux/.gitignore:
--------------------------------------------------------------------------------
1 | fullstacked.deb
--------------------------------------------------------------------------------
/platform/linux/app.cpp:
--------------------------------------------------------------------------------
1 | #include "./app.h"
2 | #include
3 |
4 | App::App()
5 | {
6 | App::instance = this;
7 | app = Gtk::Application::create("fullstacked");
8 | }
9 |
10 | void App::onMessage(char *projectId, char *type, char *message)
11 | {
12 | auto exists = windows.find(projectId);
13 | if (exists != windows.end())
14 | {
15 | exists->second->onMessage(type, message);
16 | }
17 | }
18 |
19 | void App::onClose(GtkWidget *widget, gpointer user_data)
20 | {
21 | auto i = static_cast(user_data);
22 | App::instance->windows.erase(i->id);
23 | delete i;
24 | };
25 |
26 | void App::open(std::string projectId, bool isEditor)
27 | {
28 | auto exists = windows.find(projectId);
29 | if (exists != windows.end())
30 | {
31 | exists->second->show();
32 | exists->second->present();
33 | exists->second->fullscreen();
34 | webkit_web_view_reload(exists->second->webview);
35 | }
36 | else
37 | {
38 | auto win = new Instance(projectId, isEditor);
39 | windows[projectId] = win;
40 | if(kiosk) {
41 | win->signal_realize().connect([&]{
42 | win->fullscreen();
43 | });
44 | }
45 | win->show();
46 | app->add_window(*win);
47 | }
48 | }
49 |
50 | int App::run(std::string startupId)
51 | {
52 | app->signal_startup().connect([&]{ open(startupId, startupId == ""); });
53 | return app->run();
54 | }
--------------------------------------------------------------------------------
/platform/linux/app.h:
--------------------------------------------------------------------------------
1 | #ifndef APP_H
2 | #define APP_H
3 |
4 | #include
5 | #include "./instance.h"
6 |
7 | class App
8 | {
9 | private:
10 | Glib::RefPtr app;
11 |
12 | public:
13 | inline static App *instance;
14 | std::map windows;
15 | std::string deeplink;
16 | bool kiosk = false;
17 |
18 | App();
19 |
20 | void onMessage(char *projectId, char* type, char* message);
21 |
22 | void open(std::string projectId, bool isEditor);
23 |
24 | static void onClose(GtkWidget* widget, gpointer user_data);
25 |
26 | int run(std::string startupId);
27 | };
28 |
29 | #endif
--------------------------------------------------------------------------------
/platform/linux/build.sh:
--------------------------------------------------------------------------------
1 | # ./build.sh [arm64|x86_64]
2 | cp bin/linux-$1.h bin/linux.h
3 |
4 | rm -rf out
5 |
6 | mkdir -p ./out/usr/share/fullstacked
7 | cp -r ../../out/editor ./out/usr/share/fullstacked
8 |
9 | mkdir ./out/DEBIAN
10 | cp control out/DEBIAN/control
11 |
12 | mkdir -p out/usr/bin
13 | cp -r ../../core/bin .
14 | g++ utils.cpp instance.cpp app.cpp main.cpp bin/linux-$1 -o out/usr/bin/fullstacked `pkg-config gtkmm-4.0 webkitgtk-6.0 --libs --cflags`
--------------------------------------------------------------------------------
/platform/linux/control:
--------------------------------------------------------------------------------
1 | Package: fullstacked
2 | Version: 0.1
3 | Maintainer: cplepage
4 | Architecture: amd64
5 | Description: FullStacked
6 |
--------------------------------------------------------------------------------
/platform/linux/fix.sh:
--------------------------------------------------------------------------------
1 | # update broken apparmor - bubblewrap
2 | sudo add-apt-repository ppa:apparmor-dev/apparmor-sru
3 | sudo apt update
4 | sudo apt install apparmor
--------------------------------------------------------------------------------
/platform/linux/instance.h:
--------------------------------------------------------------------------------
1 | #ifndef INSTANCE_H_
2 | #define INSTANCE_H_
3 |
4 | #include
5 | #include
6 |
7 | class Instance : public Gtk::Window
8 | {
9 | private:
10 | bool isEditor;
11 | char *header;
12 | int headerSize;
13 | bool firstTouch;
14 | WebKitUserContentManager *ucm;
15 |
16 | public:
17 | std::string id;
18 | WebKitWebView *webview;
19 |
20 | static void webKitURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData);
21 |
22 | static void onScriptMessage(WebKitUserContentManager *manager, JSCValue *value, gpointer userData);
23 |
24 | static gboolean navigationDecidePolicy(WebKitWebView *view,
25 | WebKitPolicyDecision *decision,
26 | WebKitPolicyDecisionType decision_type,
27 | gpointer user_data);
28 |
29 | bool on_window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
30 |
31 | Instance(std::string pId, bool pIsEditor);
32 | ~Instance();
33 |
34 | std::vector callLib(char *data, int size);
35 |
36 | void onMessage(char* type, char* message);
37 | };
38 |
39 | #endif
--------------------------------------------------------------------------------
/platform/linux/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "./app.h"
6 | #include "./bin/linux.h"
7 | #include
8 | #include
9 |
10 | std::string getExePath()
11 | {
12 | char result[PATH_MAX];
13 | ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
14 | std::string path = std::string(result, (count > 0) ? count : 0);
15 | return path;
16 | }
17 |
18 | std::string getEditorDir()
19 | {
20 | std::string path = getExePath();
21 | int pos = path.find_last_of("/");
22 | std::string dir = path.substr(0, pos);
23 | pos = dir.find_last_of("/");
24 | dir = path.substr(0, pos);
25 | return dir + "/share/fullstacked/editor";
26 | }
27 |
28 | void setDirectories()
29 | {
30 | std::string home = getenv("HOME");
31 | std::string root = home + "/FullStacked";
32 | std::string config = home + "/.config/fullstacked";
33 | std::string editor = getEditorDir();
34 |
35 | directories(
36 | root.data(),
37 | config.data(),
38 | editor.data());
39 | }
40 |
41 | void libCallback(char *projectId, char *type, char *msg)
42 | {
43 | App::instance->onMessage(projectId, type, msg);
44 | }
45 |
46 | void registerDesktopApp()
47 | {
48 | std::string localIconsDir = std::string(getenv("HOME")) + "/.local/share/icons";
49 | std::filesystem::create_directories(localIconsDir);
50 | std::string appIconFile = getEditorDir() + "/assets/dev-icon.png";
51 | std::filesystem::copy_file(appIconFile, localIconsDir + "/fullstacked.png", std::filesystem::copy_options::overwrite_existing);
52 |
53 | std::string localAppsDir = std::string(getenv("HOME")) + "/.local/share/applications";
54 | std::filesystem::create_directories(localAppsDir);
55 | std::ofstream localAppFile(localAppsDir + "/fullstacked.desktop");
56 | std::string contents =
57 | "[Desktop Entry]\n"
58 | "Name=FullStacked\n"
59 | "Exec=" +
60 | getExePath() + " %u\n"
61 | "Terminal=false\n"
62 | "Type=Application\n"
63 | "MimeType=x-scheme-handler/fullstacked\n"
64 | "Icon=fullstacked\n"
65 | "Categories=Development;Utility;";
66 | localAppFile << contents.c_str();
67 | localAppFile.close();
68 |
69 | system(("update-desktop-database " + localAppsDir).c_str());
70 | }
71 |
72 | int main(int argc, char *argv[])
73 | {
74 | registerDesktopApp();
75 | setDirectories();
76 | callback((void *)libCallback);
77 | auto app = new App();
78 |
79 | std::string httpPrefix = "http";
80 | std::string kioskFlag = "--kiosk";
81 | std::string startupId = "";
82 | for(int i = 1; i < argc; i++) {
83 | std::string arg(argv[i]);
84 |
85 | if(arg.compare(0, httpPrefix.size(), httpPrefix) == 0) {
86 | app->deeplink = arg;
87 | } else if(arg == kioskFlag) {
88 | app->kiosk = true;
89 | if(argc > i + 1) {
90 | startupId = std::string(argv[i + 1]);
91 | i++;
92 | }
93 | }
94 | }
95 |
96 | return app->run(startupId);
97 | }
--------------------------------------------------------------------------------
/platform/linux/pkg.sh:
--------------------------------------------------------------------------------
1 | dpkg-deb --build ./out
2 | mv out.deb fullstacked.deb
--------------------------------------------------------------------------------
/platform/linux/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef UTILS_H
2 | #define UTILS_H
3 |
4 | #include
5 | #include
6 |
7 | void numberToCharPtr(int number, char *ptr);
8 |
9 | unsigned bytesToNumber(unsigned char *bytes, int size);
10 |
11 | int deserializeNumber(char *bytes, int size);
12 |
13 | void printBuffer(char *buffer, int size);
14 |
15 | int combineBuffers(char *buf1, int lgt1, char *buf2, int lgt2, char *result);
16 |
17 | class DataValue
18 | {
19 | public:
20 | bool boolean;
21 | std::string str;
22 | int number;
23 | std::vector buffer;
24 | };
25 |
26 | enum DataType
27 | {
28 | UNDEFINED = 0,
29 | BOOLEAN = 1,
30 | STRING = 2,
31 | NUMBER = 3,
32 | BUFFER = 4
33 | };
34 |
35 | std::vector deserializeArgs(std::vector data);
36 |
37 | std::string gen_random(const int len);
38 |
39 | std::string uri_decode(std::string str);
40 |
41 | struct URL
42 | {
43 | public:
44 | URL(const std::string &url_s)
45 | {
46 | this->parse(url_s);
47 | }
48 | std::string protocol, host, path, query;
49 |
50 | std::string str();
51 |
52 | private:
53 | void parse(const std::string &url_s);
54 | };
55 |
56 | #endif
--------------------------------------------------------------------------------
/platform/node/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | *.tgz
3 | js
4 | editor
5 | index.js
6 | Demo.zip
--------------------------------------------------------------------------------
/platform/node/.npmignore:
--------------------------------------------------------------------------------
1 | .cache
2 | src
3 | *.ts
4 | !*.d.ts
5 | *.tgz
6 | !dist
7 | !Demo.zip
--------------------------------------------------------------------------------
/platform/node/build.ts:
--------------------------------------------------------------------------------
1 | import esbuild from "esbuild";
2 |
3 | esbuild.buildSync({
4 | entryPoints: ["src/index.ts"],
5 | outfile: "index.js",
6 | bundle: true,
7 | format: "esm",
8 | packages: "external",
9 | platform: "node"
10 | });
11 |
--------------------------------------------------------------------------------
/platform/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "esbuild build.ts --outfile=.cache/build.js --packages=external && node .cache/build.js",
4 | "start": "npm run build && node index.js",
5 | "prepack": "npm run build"
6 | },
7 | "type": "module",
8 | "dependencies": {
9 | "@types/ws": "^8.5.13",
10 | "fast-querystring": "^1.1.2",
11 | "ffi-rs": "^1.0.98",
12 | "ws": "^8.18.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/platform/node/src/build.ts:
--------------------------------------------------------------------------------
1 | import type esbuild from "esbuild";
2 |
3 | export function build(
4 | buildSync: typeof esbuild.buildSync,
5 | input: string,
6 | out: string,
7 | outdir: string,
8 | nodePath: string,
9 | sourcemap: esbuild.BuildOptions["sourcemap"] = "inline",
10 | splitting = true,
11 | minify: esbuild.BuildOptions["minify"] = false
12 | ) {
13 | try {
14 | buildSync({
15 | entryPoints: [
16 | {
17 | in: input,
18 | out
19 | }
20 | ],
21 | outdir,
22 | splitting,
23 | bundle: true,
24 | format: "esm",
25 | sourcemap,
26 | write: true,
27 | nodePaths: nodePath ? [nodePath] : undefined,
28 | logLevel: "silent",
29 | minify
30 | });
31 | } catch (e) {
32 | return { errors: e.errors as esbuild.ResolveResult["errors"] };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/platform/node/src/index.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import os from "os";
3 | import { setCallback, setDirectories } from "./call";
4 | import { createWebView } from "./webview";
5 | import { createInstance } from "./instance";
6 |
7 | let deeplink: string = null,
8 | deeplinkMessaged = false;
9 | if (process.argv.at(-1).startsWith("http")) {
10 | deeplink = process.argv.at(-1);
11 | }
12 |
13 | const root = path.resolve(os.homedir(), "FullStacked");
14 | await setDirectories({
15 | root,
16 | config: path.resolve(os.homedir(), ".config", "fullstacked"),
17 | editor: path.resolve(process.cwd(), "..", "..", "out", "editor")
18 | });
19 |
20 | export const platform = new TextEncoder().encode("node");
21 |
22 | type WebView = Awaited>;
23 |
24 | const webViews = new Map();
25 |
26 | const cb = (projectId: string, messageType: string, message: string) => {
27 | if (projectId === "*") {
28 | for (const w of webViews.values()) {
29 | w.message(messageType, message);
30 | }
31 | return;
32 | } else if (!projectId && messageType === "open") {
33 | openProject(message);
34 | return;
35 | }
36 |
37 | const webview = webViews.get(projectId);
38 | webview?.message(messageType, message);
39 | };
40 | await setCallback(cb);
41 |
42 | async function openProject(id: string) {
43 | let webView = webViews.get(id);
44 | if (webView) {
45 | return;
46 | }
47 |
48 | const instance = createInstance(id);
49 | webView = await createWebView(instance, () => webViews.delete(id));
50 | webViews.set(id, webView);
51 | }
52 |
53 | const instanceEditor = createInstance("", true);
54 | const instanceWebView = await createWebView(instanceEditor, null, () => {
55 | if (!deeplink || deeplinkMessaged) return;
56 | instanceWebView.message("deeplink", "fullstacked://" + deeplink);
57 | deeplinkMessaged = true;
58 | });
59 | webViews.set("", instanceWebView);
60 |
61 | ["SIGINT", "SIGTERM", "SIGQUIT"].forEach((signal) =>
62 | process.on(signal, () => process.exit())
63 | );
64 |
--------------------------------------------------------------------------------
/platform/node/src/instance.ts:
--------------------------------------------------------------------------------
1 | import { numberTo4Bytes } from "../../../lib/bridge/serialization";
2 | import { callLib } from "./call";
3 |
4 | type InstanceOpts = { id: string; isEditor: boolean };
5 |
6 | export function createInstance(
7 | id: InstanceOpts["id"],
8 | isEditor: InstanceOpts["isEditor"] = false
9 | ) {
10 | const header = createPayloadHeader({ id, isEditor });
11 |
12 | const call = (payload: Uint8Array) =>
13 | callLib(new Uint8Array([...header, ...payload]));
14 |
15 | return {
16 | id,
17 | isEditor,
18 | call
19 | };
20 | }
21 |
22 | const te = new TextEncoder();
23 |
24 | function createPayloadHeader(opts: InstanceOpts) {
25 | if (opts.isEditor) {
26 | return new Uint8Array([
27 | 1, // is editor
28 | ...numberTo4Bytes(0) // no project id
29 | ]);
30 | }
31 |
32 | const idData = te.encode(opts.id);
33 |
34 | return new Uint8Array([
35 | 0, // is not editor
36 | ...numberTo4Bytes(idData.byteLength), // project id length
37 | ...idData // project id
38 | ]);
39 | }
40 |
--------------------------------------------------------------------------------
/platform/wasm/.gitignore:
--------------------------------------------------------------------------------
1 | .wrangler
--------------------------------------------------------------------------------
/platform/wasm/build.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import esbuild from "esbuild";
3 |
4 | if (fs.existsSync("out")) {
5 | fs.rmSync("out", { recursive: true });
6 | }
7 | fs.mkdirSync("out/bin", { recursive: true });
8 |
9 | fs.cpSync("../../core/bin/wasm.wasm", "out/bin/wasm.wasm");
10 | fs.cpSync("../../core/bin/wasm.js", "out/bin/wasm.js");
11 |
12 | const wasmSize = fs.statSync("out/bin/wasm.wasm").size;
13 |
14 | const editorZipFileName = fs
15 | .readdirSync("../../out/zip")
16 | .find((item) => item.startsWith("editor"));
17 | fs.cpSync(`../../out/zip/${editorZipFileName}`, "out/editor.zip");
18 |
19 | esbuild.buildSync({
20 | entryPoints: ["src/index.ts"],
21 | outfile: "out/index.js",
22 | bundle: true,
23 | format: "esm",
24 | define: {
25 | "process.env.wasmSize": wasmSize.toString()
26 | }
27 | });
28 |
29 | ["src/dev-icon.png", "src/index.html"].forEach((f) =>
30 | fs.cpSync(f, "out" + f.slice("src".length))
31 | );
32 |
--------------------------------------------------------------------------------
/platform/wasm/dev.js:
--------------------------------------------------------------------------------
1 | import http from "http";
2 | import fs from "fs";
3 | import open from "open";
4 | import mimeTypes from "mime-types";
5 |
6 | const notFound = {
7 | code: 404,
8 | headers: {
9 | "content-type": "text/html"
10 | },
11 | body: "Not Found"
12 | };
13 |
14 | const basedir = "out";
15 |
16 | const existsAndIsFile = (pathname) => {
17 | let stat;
18 | try {
19 | stat = fs.statSync(pathname);
20 | } catch (e) {
21 | return false;
22 | }
23 |
24 | return stat.isFile();
25 | };
26 |
27 | const hanlder = (req, res) => {
28 | let pathname = req.url.split("?").shift();
29 |
30 | if (pathname.startsWith("/")) pathname = pathname.slice(1);
31 | if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
32 |
33 | pathname = basedir + "/" + pathname;
34 |
35 | if (!existsAndIsFile(pathname)) {
36 | const maybeIndex = pathname + (pathname ? "/" : "") + "index.html";
37 | if (existsAndIsFile(maybeIndex)) pathname = maybeIndex;
38 | }
39 |
40 | let stats;
41 |
42 | try {
43 | stats = fs.statSync(pathname);
44 | } catch (e) {}
45 |
46 | if (!stats || stats.isDirectory()) {
47 | res.writeHead(notFound.code, notFound.headers);
48 | res.end(notFound.body);
49 | return;
50 | }
51 |
52 | const readStream = fs.createReadStream(pathname);
53 | res.writeHead(200, {
54 | "content-type": mimeTypes.lookup(pathname),
55 | "content-length": stats.size
56 | });
57 | readStream.pipe(res);
58 | };
59 |
60 | http.createServer(hanlder).listen(9000, "0.0.0.0");
61 | open("http://localhost:9000");
62 |
--------------------------------------------------------------------------------
/platform/wasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "node build.js",
4 | "start": "npm run build && node dev.js",
5 | "deploy": "npm run build && npx wrangler pages deploy out"
6 | },
7 | "type": "module",
8 | "dependencies": {
9 | "@types/winbox": "^0.2.5",
10 | "mime-types": "^2.1.35",
11 | "winbox": "^0.2.82"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/platform/wasm/publish.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import url from "node:url";
3 | import child_process from "node:child_process";
4 |
5 | const currentDirectory = path.dirname(url.fileURLToPath(import.meta.url));
6 | const rootDirectory = path.resolve(currentDirectory, "..", "..");
7 |
8 | // build editor
9 |
10 | child_process.execSync("npm run build -- --production", {
11 | cwd: rootDirectory,
12 | stdio: "inherit"
13 | });
14 |
15 | // build core
16 |
17 | child_process.execSync("make wasm", {
18 | cwd: path.resolve(rootDirectory, "core", "build"),
19 | stdio: "inherit"
20 | });
21 |
22 | // build
23 |
24 | child_process.execSync("npm run build", {
25 | cwd: currentDirectory,
26 | stdio: "inherit"
27 | });
28 |
29 | // upload to CF Pages
30 |
31 | child_process.execSync("npx wrangler pages deploy out", {
32 | cwd: currentDirectory,
33 | stdio: "inherit"
34 | });
35 |
--------------------------------------------------------------------------------
/platform/wasm/src/dev-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/wasm/src/dev-icon.png
--------------------------------------------------------------------------------
/platform/windows/.gitignore:
--------------------------------------------------------------------------------
1 | obj
2 | *.user
3 | .vs
4 | editor
5 | *.pfx
6 | Package.StoreAssociation.xml
7 | win-*.dll
8 | publish
9 | AppPackages
--------------------------------------------------------------------------------
/platform/windows/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/platform/windows/Assets/BadgeLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/BadgeLogo.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/BadgeLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/BadgeLogo.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/BadgeLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/BadgeLogo.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/BadgeLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/BadgeLogo.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/BadgeLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/BadgeLogo.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Icon-16.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Icon-16.ico
--------------------------------------------------------------------------------
/platform/windows/Assets/LargeTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/LargeTile.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/LargeTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/LargeTile.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/LargeTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/LargeTile.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/LargeTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/LargeTile.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SmallTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SmallTile.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SmallTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SmallTile.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SmallTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SmallTile.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SmallTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SmallTile.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-24.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-16_altform-lightunplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-16_altform-lightunplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-16_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-16_altform-unplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-24.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-24_altform-lightunplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-24_altform-lightunplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-256.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-256_altform-lightunplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-256_altform-lightunplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-256_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-256_altform-unplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-32.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-32_altform-lightunplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-32_altform-lightunplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-32_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-32_altform-unplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-48_altform-lightunplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-48_altform-lightunplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Square44x44Logo.targetsize-48_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Square44x44Logo.targetsize-48_altform-unplated.png
--------------------------------------------------------------------------------
/platform/windows/Assets/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/platform/windows/Assets/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackedorg/fullstacked/a41baa78aa2537aeb6b68e25a0b17f7f121f7b59/platform/windows/Assets/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/platform/windows/FullStacked.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.11.35327.3
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FullStacked", "FullStacked.csproj", "{6A45F56D-1305-4B66-A576-D9A095F06A62}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C8EC509E-4140-4AB5-A671-987A5EA88268}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|ARM64 = Debug|ARM64
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|ARM64 = Release|ARM64
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|ARM64.ActiveCfg = Debug|ARM64
21 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|ARM64.Build.0 = Debug|ARM64
22 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|ARM64.Deploy.0 = Debug|ARM64
23 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x64.ActiveCfg = Debug|x64
24 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x64.Build.0 = Debug|x64
25 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x64.Deploy.0 = Debug|x64
26 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x86.ActiveCfg = Debug|x86
27 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x86.Build.0 = Debug|x86
28 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Debug|x86.Deploy.0 = Debug|x86
29 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|ARM64.ActiveCfg = Release|ARM64
30 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|ARM64.Build.0 = Release|ARM64
31 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|ARM64.Deploy.0 = Release|ARM64
32 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x64.ActiveCfg = Release|x64
33 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x64.Build.0 = Release|x64
34 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x64.Deploy.0 = Release|x64
35 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x86.ActiveCfg = Release|x86
36 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x86.Build.0 = Release|x86
37 | {6A45F56D-1305-4B66-A576-D9A095F06A62}.Release|x86.Deploy.0 = Release|x86
38 | EndGlobalSection
39 | GlobalSection(SolutionProperties) = preSolution
40 | HideSolutionNode = FALSE
41 | EndGlobalSection
42 | GlobalSection(ExtensibilityGlobals) = postSolution
43 | SolutionGuid = {471028D8-6CEF-4308-95BF-A5C76ECBE5BE}
44 | EndGlobalSection
45 | EndGlobal
46 |
--------------------------------------------------------------------------------
/platform/windows/Instance.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace FullStacked
5 | {
6 | internal class Instance
7 | {
8 | public String id;
9 | public Boolean isEditor;
10 |
11 | private byte[] header;
12 |
13 | public Instance(String id, Boolean isEditor = false) {
14 | this.id = id;
15 | this.isEditor = isEditor;
16 |
17 | if (isEditor)
18 | {
19 | this.header = new byte[] { 1 }; // isEditor
20 | this.header = App.combineBuffers([this.header, App.numberToByte(0)]); // no project id
21 | }
22 | else {
23 | this.header = new byte[] { 0 };
24 | byte[] idData = Encoding.UTF8.GetBytes(id);
25 | this.header = App.combineBuffers([this.header, App.numberToByte(idData.Length)]);
26 | this.header = App.combineBuffers([this.header, idData]);
27 | }
28 | }
29 |
30 | public byte[] callLib(byte[] payload) {
31 | byte[] data = App.combineBuffers([this.header, payload]);
32 |
33 | return App.call(data);
34 | }
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/platform/windows/Lib.cs:
--------------------------------------------------------------------------------
1 | namespace FullStacked
2 | {
3 | unsafe internal abstract class Lib
4 | {
5 | public abstract void setDirectories(void* root, void* config, void* editor);
6 | public abstract void setCallback(CallbackDelegate cb);
7 |
8 | public delegate void CallbackDelegate(string projectId, string messageType, string message);
9 |
10 | public abstract int callLib(byte* payload, int size, byte** response);
11 | public abstract void freePtrLib(void* ptr);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/platform/windows/LibARM64.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace FullStacked
4 | {
5 | unsafe internal class LibARM64 : Lib
6 | {
7 |
8 | const string dllName = "win-arm64.dll";
9 |
10 | [DllImport(dllName)]
11 | public static extern void directories(void* root, void* config, void* editor);
12 | [DllImport(dllName)]
13 | public static extern void callback(CallbackDelegate cb);
14 |
15 | [DllImport(dllName)]
16 | public static extern int call(byte* payload, int size, byte** response);
17 | [DllImport(dllName)]
18 | public static extern void freePtr(void* ptr);
19 |
20 | public override unsafe void setDirectories(void* root, void* config, void* editor)
21 | {
22 | directories(root, config, editor);
23 | }
24 |
25 | public override void setCallback(CallbackDelegate cb)
26 | {
27 | callback(cb);
28 | }
29 |
30 | public override unsafe int callLib(byte* payload, int size, byte** response)
31 | {
32 | return call(payload, size, response);
33 | }
34 |
35 | public override unsafe void freePtrLib(void* ptr)
36 | {
37 | freePtr(ptr);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/platform/windows/LibX64.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace FullStacked
4 | {
5 | unsafe internal class LibX64 : Lib
6 | {
7 | const string dllName = "win-x64.dll";
8 |
9 | [DllImport(dllName)]
10 | public static extern void directories(void* root, void* config, void* editor);
11 | [DllImport(dllName)]
12 | public static extern void callback(CallbackDelegate cb);
13 |
14 | [DllImport(dllName)]
15 | public static extern int call(byte* payload, int size, byte** response);
16 | [DllImport(dllName)]
17 | public static extern void freePtr(void* ptr);
18 |
19 | public override unsafe void setDirectories(void* root, void* config, void* editor)
20 | {
21 | directories(root, config, editor);
22 | }
23 |
24 | public override void setCallback(CallbackDelegate cb)
25 | {
26 | callback(cb);
27 | }
28 |
29 | public override unsafe int callLib(byte* payload, int size, byte** response)
30 | {
31 | return call(payload, size, response);
32 | }
33 |
34 | public override unsafe void freePtrLib(void* ptr)
35 | {
36 | freePtr(ptr);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/platform/windows/LibX86.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace FullStacked
5 | {
6 | unsafe internal class LibX86 : Lib
7 | {
8 | const string dllName = "win-x86.dll";
9 |
10 | [DllImport(dllName)]
11 | public static extern void directories(void* root, void* config, void* editor);
12 | [DllImport(dllName)]
13 | public static extern void callback(CallbackDelegate cb);
14 |
15 | [DllImport(dllName)]
16 | public static extern int call(byte* payload, int size, byte** response);
17 | [DllImport(dllName)]
18 | public static extern void freePtr(void* ptr);
19 |
20 | public override unsafe void setDirectories(void* root, void* config, void* editor)
21 | {
22 | directories(root, config, editor);
23 | }
24 |
25 | public override void setCallback(CallbackDelegate cb)
26 | {
27 | callback(cb);
28 | }
29 |
30 | public override unsafe int callLib(byte* payload, int size, byte** response)
31 | {
32 | return call(payload, size, response);
33 | }
34 |
35 | public override unsafe void freePtrLib(void* ptr)
36 | {
37 | freePtr(ptr);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/platform/windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | FullStacked (Beta)
9 | Charles-Philippe Lepage
10 | Assets\StoreLogo.png
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/platform/windows/Properties/PublishProfiles/win-arm64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | ARM64
9 | publish\ARM64
10 | FileSystem
11 | <_TargetId>Folder
12 | net8.0-windows10.0.19041.0
13 | win-arm64
14 | true
15 | false
16 | false
17 | false
18 |
19 |
--------------------------------------------------------------------------------
/platform/windows/Properties/PublishProfiles/win-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | x64
9 | publish\X64
10 | FileSystem
11 | <_TargetId>Folder
12 | net8.0-windows10.0.19041.0
13 | win-x64
14 | true
15 | false
16 | false
17 | false
18 |
19 |
--------------------------------------------------------------------------------
/platform/windows/Properties/PublishProfiles/win-x86.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | x86
9 | publish\X86
10 | FileSystem
11 | <_TargetId>Folder
12 | net8.0-windows10.0.19041.0
13 | win-x86
14 | true
15 | false
16 | false
17 | false
18 |
19 |
--------------------------------------------------------------------------------
/platform/windows/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "windows (Package)": {
4 | "commandName": "MsixPackage"
5 | },
6 | "windows (Unpackaged)": {
7 | "commandName": "Project"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/platform/windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | PerMonitorV2
18 |
19 |
20 |
--------------------------------------------------------------------------------
/platform/windows/publish.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import url from "node:url";
3 | import fs from "node:fs";
4 | import child_process from "node:child_process";
5 | import version from "../../version.js";
6 |
7 | const currentDirectory = path.dirname(url.fileURLToPath(import.meta.url));
8 | const rootDirectory = path.resolve(currentDirectory, "..", "..");
9 |
10 | // build editor
11 |
12 | child_process.execSync("npm run build -- --production", {
13 | cwd: rootDirectory,
14 | stdio: "inherit"
15 | });
16 |
17 | // build core
18 |
19 | child_process.execSync("cmd.exe /c windows.bat", {
20 | cwd: path.resolve(rootDirectory, "core", "build"),
21 | stdio: "inherit"
22 | });
23 |
24 | // update version
25 |
26 | const winVersion = `${version.major}.${version.minor}.${version.build}.0`
27 | const packageFile = path.resolve(currentDirectory, "Package.appxmanifest");
28 | let packageContent = fs.readFileSync(packageFile, { encoding: "utf-8" });
29 | packageContent = packageContent.replace(/\bVersion="\d+\.\d+\.\d+\.\d+"/g, `Version="${winVersion}"`)
30 | fs.writeFileSync(packageFile, packageContent);
31 |
32 | // clean
33 |
34 | const appPackages = path.resolve(currentDirectory, "AppPackages");
35 | if(fs.existsSync(appPackages))
36 | fs.rmSync(appPackages, { recursive: true })
37 |
38 | // msstore
39 |
40 | child_process.execSync("msstore init", {
41 | cwd: currentDirectory,
42 | stdio: "inherit"
43 | });
44 | child_process.execSync("msstore package", {
45 | cwd: currentDirectory,
46 | stdio: "inherit"
47 | });
48 | child_process.execSync("msstore publish", {
49 | cwd: currentDirectory,
50 | stdio: "inherit"
51 | });
52 |
--------------------------------------------------------------------------------
/test/core.ts:
--------------------------------------------------------------------------------
1 | import child_process from "child_process";
2 | import path from "path";
3 |
4 | child_process.execSync("make clean", {
5 | stdio: "inherit",
6 | cwd: path.resolve(process.cwd(), "core", "build")
7 | });
8 |
9 | child_process.execSync("make all -j8", {
10 | stdio: "inherit",
11 | cwd: path.resolve(process.cwd(), "core", "build")
12 | });
13 |
14 | process.exit(0);
15 |
--------------------------------------------------------------------------------
/test/index.ts:
--------------------------------------------------------------------------------
1 | import child_process from "child_process";
2 | import esbuild from "esbuild";
3 |
4 | const build = (testFile: string) => {
5 | const outfile = "test/.cache/test.js";
6 | esbuild.buildSync({
7 | entryPoints: [`test/${testFile}`],
8 | outfile,
9 | bundle: true,
10 | packages: "external",
11 | format: "esm"
12 | });
13 | return outfile;
14 | };
15 |
16 | // type checking
17 | child_process.execSync(`node ${build("types.ts")}`, { stdio: "inherit" });
18 |
19 | // core build
20 | // child_process.execSync(`node ${build("core.ts")}`, { stdio: "inherit" });
21 |
22 | // basic tests
23 | child_process.execSync(`node ${build("basic.ts")}`, {
24 | stdio: "inherit"
25 | });
26 |
27 | // deep links and git tests
28 | child_process.execSync(`node ${build("deeplink-git.ts")}`, {
29 | stdio: "inherit"
30 | });
31 |
--------------------------------------------------------------------------------
/test/types.ts:
--------------------------------------------------------------------------------
1 | import child_process from "child_process";
2 |
3 | // typecheck
4 | child_process.execSync("npm run typecheck", {
5 | stdio: "inherit"
6 | });
7 |
8 | process.exit(0);
9 |
--------------------------------------------------------------------------------
/test/utils.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "puppeteer";
2 |
3 | export const sleep = (ms: number) =>
4 | new Promise((resolve) => setTimeout(resolve, ms));
5 |
6 | export const throwError = (message: string) => {
7 | const error = Error(message);
8 | console.error(error);
9 | process.exit(1);
10 | };
11 |
12 | export const waitForStackNavigation = (page: Page, selector: string) => {
13 | return new Promise(async (resolve, reject) => {
14 | let clicked = false;
15 | while (!clicked) {
16 | try {
17 | const element = await page.waitForSelector(selector);
18 | await element.click();
19 | clicked = true;
20 | } catch (e) {
21 | await sleep(100);
22 | }
23 |
24 | if (clicked) {
25 | break;
26 | }
27 | }
28 | await sleep(500);
29 | resolve();
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "ES2022",
5 | "target": "ES2022",
6 | "moduleResolution": "Node"
7 | },
8 | "exclude": [
9 | "demo",
10 | "out",
11 | "platform/android",
12 | "platform/apple",
13 | "platform/docker",
14 | "platform/linux",
15 | "platform/node/editor"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/version.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import url from "node:url";
3 | import fs from "node:fs";
4 | import child_process from "node:child_process";
5 |
6 | const currentDirectory = path.dirname(url.fileURLToPath(import.meta.url));
7 |
8 | const packageJsonFile = path.join(currentDirectory, "package.json");
9 | const packageJsonContent = fs.readFileSync(packageJsonFile, {
10 | encoding: "utf-8"
11 | });
12 | const packageJson = JSON.parse(packageJsonContent);
13 |
14 | const [major, minor, patch] = packageJson.version.split(".");
15 |
16 | const branch = child_process
17 | .execSync("git rev-parse --abbrev-ref HEAD")
18 | .toString()
19 | .trim();
20 | const build = child_process
21 | .execSync(`git rev-list --count ${branch}`)
22 | .toString()
23 | .trim();
24 |
25 | const version = {
26 | major,
27 | minor,
28 | patch,
29 | branch,
30 | build
31 | };
32 |
33 | export default version;
34 |
--------------------------------------------------------------------------------