-
Welcome to flip
-
The UI Builder for the Flipper Zero
-
Effortlessly design and export user interfaces for your Flipper Zero applications.
-
-
Getting Started
-
- Begin by designing your UI using our intuitive builder. Once you are satisfied with the
- design, export it as a JSON file. This JSON can be directly used with the specified Rust crate
- to integrate into your Flipper Zero application.
-
-
-
Variables
-
- In all Text Input Fields, external variables that you have to define in the Rust code and
- include in the proc macro can be referenced to in {'{'}{'}'}
- brackets.
-
-
Actions
-
- All buttons can have actions. An action can either be switching to a view or a custom function
- you defined in the Rust proc macro code. The syntax is:
- function_name(optional_params)
-
-
UI Items
-
- The UI operates on the principle of views. Every view can have UI items (found under the UI Add Items
- - Tab) of one category. Using the Edit (pen icon) button, you can edit the already described properties
- (plus, on Header and Body Text, you can also set text alignment). You can also set the position
- of
- Moveable
items by moving them.
-
-
-
Integration with Rust
-
- To integrate the exported JSON into your Flipper Zero app, use our dedicated Rust crate.
- Simply include the crate in your project and load the JSON to render the UI components (don't
- forget to pass in your functions and variables).
-
-
Example:
-
-
+
Welcome to flip
+
The UI Builder for the Flipper Zero
+
Effortlessly design and export user interfaces for your Flipper Zero applications.
+
+
Getting Started
+
+ Begin by designing your UI using our intuitive builder. Once you are satisfied with the design,
+ export it as a JSON file. This JSON can be directly used with the specified Rust crate to
+ integrate into your Flipper Zero application.
+
+
+
Views
+
+ Every possible user interface is build from multiple Views. That's like a slide a in a
+ presentation and multiple Slides are building the whole presentation. A View can be different
+ types: Either a Dialog
,
+ Alert
, Browser
or
+ Input
. Every View comes with their own UI Elements which can be hidden
+ (dialog) or just edited (all).
+
+
+
Events
+
+ All buttons can have events. An event can be a function you defined in your Rust code or a
+ predefined event (in the proc macro), which currently are:
+ none
, next
, back
+ and
+ close
.
+
+
+
Integration with Rust
+
+ To integrate the exported JSON into your Flipper Zero app, use our dedicated Rust crate. Simply include the crate in your project and load the JSON to render the UI components (don't
+ forget to pass in your functions).
+
+
Example:
+
+
diff --git a/src/routes/Code.svelte b/src/routes/Code.svelte
new file mode 100644
index 0000000..815b940
--- /dev/null
+++ b/src/routes/Code.svelte
@@ -0,0 +1,83 @@
+
+
+
diff --git a/src/routes/builder/+page.svelte b/src/routes/builder/+page.svelte
index 55d061e..dfba501 100644
--- a/src/routes/builder/+page.svelte
+++ b/src/routes/builder/+page.svelte
@@ -1,24 +1,26 @@
-
-
+
diff --git a/src/routes/builder/EditModal.svelte b/src/routes/builder/EditModal.svelte
deleted file mode 100644
index c9462da..0000000
--- a/src/routes/builder/EditModal.svelte
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-{#if $modalStore[0]}
-
-{/if}
diff --git a/src/routes/builder/Editor.svelte b/src/routes/builder/Editor.svelte
new file mode 100644
index 0000000..2acfef8
--- /dev/null
+++ b/src/routes/builder/Editor.svelte
@@ -0,0 +1,166 @@
+
+
+
+
+ {#if $data.views.length > 0}
+
+
{$data.current + 1}. {$data.views[$data.current].type.toString()}
+
+
+ none,
+ next
, back
+ or
+ close
. The Back Button Event will be triggered when
+ the back button is pressed.`}
+ let:dialog
+ >
+
+
+
+ Back Button Event
+
+
+ {:else}
+
No Views
+ {/if}
+
+
+
+ {#if $data.views[$data.current]?.type == GuiType.Dialog}
+
+
+
+ {#each $data.views[$data.current]?.data.buttons as event}
+
+
+
+
+
+ Button
+ {#if event && event.function}
+ {event.function}
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+ {event ? event.text : 'hidden'}
+
+
+
+ {/each}
+
+ {:else if $data.views[$data.current]?.type == GuiType.Alert}
+
+
+
+
+
+
+
+
+ {$data.views[$data.current]?.data.text}
+
+
+
+
+
+
+
+
+ Button
+ {#if $data.views[$data.current].data && $data.views[$data.current].data.function}
+ {$data.views[$data.current].data.function}
+ {/if}
+
+
none,
+ next
, back
+ or
+ close
. The Button Event will be triggered when the Ok/Middle button is
+ pressed.`}
+ let:dialog
+ >
+
+
+
+
+
+ Ok
+
+ {:else if $data.views.length > 0}
+
+
+ Error
+ This View isn't implemented yet. Just wait until I've done my work!
+
+ {/if}
+
+
diff --git a/src/routes/builder/EventDialog.svelte b/src/routes/builder/EventDialog.svelte
new file mode 100644
index 0000000..4d57351
--- /dev/null
+++ b/src/routes/builder/EventDialog.svelte
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+ Edit Button Event
+
+ Define here your button text and custom/generic event: none
,
+ next
, back
+ or
+ close
. The Button Event will be triggered when the button is
+ pressed.
+
+
+
+
+
diff --git a/src/routes/builder/Grid.svelte b/src/routes/builder/Grid.svelte
deleted file mode 100644
index 7c0bbec..0000000
--- a/src/routes/builder/Grid.svelte
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
- {#if $views.pages[$views.current]}
- {#each $views.pages[$views.current].page as view (view.id)}
-
-
- {#if view.type == GuiType.Header}
-
-
- {#if view.data.text_value}
- {view.data.text_value}
- {:else}
- Unset
- {/if}
-
-
- {:else if view.type == GuiType.BodyText}
-
-
- {#if view.data.text_value}
- {view.data.text_value}
- {:else}
- Unset
- {/if}
-
-
- {:else if view.type == GuiType.Buttons}
- {#if view.data.actions}
-
- {#each view.data.actions as action}
- {#if action}
-
- {action.text_value}
- {#if action.event}
- {Object.keys(action.event)[0]}
- {/if}
-
- {:else}
- Unset
- {/if}
- {/each}
-
- {/if}
- {:else if view.type == GuiType.Alert}
- {#if view.data.text_value}
- {view.data.text_value}
- {:else}
- Unset
- {/if}
-
- {#if view.data.actions && view.data.actions[0]}
-
- {view.data.actions[0].text_value}
- {#if view.data.actions[0].event}
- {Object.keys(view.data.actions[0].event)[0]}
- {/if}
-
- {:else}
- Unset
- {/if}
-
- {:else}
- {view.name},
- {#each keys(view.data) as key (key)}
- {key}: {view.data[key]},
- {/each}
- {/if}
- {view.name}
-
-
-
-
- {/each}
- {/if}
-
diff --git a/src/routes/builder/LabelDialog.svelte b/src/routes/builder/LabelDialog.svelte
new file mode 100644
index 0000000..8ce0c4d
--- /dev/null
+++ b/src/routes/builder/LabelDialog.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+ Edit {type}
+
+ Edit the {type} properties. You can change the text, position and alignment of it.
+
+
+
+ Text
+
+
+
+
+
+ Horizontal
+
+
+
+
+
+ {#each Object.keys(Align) as align}
+ setHorizontal(align)}
+ >{align}
+ {/each}
+
+
+
+
+ Vertical
+
+
+
+
+
+ {#each Object.keys(Align) as align}
+ setVertical(align)}
+ >{align}
+ {/each}
+
+
+
+
+
+
+
+
diff --git a/src/routes/builder/LabelItem.svelte b/src/routes/builder/LabelItem.svelte
new file mode 100644
index 0000000..bb89603
--- /dev/null
+++ b/src/routes/builder/LabelItem.svelte
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ {type}
+ {#if label}
+ x: {label?.x}
+ y: {label?.y}
+ {/if}
+
+
+ {#if label}
+
+
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
+
+ {label ? label.text : 'hidden'}
+
+
+
diff --git a/src/routes/builder/Menu.svelte b/src/routes/builder/Menu.svelte
deleted file mode 100644
index 67cb755..0000000
--- a/src/routes/builder/Menu.svelte
+++ /dev/null
@@ -1,329 +0,0 @@
-
-
-
-
-
-
- Close
-
-
-
- Add Items
-
-
-
- Views
-
-
-
-
- Export
-
-
-
-
-
- {#each submenu as segment, i}
- {#if segment.tile == currentTile}
-
- {segment.title}
-
-
- {#each segment.list as { id, label, badge, select, disabled, del }}
-
-
- {#if del}
-
- {/if}
-
- {/each}
-
-
- {#if i + 1 < submenu.filter((item) => item.tile === currentTile).length}
-
- {/if}
- {/if}
- {/each}
-
-
-
-
diff --git a/src/routes/builder/SideBar.svelte b/src/routes/builder/SideBar.svelte
new file mode 100644
index 0000000..afeb92a
--- /dev/null
+++ b/src/routes/builder/SideBar.svelte
@@ -0,0 +1,247 @@
+
+
+
+
+
+
diff --git a/src/routes/builder/TextDialog.svelte b/src/routes/builder/TextDialog.svelte
new file mode 100644
index 0000000..21452c6
--- /dev/null
+++ b/src/routes/builder/TextDialog.svelte
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+ Edit {label}
+
+ {@html description}
+
+
+
+
+
+
+
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e2a1000..d8dd761 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,35 +1,69 @@
-import { join } from 'path';
+import { fontFamily } from 'tailwindcss/defaultTheme';
import type { Config } from 'tailwindcss';
-import forms from '@tailwindcss/forms';
-import typography from '@tailwindcss/typography';
-import { skeleton } from '@skeletonlabs/tw-plugin';
-export default {
- darkMode: 'class',
- content: [
- './src/**/*.{html,js,svelte,ts}',
- join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
- ],
+const config: Config = {
+ darkMode: ['class'],
+ content: ['./src/**/*.{html,js,svelte,ts}'],
+ safelist: ['dark'],
theme: {
- extend: {}
- },
- plugins: [
- forms,
- typography,
- skeleton({
- themes: {
- preset: [
- {
- name: 'vintage',
- enhancements: true
- }
- ]
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px'
+ }
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border) /
)',
+ input: 'hsl(var(--input) / )',
+ ring: 'hsl(var(--ring) / )',
+ background: 'hsl(var(--background) / )',
+ foreground: 'hsl(var(--foreground) / )',
+ primary: {
+ DEFAULT: 'hsl(var(--primary) / )',
+ foreground: 'hsl(var(--primary-foreground) / )'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary) / )',
+ foreground: 'hsl(var(--secondary-foreground) / )'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive) / )',
+ foreground: 'hsl(var(--destructive-foreground) / )'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted) / )',
+ foreground: 'hsl(var(--muted-foreground) / )'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent) / )',
+ foreground: 'hsl(var(--accent-foreground) / )'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover) / )',
+ foreground: 'hsl(var(--popover-foreground) / )'
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card) / )',
+ foreground: 'hsl(var(--card-foreground) / )'
+ },
+ 'custom-green': 'hsla(135, 34%, 70%, 0.14)',
+ 'custom-orange': 'hsla(31, 83%, 50%, 0.14)'
+ },
+ backgroundImage: {
+ 'gradient-conic': 'conic-gradient(at 50% -80%, var(--tw-gradient-stops))'
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ fontFamily: {
+ sans: [...fontFamily.sans]
}
- })
- ],
- extend: {
- scale: {
- '-1': '-1'
}
}
-} satisfies Config;
+};
+
+export default config;
diff --git a/tsconfig.json b/tsconfig.json
index 82081ab..fc93cbd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,6 +12,7 @@
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+ // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
diff --git a/vite.config.ts b/vite.config.ts
index a5767ee..bbf8c7d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,15 +1,6 @@
-import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
- plugins: [
- sveltekit(),
- purgeCss({
- safelist: {
- // any selectors that begin with "hljs-" will not be purged
- greedy: [/^hljs-/]
- }
- })
- ]
+ plugins: [sveltekit()]
});