Skip to content

Commit

Permalink
Merge pull request #29 from JiayiSoftware/feature/live-theming
Browse files Browse the repository at this point in the history
Feature/live theming
  • Loading branch information
phasephasephase authored Jan 1, 2024
2 parents bf0b324 + afae3e5 commit 7bf90c9
Show file tree
Hide file tree
Showing 39 changed files with 1,893 additions and 758 deletions.
2 changes: 1 addition & 1 deletion JiayiInstaller/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
await Download(downloadUrl, downloadPath);

Console.WriteLine("Extracting...");
ExtractAndDelete(downloadPath, path);
await ExtractAndDelete(downloadPath, path);

if (dotnet)
{
Expand Down
2 changes: 1 addition & 1 deletion JiayiLauncher.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiayiLauncher", "JiayiLaunc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiayiInstaller", "JiayiInstaller\JiayiInstaller.csproj", "{DBBA4A74-C605-452E-ACBD-DC3EE79B0303}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreLib", "StoreLib\StoreLib\StoreLib.csproj", "{33A54AFF-C6B0-4611-9833-F40635B55BBD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoreLib", "StoreLib\StoreLib\StoreLib.csproj", "{33A54AFF-C6B0-4611-9833-F40635B55BBD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
201 changes: 141 additions & 60 deletions JiayiLauncher/Appearance/CssBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,80 +1,161 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace JiayiLauncher.Appearance;



public class CssProperty
{
public string Property { get; set; }
public string? Value { get; set; }

public CssProperty(string property, string? value)
{
Property = property;
Value = value;
}

public override string ToString()
{
return $"{Property}: {Value};";
}
}

public class CssSelector
{
public string Selector { get; set; }
public List<CssProperty> Properties { get; set; }

public CssSelector(string selector, List<CssProperty> properties)
{
Selector = selector;
Properties = properties;
}

public CssSelector UpdateProperty(CssProperty prop)
{
var idx = Properties.FindIndex(x => x.Property == prop.Property);
if (idx >= 0)
{
Properties[idx] = prop;
}
else
{
Properties.Add(prop);
}
return this;
}

public CssProperty? GetProperty(string prop)
{
var idx = Properties.FindIndex(x => x.Property == prop);
return idx >= 0 ? Properties[idx] : null;
}
public string? GetPropertyValue(string prop)
{
return GetProperty(prop)?.Value;
}

public string ToStringNoSelector()
{
return string.Join("\n", Properties.Select(prop => "\t" + prop));
}

public override string ToString()
{
return $"{Selector} {{\n{ToStringNoSelector()}\n}}";
}
}

public class CssBuilder
{
private readonly List<string> _properties = new();
private readonly string _selector;

public CssBuilder(string selector)
{
_selector = selector;
}

public static CssBuilder FromFile(string path, string selector)
{
var styles = File.ReadAllText(path);

if (!styles.Contains(selector))
throw new ArgumentException($"Selector '{selector}' was not found in this file.");

// i hate windows newlines
var lines = styles.Split('\n');

if (lines.Any(x => x.Contains('\r')))
lines = styles.Split("\r\n");

var start = Array.IndexOf(lines, selector + " {");
var end = Array.IndexOf(lines, "}", start);

var builder = new CssBuilder(selector);
for (var i = start + 1; i < end; i++)
{
var line = lines[i];
if (string.IsNullOrWhiteSpace(line)) continue;
var property = line.Split(':')[0].Trim();

//var value = line.Split(':')[1].Trim().TrimEnd(';');
// in case the value here contains a url we need to join the rest of the line
var value = string.Join(':', line.Split(':').Skip(1)).Trim().TrimEnd(';');
builder.AddProperty(property, value);
}

return builder;
}

public CssBuilder AddProperty(string property, string value)
{
_properties.Add($"{property}: {value};");
private readonly List<CssSelector> _selectors;

public CssBuilder(List<CssSelector>? selectors = null)
{
_selectors = selectors ?? new List<CssSelector>();
}

public CssSelector? GetSelector(string selector)
{
var idx = _selectors.FindIndex(x => x.Selector == selector);
return idx >= 0 ? _selectors[idx] : null;
}

public CssProperty? GetProperty(string selector, string prop)
{
return GetSelector(selector)?.GetProperty(prop);
}

public CssBuilder UpdateProperty(string selector, CssProperty prop)
{
GetSelector(selector)?.UpdateProperty(prop);

return this;
}
public string GetPropertyValue(string property)

public static CssBuilder FromFile(string path)
{
foreach (var prop in _properties.Where(prop => prop.StartsWith(property)))
{
return string.Join(':', prop.Split(':').Skip(1)).Trim().TrimEnd(';');
}
var css = File.ReadAllText(path);

return string.Empty;
return Parse(css);
}

// i like linq extension methods
public List<string> GetAllPropertyValues() => _properties.Select(GetPropertyValue).ToList();
public static CssBuilder Parse(string cssString)
{
cssString = RemoveCssComments(cssString);

var result = new List<CssSelector>();

public string Build()
const string pattern = @"(?<selector>[^{]+)\s*{\s*(?<properties>[^}]+)\s*}";
var regex = new Regex(pattern, RegexOptions.IgnorePatternWhitespace);

var matches = regex.Matches(cssString);

foreach (Match match in matches)
{
var selector = match.Groups["selector"].Value.Trim();
var propertyString = match.Groups["properties"].Value;

var properties = ParseProperties(propertyString);

result.Add(new CssSelector(selector, properties));
}

return new CssBuilder(result);
}

private static List<CssProperty> ParseProperties(string propertyString)
{
var css = $"{_selector} {{\n";
var properties = new List<CssProperty>();

css = _properties.Aggregate(css, (current, property) => current + $"\t{property}\n");
const string propertyPattern = @"(?<property>[^:]+)\s*:\s*(?<value>[^;]+);+";
var propertyRegex = new Regex(propertyPattern, RegexOptions.IgnorePatternWhitespace);

css += "}\n";
return css;
var propertyMatches = propertyRegex.Matches(propertyString);

foreach (Match propertyMatch in propertyMatches)
{
var property = propertyMatch.Groups["property"].Value.Trim();
var value = propertyMatch.Groups["value"].Value.Trim();

properties.Add(new CssProperty(property, value));
}

return properties;
}

public override string ToString() => Build();
private static string RemoveCssComments(string cssString)
{
// Use regular expression to remove CSS comments
const string commentPattern = @"/\*.*?\*/";
return Regex.Replace(cssString, commentPattern, string.Empty, RegexOptions.Singleline);
}
public override string ToString()
{
return string.Join("\n\n", _selectors.Select(selector => selector.ToString()));
}
}
69 changes: 69 additions & 0 deletions JiayiLauncher/Appearance/LocalTheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using JiayiLauncher.Settings;

namespace JiayiLauncher.Appearance;

public class LocalTheme
{
private static readonly string _themeRoot = Path.Combine(ThemeState.RootPath, "themes");

public string Name;

public LocalTheme(string name)
{
Name = name;
}

public static LocalTheme[] GetAllThemes()
{
var localThemes = new List<LocalTheme>();

var path = Path.Combine(_themeRoot, ".local");
Directory.CreateDirectory(path); // Ensure directory exists
var directories = Directory.GetDirectories(Path.Combine(_themeRoot, ".local"));
foreach (var d in directories)
{
var name = new DirectoryInfo(d).Name;
var theme = new LocalTheme(name);
localThemes.Add(theme);
}

if (localThemes.Count <= 0)
{
var theme = CreateTheme("default");
if (theme != null) localThemes.Add(theme);
}

return localThemes.ToArray();
}

public static LocalTheme? CreateTheme(string name)
{
var path = Path.Combine(_themeRoot, $".local\\{name}");
if (Directory.Exists(path))
{
return null;
}

Directory.CreateDirectory(path);

var buffer = File.Create(Path.Combine(path, "theme.css"));
var defaultTheme = new ThemeState().ThemeStyles.ToString();
var byteArray = Encoding.UTF8.GetBytes(defaultTheme);
buffer.Write(byteArray, 0, byteArray.Length);
buffer.Close();

return new LocalTheme(name);
}

public static void SaveCurrentTheme()
{
var buffer = File.OpenWrite(Path.Combine(_themeRoot, JiayiSettings.Instance.Theme, "theme.css"));
var themeStyles = ThemeState.Instance.ThemeStyles.ToString();
var byteArray = Encoding.UTF8.GetBytes(themeStyles);
buffer.Write(byteArray, 0, byteArray.Length);
buffer.Close();
}
}
35 changes: 35 additions & 0 deletions JiayiLauncher/Appearance/PublicTheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using JiayiLauncher.Utils;
using Newtonsoft.Json;

namespace JiayiLauncher.Appearance;

public class PublicTheme : ThemeMetadata
{
[JsonProperty("Name")]
public string Name;

[JsonProperty("bg")]
public Uri? Background;

[JsonProperty("meta")]
public Uri Metadata;

[JsonProperty("theme")]
public Uri Theme;

public static PublicTheme[]? GetAllThemes()
{
const string url = "https://raw.githubusercontent.com/JiayiSoftware/jiayi-themes/main/all_themes.json";
var response = InternetManager.Client.GetStringAsync(url).Result;
var data = JsonConvert.DeserializeObject<PublicTheme[]>(response);

if (data != null)
{
return data;
}

Log.Write("Theme", "Failed to retrieve public themes", Log.LogLevel.Error);
return null;
}
}
Loading

0 comments on commit 7bf90c9

Please sign in to comment.