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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,77 @@ Note: SharpFM and FileMaker must be running on the same computer. In order to sh

- Head over to [Releases](https://github.com/fuzzzerd/SharpFM/releases), grab the latest version (binaries for Windows, Mac, Linux are all available there).

### Clipping from FileMaker
### Importing Clips from FileMaker

- Open SharpFM.
- Switch over to FileMaker.
- Copy something to the clipboard.
- Copy something to the clipboard (scripts, tables, layouts, etc).
- Switch back to SharpFM.
- Use the Edit menu to "Paste from FileMaker Blob".
- See your object(s) in the clips list with the Xml editor on the side.
- Use **File > New > From Clipboard** (`Ctrl+V`) to import the clip.
- The clip appears in the left panel with the appropriate editor on the right.

### Clipping from SharpFM to FileMaker
### Exporting Clips to FileMaker

- Ensure you have a clip in SharpFM
- Select the clip in the list
- Use the Edit menu to "Copy As FileMaker Blob"
- Switch to FileMaker: based on the clip type, open Database manger, Script manager, layout mode, etc.
- Paste into FileMaker as you normally would.
- Select a clip in the left panel.
- Use **File > Save > Selected clip to Clipboard** (`Ctrl+Shift+C`).
- Switch to FileMaker and open the appropriate destination (Database Manager, Script Workspace, Layout mode, etc).
- Paste as you normally would.

### Saving / Sharing XML Clips
### Editing Scripts

This is an area we can improve, with interoperability with some other similar tools. More to come? Contributions welcome.
- Select a script clip or create one with **File > New > Script** (`Ctrl+N`).
- The script editor shows a plain-text representation of the script steps with FmScript syntax highlighting.
- Edit the script text directly; changes are synced back to the underlying XML.

SharpFM has the option to persist clips between sessions by using the File menu to "Save to Db".
### Editing Tables

- Save the XML for a given clip as a separate file (copy/paste to Notepad, Nano, email body, etc)
- Share the resulting XML file.
- Use the File menu to create a New clip.
- Select the appropriate clip type (Table, Script, Layout, etc)
- Paste the raw XML into the code editor.
- Select a table clip or create one with **File > New > Table** (`Ctrl+Shift+N`).
- The table editor shows a DataGrid with columns for Field Name, Type, Kind, Required, Unique, and Comment.
- Click **+ Add Field** to add a new field, then edit its properties inline.
- Select a field and click **Remove** or press `Delete` to remove it.
- Change a field's Kind to Calculated or Summary, then click **Edit Calculation...** to open the calculation editor.

### Viewing Raw XML

- Select any clip and use **View > Show XML** (`Ctrl+Shift+X`) to open the raw XML in a separate window.
- Edits made in the XML window are synced back to the clip when the window is closed.

### Saving and Sharing Clips

SharpFM persists clips between sessions as XML files in a local folder.

- Use **File > Save > Save All To Folder** (`Ctrl+S`) to save all clips.
- Use **File > Open Folder** to load clips from a different folder.
- The clip files are plain XML and can be shared via git, email, or any text-based tool.

## Features

- [x] Copy FileMaker Scripts, Tables, or Layouts From FileMaker Pro to their XML representation and back into FileMaker.
- [x] Store FileMaker Scripts, Tables, and Layouts to xml files that can be shared via git, email or other text based tools.
- [x] Edit raw FileMaker XML code (scripts, layouts, tables) with ability to paste changes back into FileMaker.
- [x] Use AvaloniaEdit for XML editing with XML syntax highlighting.
- [ ] Better UI tools to mutate the Raw XML.
- [x] Plain-text script editor with FmScript syntax highlighting.
- [x] DataGrid table/field editor with inline editing, calculation editor, and type/kind selection.
- [x] View and edit raw XML alongside structured editors.

## Plugins

SharpFM supports plugins via the `SharpFM.Plugin` contract library. Plugins implement `IPanelPlugin` and are loaded from the `plugins/` directory at startup. You can also install and manage plugins from the **View > Manage Plugins...** menu.

A sample "Clip Inspector" plugin is included to demonstrate the plugin API.

### Writing a Plugin

1. Create a new .NET 8 class library referencing `SharpFM.Plugin`.
2. Implement `IPanelPlugin` — provide an `Id`, `DisplayName`, and `CreatePanel()` returning an Avalonia `Control`.
3. Use `IPluginHost` in `Initialize()` to observe clip selection and push XML updates.
4. Build your DLL and drop it in the `plugins/` directory.

See `src/SharpFM.Plugin.Sample/` for a complete working example.

### License

While SharpFM is licensed under GPL v3, plugins that communicate solely through the interfaces in `SharpFM.Plugin` are not required to be GPL-licensed. See the plugin interface source files for the full exception clause.

## Troubleshooting

Expand Down
30 changes: 30 additions & 0 deletions SharpFM.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E2FF2BB3
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFM.Tests", "tests\SharpFM.Tests\SharpFM.Tests.csproj", "{5B228160-ECB9-4DFC-91D7-413AE9900617}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1515B0F2-1419-4778-92A8-430A8B4931F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFM.Plugin", "src\SharpFM.Plugin\SharpFM.Plugin.csproj", "{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFM.Plugin.Sample", "src\SharpFM.Plugin.Sample\SharpFM.Plugin.Sample.csproj", "{0ACF3F64-A87C-487C-B780-B39327C1B801}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFM.Plugin.Tests", "tests\SharpFM.Plugin.Tests\SharpFM.Plugin.Tests.csproj", "{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFM.Plugin.XmlViewer", "src\SharpFM.Plugin.XmlViewer\SharpFM.Plugin.XmlViewer.csproj", "{E988ECF3-E096-4F29-88C0-27B50FD6C703}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -30,8 +40,28 @@ Global
{5B228160-ECB9-4DFC-91D7-413AE9900617}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B228160-ECB9-4DFC-91D7-413AE9900617}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B228160-ECB9-4DFC-91D7-413AE9900617}.Release|Any CPU.Build.0 = Release|Any CPU
{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395}.Release|Any CPU.Build.0 = Release|Any CPU
{0ACF3F64-A87C-487C-B780-B39327C1B801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0ACF3F64-A87C-487C-B780-B39327C1B801}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0ACF3F64-A87C-487C-B780-B39327C1B801}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0ACF3F64-A87C-487C-B780-B39327C1B801}.Release|Any CPU.Build.0 = Release|Any CPU
{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB}.Release|Any CPU.Build.0 = Release|Any CPU
{E988ECF3-E096-4F29-88C0-27B50FD6C703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E988ECF3-E096-4F29-88C0-27B50FD6C703}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E988ECF3-E096-4F29-88C0-27B50FD6C703}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E988ECF3-E096-4F29-88C0-27B50FD6C703}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5B228160-ECB9-4DFC-91D7-413AE9900617} = {E2FF2BB3-AF37-44BA-BD84-999B352D814E}
{2D7BC534-E63F-4FC2-84F1-62BC0E8A1395} = {1515B0F2-1419-4778-92A8-430A8B4931F7}
{0ACF3F64-A87C-487C-B780-B39327C1B801} = {1515B0F2-1419-4778-92A8-430A8B4931F7}
{74337D8E-5EC6-4E5F-9E9E-F2B59E8ECABB} = {E2FF2BB3-AF37-44BA-BD84-999B352D814E}
{E988ECF3-E096-4F29-88C0-27B50FD6C703} = {1515B0F2-1419-4778-92A8-430A8B4931F7}
EndGlobalSection
EndGlobal
40 changes: 40 additions & 0 deletions src/SharpFM.Plugin.Sample/ClipInspectorPanel.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SharpFM.Plugin.Sample"
x:Class="SharpFM.Plugin.Sample.ClipInspectorPanel"
x:DataType="local:ClipInspectorViewModel">

<StackPanel Margin="16" Spacing="12">
<TextBlock Classes="Fluent2Subtitle" Text="Clip Inspector" />

<StackPanel Spacing="8" IsVisible="{Binding HasClip}">
<StackPanel Spacing="2">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="Name" />
<TextBlock Classes="Fluent2Body" Text="{Binding ClipName}" />
</StackPanel>

<StackPanel Spacing="2">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="Type" />
<TextBlock Classes="Fluent2Body" Text="{Binding ClipType}" />
</StackPanel>

<StackPanel Spacing="2">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="XML Elements" />
<TextBlock Classes="Fluent2Body" Text="{Binding ElementCount}" />
</StackPanel>

<StackPanel Spacing="2">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="Approx. Size" />
<TextBlock Classes="Fluent2Body" Text="{Binding XmlSize}" />
</StackPanel>
</StackPanel>

<TextBlock
Classes="Fluent2Body"
Opacity="0.5"
IsVisible="{Binding !HasClip}"
Text="Select a clip to inspect its metadata." />
</StackPanel>

</UserControl>
12 changes: 12 additions & 0 deletions src/SharpFM.Plugin.Sample/ClipInspectorPanel.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace SharpFM.Plugin.Sample;

public partial class ClipInspectorPanel : UserControl
{
public ClipInspectorPanel()
{
InitializeComponent();
}
}
48 changes: 48 additions & 0 deletions src/SharpFM.Plugin.Sample/ClipInspectorPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using SharpFM.Plugin;

namespace SharpFM.Plugin.Sample;

public class ClipInspectorPlugin : IPanelPlugin
{
public string Id => "clip-inspector";
public string DisplayName => "Clip Inspector";
public IReadOnlyList<PluginKeyBinding> KeyBindings => [];
public IReadOnlyList<PluginMenuAction> MenuActions => [];

private IPluginHost? _host;
private ClipInspectorViewModel? _viewModel;

public void Initialize(IPluginHost host)
{
_host = host;
_host.SelectedClipChanged += OnSelectedClipChanged;
_host.ClipContentChanged += OnClipContentChanged;
}

public Control CreatePanel()
{
_viewModel = new ClipInspectorViewModel();
_viewModel.Update(_host?.SelectedClip);
return new ClipInspectorPanel { DataContext = _viewModel };
}

private void OnSelectedClipChanged(object? sender, ClipInfo? clip)
{
_viewModel?.Update(clip);
}

private void OnClipContentChanged(object? sender, ClipContentChangedArgs args)
{
_viewModel?.Update(args.Clip);
}

public void Dispose()
{
if (_host is null) return;
_host.SelectedClipChanged -= OnSelectedClipChanged;
_host.ClipContentChanged -= OnClipContentChanged;
}
}
67 changes: 67 additions & 0 deletions src/SharpFM.Plugin.Sample/ClipInspectorViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using SharpFM.Plugin;

namespace SharpFM.Plugin.Sample;

public class ClipInspectorViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

private void Notify([CallerMemberName] string name = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

private string _clipName = "(no clip selected)";
public string ClipName { get => _clipName; private set { _clipName = value; Notify(); } }

private string _clipType = "-";
public string ClipType { get => _clipType; private set { _clipType = value; Notify(); } }

private string _elementCount = "-";
public string ElementCount { get => _elementCount; private set { _elementCount = value; Notify(); } }

private string _xmlSize = "-";
public string XmlSize { get => _xmlSize; private set { _xmlSize = value; Notify(); } }

private bool _hasClip;
public bool HasClip { get => _hasClip; private set { _hasClip = value; Notify(); } }

public void Update(ClipInfo? clip)
{
if (clip is null)
{
ClipName = "(no clip selected)";
ClipType = "-";
ElementCount = "-";
XmlSize = "-";
HasClip = false;
return;
}

HasClip = true;
ClipName = clip.Name;
ClipType = clip.ClipType;
XmlSize = FormatBytes(clip.Xml.Length * 2); // rough UTF-16 estimate

try
{
var doc = XDocument.Parse(clip.Xml);
var count = doc.Descendants().Count();
ElementCount = count.ToString();
}
catch
{
ElementCount = "(invalid XML)";
}
}

private static string FormatBytes(int bytes) => bytes switch
{
< 1024 => $"{bytes} B",
< 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
_ => $"{bytes / (1024.0 * 1024.0):F1} MB"
};
}
17 changes: 17 additions & 0 deletions src/SharpFM.Plugin.Sample/SharpFM.Plugin.Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\SharpFM.Plugin\SharpFM.Plugin.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.4" />
<PackageReference Include="FluentAvaloniaUI" Version="2.2.0" />
</ItemGroup>
</Project>
20 changes: 20 additions & 0 deletions src/SharpFM.Plugin.XmlViewer/SharpFM.Plugin.XmlViewer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\SharpFM.Plugin\SharpFM.Plugin.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.4" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.66" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageReference Include="FluentAvaloniaUI" Version="2.2.0" />
</ItemGroup>
</Project>
36 changes: 36 additions & 0 deletions src/SharpFM.Plugin.XmlViewer/XmlViewerPanel.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:local="using:SharpFM.Plugin.XmlViewer"
x:Class="SharpFM.Plugin.XmlViewer.XmlViewerPanel"
x:DataType="local:XmlViewerViewModel">

<Grid RowDefinitions="Auto,*">
<!-- Header -->
<Border Grid.Row="0" Padding="12,8" Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">
<TextBlock Classes="Fluent2Caption" Text="{Binding ClipLabel}" Opacity="0.8" />
</Border>

<!-- XML Editor -->
<AvaloniaEdit:TextEditor
x:Name="xmlEditor"
Grid.Row="1"
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
ShowLineNumbers="True"
WordWrap="False"
IsVisible="{Binding HasClip}"
Document="{Binding Document}" />

<!-- Empty state -->
<TextBlock
Grid.Row="1"
Classes="Fluent2Body"
Opacity="0.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !HasClip}"
Text="Select a clip to view its XML." />
</Grid>

</UserControl>
Loading
Loading